@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
@@ -134,7 +134,7 @@ export class ClearingHouseUser {
134
134
  public getEmptyPosition(marketIndex: number): PerpPosition {
135
135
  return {
136
136
  baseAssetAmount: ZERO,
137
- remainderBaseAssetAmount: ZERO,
137
+ remainderBaseAssetAmount: 0,
138
138
  lastCumulativeFundingRate: ZERO,
139
139
  marketIndex,
140
140
  quoteAssetAmount: ZERO,
@@ -144,7 +144,6 @@ export class ClearingHouseUser {
144
144
  openAsks: ZERO,
145
145
  settledPnl: ZERO,
146
146
  lpShares: ZERO,
147
- lastFeePerLp: ZERO,
148
147
  lastNetBaseAssetAmountPerLp: ZERO,
149
148
  lastNetQuoteAssetAmountPerLp: ZERO,
150
149
  };
@@ -159,9 +158,9 @@ export class ClearingHouseUser {
159
158
  * @param orderId
160
159
  * @returns Order
161
160
  */
162
- public getOrder(orderId: BN): Order | undefined {
163
- return this.getUserAccount().orders.find((order) =>
164
- order.orderId.eq(orderId)
161
+ public getOrder(orderId: number): Order | undefined {
162
+ return this.getUserAccount().orders.find(
163
+ (order) => order.orderId === orderId
165
164
  );
166
165
  }
167
166
 
@@ -229,13 +228,11 @@ export class ClearingHouseUser {
229
228
  market.amm.baseAssetAmountStepSize
230
229
  );
231
230
 
232
- position.remainderBaseAssetAmount =
233
- position.remainderBaseAssetAmount.add(remainderBaa);
231
+ position.remainderBaseAssetAmount += remainderBaa.toNumber();
234
232
 
235
233
  if (
236
- position.remainderBaseAssetAmount
237
- .abs()
238
- .gte(market.amm.baseAssetAmountStepSize)
234
+ Math.abs(position.remainderBaseAssetAmount) >
235
+ market.amm.baseAssetAmountStepSize.toNumber()
239
236
  ) {
240
237
  const [newStandardizedBaa, newRemainderBaa] = standardize(
241
238
  position.remainderBaseAssetAmount,
@@ -243,7 +240,7 @@ export class ClearingHouseUser {
243
240
  );
244
241
  position.baseAssetAmount =
245
242
  position.baseAssetAmount.add(newStandardizedBaa);
246
- position.remainderBaseAssetAmount = newRemainderBaa;
243
+ position.remainderBaseAssetAmount = newRemainderBaa.toNumber();
247
244
  }
248
245
 
249
246
  let updateType;
@@ -492,7 +489,7 @@ export class ClearingHouseUser {
492
489
  let newTotalLiabilityValue = totalLiabilityValue;
493
490
  if (worstCaseTokenAmount.lt(ZERO)) {
494
491
  const baseLiabilityValue = this.getSpotLiabilityValue(
495
- worstCaseTokenAmount,
492
+ worstCaseTokenAmount.abs(),
496
493
  oraclePriceData,
497
494
  spotMarketAccount,
498
495
  marginCategory,
@@ -513,6 +510,7 @@ export class ClearingHouseUser {
513
510
  }
514
511
 
515
512
  const weightedTokenValue = worstCaseQuoteTokenAmount
513
+ .abs()
516
514
  .mul(weight)
517
515
  .div(SPOT_MARKET_WEIGHT_PRECISION);
518
516
 
@@ -1064,7 +1062,7 @@ export class ClearingHouseUser {
1064
1062
  const proposedPerpPosition: PerpPosition = {
1065
1063
  marketIndex: perpPosition.marketIndex,
1066
1064
  baseAssetAmount: proposedBaseAssetAmount,
1067
- remainderBaseAssetAmount: ZERO,
1065
+ remainderBaseAssetAmount: 0,
1068
1066
  quoteAssetAmount: new BN(0),
1069
1067
  lastCumulativeFundingRate: ZERO,
1070
1068
  quoteEntryAmount: new BN(0),
@@ -1073,7 +1071,6 @@ export class ClearingHouseUser {
1073
1071
  openAsks: new BN(0),
1074
1072
  settledPnl: ZERO,
1075
1073
  lpShares: ZERO,
1076
- lastFeePerLp: ZERO,
1077
1074
  lastNetBaseAssetAmountPerLp: ZERO,
1078
1075
  lastNetQuoteAssetAmountPerLp: ZERO,
1079
1076
  };
package/src/config.ts CHANGED
@@ -27,7 +27,7 @@ export const configs: { [key in DriftEnv]: DriftConfig } = {
27
27
  devnet: {
28
28
  ENV: 'devnet',
29
29
  PYTH_ORACLE_MAPPING_ADDRESS: 'BmA9Z6FjioHJPpjT39QazZyhDRUdZy2ezwx4GiDdE2u2',
30
- CLEARING_HOUSE_PROGRAM_ID: 'By7XjakxXVnQ9gMZ4VT98DenTgBCeP295A58ybzgwVPZ',
30
+ CLEARING_HOUSE_PROGRAM_ID: 'DUZwKJKAk2C9S88BYvQzck1M1i5hySQjxB4zW6tJ29Nw',
31
31
  USDC_MINT_ADDRESS: '8zGuJQqwhZafTah7Uc7Z4tXRnguqkn5KLFAP8oV6PHe2',
32
32
  PERP_MARKETS: DevnetPerpMarkets,
33
33
  SPOT_MARKETS: DevnetSpotMarkets,
@@ -4,6 +4,13 @@ import { BN } from '../';
4
4
  export const ZERO = new BN(0);
5
5
  export const ONE = new BN(1);
6
6
  export const TWO = new BN(2);
7
+ export const THREE = new BN(3);
8
+ export const FOUR = new BN(4);
9
+ export const FIVE = new BN(5);
10
+ export const SIX = new BN(6);
11
+ export const SEVEN = new BN(7);
12
+ export const EIGHT = new BN(8);
13
+ export const NINE = new BN(9);
7
14
  export const TEN = new BN(10);
8
15
  export const TEN_THOUSAND = new BN(10000);
9
16
  export const BN_MAX = new BN(Number.MAX_SAFE_INTEGER);
@@ -5,6 +5,7 @@ import {
5
5
  SPOT_MARKET_BALANCE_PRECISION_EXP,
6
6
  LAMPORTS_EXP,
7
7
  LAMPORTS_PRECISION,
8
+ SIX,
8
9
  } from './numericConstants';
9
10
 
10
11
  export type SpotMarketConfig = {
@@ -29,8 +30,8 @@ export const DevnetSpotMarkets: SpotMarketConfig[] = [
29
30
  oracle: PublicKey.default,
30
31
  oracleSource: OracleSource.QUOTE_ASSET,
31
32
  mint: new PublicKey('8zGuJQqwhZafTah7Uc7Z4tXRnguqkn5KLFAP8oV6PHe2'),
32
- precision: SPOT_MARKET_BALANCE_PRECISION,
33
- precisionExp: SPOT_MARKET_BALANCE_PRECISION_EXP,
33
+ precision: new BN(10).pow(SIX),
34
+ precisionExp: SIX,
34
35
  },
35
36
  {
36
37
  symbol: 'SOL',
@@ -48,8 +49,8 @@ export const DevnetSpotMarkets: SpotMarketConfig[] = [
48
49
  oracle: new PublicKey('HovQMDrbAgAYPCmHVSrezcSmkMtXSSUsLDFANExrZh2J'),
49
50
  oracleSource: OracleSource.PYTH,
50
51
  mint: new PublicKey('3BZPwbcqB5kKScF3TEXxwNfx5ipV13kbRVDvfVp5c6fv'),
51
- precision: SPOT_MARKET_BALANCE_PRECISION,
52
- precisionExp: SPOT_MARKET_BALANCE_PRECISION_EXP,
52
+ precision: new BN(10).pow(SIX),
53
+ precisionExp: SIX,
53
54
  serumMarket: new PublicKey('AGsmbVu3MS9u68GEYABWosQQCZwmLcBHu4pWEuBYH7Za'),
54
55
  },
55
56
  ];
package/src/dlob/DLOB.ts CHANGED
@@ -54,7 +54,7 @@ export type NodeToTrigger = {
54
54
  node: TriggerOrderNode;
55
55
  };
56
56
 
57
- type Side = 'ask' | 'bid' | 'both';
57
+ type Side = 'ask' | 'bid' | 'both' | 'nocross';
58
58
 
59
59
  export class DLOB {
60
60
  openOrders = new Map<MarketTypeStr, Set<string>>();
@@ -239,7 +239,7 @@ export class DLOB {
239
239
  type = 'trigger';
240
240
  } else if (isOneOfVariant(order.orderType, ['market', 'triggerMarket'])) {
241
241
  type = 'market';
242
- } else if (order.oraclePriceOffset.gt(ZERO)) {
242
+ } else if (!order.oraclePriceOffset.eq(ZERO)) {
243
243
  type = 'floatingLimit';
244
244
  } else {
245
245
  type = 'limit';
@@ -274,26 +274,35 @@ export class DLOB {
274
274
  // Find all the crossing nodes
275
275
  const crossingNodesToFill: Array<NodeToFill> = this.findCrossingNodesToFill(
276
276
  marketIndex,
277
- vBid,
278
- vAsk,
279
277
  slot,
280
278
  marketType,
281
279
  oraclePriceData
282
280
  );
283
281
 
282
+ const vAMMCrossingNodesToFill: Array<NodeToFill> =
283
+ this.findvAMMCrossingNodesToFill(
284
+ marketIndex,
285
+ vBid,
286
+ vAsk,
287
+ slot,
288
+ marketType,
289
+ oraclePriceData
290
+ );
291
+
284
292
  // get expired market nodes
285
- const marketNodesToFill = this.findMarketNodesToFill(
293
+ const marketNodesToFill = this.findExpiredMarketNodesToFill(
286
294
  marketIndex,
287
295
  slot,
288
296
  marketType
289
297
  );
290
- return crossingNodesToFill.concat(marketNodesToFill);
298
+ return crossingNodesToFill.concat(
299
+ vAMMCrossingNodesToFill,
300
+ marketNodesToFill
301
+ );
291
302
  }
292
303
 
293
304
  public findCrossingNodesToFill(
294
305
  marketIndex: number,
295
- vBid: BN | undefined,
296
- vAsk: BN | undefined,
297
306
  slot: number,
298
307
  marketType: MarketType,
299
308
  oraclePriceData: OraclePriceData
@@ -302,14 +311,14 @@ export class DLOB {
302
311
 
303
312
  const askGenerator = this.getAsks(
304
313
  marketIndex,
305
- vAsk,
314
+ undefined, // dont include vask
306
315
  slot,
307
316
  marketType,
308
317
  oraclePriceData
309
318
  );
310
319
  const bidGenerator = this.getBids(
311
320
  marketIndex,
312
- vBid,
321
+ undefined, // dont include vbid
313
322
  slot,
314
323
  marketType,
315
324
  oraclePriceData
@@ -334,26 +343,86 @@ export class DLOB {
334
343
  } else if (exhaustedSide === 'both') {
335
344
  nextBid = bidGenerator.next();
336
345
  nextAsk = askGenerator.next();
346
+ } else if (exhaustedSide === 'nocross') {
347
+ break;
337
348
  } else {
338
349
  console.error(`invalid exhaustedSide: ${exhaustedSide}`);
339
350
  break;
340
351
  }
341
352
 
342
- const takerIsMaker =
343
- crossingNodes?.makerNode !== undefined &&
344
- crossingNodes.node.userAccount.equals(
345
- crossingNodes.makerNode.userAccount
346
- );
353
+ for (const crossingNode of crossingNodes) {
354
+ nodesToFill.push(crossingNode);
355
+ }
356
+ }
357
+ return nodesToFill;
358
+ }
359
+
360
+ public findvAMMCrossingNodesToFill(
361
+ marketIndex: number,
362
+ vBid: BN,
363
+ vAsk: BN,
364
+ slot: number,
365
+ marketType: MarketType,
366
+ oraclePriceData: OraclePriceData
367
+ ): NodeToFill[] {
368
+ const nodesToFill = new Array<NodeToFill>();
347
369
 
348
- // Verify that each side is different user
349
- if (crossingNodes && !takerIsMaker) {
350
- nodesToFill.push(crossingNodes);
370
+ const askGenerator = this.getAsks(
371
+ marketIndex,
372
+ undefined, // dont include vask
373
+ slot,
374
+ marketType,
375
+ oraclePriceData
376
+ );
377
+ const bidGenerator = this.getBids(
378
+ marketIndex,
379
+ undefined, // dont include vbid
380
+ slot,
381
+ marketType,
382
+ oraclePriceData
383
+ );
384
+
385
+ let nextAsk = askGenerator.next();
386
+ let nextBid = bidGenerator.next();
387
+
388
+ // check for asks that cross vBid
389
+ while (!nextAsk.done) {
390
+ const askNode = nextAsk.value;
391
+ const askPrice = askNode.getPrice(oraclePriceData, slot);
392
+
393
+ if (askPrice.lte(vBid) && isAuctionComplete(askNode.order, slot)) {
394
+ nodesToFill.push({
395
+ node: askNode,
396
+ makerNode: undefined, // filled by vAMM
397
+ });
398
+ } else {
399
+ break;
351
400
  }
401
+
402
+ nextAsk = askGenerator.next();
352
403
  }
404
+
405
+ // check for bids that cross vAsk
406
+ while (!nextBid.done) {
407
+ const bidNode = nextBid.value;
408
+ const bidPrice = bidNode.getPrice(oraclePriceData, slot);
409
+
410
+ if (bidPrice.gte(vAsk) && isAuctionComplete(bidNode.order, slot)) {
411
+ nodesToFill.push({
412
+ node: bidNode,
413
+ makerNode: undefined, // filled by vAMM
414
+ });
415
+ } else {
416
+ break;
417
+ }
418
+
419
+ nextBid = bidGenerator.next();
420
+ }
421
+
353
422
  return nodesToFill;
354
423
  }
355
424
 
356
- public findMarketNodesToFill(
425
+ public findExpiredMarketNodesToFill(
357
426
  marketIndex: number,
358
427
  slot: number,
359
428
  marketType: MarketType
@@ -361,10 +430,7 @@ export class DLOB {
361
430
  const nodesToFill = new Array<NodeToFill>();
362
431
  // Then see if there are orders to fill against vamm
363
432
  for (const marketBid of this.getMarketBids(marketIndex, marketType)) {
364
- if (
365
- isAuctionComplete(marketBid.order, slot) ||
366
- isOrderExpired(marketBid.order, slot)
367
- ) {
433
+ if (isOrderExpired(marketBid.order, slot)) {
368
434
  nodesToFill.push({
369
435
  node: marketBid,
370
436
  });
@@ -372,10 +438,7 @@ export class DLOB {
372
438
  }
373
439
 
374
440
  for (const marketAsk of this.getMarketAsks(marketIndex, marketType)) {
375
- if (
376
- isAuctionComplete(marketAsk.order, slot) ||
377
- isOrderExpired(marketAsk.order, slot)
378
- ) {
441
+ if (isOrderExpired(marketAsk.order, slot)) {
379
442
  nodesToFill.push({
380
443
  node: marketAsk,
381
444
  });
@@ -450,7 +513,7 @@ export class DLOB {
450
513
  nodeLists.market.ask.getGenerator(),
451
514
  ];
452
515
 
453
- if (marketTypeStr === 'perp') {
516
+ if (marketTypeStr === 'perp' && vAsk) {
454
517
  generatorList.push(getVammNodeGenerator(vAsk));
455
518
  }
456
519
 
@@ -489,6 +552,12 @@ export class DLOB {
489
552
  );
490
553
 
491
554
  if (!bestGenerator.next.done) {
555
+ // skip this node if it's already completely filled
556
+ if (bestGenerator.next.value.isBaseFilled()) {
557
+ bestGenerator.next = bestGenerator.generator.next();
558
+ continue;
559
+ }
560
+
492
561
  yield bestGenerator.next.value;
493
562
  bestGenerator.next = bestGenerator.generator.next();
494
563
  } else {
@@ -516,7 +585,7 @@ export class DLOB {
516
585
  nodeLists.floatingLimit.bid.getGenerator(),
517
586
  nodeLists.market.bid.getGenerator(),
518
587
  ];
519
- if (marketTypeStr === 'perp') {
588
+ if (marketTypeStr === 'perp' && vBid) {
520
589
  generatorList.push(getVammNodeGenerator(vBid));
521
590
  }
522
591
  const bidGenerators = generatorList.map((generator) => {
@@ -554,6 +623,12 @@ export class DLOB {
554
623
  );
555
624
 
556
625
  if (!bestGenerator.next.done) {
626
+ // skip this node if it's already completely filled
627
+ if (bestGenerator.next.value.isBaseFilled()) {
628
+ bestGenerator.next = bestGenerator.generator.next();
629
+ continue;
630
+ }
631
+
557
632
  yield bestGenerator.next.value;
558
633
  bestGenerator.next = bestGenerator.generator.next();
559
634
  } else {
@@ -568,63 +643,32 @@ export class DLOB {
568
643
  oraclePriceData: OraclePriceData,
569
644
  slot: number
570
645
  ): {
571
- crossingNodes?: NodeToFill;
572
- exhaustedSide?: Side;
646
+ crossingNodes: NodeToFill[];
647
+ exhaustedSide: Side;
573
648
  } {
574
649
  const bidPrice = bidNode.getPrice(oraclePriceData, slot);
575
650
  const askPrice = askNode.getPrice(oraclePriceData, slot);
576
651
 
577
- const bidOrder = bidNode.order;
578
- const askOrder = askNode.order;
579
-
580
- const containsMarketOrder =
581
- (askOrder && isVariant(askOrder.orderType, 'market')) ||
582
- (bidOrder && isVariant(bidOrder.orderType, 'market'));
583
-
584
- if (!containsMarketOrder || bidPrice.lt(askPrice)) {
585
- // no market orders, and no crossing orders - return
586
- if (askNode.isVammNode() && !bidNode.isVammNode()) {
587
- return {
588
- exhaustedSide: 'bid',
589
- };
590
- } else if (!askNode.isVammNode() && bidNode.isVammNode()) {
591
- return {
592
- exhaustedSide: 'ask',
593
- };
594
- } else {
595
- return {
596
- exhaustedSide: 'both',
597
- };
598
- }
599
- }
600
-
601
- // User bid crosses the vamm ask
602
- if (askNode.isVammNode()) {
603
- if (!isAuctionComplete(bidOrder, slot)) {
604
- return {
605
- exhaustedSide: 'bid',
606
- };
607
- }
652
+ // orders don't cross - we're done walkin gup the book
653
+ if (bidPrice.lt(askPrice)) {
608
654
  return {
609
- crossingNodes: {
610
- node: bidNode,
611
- },
612
- exhaustedSide: 'bid',
655
+ crossingNodes: [],
656
+ exhaustedSide: 'nocross',
613
657
  };
614
658
  }
615
659
 
616
- // User ask crosses the vamm bid
617
- if (bidNode.isVammNode()) {
618
- if (!isAuctionComplete(askOrder, slot)) {
619
- return {
620
- exhaustedSide: 'ask',
621
- };
622
- }
660
+ const bidOrder = bidNode.order;
661
+ const askOrder = askNode.order;
662
+
663
+ // Can't match two maker orders or if maker and taker are the same
664
+ const makerIsTaker = bidNode.userAccount.equals(askNode.userAccount);
665
+ if (makerIsTaker || (bidOrder.postOnly && askOrder.postOnly)) {
666
+ // don't have a principle way to pick which one to exhaust,
667
+ // exhaust each one 50% of the time so we can try each one against other orders
668
+ const exhaustedSide = Math.random() < 0.5 ? 'bid' : 'ask';
623
669
  return {
624
- crossingNodes: {
625
- node: askNode,
626
- },
627
- exhaustedSide: 'ask',
670
+ crossingNodes: [],
671
+ exhaustedSide,
628
672
  };
629
673
  }
630
674
 
@@ -644,34 +688,71 @@ export class DLOB {
644
688
  exhaustedSide = 'bid';
645
689
  }
646
690
 
647
- // Can't match two maker orders
648
- if (bidOrder.postOnly && askOrder.postOnly) {
649
- return {
650
- exhaustedSide,
651
- };
652
- }
653
-
654
- // update the orders as if they fill - so we don't try to match them on the next iteration
691
+ // update the orders on DLOB as if they were fill - so we don't try to match them on the next iteration
692
+ // NOTE: if something goes wrong during the actual fill (transaction fails, i.e. due to orders already being filled)
693
+ // then we risk having a mismatch between this local DLOB and the actual DLOB state on the blockchain. This isn't
694
+ // a problem in the current implementation because we construct a new DLOB from the blockchain state every time, rather
695
+ // than updating the existing DLOB based on events.
655
696
  if (exhaustedSide === 'ask') {
656
- bidNode.order.baseAssetAmountFilled =
697
+ // bid partially filled
698
+ const newBidOrder = { ...bidOrder };
699
+ newBidOrder.baseAssetAmountFilled =
657
700
  bidOrder.baseAssetAmountFilled.add(askBaseRemaining);
658
- askNode.order.baseAssetAmountFilled = askOrder.baseAssetAmount;
701
+ this.getListForOrder(newBidOrder).update(
702
+ newBidOrder,
703
+ bidNode.userAccount
704
+ );
705
+
706
+ // ask completely filled
707
+ const newAskOrder = { ...askOrder };
708
+ newAskOrder.baseAssetAmountFilled = askOrder.baseAssetAmount;
709
+ this.getListForOrder(newAskOrder).update(
710
+ newAskOrder,
711
+ askNode.userAccount
712
+ );
659
713
  } else if (exhaustedSide === 'bid') {
660
- askNode.order.baseAssetAmountFilled =
714
+ // ask partially filled
715
+ const newAskOrder = { ...askOrder };
716
+ newAskOrder.baseAssetAmountFilled =
661
717
  askOrder.baseAssetAmountFilled.add(bidBaseRemaining);
662
- bidNode.order.baseAssetAmountFilled = bidOrder.baseAssetAmount;
718
+ this.getListForOrder(newAskOrder).update(
719
+ newAskOrder,
720
+ askNode.userAccount
721
+ );
722
+
723
+ // bid completely filled
724
+ const newBidOrder = { ...bidOrder };
725
+ newBidOrder.baseAssetAmountFilled = bidOrder.baseAssetAmount;
726
+ this.getListForOrder(newBidOrder).update(
727
+ newBidOrder,
728
+ bidNode.userAccount
729
+ );
663
730
  } else {
664
- askNode.order.baseAssetAmountFilled = askOrder.baseAssetAmount;
665
- bidNode.order.baseAssetAmountFilled = bidOrder.baseAssetAmount;
731
+ // both completely filled
732
+ const newBidOrder = { ...bidOrder };
733
+ newBidOrder.baseAssetAmountFilled = bidOrder.baseAssetAmount;
734
+ this.getListForOrder(newBidOrder).update(
735
+ newBidOrder,
736
+ bidNode.userAccount
737
+ );
738
+
739
+ const newAskOrder = { ...askOrder };
740
+ newAskOrder.baseAssetAmountFilled = askOrder.baseAssetAmount;
741
+ this.getListForOrder(newAskOrder).update(
742
+ newAskOrder,
743
+ askNode.userAccount
744
+ );
666
745
  }
667
746
 
668
747
  // Bid is maker
669
748
  if (bidOrder.postOnly) {
670
749
  return {
671
- crossingNodes: {
672
- node: askNode,
673
- makerNode: bidNode,
674
- },
750
+ crossingNodes: [
751
+ {
752
+ node: askNode,
753
+ makerNode: bidNode,
754
+ },
755
+ ],
675
756
  exhaustedSide,
676
757
  };
677
758
  }
@@ -679,10 +760,12 @@ export class DLOB {
679
760
  // Ask is maker
680
761
  if (askOrder.postOnly) {
681
762
  return {
682
- crossingNodes: {
683
- node: bidNode,
684
- makerNode: askNode,
685
- },
763
+ crossingNodes: [
764
+ {
765
+ node: bidNode,
766
+ makerNode: askNode,
767
+ },
768
+ ],
686
769
  exhaustedSide,
687
770
  };
688
771
  }
@@ -693,10 +776,12 @@ export class DLOB {
693
776
  ? [askNode, bidNode]
694
777
  : [bidNode, askNode];
695
778
  return {
696
- crossingNodes: {
697
- node: newerNode,
698
- makerNode: olderNode,
699
- },
779
+ crossingNodes: [
780
+ {
781
+ node: newerNode,
782
+ makerNode: olderNode,
783
+ },
784
+ ],
700
785
  exhaustedSide,
701
786
  };
702
787
  }
@@ -19,6 +19,7 @@ export interface DLOBNode {
19
19
  getPrice(oraclePriceData: OraclePriceData, slot: number): BN;
20
20
  isVammNode(): boolean;
21
21
  order: Order | undefined;
22
+ isBaseFilled(): boolean;
22
23
  haveFilled: boolean;
23
24
  userAccount: PublicKey | undefined;
24
25
  market: SpotMarketAccount | PerpMarketAccount;
@@ -87,6 +88,10 @@ export abstract class OrderNode implements DLOBNode {
87
88
  }
88
89
  }
89
90
 
91
+ isBaseFilled(): boolean {
92
+ return this.order.baseAssetAmountFilled.eq(this.order.baseAssetAmount);
93
+ }
94
+
90
95
  isVammNode(): boolean {
91
96
  return false;
92
97
  }
@@ -11,7 +11,10 @@ import { createNode, DLOBNode, DLOBNodeMap } from './DLOBNode';
11
11
 
12
12
  export type SortDirection = 'asc' | 'desc';
13
13
 
14
- export function getOrderSignature(orderId: BN, userAccount: PublicKey): string {
14
+ export function getOrderSignature(
15
+ orderId: number,
16
+ userAccount: PublicKey
17
+ ): string {
15
18
  return `${userAccount.toString()}-${orderId.toString()}`;
16
19
  }
17
20
 
@@ -49,11 +52,11 @@ export class NodeList<NodeType extends keyof DLOBNodeMap>
49
52
 
50
53
  const newNode = createNode(this.nodeType, order, market, userAccount);
51
54
 
52
- const orderId = getOrderSignature(order.orderId, userAccount);
53
- if (this.nodeMap.has(orderId)) {
55
+ const orderSignature = getOrderSignature(order.orderId, userAccount);
56
+ if (this.nodeMap.has(orderSignature)) {
54
57
  return;
55
58
  }
56
- this.nodeMap.set(orderId, newNode);
59
+ this.nodeMap.set(orderSignature, newNode);
57
60
 
58
61
  this.length += 1;
59
62
 
@@ -126,7 +129,7 @@ export class NodeList<NodeType extends keyof DLOBNodeMap>
126
129
  node.previous.next = node.next;
127
130
  }
128
131
 
129
- if (this.head && node.order.orderId.eq(this.head.order.orderId)) {
132
+ if (this.head && node.order.orderId === this.head.order.orderId) {
130
133
  this.head = node.next;
131
134
  }
132
135
 
@@ -180,6 +183,7 @@ export function* getVammNodeGenerator(
180
183
  order: undefined,
181
184
  market: undefined,
182
185
  userAccount: undefined,
186
+ isBaseFilled: () => false,
183
187
  haveFilled: false,
184
188
  };
185
189
  }
@@ -10,6 +10,7 @@ import {
10
10
  SettlePnlRecord,
11
11
  LPRecord,
12
12
  InsuranceFundRecord,
13
+ SpotInterestRecord,
13
14
  } from '../index';
14
15
 
15
16
  export type EventSubscriptionOptions = {
@@ -37,6 +38,7 @@ export const DefaultEventSubscriptionOptions: EventSubscriptionOptions = {
37
38
  'SettlePnlRecord',
38
39
  'LPRecord',
39
40
  'InsuranceFundRecord',
41
+ 'SpotInterestRecord',
40
42
  ],
41
43
  maxEventsPerType: 4096,
42
44
  orderBy: 'blockchain',
@@ -74,6 +76,7 @@ export type EventMap = {
74
76
  NewUserRecord: Event<NewUserRecord>;
75
77
  LPRecord: Event<LPRecord>;
76
78
  InsuranceFundRecord: Event<InsuranceFundRecord>;
79
+ SpotInterestRecord: Event<SpotInterestRecord>;
77
80
  };
78
81
 
79
82
  export type EventType = keyof EventMap;