@drift-labs/sdk 2.17.0 → 2.18.0-beta.0

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.
package/src/dlob/DLOB.ts CHANGED
@@ -19,7 +19,6 @@ import {
19
19
  SlotSubscriber,
20
20
  MarketTypeStr,
21
21
  StateAccount,
22
- isMarketOrder,
23
22
  mustBeTriggered,
24
23
  isTriggered,
25
24
  getLimitPrice,
@@ -28,6 +27,9 @@ import {
28
27
  OrderActionRecord,
29
28
  ZERO,
30
29
  BN_MAX,
30
+ isRestingLimitOrder,
31
+ isTakingOrder,
32
+ isFallbackAvailableLiquiditySource,
31
33
  } from '..';
32
34
  import { PublicKey } from '@solana/web3.js';
33
35
  import { DLOBNode, DLOBNodeType, TriggerOrderNode } from '..';
@@ -35,14 +37,18 @@ import { ammPaused, exchangePaused, fillPaused } from '../math/exchangeStatus';
35
37
  import { DLOBOrders } from './DLOBOrders';
36
38
 
37
39
  export type MarketNodeLists = {
38
- limit: {
39
- ask: NodeList<'limit'>;
40
- bid: NodeList<'limit'>;
40
+ restingLimit: {
41
+ ask: NodeList<'restingLimit'>;
42
+ bid: NodeList<'restingLimit'>;
41
43
  };
42
44
  floatingLimit: {
43
45
  ask: NodeList<'floatingLimit'>;
44
46
  bid: NodeList<'floatingLimit'>;
45
47
  };
48
+ takingLimit: {
49
+ ask: NodeList<'takingLimit'>;
50
+ bid: NodeList<'takingLimit'>;
51
+ };
46
52
  market: {
47
53
  ask: NodeList<'market'>;
48
54
  bid: NodeList<'market'>;
@@ -75,6 +81,7 @@ const SUPPORTED_ORDER_TYPES = [
75
81
  export class DLOB {
76
82
  openOrders = new Map<MarketTypeStr, Set<string>>();
77
83
  orderLists = new Map<MarketTypeStr, Map<number, MarketNodeLists>>();
84
+ maxSlotForRestingLimitOrders = 0;
78
85
 
79
86
  initialized = false;
80
87
 
@@ -109,6 +116,8 @@ export class DLOB {
109
116
  }
110
117
  this.orderLists.clear();
111
118
 
119
+ this.maxSlotForRestingLimitOrders = 0;
120
+
112
121
  this.init();
113
122
  }
114
123
 
@@ -117,7 +126,10 @@ export class DLOB {
117
126
  *
118
127
  * @returns a promise that resolves when the DLOB is initialized
119
128
  */
120
- public async initFromUserMap(userMap: UserMap): Promise<boolean> {
129
+ public async initFromUserMap(
130
+ userMap: UserMap,
131
+ slot: number
132
+ ): Promise<boolean> {
121
133
  if (this.initialized) {
122
134
  return false;
123
135
  }
@@ -128,7 +140,7 @@ export class DLOB {
128
140
  const userAccountPubkey = user.getUserAccountPublicKey();
129
141
 
130
142
  for (const order of userAccount.orders) {
131
- this.insertOrder(order, userAccountPubkey);
143
+ this.insertOrder(order, userAccountPubkey, slot);
132
144
  }
133
145
  }
134
146
 
@@ -136,24 +148,27 @@ export class DLOB {
136
148
  return true;
137
149
  }
138
150
 
139
- public initFromOrders(dlobOrders: DLOBOrders): boolean {
151
+ public initFromOrders(dlobOrders: DLOBOrders, slot: number): boolean {
140
152
  if (this.initialized) {
141
153
  return false;
142
154
  }
143
155
 
144
156
  for (const { user, order } of dlobOrders) {
145
- this.insertOrder(order, user);
157
+ this.insertOrder(order, user, slot);
146
158
  }
147
159
 
148
160
  this.initialized = true;
149
161
  return true;
150
162
  }
151
163
 
152
- public handleOrderRecord(record: OrderRecord): void {
153
- this.insertOrder(record.order, record.user);
164
+ public handleOrderRecord(record: OrderRecord, slot: number): void {
165
+ this.insertOrder(record.order, record.user, slot);
154
166
  }
155
167
 
156
- public handleOrderActionRecord(record: OrderActionRecord): void {
168
+ public handleOrderActionRecord(
169
+ record: OrderActionRecord,
170
+ slot: number
171
+ ): void {
157
172
  if (isOneOfVariant(record.action, ['place', 'expire'])) {
158
173
  return;
159
174
  }
@@ -162,14 +177,14 @@ export class DLOB {
162
177
  if (record.taker !== null) {
163
178
  const takerOrder = this.getOrder(record.takerOrderId, record.taker);
164
179
  if (takerOrder) {
165
- this.trigger(takerOrder, record.taker);
180
+ this.trigger(takerOrder, record.taker, slot);
166
181
  }
167
182
  }
168
183
 
169
184
  if (record.maker !== null) {
170
185
  const makerOrder = this.getOrder(record.makerOrderId, record.maker);
171
186
  if (makerOrder) {
172
- this.trigger(makerOrder, record.maker);
187
+ this.trigger(makerOrder, record.maker, slot);
173
188
  }
174
189
  }
175
190
  } else if (isVariant(record.action, 'fill')) {
@@ -179,6 +194,7 @@ export class DLOB {
179
194
  this.updateOrder(
180
195
  takerOrder,
181
196
  record.taker,
197
+ slot,
182
198
  record.takerOrderCumulativeBaseAssetAmountFilled
183
199
  );
184
200
  }
@@ -190,6 +206,7 @@ export class DLOB {
190
206
  this.updateOrder(
191
207
  makerOrder,
192
208
  record.maker,
209
+ slot,
193
210
  record.makerOrderCumulativeBaseAssetAmountFilled
194
211
  );
195
212
  }
@@ -198,14 +215,14 @@ export class DLOB {
198
215
  if (record.taker !== null) {
199
216
  const takerOrder = this.getOrder(record.takerOrderId, record.taker);
200
217
  if (takerOrder) {
201
- this.delete(takerOrder, record.taker);
218
+ this.delete(takerOrder, record.taker, slot);
202
219
  }
203
220
  }
204
221
 
205
222
  if (record.maker !== null) {
206
223
  const makerOrder = this.getOrder(record.makerOrderId, record.maker);
207
224
  if (makerOrder) {
208
- this.delete(makerOrder, record.maker);
225
+ this.delete(makerOrder, record.maker, slot);
209
226
  }
210
227
  }
211
228
  }
@@ -214,6 +231,7 @@ export class DLOB {
214
231
  public insertOrder(
215
232
  order: Order,
216
233
  userAccount: PublicKey,
234
+ slot: number,
217
235
  onInsert?: OrderBookCallback
218
236
  ): void {
219
237
  if (isVariant(order.status, 'init')) {
@@ -235,7 +253,7 @@ export class DLOB {
235
253
  .get(marketType)
236
254
  .add(getOrderSignature(order.orderId, userAccount));
237
255
  }
238
- this.getListForOrder(order)?.insert(order, marketType, userAccount);
256
+ this.getListForOrder(order, slot)?.insert(order, marketType, userAccount);
239
257
 
240
258
  if (onInsert) {
241
259
  onInsert();
@@ -244,14 +262,18 @@ export class DLOB {
244
262
 
245
263
  addOrderList(marketType: MarketTypeStr, marketIndex: number): void {
246
264
  this.orderLists.get(marketType).set(marketIndex, {
247
- limit: {
248
- ask: new NodeList('limit', 'asc'),
249
- bid: new NodeList('limit', 'desc'),
265
+ restingLimit: {
266
+ ask: new NodeList('restingLimit', 'asc'),
267
+ bid: new NodeList('restingLimit', 'desc'),
250
268
  },
251
269
  floatingLimit: {
252
270
  ask: new NodeList('floatingLimit', 'asc'),
253
271
  bid: new NodeList('floatingLimit', 'desc'),
254
272
  },
273
+ takingLimit: {
274
+ ask: new NodeList('takingLimit', 'asc'),
275
+ bid: new NodeList('takingLimit', 'asc'), // always sort ascending for market orders
276
+ },
255
277
  market: {
256
278
  ask: new NodeList('market', 'asc'),
257
279
  bid: new NodeList('market', 'asc'), // always sort ascending for market orders
@@ -266,11 +288,14 @@ export class DLOB {
266
288
  public updateOrder(
267
289
  order: Order,
268
290
  userAccount: PublicKey,
291
+ slot: number,
269
292
  cumulativeBaseAssetAmountFilled: BN,
270
293
  onUpdate?: OrderBookCallback
271
294
  ): void {
295
+ this.updateRestingLimitOrders(slot);
296
+
272
297
  if (order.baseAssetAmount.eq(cumulativeBaseAssetAmountFilled)) {
273
- this.delete(order, userAccount);
298
+ this.delete(order, userAccount, slot);
274
299
  return;
275
300
  }
276
301
 
@@ -283,7 +308,7 @@ export class DLOB {
283
308
  };
284
309
  newOrder.baseAssetAmountFilled = cumulativeBaseAssetAmountFilled;
285
310
 
286
- this.getListForOrder(order)?.update(newOrder, userAccount);
311
+ this.getListForOrder(order, slot)?.update(newOrder, userAccount);
287
312
 
288
313
  if (onUpdate) {
289
314
  onUpdate();
@@ -293,12 +318,15 @@ export class DLOB {
293
318
  public trigger(
294
319
  order: Order,
295
320
  userAccount: PublicKey,
321
+ slot: number,
296
322
  onTrigger?: OrderBookCallback
297
323
  ): void {
298
324
  if (isVariant(order.status, 'init')) {
299
325
  return;
300
326
  }
301
327
 
328
+ this.updateRestingLimitOrders(slot);
329
+
302
330
  if (isTriggered(order)) {
303
331
  return;
304
332
  }
@@ -309,7 +337,7 @@ export class DLOB {
309
337
  .trigger[isVariant(order.triggerCondition, 'above') ? 'above' : 'below'];
310
338
  triggerList.remove(order, userAccount);
311
339
 
312
- this.getListForOrder(order)?.insert(order, marketType, userAccount);
340
+ this.getListForOrder(order, slot)?.insert(order, marketType, userAccount);
313
341
  if (onTrigger) {
314
342
  onTrigger();
315
343
  }
@@ -318,19 +346,25 @@ export class DLOB {
318
346
  public delete(
319
347
  order: Order,
320
348
  userAccount: PublicKey,
349
+ slot: number,
321
350
  onDelete?: OrderBookCallback
322
351
  ): void {
323
352
  if (isVariant(order.status, 'init')) {
324
353
  return;
325
354
  }
326
355
 
327
- this.getListForOrder(order)?.remove(order, userAccount);
356
+ this.updateRestingLimitOrders(slot);
357
+
358
+ this.getListForOrder(order, slot)?.remove(order, userAccount);
328
359
  if (onDelete) {
329
360
  onDelete();
330
361
  }
331
362
  }
332
363
 
333
- public getListForOrder(order: Order): NodeList<any> | undefined {
364
+ public getListForOrder(
365
+ order: Order,
366
+ slot: number
367
+ ): NodeList<any> | undefined {
334
368
  const isInactiveTriggerOrder =
335
369
  mustBeTriggered(order) && !isTriggered(order);
336
370
 
@@ -344,7 +378,8 @@ export class DLOB {
344
378
  } else if (order.oraclePriceOffset !== 0) {
345
379
  type = 'floatingLimit';
346
380
  } else {
347
- type = 'limit';
381
+ const isResting = isRestingLimitOrder(order, slot);
382
+ type = isResting ? 'restingLimit' : 'takingLimit';
348
383
  }
349
384
 
350
385
  let subType: string;
@@ -365,6 +400,58 @@ export class DLOB {
365
400
  ];
366
401
  }
367
402
 
403
+ public updateRestingLimitOrders(slot: number): void {
404
+ if (slot <= this.maxSlotForRestingLimitOrders) {
405
+ return;
406
+ }
407
+
408
+ this.maxSlotForRestingLimitOrders = slot;
409
+
410
+ this.updateRestingLimitOrdersForMarketType(slot, 'perp');
411
+
412
+ this.updateRestingLimitOrdersForMarketType(slot, 'spot');
413
+ }
414
+
415
+ updateRestingLimitOrdersForMarketType(
416
+ slot: number,
417
+ marketTypeStr: MarketTypeStr
418
+ ): void {
419
+ for (const [_, nodeLists] of this.orderLists.get(marketTypeStr)) {
420
+ const nodesToUpdate = [];
421
+ for (const node of nodeLists.takingLimit.ask.getGenerator()) {
422
+ if (!isRestingLimitOrder(node.order, slot)) {
423
+ continue;
424
+ }
425
+
426
+ nodesToUpdate.push({
427
+ side: 'ask',
428
+ node,
429
+ });
430
+ }
431
+
432
+ for (const node of nodeLists.takingLimit.bid.getGenerator()) {
433
+ if (!isRestingLimitOrder(node.order, slot)) {
434
+ continue;
435
+ }
436
+
437
+ nodesToUpdate.push({
438
+ side: 'bid',
439
+ node,
440
+ });
441
+ }
442
+
443
+ for (const nodeToUpdate of nodesToUpdate) {
444
+ const { side, node } = nodeToUpdate;
445
+ nodeLists.takingLimit[side].remove(node.order, node.userAccount);
446
+ nodeLists.restingLimit[side].insert(
447
+ node.order,
448
+ marketTypeStr,
449
+ node.userAccount
450
+ );
451
+ }
452
+ }
453
+ }
454
+
368
455
  public getOrder(orderId: number, userAccount: PublicKey): Order | undefined {
369
456
  for (const nodeList of this.getNodeLists()) {
370
457
  const node = nodeList.get(orderId, userAccount);
@@ -393,24 +480,30 @@ export class DLOB {
393
480
 
394
481
  const isAmmPaused = ammPaused(stateAccount, marketAccount);
395
482
 
396
- const marketOrderNodesToFill: Array<NodeToFill> =
397
- this.findMarketNodesToFill(
483
+ const minAuctionDuration = isVariant(marketType, 'perp')
484
+ ? stateAccount.minPerpAuctionDuration
485
+ : 0;
486
+
487
+ const restingLimitOrderNodesToFill: Array<NodeToFill> =
488
+ this.findRestingLimitOrderNodesToFill(
398
489
  marketIndex,
399
490
  slot,
400
491
  marketType,
401
492
  oraclePriceData,
402
493
  isAmmPaused,
494
+ minAuctionDuration,
403
495
  fallbackAsk,
404
496
  fallbackBid
405
497
  );
406
498
 
407
- const limitOrderNodesToFill: Array<NodeToFill> =
408
- this.findLimitOrderNodesToFill(
499
+ const takingOrderNodesToFill: Array<NodeToFill> =
500
+ this.findTakingNodesToFill(
409
501
  marketIndex,
410
502
  slot,
411
503
  marketType,
412
504
  oraclePriceData,
413
505
  isAmmPaused,
506
+ minAuctionDuration,
414
507
  fallbackAsk,
415
508
  fallbackBid
416
509
  );
@@ -421,28 +514,30 @@ export class DLOB {
421
514
  ts,
422
515
  marketType
423
516
  );
424
- return marketOrderNodesToFill.concat(
425
- limitOrderNodesToFill,
517
+ return restingLimitOrderNodesToFill.concat(
518
+ takingOrderNodesToFill,
426
519
  expiredNodesToFill
427
520
  );
428
521
  }
429
522
 
430
- public findLimitOrderNodesToFill(
523
+ public findRestingLimitOrderNodesToFill(
431
524
  marketIndex: number,
432
525
  slot: number,
433
526
  marketType: MarketType,
434
527
  oraclePriceData: OraclePriceData,
435
528
  isAmmPaused: boolean,
529
+ minAuctionDuration: number,
436
530
  fallbackAsk: BN | undefined,
437
531
  fallbackBid: BN | undefined
438
532
  ): NodeToFill[] {
439
533
  const nodesToFill = new Array<NodeToFill>();
440
534
 
441
- const crossingNodes = this.findCrossingLimitOrders(
535
+ const crossingNodes = this.findCrossingRestingLimitOrders(
442
536
  marketIndex,
443
537
  slot,
444
538
  marketType,
445
539
  oraclePriceData,
540
+ minAuctionDuration,
446
541
  fallbackAsk,
447
542
  fallbackBid
448
543
  );
@@ -452,7 +547,7 @@ export class DLOB {
452
547
  }
453
548
 
454
549
  if (fallbackBid && !isAmmPaused) {
455
- const askGenerator = this.getLimitAsks(
550
+ const askGenerator = this.getRestingLimitAsks(
456
551
  marketIndex,
457
552
  slot,
458
553
  marketType,
@@ -466,7 +561,8 @@ export class DLOB {
466
561
  fallbackBid,
467
562
  (askPrice, fallbackPrice) => {
468
563
  return askPrice.lte(fallbackPrice);
469
- }
564
+ },
565
+ minAuctionDuration
470
566
  );
471
567
 
472
568
  for (const askCrossingFallback of asksCrossingFallback) {
@@ -475,7 +571,7 @@ export class DLOB {
475
571
  }
476
572
 
477
573
  if (fallbackAsk && !isAmmPaused) {
478
- const bidGenerator = this.getLimitBids(
574
+ const bidGenerator = this.getRestingLimitBids(
479
575
  marketIndex,
480
576
  slot,
481
577
  marketType,
@@ -489,7 +585,8 @@ export class DLOB {
489
585
  fallbackAsk,
490
586
  (bidPrice, fallbackPrice) => {
491
587
  return bidPrice.gte(fallbackPrice);
492
- }
588
+ },
589
+ minAuctionDuration
493
590
  );
494
591
 
495
592
  for (const bidCrossingFallback of bidsCrossingFallback) {
@@ -500,25 +597,31 @@ export class DLOB {
500
597
  return nodesToFill;
501
598
  }
502
599
 
503
- public findMarketNodesToFill(
600
+ public findTakingNodesToFill(
504
601
  marketIndex: number,
505
602
  slot: number,
506
603
  marketType: MarketType,
507
604
  oraclePriceData: OraclePriceData,
508
605
  isAmmPaused: boolean,
606
+ minAuctionDuration: number,
509
607
  fallbackAsk: BN | undefined,
510
608
  fallbackBid?: BN | undefined
511
609
  ): NodeToFill[] {
512
610
  const nodesToFill = new Array<NodeToFill>();
513
611
 
514
- let marketOrderGenerator = this.getMarketAsks(marketIndex, marketType);
612
+ let takingOrderGenerator = this.getTakingAsks(
613
+ marketIndex,
614
+ marketType,
615
+ slot,
616
+ oraclePriceData
617
+ );
515
618
 
516
- const marketAsksCrossingBids = this.findMarketNodesCrossingLimitNodes(
619
+ const takingAsksCrossingBids = this.findTakingNodesCrossingMakerNodes(
517
620
  marketIndex,
518
621
  slot,
519
622
  marketType,
520
623
  oraclePriceData,
521
- marketOrderGenerator,
624
+ takingOrderGenerator,
522
625
  this.getMakerLimitBids.bind(this),
523
626
  (takerPrice, makerPrice) => {
524
627
  if (isVariant(marketType, 'spot')) {
@@ -534,37 +637,48 @@ export class DLOB {
534
637
  },
535
638
  fallbackAsk
536
639
  );
537
- for (const marketAskCrossingBid of marketAsksCrossingBids) {
538
- nodesToFill.push(marketAskCrossingBid);
640
+ for (const takingAskCrossingBid of takingAsksCrossingBids) {
641
+ nodesToFill.push(takingAskCrossingBid);
539
642
  }
540
643
 
541
644
  if (fallbackBid && !isAmmPaused) {
542
- marketOrderGenerator = this.getMarketAsks(marketIndex, marketType);
543
- const marketAsksCrossingFallback =
645
+ takingOrderGenerator = this.getTakingAsks(
646
+ marketIndex,
647
+ marketType,
648
+ slot,
649
+ oraclePriceData
650
+ );
651
+ const takingAsksCrossingFallback =
544
652
  this.findNodesCrossingFallbackLiquidity(
545
653
  marketType,
546
654
  slot,
547
655
  oraclePriceData,
548
- marketOrderGenerator,
656
+ takingOrderGenerator,
549
657
  fallbackBid,
550
658
  (takerPrice, fallbackPrice) => {
551
659
  return takerPrice === undefined || takerPrice.lte(fallbackPrice);
552
- }
660
+ },
661
+ minAuctionDuration
553
662
  );
554
663
 
555
- for (const marketAskCrossingFallback of marketAsksCrossingFallback) {
556
- nodesToFill.push(marketAskCrossingFallback);
664
+ for (const takingAskCrossingFallback of takingAsksCrossingFallback) {
665
+ nodesToFill.push(takingAskCrossingFallback);
557
666
  }
558
667
  }
559
668
 
560
- marketOrderGenerator = this.getMarketBids(marketIndex, marketType);
669
+ takingOrderGenerator = this.getTakingBids(
670
+ marketIndex,
671
+ marketType,
672
+ slot,
673
+ oraclePriceData
674
+ );
561
675
 
562
- const marketBidsToFill = this.findMarketNodesCrossingLimitNodes(
676
+ const takingBidsToFill = this.findTakingNodesCrossingMakerNodes(
563
677
  marketIndex,
564
678
  slot,
565
679
  marketType,
566
680
  oraclePriceData,
567
- marketOrderGenerator,
681
+ takingOrderGenerator,
568
682
  this.getMakerLimitAsks.bind(this),
569
683
  (takerPrice, makerPrice) => {
570
684
  if (isVariant(marketType, 'spot')) {
@@ -582,24 +696,30 @@ export class DLOB {
582
696
  fallbackBid
583
697
  );
584
698
 
585
- for (const marketBidToFill of marketBidsToFill) {
586
- nodesToFill.push(marketBidToFill);
699
+ for (const takingBidToFill of takingBidsToFill) {
700
+ nodesToFill.push(takingBidToFill);
587
701
  }
588
702
 
589
703
  if (fallbackAsk && !isAmmPaused) {
590
- marketOrderGenerator = this.getMarketBids(marketIndex, marketType);
591
- const marketBidsCrossingFallback =
704
+ takingOrderGenerator = this.getTakingBids(
705
+ marketIndex,
706
+ marketType,
707
+ slot,
708
+ oraclePriceData
709
+ );
710
+ const takingBidsCrossingFallback =
592
711
  this.findNodesCrossingFallbackLiquidity(
593
712
  marketType,
594
713
  slot,
595
714
  oraclePriceData,
596
- marketOrderGenerator,
715
+ takingOrderGenerator,
597
716
  fallbackAsk,
598
717
  (takerPrice, fallbackPrice) => {
599
718
  return takerPrice === undefined || takerPrice.gte(fallbackPrice);
600
- }
719
+ },
720
+ minAuctionDuration
601
721
  );
602
- for (const marketBidCrossingFallback of marketBidsCrossingFallback) {
722
+ for (const marketBidCrossingFallback of takingBidsCrossingFallback) {
603
723
  nodesToFill.push(marketBidCrossingFallback);
604
724
  }
605
725
  }
@@ -607,7 +727,7 @@ export class DLOB {
607
727
  return nodesToFill;
608
728
  }
609
729
 
610
- public findMarketNodesCrossingLimitNodes(
730
+ public findTakingNodesCrossingMakerNodes(
611
731
  marketIndex: number,
612
732
  slot: number,
613
733
  marketType: MarketType,
@@ -671,7 +791,7 @@ export class DLOB {
671
791
  const newMakerOrder = { ...makerOrder };
672
792
  newMakerOrder.baseAssetAmountFilled =
673
793
  makerOrder.baseAssetAmountFilled.add(baseFilled);
674
- this.getListForOrder(newMakerOrder).update(
794
+ this.getListForOrder(newMakerOrder, slot).update(
675
795
  newMakerOrder,
676
796
  makerNode.userAccount
677
797
  );
@@ -679,7 +799,7 @@ export class DLOB {
679
799
  const newTakerOrder = { ...takerOrder };
680
800
  newTakerOrder.baseAssetAmountFilled =
681
801
  takerOrder.baseAssetAmountFilled.add(baseFilled);
682
- this.getListForOrder(newTakerOrder).update(
802
+ this.getListForOrder(newTakerOrder, slot).update(
683
803
  newTakerOrder,
684
804
  takerNode.userAccount
685
805
  );
@@ -701,7 +821,8 @@ export class DLOB {
701
821
  oraclePriceData: OraclePriceData,
702
822
  nodeGenerator: Generator<DLOBNode>,
703
823
  fallbackPrice: BN,
704
- doesCross: (nodePrice: BN | undefined, fallbackPrice: BN) => boolean
824
+ doesCross: (nodePrice: BN | undefined, fallbackPrice: BN) => boolean,
825
+ minAuctionDuration: number
705
826
  ): NodeToFill[] {
706
827
  const nodesToFill = new Array<NodeToFill>();
707
828
 
@@ -721,7 +842,12 @@ export class DLOB {
721
842
 
722
843
  // fallback is available if auction is complete or it's a spot order
723
844
  const fallbackAvailable =
724
- isVariant(marketType, 'spot') || isAuctionComplete(node.order, slot);
845
+ isVariant(marketType, 'spot') ||
846
+ isFallbackAvailableLiquiditySource(
847
+ node.order,
848
+ minAuctionDuration,
849
+ slot
850
+ );
725
851
 
726
852
  if (crosses && fallbackAvailable) {
727
853
  nodesToFill.push({
@@ -752,12 +878,14 @@ export class DLOB {
752
878
 
753
879
  // All bids/asks that can expire
754
880
  const bidGenerators = [
755
- nodeLists.limit.bid.getGenerator(),
881
+ nodeLists.takingLimit.bid.getGenerator(),
882
+ nodeLists.restingLimit.bid.getGenerator(),
756
883
  nodeLists.floatingLimit.bid.getGenerator(),
757
884
  nodeLists.market.bid.getGenerator(),
758
885
  ];
759
886
  const askGenerators = [
760
- nodeLists.limit.ask.getGenerator(),
887
+ nodeLists.takingLimit.ask.getGenerator(),
888
+ nodeLists.restingLimit.ask.getGenerator(),
761
889
  nodeLists.floatingLimit.ask.getGenerator(),
762
890
  nodeLists.market.ask.getGenerator(),
763
891
  ];
@@ -788,31 +916,40 @@ export class DLOB {
788
916
  public findJitAuctionNodesToFill(
789
917
  marketIndex: number,
790
918
  slot: number,
919
+ oraclePriceData: OraclePriceData,
791
920
  marketType: MarketType
792
921
  ): NodeToFill[] {
793
922
  const nodesToFill = new Array<NodeToFill>();
794
923
  // Then see if there are orders still in JIT auction
795
- for (const marketBid of this.getMarketBids(marketIndex, marketType)) {
796
- if (!isAuctionComplete(marketBid.order, slot)) {
797
- nodesToFill.push({
798
- node: marketBid,
799
- });
800
- }
924
+ for (const marketBid of this.getTakingBids(
925
+ marketIndex,
926
+ marketType,
927
+ slot,
928
+ oraclePriceData
929
+ )) {
930
+ nodesToFill.push({
931
+ node: marketBid,
932
+ });
801
933
  }
802
934
 
803
- for (const marketAsk of this.getMarketAsks(marketIndex, marketType)) {
804
- if (!isAuctionComplete(marketAsk.order, slot)) {
805
- nodesToFill.push({
806
- node: marketAsk,
807
- });
808
- }
935
+ for (const marketAsk of this.getTakingAsks(
936
+ marketIndex,
937
+ marketType,
938
+ slot,
939
+ oraclePriceData
940
+ )) {
941
+ nodesToFill.push({
942
+ node: marketAsk,
943
+ });
809
944
  }
810
945
  return nodesToFill;
811
946
  }
812
947
 
813
- *getMarketBids(
948
+ *getTakingBids(
814
949
  marketIndex: number,
815
- marketType: MarketType
950
+ marketType: MarketType,
951
+ slot: number,
952
+ oraclePriceData: OraclePriceData
816
953
  ): Generator<DLOBNode> {
817
954
  const marketTypeStr = getVariant(marketType) as MarketTypeStr;
818
955
  const orderLists = this.orderLists.get(marketTypeStr).get(marketIndex);
@@ -820,18 +957,28 @@ export class DLOB {
820
957
  return;
821
958
  }
822
959
 
823
- const generator = orderLists.market.bid.getGenerator();
824
- for (const marketBidNode of generator) {
825
- if (marketBidNode.isBaseFilled()) {
826
- continue;
960
+ this.updateRestingLimitOrders(slot);
961
+
962
+ const generatorList = [
963
+ orderLists.market.bid.getGenerator(),
964
+ orderLists.takingLimit.bid.getGenerator(),
965
+ ];
966
+
967
+ yield* this.getBestNode(
968
+ generatorList,
969
+ oraclePriceData,
970
+ slot,
971
+ (bestNode, currentNode) => {
972
+ return bestNode.order.slot.lt(currentNode.order.slot);
827
973
  }
828
- yield marketBidNode;
829
- }
974
+ );
830
975
  }
831
976
 
832
- *getMarketAsks(
977
+ *getTakingAsks(
833
978
  marketIndex: number,
834
- marketType: MarketType
979
+ marketType: MarketType,
980
+ slot: number,
981
+ oraclePriceData: OraclePriceData
835
982
  ): Generator<DLOBNode> {
836
983
  const marketTypeStr = getVariant(marketType) as MarketTypeStr;
837
984
  const orderLists = this.orderLists.get(marketTypeStr).get(marketIndex);
@@ -839,20 +986,33 @@ export class DLOB {
839
986
  return;
840
987
  }
841
988
 
842
- const generator = orderLists.market.ask.getGenerator();
843
- for (const marketAskNode of generator) {
844
- if (marketAskNode.isBaseFilled()) {
845
- continue;
989
+ this.updateRestingLimitOrders(slot);
990
+
991
+ const generatorList = [
992
+ orderLists.market.ask.getGenerator(),
993
+ orderLists.takingLimit.ask.getGenerator(),
994
+ ];
995
+
996
+ yield* this.getBestNode(
997
+ generatorList,
998
+ oraclePriceData,
999
+ slot,
1000
+ (bestNode, currentNode) => {
1001
+ return bestNode.order.slot.lt(currentNode.order.slot);
846
1002
  }
847
- yield marketAskNode;
848
- }
1003
+ );
849
1004
  }
850
1005
 
851
1006
  private *getBestNode(
852
1007
  generatorList: Array<Generator<DLOBNode>>,
853
1008
  oraclePriceData: OraclePriceData,
854
1009
  slot: number,
855
- compareFcn: (bestPrice: BN, currentPrice: BN) => boolean
1010
+ compareFcn: (
1011
+ bestDLOBNode: DLOBNode,
1012
+ currentDLOBNode: DLOBNode,
1013
+ slot: number,
1014
+ oraclePriceData: OraclePriceData
1015
+ ) => boolean
856
1016
  ): Generator<DLOBNode> {
857
1017
  const generators = generatorList.map((generator) => {
858
1018
  return {
@@ -876,18 +1036,7 @@ export class DLOB {
876
1036
  const bestValue = bestGenerator.next.value as DLOBNode;
877
1037
  const currentValue = currentGenerator.next.value as DLOBNode;
878
1038
 
879
- // always return the market orders first
880
- if (bestValue.order && isMarketOrder(bestValue.order)) {
881
- return bestGenerator;
882
- }
883
- if (currentValue.order && isMarketOrder(currentValue.order)) {
884
- return currentGenerator;
885
- }
886
-
887
- const bestPrice = bestValue.getPrice(oraclePriceData, slot);
888
- const currentPrice = currentValue.getPrice(oraclePriceData, slot);
889
-
890
- return compareFcn(bestPrice, currentPrice)
1039
+ return compareFcn(bestValue, currentValue, slot, oraclePriceData)
891
1040
  ? bestGenerator
892
1041
  : currentGenerator;
893
1042
  }
@@ -908,7 +1057,7 @@ export class DLOB {
908
1057
  }
909
1058
  }
910
1059
 
911
- *getLimitAsks(
1060
+ *getRestingLimitAsks(
912
1061
  marketIndex: number,
913
1062
  slot: number,
914
1063
  marketType: MarketType,
@@ -917,6 +1066,9 @@ export class DLOB {
917
1066
  if (isVariant(marketType, 'spot') && !oraclePriceData) {
918
1067
  throw new Error('Must provide OraclePriceData to get spot asks');
919
1068
  }
1069
+
1070
+ this.updateRestingLimitOrders(slot);
1071
+
920
1072
  const marketTypeStr = getVariant(marketType) as MarketTypeStr;
921
1073
  const nodeLists = this.orderLists.get(marketTypeStr).get(marketIndex);
922
1074
 
@@ -925,7 +1077,7 @@ export class DLOB {
925
1077
  }
926
1078
 
927
1079
  const generatorList = [
928
- nodeLists.limit.ask.getGenerator(),
1080
+ nodeLists.restingLimit.ask.getGenerator(),
929
1081
  nodeLists.floatingLimit.ask.getGenerator(),
930
1082
  ];
931
1083
 
@@ -933,15 +1085,17 @@ export class DLOB {
933
1085
  generatorList,
934
1086
  oraclePriceData,
935
1087
  slot,
936
- (bestPrice, currentPrice) => {
937
- return bestPrice.lt(currentPrice);
1088
+ (bestNode, currentNode, slot, oraclePriceData) => {
1089
+ return bestNode
1090
+ .getPrice(oraclePriceData, slot)
1091
+ .lt(currentNode.getPrice(oraclePriceData, slot));
938
1092
  }
939
1093
  );
940
1094
  }
941
1095
 
942
1096
  /**
943
- * Filters the limit asks that are post only, have been place for sufficiently long or are above the fallback bid
944
- * Market orders can only fill against orders that meet this criteria
1097
+ * Filters the limit asks that are resting and do not cross fallback bid
1098
+ * Taking orders can only fill against orders that meet this criteria
945
1099
  *
946
1100
  * @returns
947
1101
  */
@@ -952,28 +1106,23 @@ export class DLOB {
952
1106
  oraclePriceData: OraclePriceData,
953
1107
  fallbackBid?: BN
954
1108
  ): Generator<DLOBNode> {
955
- for (const node of this.getLimitAsks(
1109
+ for (const node of this.getRestingLimitAsks(
956
1110
  marketIndex,
957
1111
  slot,
958
1112
  marketType,
959
1113
  oraclePriceData
960
1114
  )) {
961
- if (this.isRestingLimitOrder(node.order, slot)) {
962
- yield node;
963
- } else if (
1115
+ if (
964
1116
  fallbackBid &&
965
- node.getPrice(oraclePriceData, slot).gt(fallbackBid)
1117
+ node.getPrice(oraclePriceData, slot).lte(fallbackBid)
966
1118
  ) {
967
- yield node;
1119
+ continue;
968
1120
  }
1121
+ yield node;
969
1122
  }
970
1123
  }
971
1124
 
972
- isRestingLimitOrder(order: Order, slot: number): boolean {
973
- return order.postOnly || new BN(slot).sub(order.slot).gte(new BN(45));
974
- }
975
-
976
- *getLimitBids(
1125
+ *getRestingLimitBids(
977
1126
  marketIndex: number,
978
1127
  slot: number,
979
1128
  marketType: MarketType,
@@ -983,6 +1132,8 @@ export class DLOB {
983
1132
  throw new Error('Must provide OraclePriceData to get spot bids');
984
1133
  }
985
1134
 
1135
+ this.updateRestingLimitOrders(slot);
1136
+
986
1137
  const marketTypeStr = getVariant(marketType) as MarketTypeStr;
987
1138
  const nodeLists = this.orderLists.get(marketTypeStr).get(marketIndex);
988
1139
 
@@ -991,7 +1142,7 @@ export class DLOB {
991
1142
  }
992
1143
 
993
1144
  const generatorList = [
994
- nodeLists.limit.bid.getGenerator(),
1145
+ nodeLists.restingLimit.bid.getGenerator(),
995
1146
  nodeLists.floatingLimit.bid.getGenerator(),
996
1147
  ];
997
1148
 
@@ -999,8 +1150,10 @@ export class DLOB {
999
1150
  generatorList,
1000
1151
  oraclePriceData,
1001
1152
  slot,
1002
- (bestPrice, currentPrice) => {
1003
- return bestPrice.gt(currentPrice);
1153
+ (bestNode, currentNode, slot, oraclePriceData) => {
1154
+ return bestNode
1155
+ .getPrice(oraclePriceData, slot)
1156
+ .gt(currentNode.getPrice(oraclePriceData, slot));
1004
1157
  }
1005
1158
  );
1006
1159
  }
@@ -1018,20 +1171,19 @@ export class DLOB {
1018
1171
  oraclePriceData: OraclePriceData,
1019
1172
  fallbackAsk?: BN
1020
1173
  ): Generator<DLOBNode> {
1021
- for (const node of this.getLimitBids(
1174
+ for (const node of this.getRestingLimitBids(
1022
1175
  marketIndex,
1023
1176
  slot,
1024
1177
  marketType,
1025
1178
  oraclePriceData
1026
1179
  )) {
1027
- if (this.isRestingLimitOrder(node.order, slot)) {
1028
- yield node;
1029
- } else if (
1180
+ if (
1030
1181
  fallbackAsk &&
1031
- node.getPrice(oraclePriceData, slot).lt(fallbackAsk)
1182
+ node.getPrice(oraclePriceData, slot).gte(fallbackAsk)
1032
1183
  ) {
1033
- yield node;
1184
+ continue;
1034
1185
  }
1186
+ yield node;
1035
1187
  }
1036
1188
  }
1037
1189
 
@@ -1047,8 +1199,8 @@ export class DLOB {
1047
1199
  }
1048
1200
 
1049
1201
  const generatorList = [
1050
- this.getMarketAsks(marketIndex, marketType),
1051
- this.getLimitAsks(marketIndex, slot, marketType, oraclePriceData),
1202
+ this.getTakingAsks(marketIndex, marketType, slot, oraclePriceData),
1203
+ this.getRestingLimitAsks(marketIndex, slot, marketType, oraclePriceData),
1052
1204
  ];
1053
1205
 
1054
1206
  const marketTypeStr = getVariant(marketType) as MarketTypeStr;
@@ -1060,8 +1212,29 @@ export class DLOB {
1060
1212
  generatorList,
1061
1213
  oraclePriceData,
1062
1214
  slot,
1063
- (bestPrice, currentPrice) => {
1064
- return bestPrice.lt(currentPrice);
1215
+ (bestNode, currentNode, slot, oraclePriceData) => {
1216
+ const bestNodeTaking = bestNode.order
1217
+ ? isTakingOrder(bestNode.order, slot)
1218
+ : false;
1219
+ const currentNodeTaking = currentNode.order
1220
+ ? isTakingOrder(currentNode.order, slot)
1221
+ : false;
1222
+
1223
+ if (bestNodeTaking && currentNodeTaking) {
1224
+ return bestNode.order.slot.lt(currentNode.order.slot);
1225
+ }
1226
+
1227
+ if (bestNodeTaking) {
1228
+ return true;
1229
+ }
1230
+
1231
+ if (currentNodeTaking) {
1232
+ return false;
1233
+ }
1234
+
1235
+ return bestNode
1236
+ .getPrice(oraclePriceData, slot)
1237
+ .lt(currentNode.getPrice(oraclePriceData, slot));
1065
1238
  }
1066
1239
  );
1067
1240
  }
@@ -1078,8 +1251,8 @@ export class DLOB {
1078
1251
  }
1079
1252
 
1080
1253
  const generatorList = [
1081
- this.getMarketBids(marketIndex, marketType),
1082
- this.getLimitBids(marketIndex, slot, marketType, oraclePriceData),
1254
+ this.getTakingBids(marketIndex, marketType, slot, oraclePriceData),
1255
+ this.getRestingLimitBids(marketIndex, slot, marketType, oraclePriceData),
1083
1256
  ];
1084
1257
 
1085
1258
  const marketTypeStr = getVariant(marketType) as MarketTypeStr;
@@ -1091,29 +1264,51 @@ export class DLOB {
1091
1264
  generatorList,
1092
1265
  oraclePriceData,
1093
1266
  slot,
1094
- (bestPrice, currentPrice) => {
1095
- return bestPrice.gt(currentPrice);
1267
+ (bestNode, currentNode, slot, oraclePriceData) => {
1268
+ const bestNodeTaking = bestNode.order
1269
+ ? isTakingOrder(bestNode.order, slot)
1270
+ : false;
1271
+ const currentNodeTaking = currentNode.order
1272
+ ? isTakingOrder(currentNode.order, slot)
1273
+ : false;
1274
+
1275
+ if (bestNodeTaking && currentNodeTaking) {
1276
+ return bestNode.order.slot.lt(currentNode.order.slot);
1277
+ }
1278
+
1279
+ if (bestNodeTaking) {
1280
+ return true;
1281
+ }
1282
+
1283
+ if (currentNodeTaking) {
1284
+ return false;
1285
+ }
1286
+
1287
+ return bestNode
1288
+ .getPrice(oraclePriceData, slot)
1289
+ .gt(currentNode.getPrice(oraclePriceData, slot));
1096
1290
  }
1097
1291
  );
1098
1292
  }
1099
1293
 
1100
- findCrossingLimitOrders(
1294
+ findCrossingRestingLimitOrders(
1101
1295
  marketIndex: number,
1102
1296
  slot: number,
1103
1297
  marketType: MarketType,
1104
1298
  oraclePriceData: OraclePriceData,
1299
+ minAuctionDuration: number,
1105
1300
  fallbackAsk: BN | undefined,
1106
1301
  fallbackBid: BN | undefined
1107
1302
  ): NodeToFill[] {
1108
1303
  const nodesToFill = new Array<NodeToFill>();
1109
1304
 
1110
- for (const askNode of this.getLimitAsks(
1305
+ for (const askNode of this.getRestingLimitAsks(
1111
1306
  marketIndex,
1112
1307
  slot,
1113
1308
  marketType,
1114
1309
  oraclePriceData
1115
1310
  )) {
1116
- for (const bidNode of this.getLimitBids(
1311
+ for (const bidNode of this.getRestingLimitBids(
1117
1312
  marketIndex,
1118
1313
  slot,
1119
1314
  marketType,
@@ -1142,7 +1337,13 @@ export class DLOB {
1142
1337
  );
1143
1338
 
1144
1339
  // extra guard against bad fills for limit orders where auction is incomplete
1145
- if (!isAuctionComplete(takerNode.order, slot)) {
1340
+ if (
1341
+ !isFallbackAvailableLiquiditySource(
1342
+ takerNode.order,
1343
+ minAuctionDuration,
1344
+ slot
1345
+ )
1346
+ ) {
1146
1347
  let bidPrice: BN;
1147
1348
  let askPrice: BN;
1148
1349
  if (isVariant(takerNode.order.direction, 'long')) {
@@ -1176,7 +1377,7 @@ export class DLOB {
1176
1377
  const newBidOrder = { ...bidOrder };
1177
1378
  newBidOrder.baseAssetAmountFilled =
1178
1379
  bidOrder.baseAssetAmountFilled.add(baseFilled);
1179
- this.getListForOrder(newBidOrder).update(
1380
+ this.getListForOrder(newBidOrder, slot).update(
1180
1381
  newBidOrder,
1181
1382
  bidNode.userAccount
1182
1383
  );
@@ -1185,7 +1386,7 @@ export class DLOB {
1185
1386
  const newAskOrder = { ...askOrder };
1186
1387
  newAskOrder.baseAssetAmountFilled =
1187
1388
  askOrder.baseAssetAmountFilled.add(baseFilled);
1188
- this.getListForOrder(newAskOrder).update(
1389
+ this.getListForOrder(newAskOrder, slot).update(
1189
1390
  newAskOrder,
1190
1391
  askNode.userAccount
1191
1392
  );
@@ -1288,11 +1489,9 @@ export class DLOB {
1288
1489
  if (triggerAboveList) {
1289
1490
  for (const node of triggerAboveList.getGenerator()) {
1290
1491
  if (oraclePrice.gt(node.order.triggerPrice)) {
1291
- if (isAuctionComplete(node.order, slot)) {
1292
- nodesToTrigger.push({
1293
- node: node,
1294
- });
1295
- }
1492
+ nodesToTrigger.push({
1493
+ node: node,
1494
+ });
1296
1495
  } else {
1297
1496
  break;
1298
1497
  }
@@ -1305,11 +1504,9 @@ export class DLOB {
1305
1504
  if (triggerBelowList) {
1306
1505
  for (const node of triggerBelowList.getGenerator()) {
1307
1506
  if (oraclePrice.lt(node.order.triggerPrice)) {
1308
- if (isAuctionComplete(node.order, slot)) {
1309
- nodesToTrigger.push({
1310
- node: node,
1311
- });
1312
- }
1507
+ nodesToTrigger.push({
1508
+ node: node,
1509
+ });
1313
1510
  } else {
1314
1511
  break;
1315
1512
  }
@@ -1438,8 +1635,10 @@ export class DLOB {
1438
1635
 
1439
1636
  *getNodeLists(): Generator<NodeList<DLOBNodeType>> {
1440
1637
  for (const [_, nodeLists] of this.orderLists.get('perp')) {
1441
- yield nodeLists.limit.bid;
1442
- yield nodeLists.limit.ask;
1638
+ yield nodeLists.restingLimit.bid;
1639
+ yield nodeLists.restingLimit.ask;
1640
+ yield nodeLists.takingLimit.bid;
1641
+ yield nodeLists.takingLimit.ask;
1443
1642
  yield nodeLists.market.bid;
1444
1643
  yield nodeLists.market.ask;
1445
1644
  yield nodeLists.floatingLimit.bid;
@@ -1449,8 +1648,10 @@ export class DLOB {
1449
1648
  }
1450
1649
 
1451
1650
  for (const [_, nodeLists] of this.orderLists.get('spot')) {
1452
- yield nodeLists.limit.bid;
1453
- yield nodeLists.limit.ask;
1651
+ yield nodeLists.restingLimit.bid;
1652
+ yield nodeLists.restingLimit.ask;
1653
+ yield nodeLists.takingLimit.bid;
1654
+ yield nodeLists.takingLimit.ask;
1454
1655
  yield nodeLists.market.bid;
1455
1656
  yield nodeLists.market.ask;
1456
1657
  yield nodeLists.floatingLimit.bid;