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

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 (50) 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 +6 -5
  12. package/lib/dlob/DLOB.js +130 -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 +1 -1
  16. package/lib/dlob/NodeList.js +5 -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 +117 -40
  29. package/lib/types.js +33 -3
  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 +188 -103
  38. package/src/dlob/DLOBNode.ts +5 -0
  39. package/src/dlob/NodeList.ts +9 -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 +81 -40
  48. package/tests/bn/test.ts +12 -7
  49. package/tests/dlob/helpers.ts +56 -33
  50. package/tests/dlob/test.ts +1227 -404
@@ -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>>;
@@ -54,16 +54,17 @@ export declare class DLOB {
54
54
  trigger(order: Order, userAccount: PublicKey, onTrigger?: OrderBookCallback): void;
55
55
  getListForOrder(order: Order): NodeList<any> | undefined;
56
56
  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[];
57
+ findCrossingNodesToFill(marketIndex: number, slot: number, marketType: MarketType, oraclePriceData: OraclePriceData): NodeToFill[];
58
+ findvAMMCrossingNodesToFill(marketIndex: number, vBid: BN, vAsk: BN, slot: number, marketType: MarketType, oraclePriceData: OraclePriceData): NodeToFill[];
59
+ findExpiredMarketNodesToFill(marketIndex: number, slot: number, marketType: MarketType): NodeToFill[];
59
60
  findJitAuctionNodesToFill(marketIndex: number, slot: number, marketType: MarketType): NodeToFill[];
60
61
  getMarketBids(marketIndex: number, marketType: MarketType): Generator<DLOBNode>;
61
62
  getMarketAsks(marketIndex: number, marketType: MarketType): Generator<DLOBNode>;
62
63
  getAsks(marketIndex: number, vAsk: BN | undefined, slot: number, marketType: MarketType, oraclePriceData: OraclePriceData): Generator<DLOBNode>;
63
64
  getBids(marketIndex: number, vBid: BN | undefined, slot: number, marketType: MarketType, oraclePriceData: OraclePriceData): Generator<DLOBNode>;
64
65
  findCrossingOrders(askNode: DLOBNode, bidNode: DLOBNode, oraclePriceData: OraclePriceData, slot: number): {
65
- crossingNodes?: NodeToFill;
66
- exhaustedSide?: Side;
66
+ crossingNodes: NodeToFill[];
67
+ exhaustedSide: Side;
67
68
  };
68
69
  getBestAsk(marketIndex: number, vAsk: BN | undefined, slot: number, marketType: MarketType, oraclePriceData: OraclePriceData): BN;
69
70
  getBestBid(marketIndex: number, vBid: BN | undefined, slot: number, marketType: MarketType, oraclePriceData: OraclePriceData): BN;
package/lib/dlob/DLOB.js CHANGED
@@ -142,7 +142,7 @@ class DLOB {
142
142
  else if ((0, __1.isOneOfVariant)(order.orderType, ['market', 'triggerMarket'])) {
143
143
  type = 'market';
144
144
  }
145
- else if (order.oraclePriceOffset.gt(__1.ZERO)) {
145
+ else if (!order.oraclePriceOffset.eq(__1.ZERO)) {
146
146
  type = 'floatingLimit';
147
147
  }
148
148
  else {
@@ -163,15 +163,18 @@ class DLOB {
163
163
  }
164
164
  findNodesToFill(marketIndex, vBid, vAsk, slot, marketType, oraclePriceData) {
165
165
  // Find all the crossing nodes
166
- const crossingNodesToFill = this.findCrossingNodesToFill(marketIndex, vBid, vAsk, slot, marketType, oraclePriceData);
166
+ const crossingNodesToFill = this.findCrossingNodesToFill(marketIndex, slot, marketType, oraclePriceData);
167
+ const vAMMCrossingNodesToFill = this.findvAMMCrossingNodesToFill(marketIndex, vBid, vAsk, slot, marketType, oraclePriceData);
167
168
  // get expired market nodes
168
- const marketNodesToFill = this.findMarketNodesToFill(marketIndex, slot, marketType);
169
- return crossingNodesToFill.concat(marketNodesToFill);
169
+ const marketNodesToFill = this.findExpiredMarketNodesToFill(marketIndex, slot, marketType);
170
+ return crossingNodesToFill.concat(vAMMCrossingNodesToFill, marketNodesToFill);
170
171
  }
171
- findCrossingNodesToFill(marketIndex, vBid, vAsk, slot, marketType, oraclePriceData) {
172
+ findCrossingNodesToFill(marketIndex, slot, marketType, oraclePriceData) {
172
173
  const nodesToFill = new Array();
173
- const askGenerator = this.getAsks(marketIndex, vAsk, slot, marketType, oraclePriceData);
174
- const bidGenerator = this.getBids(marketIndex, vBid, slot, marketType, oraclePriceData);
174
+ const askGenerator = this.getAsks(marketIndex, undefined, // dont include vask
175
+ slot, marketType, oraclePriceData);
176
+ const bidGenerator = this.getBids(marketIndex, undefined, // dont include vbid
177
+ slot, marketType, oraclePriceData);
175
178
  let nextAsk = askGenerator.next();
176
179
  let nextBid = bidGenerator.next();
177
180
  // First try to find orders that cross
@@ -187,33 +190,71 @@ class DLOB {
187
190
  nextBid = bidGenerator.next();
188
191
  nextAsk = askGenerator.next();
189
192
  }
193
+ else if (exhaustedSide === 'nocross') {
194
+ break;
195
+ }
190
196
  else {
191
197
  console.error(`invalid exhaustedSide: ${exhaustedSide}`);
192
198
  break;
193
199
  }
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);
200
+ for (const crossingNode of crossingNodes) {
201
+ nodesToFill.push(crossingNode);
199
202
  }
200
203
  }
201
204
  return nodesToFill;
202
205
  }
203
- findMarketNodesToFill(marketIndex, slot, marketType) {
206
+ findvAMMCrossingNodesToFill(marketIndex, vBid, vAsk, slot, marketType, oraclePriceData) {
207
+ const nodesToFill = new Array();
208
+ const askGenerator = this.getAsks(marketIndex, undefined, // dont include vask
209
+ slot, marketType, oraclePriceData);
210
+ const bidGenerator = this.getBids(marketIndex, undefined, // dont include vbid
211
+ slot, marketType, oraclePriceData);
212
+ let nextAsk = askGenerator.next();
213
+ let nextBid = bidGenerator.next();
214
+ // check for asks that cross vBid
215
+ while (!nextAsk.done) {
216
+ const askNode = nextAsk.value;
217
+ const askPrice = askNode.getPrice(oraclePriceData, slot);
218
+ if (askPrice.lte(vBid) && (0, __1.isAuctionComplete)(askNode.order, slot)) {
219
+ nodesToFill.push({
220
+ node: askNode,
221
+ makerNode: undefined, // filled by vAMM
222
+ });
223
+ }
224
+ else {
225
+ break;
226
+ }
227
+ nextAsk = askGenerator.next();
228
+ }
229
+ // check for bids that cross vAsk
230
+ while (!nextBid.done) {
231
+ const bidNode = nextBid.value;
232
+ const bidPrice = bidNode.getPrice(oraclePriceData, slot);
233
+ if (bidPrice.gte(vAsk) && (0, __1.isAuctionComplete)(bidNode.order, slot)) {
234
+ nodesToFill.push({
235
+ node: bidNode,
236
+ makerNode: undefined, // filled by vAMM
237
+ });
238
+ }
239
+ else {
240
+ break;
241
+ }
242
+ nextBid = bidGenerator.next();
243
+ }
244
+ return nodesToFill;
245
+ }
246
+ findExpiredMarketNodesToFill(marketIndex, slot, marketType) {
204
247
  const nodesToFill = new Array();
205
248
  // Then see if there are orders to fill against vamm
206
249
  for (const marketBid of this.getMarketBids(marketIndex, marketType)) {
207
- if ((0, __1.isAuctionComplete)(marketBid.order, slot) ||
208
- (0, __1.isOrderExpired)(marketBid.order, slot)) {
250
+ if ((0, __1.isOrderExpired)(marketBid.order, slot)) {
209
251
  nodesToFill.push({
210
252
  node: marketBid,
211
253
  });
212
254
  }
213
255
  }
214
256
  for (const marketAsk of this.getMarketAsks(marketIndex, marketType)) {
215
- if ((0, __1.isAuctionComplete)(marketAsk.order, slot) ||
216
- (0, __1.isOrderExpired)(marketAsk.order, slot)) {
257
+ if ((0, __1.isOrderExpired)(marketAsk.order, slot)) {
217
258
  nodesToFill.push({
218
259
  node: marketAsk,
219
260
  });
@@ -265,7 +306,7 @@ class DLOB {
265
306
  nodeLists.floatingLimit.ask.getGenerator(),
266
307
  nodeLists.market.ask.getGenerator(),
267
308
  ];
268
- if (marketTypeStr === 'perp') {
309
+ if (marketTypeStr === 'perp' && vAsk) {
269
310
  generatorList.push((0, NodeList_1.getVammNodeGenerator)(vAsk));
270
311
  }
271
312
  const askGenerators = generatorList.map((generator) => {
@@ -290,6 +331,11 @@ class DLOB {
290
331
  : currentGenerator;
291
332
  });
292
333
  if (!bestGenerator.next.done) {
334
+ // skip this node if it's already completely filled
335
+ if (bestGenerator.next.value.isBaseFilled()) {
336
+ bestGenerator.next = bestGenerator.generator.next();
337
+ continue;
338
+ }
293
339
  yield bestGenerator.next.value;
294
340
  bestGenerator.next = bestGenerator.generator.next();
295
341
  }
@@ -309,7 +355,7 @@ class DLOB {
309
355
  nodeLists.floatingLimit.bid.getGenerator(),
310
356
  nodeLists.market.bid.getGenerator(),
311
357
  ];
312
- if (marketTypeStr === 'perp') {
358
+ if (marketTypeStr === 'perp' && vBid) {
313
359
  generatorList.push((0, NodeList_1.getVammNodeGenerator)(vBid));
314
360
  }
315
361
  const bidGenerators = generatorList.map((generator) => {
@@ -334,6 +380,11 @@ class DLOB {
334
380
  : currentGenerator;
335
381
  });
336
382
  if (!bestGenerator.next.done) {
383
+ // skip this node if it's already completely filled
384
+ if (bestGenerator.next.value.isBaseFilled()) {
385
+ bestGenerator.next = bestGenerator.generator.next();
386
+ continue;
387
+ }
337
388
  yield bestGenerator.next.value;
338
389
  bestGenerator.next = bestGenerator.generator.next();
339
390
  }
@@ -345,54 +396,24 @@ class DLOB {
345
396
  findCrossingOrders(askNode, bidNode, oraclePriceData, slot) {
346
397
  const bidPrice = bidNode.getPrice(oraclePriceData, slot);
347
398
  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
- }
399
+ // orders don't cross - we're done walkin gup the book
400
+ if (bidPrice.lt(askPrice)) {
377
401
  return {
378
- crossingNodes: {
379
- node: bidNode,
380
- },
381
- exhaustedSide: 'bid',
402
+ crossingNodes: [],
403
+ exhaustedSide: 'nocross',
382
404
  };
383
405
  }
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
- }
406
+ const bidOrder = bidNode.order;
407
+ const askOrder = askNode.order;
408
+ // Can't match two maker orders or if maker and taker are the same
409
+ const makerIsTaker = bidNode.userAccount.equals(askNode.userAccount);
410
+ if (makerIsTaker || (bidOrder.postOnly && askOrder.postOnly)) {
411
+ // don't have a principle way to pick which one to exhaust,
412
+ // exhaust each one 50% of the time so we can try each one against other orders
413
+ const exhaustedSide = Math.random() < 0.5 ? 'bid' : 'ask';
391
414
  return {
392
- crossingNodes: {
393
- node: askNode,
394
- },
395
- exhaustedSide: 'ask',
415
+ crossingNodes: [],
416
+ exhaustedSide,
396
417
  };
397
418
  }
398
419
  const bidBaseRemaining = bidOrder.baseAssetAmount.sub(bidOrder.baseAssetAmountFilled);
@@ -407,44 +428,63 @@ class DLOB {
407
428
  else {
408
429
  exhaustedSide = 'bid';
409
430
  }
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
431
+ // update the orders on DLOB as if they were fill - so we don't try to match them on the next iteration
432
+ // NOTE: if something goes wrong during the actual fill (transaction fails, i.e. due to orders already being filled)
433
+ // then we risk having a mismatch between this local DLOB and the actual DLOB state on the blockchain. This isn't
434
+ // a problem in the current implementation because we construct a new DLOB from the blockchain state every time, rather
435
+ // than updating the existing DLOB based on events.
417
436
  if (exhaustedSide === 'ask') {
418
- bidNode.order.baseAssetAmountFilled =
437
+ // bid partially filled
438
+ const newBidOrder = { ...bidOrder };
439
+ newBidOrder.baseAssetAmountFilled =
419
440
  bidOrder.baseAssetAmountFilled.add(askBaseRemaining);
420
- askNode.order.baseAssetAmountFilled = askOrder.baseAssetAmount;
441
+ this.getListForOrder(newBidOrder).update(newBidOrder, bidNode.userAccount);
442
+ // ask completely filled
443
+ const newAskOrder = { ...askOrder };
444
+ newAskOrder.baseAssetAmountFilled = askOrder.baseAssetAmount;
445
+ this.getListForOrder(newAskOrder).update(newAskOrder, askNode.userAccount);
421
446
  }
422
447
  else if (exhaustedSide === 'bid') {
423
- askNode.order.baseAssetAmountFilled =
448
+ // ask partially filled
449
+ const newAskOrder = { ...askOrder };
450
+ newAskOrder.baseAssetAmountFilled =
424
451
  askOrder.baseAssetAmountFilled.add(bidBaseRemaining);
425
- bidNode.order.baseAssetAmountFilled = bidOrder.baseAssetAmount;
452
+ this.getListForOrder(newAskOrder).update(newAskOrder, askNode.userAccount);
453
+ // bid completely filled
454
+ const newBidOrder = { ...bidOrder };
455
+ newBidOrder.baseAssetAmountFilled = bidOrder.baseAssetAmount;
456
+ this.getListForOrder(newBidOrder).update(newBidOrder, bidNode.userAccount);
426
457
  }
427
458
  else {
428
- askNode.order.baseAssetAmountFilled = askOrder.baseAssetAmount;
429
- bidNode.order.baseAssetAmountFilled = bidOrder.baseAssetAmount;
459
+ // both completely filled
460
+ const newBidOrder = { ...bidOrder };
461
+ newBidOrder.baseAssetAmountFilled = bidOrder.baseAssetAmount;
462
+ this.getListForOrder(newBidOrder).update(newBidOrder, bidNode.userAccount);
463
+ const newAskOrder = { ...askOrder };
464
+ newAskOrder.baseAssetAmountFilled = askOrder.baseAssetAmount;
465
+ this.getListForOrder(newAskOrder).update(newAskOrder, askNode.userAccount);
430
466
  }
431
467
  // Bid is maker
432
468
  if (bidOrder.postOnly) {
433
469
  return {
434
- crossingNodes: {
435
- node: askNode,
436
- makerNode: bidNode,
437
- },
470
+ crossingNodes: [
471
+ {
472
+ node: askNode,
473
+ makerNode: bidNode,
474
+ },
475
+ ],
438
476
  exhaustedSide,
439
477
  };
440
478
  }
441
479
  // Ask is maker
442
480
  if (askOrder.postOnly) {
443
481
  return {
444
- crossingNodes: {
445
- node: bidNode,
446
- makerNode: askNode,
447
- },
482
+ crossingNodes: [
483
+ {
484
+ node: bidNode,
485
+ makerNode: askNode,
486
+ },
487
+ ],
448
488
  exhaustedSide,
449
489
  };
450
490
  }
@@ -454,10 +494,12 @@ class DLOB {
454
494
  ? [askNode, bidNode]
455
495
  : [bidNode, askNode];
456
496
  return {
457
- crossingNodes: {
458
- node: newerNode,
459
- makerNode: olderNode,
460
- },
497
+ crossingNodes: [
498
+ {
499
+ node: newerNode,
500
+ makerNode: olderNode,
501
+ },
502
+ ],
461
503
  exhaustedSide,
462
504
  };
463
505
  }
@@ -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
  }
@@ -25,11 +25,11 @@ class NodeList {
25
25
  market = market;
26
26
  }
27
27
  const newNode = (0, DLOBNode_1.createNode)(this.nodeType, order, market, userAccount);
28
- const orderId = getOrderSignature(order.orderId, userAccount);
29
- if (this.nodeMap.has(orderId)) {
28
+ const orderSignature = getOrderSignature(order.orderId, userAccount);
29
+ if (this.nodeMap.has(orderSignature)) {
30
30
  return;
31
31
  }
32
- this.nodeMap.set(orderId, newNode);
32
+ this.nodeMap.set(orderSignature, newNode);
33
33
  this.length += 1;
34
34
  if (this.head === undefined) {
35
35
  this.head = newNode;
@@ -86,7 +86,7 @@ class NodeList {
86
86
  if (node.previous) {
87
87
  node.previous.next = node.next;
88
88
  }
89
- if (this.head && node.order.orderId.eq(this.head.order.orderId)) {
89
+ if (this.head && node.order.orderId === this.head.order.orderId) {
90
90
  this.head = node.next;
91
91
  }
92
92
  node.previous = undefined;
@@ -132,6 +132,7 @@ function* getVammNodeGenerator(price) {
132
132
  order: undefined,
133
133
  market: undefined,
134
134
  userAccount: undefined,
135
+ isBaseFilled: () => false,
135
136
  haveFilled: false,
136
137
  };
137
138
  }
@@ -1,5 +1,5 @@
1
1
  import { Commitment, TransactionSignature } from '@solana/web3.js';
2
- import { DepositRecord, FundingPaymentRecord, FundingRateRecord, LiquidationRecord, NewUserRecord, OrderActionRecord, OrderRecord, SettlePnlRecord, LPRecord, InsuranceFundRecord } from '../index';
2
+ import { DepositRecord, FundingPaymentRecord, FundingRateRecord, LiquidationRecord, NewUserRecord, OrderActionRecord, OrderRecord, SettlePnlRecord, LPRecord, InsuranceFundRecord, SpotInterestRecord } from '../index';
3
3
  export declare type EventSubscriptionOptions = {
4
4
  eventTypes?: EventType[];
5
5
  maxEventsPerType?: number;
@@ -32,6 +32,7 @@ export declare type EventMap = {
32
32
  NewUserRecord: Event<NewUserRecord>;
33
33
  LPRecord: Event<LPRecord>;
34
34
  InsuranceFundRecord: Event<InsuranceFundRecord>;
35
+ SpotInterestRecord: Event<SpotInterestRecord>;
35
36
  };
36
37
  export declare type EventType = keyof EventMap;
37
38
  export interface EventSubscriberEvents {
@@ -13,6 +13,7 @@ exports.DefaultEventSubscriptionOptions = {
13
13
  'SettlePnlRecord',
14
14
  'LPRecord',
15
15
  'InsuranceFundRecord',
16
+ 'SpotInterestRecord',
16
17
  ],
17
18
  maxEventsPerType: 4096,
18
19
  orderBy: 'blockchain',
@@ -69,6 +69,7 @@ export declare class BigNum {
69
69
  * @returns
70
70
  */
71
71
  toFixed(fixedPrecision: number): string;
72
+ private getZeroes;
72
73
  /**
73
74
  * Pretty print to the specified number of significant figures
74
75
  * @param fixedPrecision