@drift-labs/sdk 2.143.0-beta.7 → 2.143.0-beta.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/VERSION +1 -1
  2. package/lib/browser/adminClient.d.ts +2 -2
  3. package/lib/browser/adminClient.js +5 -5
  4. package/lib/browser/dlob/DLOB.d.ts +9 -3
  5. package/lib/browser/dlob/DLOB.js +10 -13
  6. package/lib/browser/idl/drift.json +18 -22
  7. package/lib/browser/math/auction.d.ts +3 -2
  8. package/lib/browser/math/auction.js +23 -4
  9. package/lib/browser/math/margin.d.ts +2 -1
  10. package/lib/browser/math/margin.js +31 -3
  11. package/lib/browser/math/market.js +38 -18
  12. package/lib/browser/math/oracles.d.ts +2 -1
  13. package/lib/browser/math/oracles.js +55 -1
  14. package/lib/browser/math/orders.d.ts +3 -2
  15. package/lib/browser/math/orders.js +13 -3
  16. package/lib/browser/types.d.ts +12 -1
  17. package/lib/browser/types.js +12 -1
  18. package/lib/node/adminClient.d.ts +2 -2
  19. package/lib/node/adminClient.d.ts.map +1 -1
  20. package/lib/node/adminClient.js +5 -5
  21. package/lib/node/dlob/DLOB.d.ts +9 -3
  22. package/lib/node/dlob/DLOB.d.ts.map +1 -1
  23. package/lib/node/dlob/DLOB.js +10 -13
  24. package/lib/node/idl/drift.json +18 -22
  25. package/lib/node/math/auction.d.ts +3 -2
  26. package/lib/node/math/auction.d.ts.map +1 -1
  27. package/lib/node/math/auction.js +23 -4
  28. package/lib/node/math/margin.d.ts +2 -1
  29. package/lib/node/math/margin.d.ts.map +1 -1
  30. package/lib/node/math/margin.js +31 -3
  31. package/lib/node/math/market.d.ts.map +1 -1
  32. package/lib/node/math/market.js +38 -18
  33. package/lib/node/math/oracles.d.ts +2 -1
  34. package/lib/node/math/oracles.d.ts.map +1 -1
  35. package/lib/node/math/oracles.js +55 -1
  36. package/lib/node/math/orders.d.ts +3 -2
  37. package/lib/node/math/orders.d.ts.map +1 -1
  38. package/lib/node/math/orders.js +13 -3
  39. package/lib/node/types.d.ts +12 -1
  40. package/lib/node/types.d.ts.map +1 -1
  41. package/lib/node/types.js +12 -1
  42. package/package.json +1 -1
  43. package/src/adminClient.ts +10 -10
  44. package/src/dlob/DLOB.ts +28 -15
  45. package/src/idl/drift.json +18 -22
  46. package/src/math/auction.ts +50 -7
  47. package/src/math/margin.ts +44 -5
  48. package/src/math/market.ts +61 -31
  49. package/src/math/oracles.ts +88 -0
  50. package/src/math/orders.ts +30 -2
  51. package/src/types.ts +13 -1
  52. package/tests/dlob/helpers.ts +4 -4
