@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
@@ -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>>();
@@ -135,6 +135,32 @@ export class DLOB {
135
135
  }
136
136
  }
137
137
 
138
+ public clear() {
139
+ for (const marketType of this.openOrders.keys()) {
140
+ this.openOrders.get(marketType).clear();
141
+ }
142
+ this.openOrders.clear();
143
+
144
+ for (const marketType of this.orderLists.keys()) {
145
+ for (const marketIndex of this.orderLists.get(marketType).keys()) {
146
+ const marketNodeLists = this.orderLists
147
+ .get(marketType)
148
+ .get(marketIndex);
149
+ for (const side of Object.keys(marketNodeLists)) {
150
+ for (const orderType of Object.keys(marketNodeLists[side])) {
151
+ marketNodeLists[side][orderType].clear();
152
+ }
153
+ }
154
+ }
155
+ }
156
+ this.orderLists.clear();
157
+
158
+ for (const marketType of this.marketIndexToAccount.keys()) {
159
+ this.marketIndexToAccount.get(marketType).clear();
160
+ }
161
+ this.marketIndexToAccount.clear();
162
+ }
163
+
138
164
  /**
139
165
  * initializes a new DLOB instance
140
166
  *
@@ -239,7 +265,7 @@ export class DLOB {
239
265
  type = 'trigger';
240
266
  } else if (isOneOfVariant(order.orderType, ['market', 'triggerMarket'])) {
241
267
  type = 'market';
242
- } else if (order.oraclePriceOffset.gt(ZERO)) {
268
+ } else if (!order.oraclePriceOffset.eq(ZERO)) {
243
269
  type = 'floatingLimit';
244
270
  } else {
245
271
  type = 'limit';
@@ -274,26 +300,35 @@ export class DLOB {
274
300
  // Find all the crossing nodes
275
301
  const crossingNodesToFill: Array<NodeToFill> = this.findCrossingNodesToFill(
276
302
  marketIndex,
277
- vBid,
278
- vAsk,
279
303
  slot,
280
304
  marketType,
281
305
  oraclePriceData
282
306
  );
283
307
 
308
+ const vAMMCrossingNodesToFill: Array<NodeToFill> =
309
+ this.findvAMMCrossingNodesToFill(
310
+ marketIndex,
311
+ vBid,
312
+ vAsk,
313
+ slot,
314
+ marketType,
315
+ oraclePriceData
316
+ );
317
+
284
318
  // get expired market nodes
285
- const marketNodesToFill = this.findMarketNodesToFill(
319
+ const marketNodesToFill = this.findExpiredMarketNodesToFill(
286
320
  marketIndex,
287
321
  slot,
288
322
  marketType
289
323
  );
290
- return crossingNodesToFill.concat(marketNodesToFill);
324
+ return crossingNodesToFill.concat(
325
+ vAMMCrossingNodesToFill,
326
+ marketNodesToFill
327
+ );
291
328
  }
292
329
 
293
330
  public findCrossingNodesToFill(
294
331
  marketIndex: number,
295
- vBid: BN | undefined,
296
- vAsk: BN | undefined,
297
332
  slot: number,
298
333
  marketType: MarketType,
299
334
  oraclePriceData: OraclePriceData
@@ -302,14 +337,14 @@ export class DLOB {
302
337
 
303
338
  const askGenerator = this.getAsks(
304
339
  marketIndex,
305
- vAsk,
340
+ undefined, // dont include vask
306
341
  slot,
307
342
  marketType,
308
343
  oraclePriceData
309
344
  );
310
345
  const bidGenerator = this.getBids(
311
346
  marketIndex,
312
- vBid,
347
+ undefined, // dont include vbid
313
348
  slot,
314
349
  marketType,
315
350
  oraclePriceData
@@ -334,26 +369,86 @@ export class DLOB {
334
369
  } else if (exhaustedSide === 'both') {
335
370
  nextBid = bidGenerator.next();
336
371
  nextAsk = askGenerator.next();
372
+ } else if (exhaustedSide === 'nocross') {
373
+ break;
337
374
  } else {
338
375
  console.error(`invalid exhaustedSide: ${exhaustedSide}`);
339
376
  break;
340
377
  }
341
378
 
342
- const takerIsMaker =
343
- crossingNodes?.makerNode !== undefined &&
344
- crossingNodes.node.userAccount.equals(
345
- crossingNodes.makerNode.userAccount
346
- );
379
+ for (const crossingNode of crossingNodes) {
380
+ nodesToFill.push(crossingNode);
381
+ }
382
+ }
383
+ return nodesToFill;
384
+ }
385
+
386
+ public findvAMMCrossingNodesToFill(
387
+ marketIndex: number,
388
+ vBid: BN,
389
+ vAsk: BN,
390
+ slot: number,
391
+ marketType: MarketType,
392
+ oraclePriceData: OraclePriceData
393
+ ): NodeToFill[] {
394
+ const nodesToFill = new Array<NodeToFill>();
395
+
396
+ const askGenerator = this.getAsks(
397
+ marketIndex,
398
+ undefined, // dont include vask
399
+ slot,
400
+ marketType,
401
+ oraclePriceData
402
+ );
403
+ const bidGenerator = this.getBids(
404
+ marketIndex,
405
+ undefined, // dont include vbid
406
+ slot,
407
+ marketType,
408
+ oraclePriceData
409
+ );
410
+
411
+ let nextAsk = askGenerator.next();
412
+ let nextBid = bidGenerator.next();
413
+
414
+ // check for asks that cross vBid
415
+ while (!nextAsk.done) {
416
+ const askNode = nextAsk.value;
417
+ const askPrice = askNode.getPrice(oraclePriceData, slot);
418
+
419
+ if (askPrice.lte(vBid) && isAuctionComplete(askNode.order, slot)) {
420
+ nodesToFill.push({
421
+ node: askNode,
422
+ makerNode: undefined, // filled by vAMM
423
+ });
424
+ } else {
425
+ break;
426
+ }
427
+
428
+ nextAsk = askGenerator.next();
429
+ }
347
430
 
348
- // Verify that each side is different user
349
- if (crossingNodes && !takerIsMaker) {
350
- nodesToFill.push(crossingNodes);
431
+ // check for bids that cross vAsk
432
+ while (!nextBid.done) {
433
+ const bidNode = nextBid.value;
434
+ const bidPrice = bidNode.getPrice(oraclePriceData, slot);
435
+
436
+ if (bidPrice.gte(vAsk) && isAuctionComplete(bidNode.order, slot)) {
437
+ nodesToFill.push({
438
+ node: bidNode,
439
+ makerNode: undefined, // filled by vAMM
440
+ });
441
+ } else {
442
+ break;
351
443
  }
444
+
445
+ nextBid = bidGenerator.next();
352
446
  }
447
+
353
448
  return nodesToFill;
354
449
  }
355
450
 
356
- public findMarketNodesToFill(
451
+ public findExpiredMarketNodesToFill(
357
452
  marketIndex: number,
358
453
  slot: number,
359
454
  marketType: MarketType
@@ -361,10 +456,7 @@ export class DLOB {
361
456
  const nodesToFill = new Array<NodeToFill>();
362
457
  // Then see if there are orders to fill against vamm
363
458
  for (const marketBid of this.getMarketBids(marketIndex, marketType)) {
364
- if (
365
- isAuctionComplete(marketBid.order, slot) ||
366
- isOrderExpired(marketBid.order, slot)
367
- ) {
459
+ if (isOrderExpired(marketBid.order, slot)) {
368
460
  nodesToFill.push({
369
461
  node: marketBid,
370
462
  });
@@ -372,10 +464,7 @@ export class DLOB {
372
464
  }
373
465
 
374
466
  for (const marketAsk of this.getMarketAsks(marketIndex, marketType)) {
375
- if (
376
- isAuctionComplete(marketAsk.order, slot) ||
377
- isOrderExpired(marketAsk.order, slot)
378
- ) {
467
+ if (isOrderExpired(marketAsk.order, slot)) {
379
468
  nodesToFill.push({
380
469
  node: marketAsk,
381
470
  });
@@ -450,9 +539,12 @@ export class DLOB {
450
539
  nodeLists.market.ask.getGenerator(),
451
540
  ];
452
541
 
453
- if (marketTypeStr === 'perp') {
542
+ if (marketTypeStr === 'perp' && vAsk) {
454
543
  generatorList.push(getVammNodeGenerator(vAsk));
455
544
  }
545
+ if (generatorList.length === 0) {
546
+ throw new Error('No ask generators found');
547
+ }
456
548
 
457
549
  const askGenerators = generatorList.map((generator) => {
458
550
  return {
@@ -489,6 +581,12 @@ export class DLOB {
489
581
  );
490
582
 
491
583
  if (!bestGenerator.next.done) {
584
+ // skip this node if it's already completely filled
585
+ if (bestGenerator.next.value.isBaseFilled()) {
586
+ bestGenerator.next = bestGenerator.generator.next();
587
+ continue;
588
+ }
589
+
492
590
  yield bestGenerator.next.value;
493
591
  bestGenerator.next = bestGenerator.generator.next();
494
592
  } else {
@@ -516,9 +614,13 @@ export class DLOB {
516
614
  nodeLists.floatingLimit.bid.getGenerator(),
517
615
  nodeLists.market.bid.getGenerator(),
518
616
  ];
519
- if (marketTypeStr === 'perp') {
617
+ if (marketTypeStr === 'perp' && vBid) {
520
618
  generatorList.push(getVammNodeGenerator(vBid));
521
619
  }
620
+ if (generatorList.length === 0) {
621
+ throw new Error('No bid generators found');
622
+ }
623
+
522
624
  const bidGenerators = generatorList.map((generator) => {
523
625
  return {
524
626
  next: generator.next(),
@@ -554,6 +656,12 @@ export class DLOB {
554
656
  );
555
657
 
556
658
  if (!bestGenerator.next.done) {
659
+ // skip this node if it's already completely filled
660
+ if (bestGenerator.next.value.isBaseFilled()) {
661
+ bestGenerator.next = bestGenerator.generator.next();
662
+ continue;
663
+ }
664
+
557
665
  yield bestGenerator.next.value;
558
666
  bestGenerator.next = bestGenerator.generator.next();
559
667
  } else {
@@ -568,63 +676,32 @@ export class DLOB {
568
676
  oraclePriceData: OraclePriceData,
569
677
  slot: number
570
678
  ): {
571
- crossingNodes?: NodeToFill;
572
- exhaustedSide?: Side;
679
+ crossingNodes: NodeToFill[];
680
+ exhaustedSide: Side;
573
681
  } {
574
682
  const bidPrice = bidNode.getPrice(oraclePriceData, slot);
575
683
  const askPrice = askNode.getPrice(oraclePriceData, slot);
576
684
 
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
- }
685
+ // orders don't cross - we're done walkin gup the book
686
+ if (bidPrice.lt(askPrice)) {
608
687
  return {
609
- crossingNodes: {
610
- node: bidNode,
611
- },
612
- exhaustedSide: 'bid',
688
+ crossingNodes: [],
689
+ exhaustedSide: 'nocross',
613
690
  };
614
691
  }
615
692
 
616
- // User ask crosses the vamm bid
617
- if (bidNode.isVammNode()) {
618
- if (!isAuctionComplete(askOrder, slot)) {
619
- return {
620
- exhaustedSide: 'ask',
621
- };
622
- }
693
+ const bidOrder = bidNode.order;
694
+ const askOrder = askNode.order;
695
+
696
+ // Can't match two maker orders or if maker and taker are the same
697
+ const makerIsTaker = bidNode.userAccount.equals(askNode.userAccount);
698
+ if (makerIsTaker || (bidOrder.postOnly && askOrder.postOnly)) {
699
+ // don't have a principle way to pick which one to exhaust,
700
+ // exhaust each one 50% of the time so we can try each one against other orders
701
+ const exhaustedSide = Math.random() < 0.5 ? 'bid' : 'ask';
623
702
  return {
624
- crossingNodes: {
625
- node: askNode,
626
- },
627
- exhaustedSide: 'ask',
703
+ crossingNodes: [],
704
+ exhaustedSide,
628
705
  };
629
706
  }
630
707
 
@@ -644,34 +721,71 @@ export class DLOB {
644
721
  exhaustedSide = 'bid';
645
722
  }
646
723
 
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
724
+ // update the orders on DLOB as if they were fill - so we don't try to match them on the next iteration
725
+ // NOTE: if something goes wrong during the actual fill (transaction fails, i.e. due to orders already being filled)
726
+ // then we risk having a mismatch between this local DLOB and the actual DLOB state on the blockchain. This isn't
727
+ // a problem in the current implementation because we construct a new DLOB from the blockchain state every time, rather
728
+ // than updating the existing DLOB based on events.
655
729
  if (exhaustedSide === 'ask') {
656
- bidNode.order.baseAssetAmountFilled =
730
+ // bid partially filled
731
+ const newBidOrder = { ...bidOrder };
732
+ newBidOrder.baseAssetAmountFilled =
657
733
  bidOrder.baseAssetAmountFilled.add(askBaseRemaining);
658
- askNode.order.baseAssetAmountFilled = askOrder.baseAssetAmount;
734
+ this.getListForOrder(newBidOrder).update(
735
+ newBidOrder,
736
+ bidNode.userAccount
737
+ );
738
+
739
+ // ask completely filled
740
+ const newAskOrder = { ...askOrder };
741
+ newAskOrder.baseAssetAmountFilled = askOrder.baseAssetAmount;
742
+ this.getListForOrder(newAskOrder).update(
743
+ newAskOrder,
744
+ askNode.userAccount
745
+ );
659
746
  } else if (exhaustedSide === 'bid') {
660
- askNode.order.baseAssetAmountFilled =
747
+ // ask partially filled
748
+ const newAskOrder = { ...askOrder };
749
+ newAskOrder.baseAssetAmountFilled =
661
750
  askOrder.baseAssetAmountFilled.add(bidBaseRemaining);
662
- bidNode.order.baseAssetAmountFilled = bidOrder.baseAssetAmount;
751
+ this.getListForOrder(newAskOrder).update(
752
+ newAskOrder,
753
+ askNode.userAccount
754
+ );
755
+
756
+ // bid completely filled
757
+ const newBidOrder = { ...bidOrder };
758
+ newBidOrder.baseAssetAmountFilled = bidOrder.baseAssetAmount;
759
+ this.getListForOrder(newBidOrder).update(
760
+ newBidOrder,
761
+ bidNode.userAccount
762
+ );
663
763
  } else {
664
- askNode.order.baseAssetAmountFilled = askOrder.baseAssetAmount;
665
- bidNode.order.baseAssetAmountFilled = bidOrder.baseAssetAmount;
764
+ // both completely filled
765
+ const newBidOrder = { ...bidOrder };
766
+ newBidOrder.baseAssetAmountFilled = bidOrder.baseAssetAmount;
767
+ this.getListForOrder(newBidOrder).update(
768
+ newBidOrder,
769
+ bidNode.userAccount
770
+ );
771
+
772
+ const newAskOrder = { ...askOrder };
773
+ newAskOrder.baseAssetAmountFilled = askOrder.baseAssetAmount;
774
+ this.getListForOrder(newAskOrder).update(
775
+ newAskOrder,
776
+ askNode.userAccount
777
+ );
666
778
  }
667
779
 
668
780
  // Bid is maker
669
781
  if (bidOrder.postOnly) {
670
782
  return {
671
- crossingNodes: {
672
- node: askNode,
673
- makerNode: bidNode,
674
- },
783
+ crossingNodes: [
784
+ {
785
+ node: askNode,
786
+ makerNode: bidNode,
787
+ },
788
+ ],
675
789
  exhaustedSide,
676
790
  };
677
791
  }
@@ -679,10 +793,12 @@ export class DLOB {
679
793
  // Ask is maker
680
794
  if (askOrder.postOnly) {
681
795
  return {
682
- crossingNodes: {
683
- node: bidNode,
684
- makerNode: askNode,
685
- },
796
+ crossingNodes: [
797
+ {
798
+ node: bidNode,
799
+ makerNode: askNode,
800
+ },
801
+ ],
686
802
  exhaustedSide,
687
803
  };
688
804
  }
@@ -693,10 +809,12 @@ export class DLOB {
693
809
  ? [askNode, bidNode]
694
810
  : [bidNode, askNode];
695
811
  return {
696
- crossingNodes: {
697
- node: newerNode,
698
- makerNode: olderNode,
699
- },
812
+ crossingNodes: [
813
+ {
814
+ node: newerNode,
815
+ makerNode: olderNode,
816
+ },
817
+ ],
700
818
  exhaustedSide,
701
819
  };
702
820
  }
@@ -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
 
@@ -31,6 +34,12 @@ export class NodeList<NodeType extends keyof DLOBNodeMap>
31
34
  private sortDirection: SortDirection
32
35
  ) {}
33
36
 
37
+ public clear() {
38
+ this.head = undefined;
39
+ this.length = 0;
40
+ this.nodeMap.clear();
41
+ }
42
+
34
43
  public insert(
35
44
  order: Order,
36
45
  marketType: MarketTypeStr,
@@ -49,11 +58,11 @@ export class NodeList<NodeType extends keyof DLOBNodeMap>
49
58
 
50
59
  const newNode = createNode(this.nodeType, order, market, userAccount);
51
60
 
52
- const orderId = getOrderSignature(order.orderId, userAccount);
53
- if (this.nodeMap.has(orderId)) {
61
+ const orderSignature = getOrderSignature(order.orderId, userAccount);
62
+ if (this.nodeMap.has(orderSignature)) {
54
63
  return;
55
64
  }
56
- this.nodeMap.set(orderId, newNode);
65
+ this.nodeMap.set(orderSignature, newNode);
57
66
 
58
67
  this.length += 1;
59
68
 
@@ -126,7 +135,7 @@ export class NodeList<NodeType extends keyof DLOBNodeMap>
126
135
  node.previous.next = node.next;
127
136
  }
128
137
 
129
- if (this.head && node.order.orderId.eq(this.head.order.orderId)) {
138
+ if (this.head && node.order.orderId === this.head.order.orderId) {
130
139
  this.head = node.next;
131
140
  }
132
141
 
@@ -180,6 +189,7 @@ export function* getVammNodeGenerator(
180
189
  order: undefined,
181
190
  market: undefined,
182
191
  userAccount: undefined,
192
+ isBaseFilled: () => false,
183
193
  haveFilled: false,
184
194
  };
185
195
  }