@drift-labs/sdk 0.2.0-master.30 → 0.2.0-master.32

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 (60) hide show
  1. package/lib/admin.d.ts +11 -7
  2. package/lib/admin.js +50 -12
  3. package/lib/clearingHouse.d.ts +26 -23
  4. package/lib/clearingHouse.js +290 -652
  5. package/lib/clearingHouseUser.d.ts +1 -1
  6. package/lib/clearingHouseUser.js +9 -12
  7. package/lib/config.js +1 -1
  8. package/lib/constants/numericConstants.d.ts +7 -0
  9. package/lib/constants/numericConstants.js +8 -1
  10. package/lib/constants/spotMarkets.js +4 -4
  11. package/lib/dlob/DLOB.d.ts +7 -5
  12. package/lib/dlob/DLOB.js +159 -88
  13. package/lib/dlob/DLOBNode.d.ts +2 -0
  14. package/lib/dlob/DLOBNode.js +3 -0
  15. package/lib/dlob/NodeList.d.ts +2 -1
  16. package/lib/dlob/NodeList.js +10 -4
  17. package/lib/events/types.d.ts +2 -1
  18. package/lib/events/types.js +1 -0
  19. package/lib/factory/bigNum.d.ts +1 -0
  20. package/lib/factory/bigNum.js +14 -0
  21. package/lib/idl/clearing_house.json +1091 -611
  22. package/lib/math/amm.d.ts +2 -2
  23. package/lib/math/amm.js +31 -28
  24. package/lib/math/market.d.ts +1 -1
  25. package/lib/math/market.js +6 -6
  26. package/lib/math/spotBalance.js +7 -8
  27. package/lib/math/trade.js +11 -11
  28. package/lib/types.d.ts +118 -41
  29. package/lib/types.js +34 -4
  30. package/package.json +4 -2
  31. package/src/admin.ts +129 -29
  32. package/src/clearingHouse.ts +380 -787
  33. package/src/clearingHouseUser.ts +11 -14
  34. package/src/config.ts +1 -1
  35. package/src/constants/numericConstants.ts +7 -0
  36. package/src/constants/spotMarkets.ts +5 -4
  37. package/src/dlob/DLOB.ts +221 -103
  38. package/src/dlob/DLOBNode.ts +5 -0
  39. package/src/dlob/NodeList.ts +15 -5
  40. package/src/events/types.ts +3 -0
  41. package/src/factory/bigNum.ts +23 -0
  42. package/src/idl/clearing_house.json +1091 -611
  43. package/src/math/amm.ts +42 -29
  44. package/src/math/market.ts +9 -4
  45. package/src/math/spotBalance.ts +11 -8
  46. package/src/math/trade.ts +11 -11
  47. package/src/types.ts +82 -41
  48. package/tests/bn/test.ts +13 -8
  49. package/tests/dlob/helpers.ts +56 -33
  50. package/tests/dlob/test.ts +1397 -495
  51. package/src/assert/assert.js +0 -9
  52. package/src/events/eventList.js +0 -77
  53. package/src/examples/makeTradeExample.js +0 -157
  54. package/src/token/index.js +0 -38
  55. package/src/tx/types.js +0 -2
  56. package/src/tx/utils.js +0 -17
  57. package/src/util/computeUnits.js +0 -27
  58. package/src/util/getTokenAddress.js +0 -9
  59. package/src/util/promiseTimeout.js +0 -14
  60. package/src/util/tps.js +0 -27