package/src/dlob/DLOB.ts CHANGED
@@ -467,10 +467,6 @@ export class DLOB {
467
467
 
468
468
  const isAmmPaused = ammPaused(stateAccount, marketAccount);
469
469
 
470
- const minAuctionDuration = isVariant(marketType, 'perp')
471
- ? stateAccount.minPerpAuctionDuration
472
- : 0;
473
-
474
470
  const { makerRebateNumerator, makerRebateDenominator } =
475
471
  this.getMakerRebate(marketType, stateAccount, marketAccount);
476
472
 
@@ -481,7 +477,8 @@ export class DLOB {
481
477
  marketType,
482
478
  oraclePriceData,
483
479
  isAmmPaused,
484
- minAuctionDuration,
480
+ stateAccount,
481
+ marketAccount,
485
482
  fallbackAsk,
486
483
  fallbackBid
487
484
  );
@@ -493,7 +490,8 @@ export class DLOB {
493
490
  marketType,
494
491
  oraclePriceData,
495
492
  isAmmPaused,
496
- minAuctionDuration,
493
+ stateAccount,
494
+ marketAccount,
497
495
  makerRebateNumerator,
498
496
  makerRebateDenominator,
499
497
  fallbackAsk,
@@ -597,7 +595,10 @@ export class DLOB {
597
595
  ? OraclePriceData
598
596
  : MMOraclePriceData,
599
597
  isAmmPaused: boolean,
600
- minAuctionDuration: number,
598
+ stateAccount: StateAccount,
599
+ marketAccount: T extends { spot: unknown }
600
+ ? SpotMarketAccount
601
+ : PerpMarketAccount,
601
602
  makerRebateNumerator: number,
602
603
  makerRebateDenominator: number,
603
604
  fallbackAsk: BN | undefined,
@@ -636,7 +637,8 @@ export class DLOB {
636
637
  (askPrice) => {
637
638
  return askPrice.lte(fallbackBidWithBuffer);
638
639
  },
639
- minAuctionDuration
640
+ stateAccount,
641
+ marketAccount
640
642
  );
641
643
 
642
644
  for (const askCrossingFallback of asksCrossingFallback) {
@@ -664,7 +666,8 @@ export class DLOB {
664
666
  (bidPrice) => {
665
667
  return bidPrice.gte(fallbackAskWithBuffer);
666
668
  },
667
- minAuctionDuration
669
+ stateAccount,
670
+ marketAccount
668
671
  );
669
672
 
670
673
  for (const bidCrossingFallback of bidsCrossingFallback) {
@@ -683,7 +686,10 @@ export class DLOB {
683
686
  ? OraclePriceData
684
687
  : MMOraclePriceData,
685
688
  isAmmPaused: boolean,
686
- minAuctionDuration: number,
689
+ state: StateAccount,
690
+ marketAccount: T extends { spot: unknown }
691
+ ? SpotMarketAccount
692
+ : PerpMarketAccount,
687
693
  fallbackAsk: BN | undefined,
688
694
  fallbackBid?: BN | undefined
689
695
  ): NodeToFill[] {
@@ -736,7 +742,8 @@ export class DLOB {
736
742
  (takerPrice) => {
737
743
  return takerPrice === undefined || takerPrice.lte(fallbackBid);
738
744
  },
739
- minAuctionDuration
745
+ state,
746
+ marketAccount
740
747
  );
741
748
 
742
749
  for (const takingAskCrossingFallback of takingAsksCrossingFallback) {
@@ -793,7 +800,8 @@ export class DLOB {
793
800
  (takerPrice) => {
794
801
  return takerPrice === undefined || takerPrice.gte(fallbackAsk);
795
802
  },
796
- minAuctionDuration
803
+ state,
804
+ marketAccount
797
805
  );
798
806
  for (const marketBidCrossingFallback of takingBidsCrossingFallback) {
799
807
  nodesToFill.push(marketBidCrossingFallback);
@@ -911,7 +919,10 @@ export class DLOB {
911
919
  : MMOraclePriceData,
912
920
  nodeGenerator: Generator<DLOBNode>,
913
921
  doesCross: (nodePrice: BN | undefined) => boolean,
914
- minAuctionDuration: number
922
+ state: StateAccount,
923
+ marketAccount: T extends { spot: unknown }
924
+ ? SpotMarketAccount
925
+ : PerpMarketAccount
915
926
  ): NodeToFill[] {
916
927
  const nodesToFill = new Array<NodeToFill>();
917
928
 
@@ -934,8 +945,10 @@ export class DLOB {
934
945
  isVariant(marketType, 'spot') ||
935
946
  isFallbackAvailableLiquiditySource(
936
947
  node.order,
937
- minAuctionDuration,
938
- slot
948
+ oraclePriceData as MMOraclePriceData,
949
+ slot,
950
+ state,
951
+ marketAccount as PerpMarketAccount
939
952
  );
940
953
 
941
954
  if (crosses && fallbackAvailable) {
@@ -6566,7 +6566,7 @@
6566
6566
  ]
6567
6567
  },
6568
6568
  {
6569
- "name": "updatePerpMarketTakerSpeedBumpOverride",
6569
+ "name": "updatePerpMarketOracleLowRiskSlotDelayOverride",
6570
6570
  "accounts": [
6571
6571
  {
6572
6572
  "name": "admin",
@@ -6586,7 +6586,7 @@
6586
6586
  ],
6587
6587
  "args": [
6588
6588
  {
6589
- "name": "takerSpeedBumpOverride",
6589
+ "name": "oracleLowRiskSlotDelayOverride",
6590
6590
  "type": "i8"
6591
6591
  }
6592
6592
  ]
@@ -11194,7 +11194,7 @@
11194
11194
  "type": "i8"
11195
11195
  },
11196
11196
  {
11197
- "name": "takerSpeedBumpOverride",
11197
+ "name": "oracleLowRiskSlotDelayOverride",
11198
11198
  "docs": [
11199
11199
  "the override for the state.min_perp_auction_duration",
11200
11200
  "0 is no override, -1 is disable speed bump, 1-100 is literal speed bump"
@@ -12278,7 +12278,17 @@
12278
12278
  "name": "InsufficientDataPoints"
12279
12279
  },
12280
12280
  {
12281
- "name": "StaleForAMM"
12281
+ "name": "StaleForAMM",
12282
+ "fields": [
12283
+ {
12284
+ "name": "immediate",
12285
+ "type": "bool"
12286
+ },
12287
+ {
12288
+ "name": "lowRisk",
12289
+ "type": "bool"
12290
+ }
12291
+ ]
12282
12292
  },
12283
12293
  {
12284
12294
  "name": "Valid"
@@ -12304,7 +12314,10 @@
12304
12314
  "name": "FillOrderMatch"
12305
12315
  },
12306
12316
  {
12307
- "name": "FillOrderAmm"
12317
+ "name": "FillOrderAmmLowRisk"
12318
+ },
12319
+ {
12320
+ "name": "FillOrderAmmImmediate"
12308
12321
  },
12309
12322
  {
12310
12323
  "name": "Liquidate"
@@ -12954,23 +12967,6 @@
12954
12967
  ]
12955
12968
  }
12956
12969
  },
12957
- {
12958
- "name": "AMMAvailability",
12959
- "type": {
12960
- "kind": "enum",
12961
- "variants": [
12962
- {
12963
- "name": "Immediate"
12964
- },
12965
- {
12966
- "name": "AfterMinDuration"
12967
- },
12968
- {
12969
- "name": "Unavailable"
12970
- }
12971
- ]
12972
- }
12973
- },
12974
12970
  {
12975
12971
  "name": "RevenueShareOrderBitFlag",
12976
12972
  "type": {
@@ -1,4 +1,12 @@
1
- import { isOneOfVariant, isVariant, Order, PositionDirection } from '../types';
1
+ import {
2
+ isOneOfVariant,
3
+ isVariant,
4
+ OracleValidity,
5
+ Order,
6
+ PerpOperation,
7
+ PositionDirection,
8
+ StateAccount,
9
+ } from '../types';
2
10
  import { BN } from '@coral-xyz/anchor';
3
11
  import {
4
12
  ONE,
@@ -8,6 +16,10 @@ import {
8
16
  } from '../constants/numericConstants';
9
17
  import { getVariant, OrderBitFlag, PerpMarketAccount } from '../types';
10
18
  import { getPerpMarketTierNumber } from './tiers';
19
+ import { MMOraclePriceData } from '../oracles/types';
20
+ import { isLowRiskForAmm } from './orders';
21
+ import { getOracleValidity } from './oracles';
22
+ import { isOperationPaused } from './exchangeStatus';
11
23
 
12
24
  export function isAuctionComplete(order: Order, slot: number): boolean {
13
25
  if (order.auctionDuration === 0) {
@@ -19,18 +31,49 @@ export function isAuctionComplete(order: Order, slot: number): boolean {
19
31
 
20
32
  export function isFallbackAvailableLiquiditySource(
21
33
  order: Order,
22
- minAuctionDuration: number,
23
- slot: number
34
+ mmOraclePriceData: MMOraclePriceData,
35
+ slot: number,
36
+ state: StateAccount,
37
+ market: PerpMarketAccount,
38
+ isLiquidation?: boolean
24
39
  ): boolean {
25
- if (minAuctionDuration === 0) {
26
- return true;
40
+ if (isOperationPaused(market.pausedOperations, PerpOperation.AMM_FILL)) {
41
+ return false;
42
+ }
43
+
44
+ // TODO: include too much drawdown check & mm oracle volatility
45
+
46
+ const oracleValidity = getOracleValidity(
47
+ market!,
48
+ {
49
+ price: mmOraclePriceData.price,
50
+ slot: mmOraclePriceData.slot,
51
+ confidence: mmOraclePriceData.confidence,
52
+ hasSufficientNumberOfDataPoints:
53
+ mmOraclePriceData.hasSufficientNumberOfDataPoints,
54
+ },
55
+ state.oracleGuardRails,
56
+ new BN(slot)
57
+ );
58
+ if (oracleValidity <= OracleValidity.StaleForAMMLowRisk) {
59
+ return false;
27
60
  }
28
61
 
29
- if ((order.bitFlags & OrderBitFlag.SafeTriggerOrder) !== 0) {
62
+ if (oracleValidity == OracleValidity.Valid) {
30
63
  return true;
31
64
  }
32
65
 
33
- return new BN(slot).sub(order.slot).gt(new BN(minAuctionDuration));
66
+ const isOrderLowRiskForAmm = isLowRiskForAmm(
67
+ order,
68
+ mmOraclePriceData,
69
+ isLiquidation
70
+ );
71
+
72
+ if (!isOrderLowRiskForAmm) {
73
+ return false;
74
+ } else {
75
+ return true;
76
+ }
34
77
  }
35
78
 
36
79
  /**
@@ -10,6 +10,7 @@ import {
10
10
  MARGIN_PRECISION,
11
11
  PRICE_PRECISION,
12
12
  QUOTE_PRECISION,
13
+ PERCENTAGE_PRECISION,
13
14
  } from '../constants/numericConstants';
14
15
  import { BN } from '@coral-xyz/anchor';
15
16
  import { OraclePriceData } from '../oracles/types';
@@ -32,7 +33,8 @@ export function calculateSizePremiumLiabilityWeight(
32
33
  size: BN, // AMM_RESERVE_PRECISION
33
34
  imfFactor: BN,
34
35
  liabilityWeight: BN,
35
- precision: BN
36
+ precision: BN,
37
+ isBounded = true
36
38
  ): BN {
37
39
  if (imfFactor.eq(ZERO)) {
38
40
  return liabilityWeight;
@@ -53,10 +55,13 @@ export function calculateSizePremiumLiabilityWeight(
53
55
  .div(denom) // 1e5
54
56
  );
55
57
 
56
- const maxLiabilityWeight = BN.max(
57
- liabilityWeight,
58
- sizePremiumLiabilityWeight
59
- );
58
+ let maxLiabilityWeight;
59
+ if (isBounded) {
60
+ maxLiabilityWeight = BN.max(liabilityWeight, sizePremiumLiabilityWeight);
61
+ } else {
62
+ maxLiabilityWeight = sizePremiumLiabilityWeight;
63
+ }
64
+
60
65
  return maxLiabilityWeight;
61
66
  }
62
67
 
@@ -370,3 +375,37 @@ export function calculateUserMaxPerpOrderSize(
370
375
 
371
376
  return user.getMaxTradeSizeUSDCForPerp(targetMarketIndex, tradeSide);
372
377
  }
378
+
379
+ export function calcHighLeverageModeInitialMarginRatioFromSize(
380
+ preSizeAdjMarginRatio: BN,
381
+ sizeAdjMarginRatio: BN,
382
+ defaultMarginRatio: BN
383
+ ): BN {
384
+ let result: BN;
385
+
386
+ if (sizeAdjMarginRatio.lt(preSizeAdjMarginRatio)) {
387
+ const sizePctDiscountFactor = PERCENTAGE_PRECISION.sub(
388
+ preSizeAdjMarginRatio
389
+ .sub(sizeAdjMarginRatio)
390
+ .mul(PERCENTAGE_PRECISION)
391
+ .div(preSizeAdjMarginRatio.div(new BN(5)))
392
+ );
393
+
394
+ const hlmMarginDelta = BN.max(
395
+ preSizeAdjMarginRatio.sub(defaultMarginRatio),
396
+ new BN(1)
397
+ );
398
+
399
+ const hlmMarginDeltaProportion = hlmMarginDelta
400
+ .mul(sizePctDiscountFactor)
401
+ .div(PERCENTAGE_PRECISION);
402
+
403
+ result = hlmMarginDeltaProportion.add(defaultMarginRatio);
404
+ } else if (sizeAdjMarginRatio.eq(preSizeAdjMarginRatio)) {
405
+ result = defaultMarginRatio;
406
+ } else {
407
+ result = sizeAdjMarginRatio;
408
+ }
409
+
410
+ return result;
411
+ }
@@ -19,6 +19,7 @@ import {
19
19
  import {
20
20
  calculateSizeDiscountAssetWeight,
21
21
  calculateSizePremiumLiabilityWeight,
22
+ calcHighLeverageModeInitialMarginRatioFromSize,
22
23
  } from './margin';
23
24
  import { MMOraclePriceData, OraclePriceData } from '../oracles/types';
24
25
  import {
@@ -143,45 +144,74 @@ export function calculateMarketMarginRatio(
143
144
  customMarginRatio = 0,
144
145
  userHighLeverageMode = false
145
146
  ): number {
146
- let marginRationInitial;
147
- let marginRatioMaintenance;
147
+ if (market.status === 'Settlement') return 0;
148
148
 
149
- if (
149
+ const isHighLeverageUser =
150
150
  userHighLeverageMode &&
151
151
  market.highLeverageMarginRatioInitial > 0 &&
152
- market.highLeverageMarginRatioMaintenance
153
- ) {
154
- marginRationInitial = market.highLeverageMarginRatioInitial;
155
- marginRatioMaintenance = market.highLeverageMarginRatioMaintenance;
156
- } else {
157
- marginRationInitial = market.marginRatioInitial;
158
- marginRatioMaintenance = market.marginRatioMaintenance;
159
- }
152
+ market.highLeverageMarginRatioMaintenance > 0;
153
+
154
+ const marginRatioInitial = isHighLeverageUser
155
+ ? market.highLeverageMarginRatioInitial
156
+ : market.marginRatioInitial;
157
+
158
+ const marginRatioMaintenance = isHighLeverageUser
159
+ ? market.highLeverageMarginRatioMaintenance
160
+ : market.marginRatioMaintenance;
160
161
 
161
- let marginRatio;
162
+ let defaultMarginRatio: number;
162
163
  switch (marginCategory) {
163
- case 'Initial': {
164
- // use lowest leverage between max allowed and optional user custom max
165
- marginRatio = Math.max(
166
- calculateSizePremiumLiabilityWeight(
167
- size,
168
- new BN(market.imfFactor),
169
- new BN(marginRationInitial),
170
- MARGIN_PRECISION
171
- ).toNumber(),
172
- customMarginRatio
173
- );
164
+ case 'Initial':
165
+ defaultMarginRatio = marginRatioInitial;
174
166
  break;
175
- }
176
- case 'Maintenance': {
177
- marginRatio = calculateSizePremiumLiabilityWeight(
178
- size,
179
- new BN(market.imfFactor),
180
- new BN(marginRatioMaintenance),
181
- MARGIN_PRECISION
182
- ).toNumber();
167
+ case 'Maintenance':
168
+ defaultMarginRatio = marginRatioMaintenance;
183
169
  break;
170
+ default:
171
+ throw new Error('Invalid margin category');
172
+ }
173
+
174
+ let marginRatio: number;
175
+
176
+ if (isHighLeverageUser && marginCategory !== 'Maintenance') {
177
+ // Use ordinary-mode initial/fill ratios for size-adjusted calc
178
+ let preSizeAdjMarginRatio: number;
179
+ switch (marginCategory) {
180
+ case 'Initial':
181
+ preSizeAdjMarginRatio = market.marginRatioInitial;
182
+ break;
183
+ default:
184
+ preSizeAdjMarginRatio = marginRatioMaintenance;
185
+ break;
184
186
  }
187
+
188
+ const sizeAdjMarginRatio = calculateSizePremiumLiabilityWeight(
189
+ size,
190
+ new BN(market.imfFactor),
191
+ new BN(preSizeAdjMarginRatio),
192
+ MARGIN_PRECISION,
193
+ false
194
+ ).toNumber();
195
+
196
+ marginRatio = calcHighLeverageModeInitialMarginRatioFromSize(
197
+ new BN(preSizeAdjMarginRatio),
198
+ new BN(sizeAdjMarginRatio),
199
+ new BN(defaultMarginRatio)
200
+ ).toNumber();
201
+ } else {
202
+ const sizeAdjMarginRatio = calculateSizePremiumLiabilityWeight(
203
+ size,
204
+ new BN(market.imfFactor),
205
+ new BN(defaultMarginRatio),
206
+ MARGIN_PRECISION,
207
+ true
208
+ ).toNumber();
209
+
210
+ marginRatio = Math.max(defaultMarginRatio, sizeAdjMarginRatio);
211
+ }
212
+
213
+ if (marginCategory === 'Initial') {
214
+ marginRatio = Math.max(marginRatio, customMarginRatio);
185
215
  }
186
216
 
187
217
  return marginRatio;
@@ -3,7 +3,9 @@ import {
3
3
  HistoricalOracleData,
4
4
  OracleGuardRails,
5
5
  OracleSource,
6
+ OracleValidity,
6
7
  PerpMarketAccount,
8
+ isOneOfVariant,
7
9
  isVariant,
8
10
  } from '../types';
9
11
  import { OraclePriceData } from '../oracles/types';
@@ -14,6 +16,7 @@ import {
14
16
  ZERO,
15
17
  FIVE_MINUTE,
16
18
  PERCENTAGE_PRECISION,
19
+ FIVE,
17
20
  } from '../constants/numericConstants';
18
21
  import { assert } from '../assert/assert';
19
22
  import { BN } from '@coral-xyz/anchor';
@@ -51,6 +54,91 @@ export function getMaxConfidenceIntervalMultiplier(
51
54
  return maxConfidenceIntervalMultiplier;
52
55
  }
53
56
 
57
+ export function getOracleValidity(
58
+ market: PerpMarketAccount,
59
+ oraclePriceData: OraclePriceData,
60
+ oracleGuardRails: OracleGuardRails,
61
+ slot: BN,
62
+ oracleStalenessBuffer = FIVE
63
+ ): OracleValidity {
64
+ const isNonPositive = oraclePriceData.price.lte(ZERO);
65
+ const isTooVolatile = BN.max(
66
+ oraclePriceData.price,
67
+ market.amm.historicalOracleData.lastOraclePriceTwap
68
+ )
69
+ .div(
70
+ BN.max(
71
+ ONE,
72
+ BN.min(
73
+ oraclePriceData.price,
74
+ market.amm.historicalOracleData.lastOraclePriceTwap
75
+ )
76
+ )
77
+ )
78
+ .gt(oracleGuardRails.validity.tooVolatileRatio);
79
+
80
+ const confPctOfPrice = oraclePriceData.confidence
81
+ .mul(BID_ASK_SPREAD_PRECISION)
82
+ .div(oraclePriceData.price);
83
+ const isConfTooLarge = confPctOfPrice.gt(
84
+ oracleGuardRails.validity.confidenceIntervalMaxSize.mul(
85
+ getMaxConfidenceIntervalMultiplier(market)
86
+ )
87
+ );
88
+
89
+ const oracleDelay = slot.sub(oraclePriceData.slot).sub(oracleStalenessBuffer);
90
+
91
+ let isStaleForAmmImmediate = true;
92
+ if (market.amm.oracleSlotDelayOverride != 0) {
93
+ isStaleForAmmImmediate = oracleDelay.gt(
94
+ BN.max(new BN(market.amm.oracleSlotDelayOverride), ZERO)
95
+ );
96
+ }
97
+
98
+ let isStaleForAmmLowRisk = false;
99
+ if (market.amm.oracleLowRiskSlotDelayOverride != 0) {
100
+ isStaleForAmmLowRisk = oracleDelay.gt(
101
+ BN.max(new BN(market.amm.oracleLowRiskSlotDelayOverride), ZERO)
102
+ );
103
+ } else {
104
+ isStaleForAmmLowRisk = oracleDelay.gt(
105
+ oracleGuardRails.validity.slotsBeforeStaleForAmm
106
+ );
107
+ }
108
+
109
+ let isStaleForMargin = oracleDelay.gt(
110
+ new BN(oracleGuardRails.validity.slotsBeforeStaleForMargin)
111
+ );
112
+ if (
113
+ isOneOfVariant(market.amm.oracleSource, [
114
+ 'pythStableCoinPull',
115
+ 'pythLazerStableCoin',
116
+ ])
117
+ ) {
118
+ isStaleForMargin = oracleDelay.gt(
119
+ new BN(oracleGuardRails.validity.slotsBeforeStaleForMargin).muln(3)
120
+ );
121
+ }
122
+
123
+ if (isNonPositive) {
124
+ return OracleValidity.NonPositive;
125
+ } else if (isTooVolatile) {
126
+ return OracleValidity.TooVolatile;
127
+ } else if (isConfTooLarge) {
128
+ return OracleValidity.TooUncertain;
129
+ } else if (isStaleForMargin) {
130
+ return OracleValidity.StaleForMargin;
131
+ } else if (!oraclePriceData.hasSufficientNumberOfDataPoints) {
132
+ return OracleValidity.InsufficientDataPoints;
133
+ } else if (isStaleForAmmLowRisk) {
134
+ return OracleValidity.StaleForAMMLowRisk;
135
+ } else if (isStaleForAmmImmediate) {
136
+ return OracleValidity.isStaleForAmmImmediate;
137
+ } else {
138
+ return OracleValidity.Valid;
139
+ }
140
+ }
141
+
54
142
  export function isOracleValid(
55
143
  market: PerpMarketAccount,
56
144
  oraclePriceData: OraclePriceData,
@@ -8,6 +8,8 @@ import {
8
8
  PositionDirection,
9
9
  ProtectedMakerParams,
10
10
  MarketTypeStr,
11
+ OrderBitFlag,
12
+ StateAccount,
11
13
  } from '../types';
12
14
  import {
13
15
  ZERO,
@@ -243,10 +245,16 @@ export function isFillableByVAMM(
243
245
  mmOraclePriceData: MMOraclePriceData,
244
246
  slot: number,
245
247
  ts: number,
246
- minAuctionDuration: number
248
+ state: StateAccount
247
249
  ): boolean {
248
250
  return (
249
- (isFallbackAvailableLiquiditySource(order, minAuctionDuration, slot) &&
251
+ (isFallbackAvailableLiquiditySource(
252
+ order,
253
+ mmOraclePriceData,
254
+ slot,
255
+ state,
256
+ market
257
+ ) &&
250
258
  calculateBaseAssetAmountForAmmToFulfill(
251
259
  order,
252
260
  market,
@@ -257,6 +265,26 @@ export function isFillableByVAMM(
257
265
  );
258
266
  }
259
267
 
268
+ export function isLowRiskForAmm(
269
+ order: Order,
270
+ mmOraclePriceData: MMOraclePriceData,
271
+ isLiquidation?: boolean
272
+ ): boolean {
273
+ if (isVariant(order.marketType, 'spot')) {
274
+ return false;
275
+ }
276
+
277
+ const orderOlderThanOracleDelay = new BN(order.slot).lte(
278
+ mmOraclePriceData.slot
279
+ );
280
+
281
+ return (
282
+ orderOlderThanOracleDelay ||
283
+ isLiquidation ||
284
+ (order.bitFlags & OrderBitFlag.SafeTriggerOrder) !== 0
285
+ );
286
+ }
287
+
260
288
  export function calculateBaseAssetAmountForAmmToFulfill(
261
289
  order: Order,
262
290
  market: PerpMarketAccount,
package/src/types.ts CHANGED
@@ -1107,7 +1107,8 @@ export type AMM = {
1107
1107
  quoteAssetAmountWithUnsettledLp: BN;
1108
1108
  referencePriceOffset: number;
1109
1109
 
1110
- takerSpeedBumpOverride: number;
1110
+ oracleLowRiskSlotDelayOverride: number;
1111
+ oracleSlotDelayOverride: number;
1111
1112
  ammSpreadAdjustment: number;
1112
1113
  ammInventorySpreadAdjustment: number;
1113
1114
 
@@ -1473,6 +1474,17 @@ export type OracleGuardRails = {
1473
1474
  };
1474
1475
  };
1475
1476
 
1477
+ export enum OracleValidity {
1478
+ NonPositive = 0,
1479
+ TooVolatile = 1,
1480
+ TooUncertain = 2,
1481
+ StaleForMargin = 3,
1482
+ InsufficientDataPoints = 4,
1483
+ StaleForAMMLowRisk = 5,
1484
+ isStaleForAmmImmediate = 6,
1485
+ Valid = 7,
1486
+ }
1487
+
1476
1488
  export type PrelaunchOracle = {
1477
1489
  price: BN;
1478
1490
  maxPrice: BN;
@@ -145,7 +145,7 @@ export const mockAMM: AMM = {
145
145
  quoteAssetAmountWithUnsettledLp: new BN(0),
146
146
  referencePriceOffset: 0,
147
147
 
148
- takerSpeedBumpOverride: 0,
148
+ oracleLowRiskSlotDelayOverride: 0,
149
149
  ammSpreadAdjustment: 0,
150
150
  ammInventorySpreadAdjustment: 0,
151
151
  mmOracleSequenceId: new BN(0),
@@ -672,9 +672,9 @@ export class MockUserMap implements UserMapInterface {
672
672
  });
673
673
  }
674
674
 
675
- public async subscribe(): Promise<void> {}
675
+ public async subscribe(): Promise<void> { }
676
676
 
677
- public async unsubscribe(): Promise<void> {}
677
+ public async unsubscribe(): Promise<void> { }
678
678
 
679
679
  public async addPubkey(userAccountPublicKey: PublicKey): Promise<void> {
680
680
  const user = new User({
@@ -733,7 +733,7 @@ export class MockUserMap implements UserMapInterface {
733
733
  );
734
734
  }
735
735
 
736
- public async updateWithOrderRecord(_record: OrderRecord): Promise<void> {}
736
+ public async updateWithOrderRecord(_record: OrderRecord): Promise<void> { }
737
737
 
738
738
  public values(): IterableIterator<User> {
739
739
  return this.userMap.values();