@@ -42,7 +42,7 @@ export declare class ClearingHouseUser {
42
42
  * @param orderId
43
43
  * @returns Order
44
44
  */
45
- getOrder(orderId: BN): Order | undefined;
45
+ getOrder(orderId: number): Order | undefined;
46
46
  /**
47
47
  * @param userOrderId
48
48
  * @returns Order
@@ -65,7 +65,7 @@ class ClearingHouseUser {
65
65
  getEmptyPosition(marketIndex) {
66
66
  return {
67
67
  baseAssetAmount: numericConstants_1.ZERO,
68
- remainderBaseAssetAmount: numericConstants_1.ZERO,
68
+ remainderBaseAssetAmount: 0,
69
69
  lastCumulativeFundingRate: numericConstants_1.ZERO,
70
70
  marketIndex,
71
71
  quoteAssetAmount: numericConstants_1.ZERO,
@@ -75,7 +75,6 @@ class ClearingHouseUser {
75
75
  openAsks: numericConstants_1.ZERO,
76
76
  settledPnl: numericConstants_1.ZERO,
77
77
  lpShares: numericConstants_1.ZERO,
78
- lastFeePerLp: numericConstants_1.ZERO,
79
78
  lastNetBaseAssetAmountPerLp: numericConstants_1.ZERO,
80
79
  lastNetQuoteAssetAmountPerLp: numericConstants_1.ZERO,
81
80
  };
@@ -89,7 +88,7 @@ class ClearingHouseUser {
89
88
  * @returns Order
90
89
  */
91
90
  getOrder(orderId) {
92
- return this.getUserAccount().orders.find((order) => order.orderId.eq(orderId));
91
+ return this.getUserAccount().orders.find((order) => order.orderId === orderId);
93
92
  }
94
93
  /**
95
94
  * @param userOrderId
@@ -134,15 +133,13 @@ class ClearingHouseUser {
134
133
  return [standardizedAmount, remainder];
135
134
  }
136
135
  const [standardizedBaa, remainderBaa] = standardize(deltaBaa, market.amm.baseAssetAmountStepSize);
137
- position.remainderBaseAssetAmount =
138
- position.remainderBaseAssetAmount.add(remainderBaa);
139
- if (position.remainderBaseAssetAmount
140
- .abs()
141
- .gte(market.amm.baseAssetAmountStepSize)) {
136
+ position.remainderBaseAssetAmount += remainderBaa.toNumber();
137
+ if (Math.abs(position.remainderBaseAssetAmount) >
138
+ market.amm.baseAssetAmountStepSize.toNumber()) {
142
139
  const [newStandardizedBaa, newRemainderBaa] = standardize(position.remainderBaseAssetAmount, market.amm.baseAssetAmountStepSize);
143
140
  position.baseAssetAmount =
144
141
  position.baseAssetAmount.add(newStandardizedBaa);
145
- position.remainderBaseAssetAmount = newRemainderBaa;
142
+ position.remainderBaseAssetAmount = newRemainderBaa.toNumber();
146
143
  }
147
144
  let updateType;
148
145
  if (position.baseAssetAmount.eq(numericConstants_1.ZERO)) {
@@ -300,7 +297,7 @@ class ClearingHouseUser {
300
297
  const [worstCaseTokenAmount, worstCaseQuoteTokenAmount] = (0, spotPosition_1.getWorstCaseTokenAmounts)(spotPosition, spotMarketAccount, this.getOracleDataForSpotMarket(spotPosition.marketIndex));
301
298
  let newTotalLiabilityValue = totalLiabilityValue;
302
299
  if (worstCaseTokenAmount.lt(numericConstants_1.ZERO)) {
303
- const baseLiabilityValue = this.getSpotLiabilityValue(worstCaseTokenAmount, oraclePriceData, spotMarketAccount, marginCategory, liquidationBuffer);
300
+ const baseLiabilityValue = this.getSpotLiabilityValue(worstCaseTokenAmount.abs(), oraclePriceData, spotMarketAccount, marginCategory, liquidationBuffer);
304
301
  newTotalLiabilityValue =
305
302
  newTotalLiabilityValue.add(baseLiabilityValue);
306
303
  }
@@ -310,6 +307,7 @@ class ClearingHouseUser {
310
307
  weight = _1.BN.max(weight, new _1.BN(this.getUserAccount().customMarginRatio));
311
308
  }
312
309
  const weightedTokenValue = worstCaseQuoteTokenAmount
310
+ .abs()
313
311
  .mul(weight)
314
312
  .div(numericConstants_1.SPOT_MARKET_WEIGHT_PRECISION);
315
313
  newTotalLiabilityValue =
@@ -615,7 +613,7 @@ class ClearingHouseUser {
615
613
  const proposedPerpPosition = {
616
614
  marketIndex: perpPosition.marketIndex,
617
615
  baseAssetAmount: proposedBaseAssetAmount,
618
- remainderBaseAssetAmount: numericConstants_1.ZERO,
616
+ remainderBaseAssetAmount: 0,
619
617
  quoteAssetAmount: new _1.BN(0),
620
618
  lastCumulativeFundingRate: numericConstants_1.ZERO,
621
619
  quoteEntryAmount: new _1.BN(0),
@@ -624,7 +622,6 @@ class ClearingHouseUser {
624
622
  openAsks: new _1.BN(0),
625
623
  settledPnl: numericConstants_1.ZERO,
626
624
  lpShares: numericConstants_1.ZERO,
627
- lastFeePerLp: numericConstants_1.ZERO,
628
625
  lastNetBaseAssetAmountPerLp: numericConstants_1.ZERO,
629
626
  lastNetQuoteAssetAmountPerLp: numericConstants_1.ZERO,
630
627
  };
package/lib/config.js CHANGED
@@ -7,7 +7,7 @@ exports.configs = {
7
7
  devnet: {
8
8
  ENV: 'devnet',
9
9
  PYTH_ORACLE_MAPPING_ADDRESS: 'BmA9Z6FjioHJPpjT39QazZyhDRUdZy2ezwx4GiDdE2u2',
10
- CLEARING_HOUSE_PROGRAM_ID: 'By7XjakxXVnQ9gMZ4VT98DenTgBCeP295A58ybzgwVPZ',
10
+ CLEARING_HOUSE_PROGRAM_ID: 'DUZwKJKAk2C9S88BYvQzck1M1i5hySQjxB4zW6tJ29Nw',
11
11
  USDC_MINT_ADDRESS: '8zGuJQqwhZafTah7Uc7Z4tXRnguqkn5KLFAP8oV6PHe2',
12
12
  PERP_MARKETS: perpMarkets_1.DevnetPerpMarkets,
13
13
  SPOT_MARKETS: spotMarkets_1.DevnetSpotMarkets,
@@ -3,6 +3,13 @@ import { BN } from '../';
3
3
  export declare const ZERO: BN;
4
4
  export declare const ONE: BN;
5
5
  export declare const TWO: BN;
6
+ export declare const THREE: BN;
7
+ export declare const FOUR: BN;
8
+ export declare const FIVE: BN;
9
+ export declare const SIX: BN;
10
+ export declare const SEVEN: BN;
11
+ export declare const EIGHT: BN;
12
+ export declare const NINE: BN;
6
13
  export declare const TEN: BN;
7
14
  export declare const TEN_THOUSAND: BN;
8
15
  export declare const BN_MAX: BN;
@@ -1,11 +1,18 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.LAMPORTS_EXP = exports.LAMPORTS_PRECISION = exports.QUOTE_SPOT_MARKET_INDEX = exports.ONE_YEAR = exports.BID_ASK_SPREAD_PRECISION = exports.MARGIN_PRECISION = exports.AMM_TIMES_PEG_TO_QUOTE_PRECISION_RATIO = exports.PRICE_TO_QUOTE_PRECISION = exports.PRICE_DIV_PEG = exports.AMM_TO_QUOTE_PRECISION_RATIO = exports.BASE_PRECISION_EXP = exports.BASE_PRECISION = exports.AMM_RESERVE_PRECISION = exports.PEG_PRECISION = exports.FUNDING_RATE_BUFFER_PRECISION = exports.PRICE_PRECISION = exports.QUOTE_PRECISION = exports.LIQUIDATION_FEE_PRECISION = exports.SPOT_MARKET_IMF_PRECISION = exports.SPOT_MARKET_IMF_PRECISION_EXP = exports.SPOT_MARKET_BALANCE_PRECISION = exports.SPOT_MARKET_BALANCE_PRECISION_EXP = exports.SPOT_MARKET_WEIGHT_PRECISION = exports.SPOT_MARKET_UTILIZATION_PRECISION = exports.SPOT_MARKET_CUMULATIVE_INTEREST_PRECISION = exports.SPOT_MARKET_CUMULATIVE_INTEREST_PRECISION_EXP = exports.SPOT_MARKET_RATE_PRECISION = exports.SPOT_MARKET_RATE_PRECISION_EXP = exports.AMM_RESERVE_PRECISION_EXP = exports.PEG_PRECISION_EXP = exports.FUNDING_RATE_PRECISION_EXP = exports.PRICE_PRECISION_EXP = exports.FUNDING_RATE_BUFFER_PRECISION_EXP = exports.QUOTE_PRECISION_EXP = exports.MAX_LEVERAGE = exports.TEN_MILLION = exports.BN_MAX = exports.TEN_THOUSAND = exports.TEN = exports.TWO = exports.ONE = exports.ZERO = void 0;
3
+ exports.LAMPORTS_EXP = exports.LAMPORTS_PRECISION = exports.QUOTE_SPOT_MARKET_INDEX = exports.ONE_YEAR = exports.BID_ASK_SPREAD_PRECISION = exports.MARGIN_PRECISION = exports.AMM_TIMES_PEG_TO_QUOTE_PRECISION_RATIO = exports.PRICE_TO_QUOTE_PRECISION = exports.PRICE_DIV_PEG = exports.AMM_TO_QUOTE_PRECISION_RATIO = exports.BASE_PRECISION_EXP = exports.BASE_PRECISION = exports.AMM_RESERVE_PRECISION = exports.PEG_PRECISION = exports.FUNDING_RATE_BUFFER_PRECISION = exports.PRICE_PRECISION = exports.QUOTE_PRECISION = exports.LIQUIDATION_FEE_PRECISION = exports.SPOT_MARKET_IMF_PRECISION = exports.SPOT_MARKET_IMF_PRECISION_EXP = exports.SPOT_MARKET_BALANCE_PRECISION = exports.SPOT_MARKET_BALANCE_PRECISION_EXP = exports.SPOT_MARKET_WEIGHT_PRECISION = exports.SPOT_MARKET_UTILIZATION_PRECISION = exports.SPOT_MARKET_CUMULATIVE_INTEREST_PRECISION = exports.SPOT_MARKET_CUMULATIVE_INTEREST_PRECISION_EXP = exports.SPOT_MARKET_RATE_PRECISION = exports.SPOT_MARKET_RATE_PRECISION_EXP = exports.AMM_RESERVE_PRECISION_EXP = exports.PEG_PRECISION_EXP = exports.FUNDING_RATE_PRECISION_EXP = exports.PRICE_PRECISION_EXP = exports.FUNDING_RATE_BUFFER_PRECISION_EXP = exports.QUOTE_PRECISION_EXP = exports.MAX_LEVERAGE = exports.TEN_MILLION = exports.BN_MAX = exports.TEN_THOUSAND = exports.TEN = exports.NINE = exports.EIGHT = exports.SEVEN = exports.SIX = exports.FIVE = exports.FOUR = exports.THREE = exports.TWO = exports.ONE = exports.ZERO = void 0;
4
4
  const web3_js_1 = require("@solana/web3.js");
5
5
  const __1 = require("../");
6
6
  exports.ZERO = new __1.BN(0);
7
7
  exports.ONE = new __1.BN(1);
8
8
  exports.TWO = new __1.BN(2);
9
+ exports.THREE = new __1.BN(3);
10
+ exports.FOUR = new __1.BN(4);
11
+ exports.FIVE = new __1.BN(5);
12
+ exports.SIX = new __1.BN(6);
13
+ exports.SEVEN = new __1.BN(7);
14
+ exports.EIGHT = new __1.BN(8);
15
+ exports.NINE = new __1.BN(9);
9
16
  exports.TEN = new __1.BN(10);
10
17
  exports.TEN_THOUSAND = new __1.BN(10000);
11
18
  exports.BN_MAX = new __1.BN(Number.MAX_SAFE_INTEGER);
@@ -12,8 +12,8 @@ exports.DevnetSpotMarkets = [
12
12
  oracle: web3_js_1.PublicKey.default,
13
13
  oracleSource: __1.OracleSource.QUOTE_ASSET,
14
14
  mint: new web3_js_1.PublicKey('8zGuJQqwhZafTah7Uc7Z4tXRnguqkn5KLFAP8oV6PHe2'),
15
- precision: numericConstants_1.SPOT_MARKET_BALANCE_PRECISION,
16
- precisionExp: numericConstants_1.SPOT_MARKET_BALANCE_PRECISION_EXP,
15
+ precision: new __1.BN(10).pow(numericConstants_1.SIX),
16
+ precisionExp: numericConstants_1.SIX,
17
17
  },
18
18
  {
19
19
  symbol: 'SOL',
@@ -31,8 +31,8 @@ exports.DevnetSpotMarkets = [
31
31
  oracle: new web3_js_1.PublicKey('HovQMDrbAgAYPCmHVSrezcSmkMtXSSUsLDFANExrZh2J'),
32
32
  oracleSource: __1.OracleSource.PYTH,
33
33
  mint: new web3_js_1.PublicKey('3BZPwbcqB5kKScF3TEXxwNfx5ipV13kbRVDvfVp5c6fv'),
34
- precision: numericConstants_1.SPOT_MARKET_BALANCE_PRECISION,
35
- precisionExp: numericConstants_1.SPOT_MARKET_BALANCE_PRECISION_EXP,
34
+ precision: new __1.BN(10).pow(numericConstants_1.SIX),
35
+ precisionExp: numericConstants_1.SIX,
36
36
  serumMarket: new web3_js_1.PublicKey('AGsmbVu3MS9u68GEYABWosQQCZwmLcBHu4pWEuBYH7Za'),
37
37
  },
38
38
  ];
@@ -29,7 +29,7 @@ export declare type NodeToFill = {
29
29
  export declare type NodeToTrigger = {
30
30
  node: TriggerOrderNode;
31
31
  };
32
- declare type Side = 'ask' | 'bid' | 'both';
32
+ declare type Side = 'ask' | 'bid' | 'both' | 'nocross';
33
33
  export declare class DLOB {
34
34
  openOrders: Map<MarketTypeStr, Set<string>>;
35
35
  orderLists: Map<MarketTypeStr, Map<number, MarketNodeLists>>;
@@ -43,6 +43,7 @@ export declare class DLOB {
43
43
  * @param silent set to true to prevent logging on inserts and removals
44
44
  */
45
45
  constructor(perpMarkets: PerpMarketAccount[], spotMarkets: SpotMarketAccount[], silent?: boolean);
46
+ clear(): void;
46
47
  /**
47
48
  * initializes a new DLOB instance
48
49
  *
@@ -54,16 +55,17 @@ export declare class DLOB {
54
55
  trigger(order: Order, userAccount: PublicKey, onTrigger?: OrderBookCallback): void;
55
56
  getListForOrder(order: Order): NodeList<any> | undefined;
56
57
  findNodesToFill(marketIndex: number, vBid: BN | undefined, vAsk: BN | undefined, slot: number, marketType: MarketType, oraclePriceData: OraclePriceData): NodeToFill[];
57
- findCrossingNodesToFill(marketIndex: number, vBid: BN | undefined, vAsk: BN | undefined, slot: number, marketType: MarketType, oraclePriceData: OraclePriceData): NodeToFill[];
58
- findMarketNodesToFill(marketIndex: number, slot: number, marketType: MarketType): NodeToFill[];
58
+ findCrossingNodesToFill(marketIndex: number, slot: number, marketType: MarketType, oraclePriceData: OraclePriceData): NodeToFill[];
59
+ findvAMMCrossingNodesToFill(marketIndex: number, vBid: BN, vAsk: BN, slot: number, marketType: MarketType, oraclePriceData: OraclePriceData): NodeToFill[];
60
+ findExpiredMarketNodesToFill(marketIndex: number, slot: number, marketType: MarketType): NodeToFill[];
59
61
  findJitAuctionNodesToFill(marketIndex: number, slot: number, marketType: MarketType): NodeToFill[];
60
62
  getMarketBids(marketIndex: number, marketType: MarketType): Generator<DLOBNode>;
61
63
  getMarketAsks(marketIndex: number, marketType: MarketType): Generator<DLOBNode>;
62
64
  getAsks(marketIndex: number, vAsk: BN | undefined, slot: number, marketType: MarketType, oraclePriceData: OraclePriceData): Generator<DLOBNode>;
63
65
  getBids(marketIndex: number, vBid: BN | undefined, slot: number, marketType: MarketType, oraclePriceData: OraclePriceData): Generator<DLOBNode>;
64
66
  findCrossingOrders(askNode: DLOBNode, bidNode: DLOBNode, oraclePriceData: OraclePriceData, slot: number): {
65
- crossingNodes?: NodeToFill;
66
- exhaustedSide?: Side;
67
+ crossingNodes: NodeToFill[];
68
+ exhaustedSide: Side;
67
69
  };
68
70
  getBestAsk(marketIndex: number, vAsk: BN | undefined, slot: number, marketType: MarketType, oraclePriceData: OraclePriceData): BN;
69
71
  getBestBid(marketIndex: number, vBid: BN | undefined, slot: number, marketType: MarketType, oraclePriceData: OraclePriceData): BN;
package/lib/dlob/DLOB.js CHANGED
@@ -68,6 +68,29 @@ class DLOB {
68
68
  });
69
69
  }
70
70
  }
71
+ clear() {
72
+ for (const marketType of this.openOrders.keys()) {
73
+ this.openOrders.get(marketType).clear();
74
+ }
75
+ this.openOrders.clear();
76
+ for (const marketType of this.orderLists.keys()) {
77
+ for (const marketIndex of this.orderLists.get(marketType).keys()) {
78
+ const marketNodeLists = this.orderLists
79
+ .get(marketType)
80
+ .get(marketIndex);
81
+ for (const side of Object.keys(marketNodeLists)) {
82
+ for (const orderType of Object.keys(marketNodeLists[side])) {
83
+ marketNodeLists[side][orderType].clear();
84
+ }
85
+ }
86
+ }
87
+ }
88
+ this.orderLists.clear();
89
+ for (const marketType of this.marketIndexToAccount.keys()) {
90
+ this.marketIndexToAccount.get(marketType).clear();
91
+ }
92
+ this.marketIndexToAccount.clear();
93
+ }
71
94
  /**
72
95
  * initializes a new DLOB instance
73
96
  *
@@ -142,7 +165,7 @@ class DLOB {
142
165
  else if ((0, __1.isOneOfVariant)(order.orderType, ['market', 'triggerMarket'])) {
143
166
  type = 'market';
144
167
  }
145
- else if (order.oraclePriceOffset.gt(__1.ZERO)) {
168
+ else if (!order.oraclePriceOffset.eq(__1.ZERO)) {
146
169
  type = 'floatingLimit';
147
170
  }
148
171
  else {
@@ -163,15 +186,18 @@ class DLOB {
163
186
  }
164
187
  findNodesToFill(marketIndex, vBid, vAsk, slot, marketType, oraclePriceData) {
165
188
  // Find all the crossing nodes
166
- const crossingNodesToFill = this.findCrossingNodesToFill(marketIndex, vBid, vAsk, slot, marketType, oraclePriceData);
189
+ const crossingNodesToFill = this.findCrossingNodesToFill(marketIndex, slot, marketType, oraclePriceData);
190
+ const vAMMCrossingNodesToFill = this.findvAMMCrossingNodesToFill(marketIndex, vBid, vAsk, slot, marketType, oraclePriceData);
167
191
  // get expired market nodes
168
- const marketNodesToFill = this.findMarketNodesToFill(marketIndex, slot, marketType);
169
- return crossingNodesToFill.concat(marketNodesToFill);
192
+ const marketNodesToFill = this.findExpiredMarketNodesToFill(marketIndex, slot, marketType);
193
+ return crossingNodesToFill.concat(vAMMCrossingNodesToFill, marketNodesToFill);
170
194
  }
171
- findCrossingNodesToFill(marketIndex, vBid, vAsk, slot, marketType, oraclePriceData) {
195
+ findCrossingNodesToFill(marketIndex, slot, marketType, oraclePriceData) {
172
196
  const nodesToFill = new Array();
173
- const askGenerator = this.getAsks(marketIndex, vAsk, slot, marketType, oraclePriceData);
174
- const bidGenerator = this.getBids(marketIndex, vBid, slot, marketType, oraclePriceData);
197
+ const askGenerator = this.getAsks(marketIndex, undefined, // dont include vask
198
+ slot, marketType, oraclePriceData);
199
+ const bidGenerator = this.getBids(marketIndex, undefined, // dont include vbid
200
+ slot, marketType, oraclePriceData);
175
201
  let nextAsk = askGenerator.next();
176
202
  let nextBid = bidGenerator.next();
177
203
  // First try to find orders that cross
@@ -187,33 +213,71 @@ class DLOB {
187
213
  nextBid = bidGenerator.next();
188
214
  nextAsk = askGenerator.next();
189
215
  }
216
+ else if (exhaustedSide === 'nocross') {
217
+ break;
218
+ }
190
219
  else {
191
220
  console.error(`invalid exhaustedSide: ${exhaustedSide}`);
192
221
  break;
193
222
  }
194
- const takerIsMaker = (crossingNodes === null || crossingNodes === void 0 ? void 0 : crossingNodes.makerNode) !== undefined &&
195
- crossingNodes.node.userAccount.equals(crossingNodes.makerNode.userAccount);
196
- // Verify that each side is different user
197
- if (crossingNodes && !takerIsMaker) {
198
- nodesToFill.push(crossingNodes);
223
+ for (const crossingNode of crossingNodes) {
224
+ nodesToFill.push(crossingNode);
199
225
  }
200
226
  }
201
227
  return nodesToFill;
202
228
  }
203
- findMarketNodesToFill(marketIndex, slot, marketType) {
229
+ findvAMMCrossingNodesToFill(marketIndex, vBid, vAsk, slot, marketType, oraclePriceData) {
230
+ const nodesToFill = new Array();
231
+ const askGenerator = this.getAsks(marketIndex, undefined, // dont include vask
232
+ slot, marketType, oraclePriceData);
233
+ const bidGenerator = this.getBids(marketIndex, undefined, // dont include vbid
234
+ slot, marketType, oraclePriceData);
235
+ let nextAsk = askGenerator.next();
236
+ let nextBid = bidGenerator.next();
237
+ // check for asks that cross vBid
238
+ while (!nextAsk.done) {
239
+ const askNode = nextAsk.value;
240
+ const askPrice = askNode.getPrice(oraclePriceData, slot);
241
+ if (askPrice.lte(vBid) && (0, __1.isAuctionComplete)(askNode.order, slot)) {
242
+ nodesToFill.push({
243
+ node: askNode,
244
+ makerNode: undefined, // filled by vAMM
245
+ });
246
+ }
247
+ else {
248
+ break;
249
+ }
250
+ nextAsk = askGenerator.next();
251
+ }
252
+ // check for bids that cross vAsk
253
+ while (!nextBid.done) {
254
+ const bidNode = nextBid.value;
255
+ const bidPrice = bidNode.getPrice(oraclePriceData, slot);
256
+ if (bidPrice.gte(vAsk) && (0, __1.isAuctionComplete)(bidNode.order, slot)) {
257
+ nodesToFill.push({
258
+ node: bidNode,
259
+ makerNode: undefined, // filled by vAMM
260
+ });
261
+ }
262
+ else {
263
+ break;
264
+ }
265
+ nextBid = bidGenerator.next();
266
+ }
267
+ return nodesToFill;
268
+ }
269
+ findExpiredMarketNodesToFill(marketIndex, slot, marketType) {
204
270
  const nodesToFill = new Array();
205
271
  // Then see if there are orders to fill against vamm
206
272
  for (const marketBid of this.getMarketBids(marketIndex, marketType)) {
207
- if ((0, __1.isAuctionComplete)(marketBid.order, slot) ||
208
- (0, __1.isOrderExpired)(marketBid.order, slot)) {
273
+ if ((0, __1.isOrderExpired)(marketBid.order, slot)) {
209
274
  nodesToFill.push({
210
275
  node: marketBid,
211
276
  });
212
277
  }
213
278
  }
214
279
  for (const marketAsk of this.getMarketAsks(marketIndex, marketType)) {
215
- if ((0, __1.isAuctionComplete)(marketAsk.order, slot) ||
216
- (0, __1.isOrderExpired)(marketAsk.order, slot)) {
280
+ if ((0, __1.isOrderExpired)(marketAsk.order, slot)) {
217
281
  nodesToFill.push({
218
282
  node: marketAsk,
219
283
  });
@@ -265,9 +329,12 @@ class DLOB {
265
329
  nodeLists.floatingLimit.ask.getGenerator(),
266
330
  nodeLists.market.ask.getGenerator(),
267
331
  ];
268
- if (marketTypeStr === 'perp') {
332
+ if (marketTypeStr === 'perp' && vAsk) {
269
333
  generatorList.push((0, NodeList_1.getVammNodeGenerator)(vAsk));
270
334
  }
335
+ if (generatorList.length === 0) {
336
+ throw new Error('No ask generators found');
337
+ }
271
338
  const askGenerators = generatorList.map((generator) => {
272
339
  return {
273
340
  next: generator.next(),
@@ -290,6 +357,11 @@ class DLOB {
290
357
  : currentGenerator;
291
358
  });
292
359
  if (!bestGenerator.next.done) {
360
+ // skip this node if it's already completely filled
361
+ if (bestGenerator.next.value.isBaseFilled()) {
362
+ bestGenerator.next = bestGenerator.generator.next();
363
+ continue;
364
+ }
293
365
  yield bestGenerator.next.value;
294
366
  bestGenerator.next = bestGenerator.generator.next();
295
367
  }
@@ -309,9 +381,12 @@ class DLOB {
309
381
  nodeLists.floatingLimit.bid.getGenerator(),
310
382
  nodeLists.market.bid.getGenerator(),
311
383
  ];
312
- if (marketTypeStr === 'perp') {
384
+ if (marketTypeStr === 'perp' && vBid) {
313
385
  generatorList.push((0, NodeList_1.getVammNodeGenerator)(vBid));
314
386
  }
387
+ if (generatorList.length === 0) {
388
+ throw new Error('No bid generators found');
389
+ }
315
390
  const bidGenerators = generatorList.map((generator) => {
316
391
  return {
317
392
  next: generator.next(),
@@ -334,6 +409,11 @@ class DLOB {
334
409
  : currentGenerator;
335
410
  });
336
411
  if (!bestGenerator.next.done) {
412
+ // skip this node if it's already completely filled
413
+ if (bestGenerator.next.value.isBaseFilled()) {
414
+ bestGenerator.next = bestGenerator.generator.next();
415
+ continue;
416
+ }
337
417
  yield bestGenerator.next.value;
338
418
  bestGenerator.next = bestGenerator.generator.next();
339
419
  }
@@ -345,54 +425,24 @@ class DLOB {
345
425
  findCrossingOrders(askNode, bidNode, oraclePriceData, slot) {
346
426
  const bidPrice = bidNode.getPrice(oraclePriceData, slot);
347
427
  const askPrice = askNode.getPrice(oraclePriceData, slot);
348
- const bidOrder = bidNode.order;
349
- const askOrder = askNode.order;
350
- const containsMarketOrder = (askOrder && (0, __1.isVariant)(askOrder.orderType, 'market')) ||
351
- (bidOrder && (0, __1.isVariant)(bidOrder.orderType, 'market'));
352
- if (!containsMarketOrder || bidPrice.lt(askPrice)) {
353
- // no market orders, and no crossing orders - return
354
- if (askNode.isVammNode() && !bidNode.isVammNode()) {
355
- return {
356
- exhaustedSide: 'bid',
357
- };
358
- }
359
- else if (!askNode.isVammNode() && bidNode.isVammNode()) {
360
- return {
361
- exhaustedSide: 'ask',
362
- };
363
- }
364
- else {
365
- return {
366
- exhaustedSide: 'both',
367
- };
368
- }
369
- }
370
- // User bid crosses the vamm ask
371
- if (askNode.isVammNode()) {
372
- if (!(0, __1.isAuctionComplete)(bidOrder, slot)) {
373
- return {
374
- exhaustedSide: 'bid',
375
- };
376
- }
428
+ // orders don't cross - we're done walkin gup the book
429
+ if (bidPrice.lt(askPrice)) {
377
430
  return {
378
- crossingNodes: {
379
- node: bidNode,
380
- },
381
- exhaustedSide: 'bid',
431
+ crossingNodes: [],
432
+ exhaustedSide: 'nocross',
382
433
  };
383
434
  }
384
- // User ask crosses the vamm bid
385
- if (bidNode.isVammNode()) {
386
- if (!(0, __1.isAuctionComplete)(askOrder, slot)) {
387
- return {
388
- exhaustedSide: 'ask',
389
- };
390
- }
435
+ const bidOrder = bidNode.order;
436
+ const askOrder = askNode.order;
437
+ // Can't match two maker orders or if maker and taker are the same
438
+ const makerIsTaker = bidNode.userAccount.equals(askNode.userAccount);
439
+ if (makerIsTaker || (bidOrder.postOnly && askOrder.postOnly)) {
440
+ // don't have a principle way to pick which one to exhaust,
441
+ // exhaust each one 50% of the time so we can try each one against other orders
442
+ const exhaustedSide = Math.random() < 0.5 ? 'bid' : 'ask';
391
443
  return {
392
- crossingNodes: {
393
- node: askNode,
394
- },
395
- exhaustedSide: 'ask',
444
+ crossingNodes: [],
445
+ exhaustedSide,
396
446
  };
397
447
  }
398
448
  const bidBaseRemaining = bidOrder.baseAssetAmount.sub(bidOrder.baseAssetAmountFilled);
@@ -407,44 +457,63 @@ class DLOB {
407
457
  else {
408
458
  exhaustedSide = 'bid';
409
459
  }
410
- // Can't match two maker orders
411
- if (bidOrder.postOnly && askOrder.postOnly) {
412
- return {
413
- exhaustedSide,
414
- };
415
- }
416
- // update the orders as if they fill - so we don't try to match them on the next iteration
460
+ // update the orders on DLOB as if they were fill - so we don't try to match them on the next iteration
461
+ // NOTE: if something goes wrong during the actual fill (transaction fails, i.e. due to orders already being filled)
462
+ // then we risk having a mismatch between this local DLOB and the actual DLOB state on the blockchain. This isn't
463
+ // a problem in the current implementation because we construct a new DLOB from the blockchain state every time, rather
464
+ // than updating the existing DLOB based on events.
417
465
  if (exhaustedSide === 'ask') {
418
- bidNode.order.baseAssetAmountFilled =
466
+ // bid partially filled
467
+ const newBidOrder = { ...bidOrder };
468
+ newBidOrder.baseAssetAmountFilled =
419
469
  bidOrder.baseAssetAmountFilled.add(askBaseRemaining);
420
- askNode.order.baseAssetAmountFilled = askOrder.baseAssetAmount;
470
+ this.getListForOrder(newBidOrder).update(newBidOrder, bidNode.userAccount);
471
+ // ask completely filled
472
+ const newAskOrder = { ...askOrder };
473
+ newAskOrder.baseAssetAmountFilled = askOrder.baseAssetAmount;
474
+ this.getListForOrder(newAskOrder).update(newAskOrder, askNode.userAccount);
421
475
  }
422
476
  else if (exhaustedSide === 'bid') {
423
- askNode.order.baseAssetAmountFilled =
477
+ // ask partially filled
478
+ const newAskOrder = { ...askOrder };
479
+ newAskOrder.baseAssetAmountFilled =
424
480
  askOrder.baseAssetAmountFilled.add(bidBaseRemaining);
425
- bidNode.order.baseAssetAmountFilled = bidOrder.baseAssetAmount;
481
+ this.getListForOrder(newAskOrder).update(newAskOrder, askNode.userAccount);
482
+ // bid completely filled
483
+ const newBidOrder = { ...bidOrder };
484
+ newBidOrder.baseAssetAmountFilled = bidOrder.baseAssetAmount;
485
+ this.getListForOrder(newBidOrder).update(newBidOrder, bidNode.userAccount);
426
486
  }
427
487
  else {
428
- askNode.order.baseAssetAmountFilled = askOrder.baseAssetAmount;
429
- bidNode.order.baseAssetAmountFilled = bidOrder.baseAssetAmount;
488
+ // both completely filled
489
+ const newBidOrder = { ...bidOrder };
490
+ newBidOrder.baseAssetAmountFilled = bidOrder.baseAssetAmount;
491
+ this.getListForOrder(newBidOrder).update(newBidOrder, bidNode.userAccount);
492
+ const newAskOrder = { ...askOrder };
493
+ newAskOrder.baseAssetAmountFilled = askOrder.baseAssetAmount;
494
+ this.getListForOrder(newAskOrder).update(newAskOrder, askNode.userAccount);
430
495
  }
431
496
  // Bid is maker
432
497
  if (bidOrder.postOnly) {
433
498
  return {
434
- crossingNodes: {
435
- node: askNode,
436
- makerNode: bidNode,
437
- },
499
+ crossingNodes: [
500
+ {
501
+ node: askNode,
502
+ makerNode: bidNode,
503
+ },
504
+ ],
438
505
  exhaustedSide,
439
506
  };
440
507
  }
441
508
  // Ask is maker
442
509
  if (askOrder.postOnly) {
443
510
  return {
444
- crossingNodes: {
445
- node: bidNode,
446
- makerNode: askNode,
447
- },
511
+ crossingNodes: [
512
+ {
513
+ node: bidNode,
514
+ makerNode: askNode,
515
+ },
516
+ ],
448
517
  exhaustedSide,
449
518
  };
450
519
  }
@@ -454,10 +523,12 @@ class DLOB {
454
523
  ? [askNode, bidNode]
455
524
  : [bidNode, askNode];
456
525
  return {
457
- crossingNodes: {
458
- node: newerNode,
459
- makerNode: olderNode,
460
- },
526
+ crossingNodes: [
527
+ {
528
+ node: newerNode,
529
+ makerNode: olderNode,
530
+ },
531
+ ],
461
532
  exhaustedSide,
462
533
  };
463
534
  }
@@ -5,6 +5,7 @@ export interface DLOBNode {
5
5
  getPrice(oraclePriceData: OraclePriceData, slot: number): BN;
6
6
  isVammNode(): boolean;
7
7
  order: Order | undefined;
8
+ isBaseFilled(): boolean;
8
9
  haveFilled: boolean;
9
10
  userAccount: PublicKey | undefined;
10
11
  market: SpotMarketAccount | PerpMarketAccount;
@@ -20,6 +21,7 @@ export declare abstract class OrderNode implements DLOBNode {
20
21
  abstract getSortValue(order: Order): BN;
21
22
  getLabel(): string;
22
23
  getPrice(oraclePriceData: OraclePriceData, slot: number): BN;
24
+ isBaseFilled(): boolean;
23
25
  isVammNode(): boolean;
24
26
  }
25
27
  export declare class LimitOrderNode extends OrderNode {
@@ -36,6 +36,9 @@ class OrderNode {
36
36
  console.error(`Unknown market type: ${this.order.marketType}`);
37
37
  }
38
38
  }
39
+ isBaseFilled() {
40
+ return this.order.baseAssetAmountFilled.eq(this.order.baseAssetAmount);
41
+ }
39
42
  isVammNode() {
40
43
  return false;
41
44
  }
@@ -3,7 +3,7 @@ import { BN, MarketTypeStr, Order, PerpMarketAccount, SpotMarketAccount } from '
3
3
  import { PublicKey } from '@solana/web3.js';
4
4
  import { DLOBNode, DLOBNodeMap } from './DLOBNode';
5
5
  export declare type SortDirection = 'asc' | 'desc';
6
- export declare function getOrderSignature(orderId: BN, userAccount: PublicKey): string;
6
+ export declare function getOrderSignature(orderId: number, userAccount: PublicKey): string;
7
7
  export interface DLOBNodeGenerator {
8
8
  getGenerator(): Generator<DLOBNode>;
9
9
  }
@@ -14,6 +14,7 @@ export declare class NodeList<NodeType extends keyof DLOBNodeMap> implements DLO
14
14
  length: number;
15
15
  nodeMap: Map<string, DLOBNodeMap[NodeType]>;
16
16
  constructor(nodeType: NodeType, sortDirection: SortDirection);
17
+ clear(): void;
17
18
  insert(order: Order, marketType: MarketTypeStr, market: PerpMarketAccount | SpotMarketAccount, userAccount: PublicKey): void;
18
19
  prependNode(currentNode: DLOBNodeMap[NodeType], newNode: DLOBNodeMap[NodeType]): boolean;
19
20
  update(order: Order, userAccount: PublicKey): void;
@@ -14,6 +14,11 @@ class NodeList {
14
14
  this.length = 0;
15
15
  this.nodeMap = new Map();
16
16
  }
17
+ clear() {
18
+ this.head = undefined;
19
+ this.length = 0;
20
+ this.nodeMap.clear();
21
+ }
17
22
  insert(order, marketType, market, userAccount) {
18
23
  if ((0, __1.isVariant)(order.status, 'init')) {
19
24
  return;
@@ -25,11 +30,11 @@ class NodeList {
25
30
  market = market;
26
31
  }
27
32
  const newNode = (0, DLOBNode_1.createNode)(this.nodeType, order, market, userAccount);
28
- const orderId = getOrderSignature(order.orderId, userAccount);
29
- if (this.nodeMap.has(orderId)) {
33
+ const orderSignature = getOrderSignature(order.orderId, userAccount);
34
+ if (this.nodeMap.has(orderSignature)) {
30
35
  return;
31
36
  }
32
- this.nodeMap.set(orderId, newNode);
37
+ this.nodeMap.set(orderSignature, newNode);
33
38
  this.length += 1;
34
39
  if (this.head === undefined) {
35
40
  this.head = newNode;
@@ -86,7 +91,7 @@ class NodeList {
86
91
  if (node.previous) {
87
92
  node.previous.next = node.next;
88
93
  }
89
- if (this.head && node.order.orderId.eq(this.head.order.orderId)) {
94
+ if (this.head && node.order.orderId === this.head.order.orderId) {
90
95
  this.head = node.next;
91
96
  }
92
97
  node.previous = undefined;
@@ -132,6 +137,7 @@ function* getVammNodeGenerator(price) {
132
137
  order: undefined,
133
138
  market: undefined,
134
139
  userAccount: undefined,
140
+ isBaseFilled: () => false,
135
141
  haveFilled: false,
136
142
  };
137
143
  }