@drift-labs/sdk 2.0.13 → 2.0.14

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.
@@ -57,15 +57,20 @@ export declare class DLOB {
57
57
  trigger(order: Order, userAccount: PublicKey, onTrigger?: OrderBookCallback): void;
58
58
  getListForOrder(order: Order): NodeList<any> | undefined;
59
59
  findNodesToFill(marketIndex: number, fallbackBid: BN | undefined, fallbackAsk: BN | undefined, slot: number, ts: number, marketType: MarketType, oraclePriceData: OraclePriceData): NodeToFill[];
60
- findCrossingNodesToFill(marketIndex: number, slot: number, marketType: MarketType, oraclePriceData: OraclePriceData): NodeToFill[];
61
- findFallbackCrossingNodesToFill(marketIndex: number, fallbackBid: BN, fallbackAsk: BN, slot: number, marketType: MarketType, oraclePriceData: OraclePriceData): NodeToFill[];
60
+ findLimitOrderNodesToFill(marketIndex: number, slot: number, marketType: MarketType, oraclePriceData: OraclePriceData, isAmmPaused: boolean, fallbackAsk: BN | undefined, fallbackBid: BN | undefined): NodeToFill[];
61
+ findMarketNodesToFill(marketIndex: number, slot: number, marketType: MarketType, oraclePriceData: OraclePriceData, isAmmPaused: boolean, fallbackAsk: BN | undefined, fallbackBid?: BN | undefined): NodeToFill[];
62
+ findMarketNodesCrossingLimitNodes(slot: number, oraclePriceData: OraclePriceData, takerNodeGenerator: Generator<DLOBNode>, makerNodeGenerator: Generator<DLOBNode>, doesCross: (takerPrice: BN | undefined, makerPrice: BN) => boolean): NodeToFill[];
63
+ findNodesCrossingFallbackLiquidity(marketType: MarketType, slot: number, oraclePriceData: OraclePriceData, nodeGenerator: Generator<DLOBNode>, fallbackPrice: BN, doesCross: (nodePrice: BN | undefined, fallbackPrice: BN) => boolean): NodeToFill[];
62
64
  findExpiredNodesToFill(marketIndex: number, ts: number, marketType: MarketType): NodeToFill[];
63
65
  findJitAuctionNodesToFill(marketIndex: number, slot: number, marketType: MarketType): NodeToFill[];
64
66
  getMarketBids(marketIndex: number, marketType: MarketType): Generator<DLOBNode>;
65
67
  getMarketAsks(marketIndex: number, marketType: MarketType): Generator<DLOBNode>;
68
+ private getBestNode;
69
+ getLimitAsks(marketIndex: number, slot: number, marketType: MarketType, oraclePriceData: OraclePriceData): Generator<DLOBNode>;
70
+ getLimitBids(marketIndex: number, slot: number, marketType: MarketType, oraclePriceData: OraclePriceData): Generator<DLOBNode>;
66
71
  getAsks(marketIndex: number, fallbackAsk: BN | undefined, slot: number, marketType: MarketType, oraclePriceData: OraclePriceData): Generator<DLOBNode>;
67
72
  getBids(marketIndex: number, fallbackBid: BN | undefined, slot: number, marketType: MarketType, oraclePriceData: OraclePriceData): Generator<DLOBNode>;
68
- findCrossingOrders(askNode: DLOBNode, bidNode: DLOBNode, oraclePriceData: OraclePriceData, slot: number): {
73
+ findCrossingLimitOrders(askNode: DLOBNode, bidNode: DLOBNode, oraclePriceData: OraclePriceData, slot: number): {
69
74
  crossingNodes: NodeToFill[];
70
75
  exhaustedSide: Side;
71
76
  };
package/lib/dlob/DLOB.js CHANGED
@@ -180,27 +180,22 @@ class DLOB {
180
180
  if ((0, exchangeStatus_1.fillPaused)(this.stateAccount, marketAccount)) {
181
181
  return [];
182
182
  }
183
- // Find all the crossing nodes
184
- const crossingNodesToFill = this.findCrossingNodesToFill(marketIndex, slot, marketType, oraclePriceData);
185
- let fallbackCrossingNodesToFill = new Array();
186
- if (!(0, exchangeStatus_1.ammPaused)(this.stateAccount, marketAccount)) {
187
- fallbackCrossingNodesToFill = this.findFallbackCrossingNodesToFill(marketIndex, fallbackBid, fallbackAsk, slot, marketType, oraclePriceData);
188
- }
183
+ const isAmmPaused = (0, exchangeStatus_1.ammPaused)(this.stateAccount, marketAccount);
184
+ const marketOrderNodesToFill = this.findMarketNodesToFill(marketIndex, slot, marketType, oraclePriceData, isAmmPaused, fallbackAsk, fallbackBid);
185
+ const limitOrderNodesToFill = this.findLimitOrderNodesToFill(marketIndex, slot, marketType, oraclePriceData, isAmmPaused, fallbackAsk, fallbackBid);
189
186
  // get expired market nodes
190
187
  const expiredNodesToFill = this.findExpiredNodesToFill(marketIndex, ts, marketType);
191
- return crossingNodesToFill.concat(fallbackCrossingNodesToFill, expiredNodesToFill);
188
+ return marketOrderNodesToFill.concat(limitOrderNodesToFill, expiredNodesToFill);
192
189
  }
193
- findCrossingNodesToFill(marketIndex, slot, marketType, oraclePriceData) {
190
+ findLimitOrderNodesToFill(marketIndex, slot, marketType, oraclePriceData, isAmmPaused, fallbackAsk, fallbackBid) {
194
191
  const nodesToFill = new Array();
195
- const askGenerator = this.getAsks(marketIndex, undefined, // dont include vask
196
- slot, marketType, oraclePriceData);
197
- const bidGenerator = this.getBids(marketIndex, undefined, // dont include vbid
198
- slot, marketType, oraclePriceData);
192
+ let askGenerator = this.getLimitAsks(marketIndex, slot, marketType, oraclePriceData);
193
+ let bidGenerator = this.getLimitBids(marketIndex, slot, marketType, oraclePriceData);
199
194
  let nextAsk = askGenerator.next();
200
195
  let nextBid = bidGenerator.next();
201
196
  // First try to find orders that cross
202
197
  while (!nextAsk.done && !nextBid.done) {
203
- const { crossingNodes, exhaustedSide } = this.findCrossingOrders(nextAsk.value, nextBid.value, oraclePriceData, slot);
198
+ const { crossingNodes, exhaustedSide } = this.findCrossingLimitOrders(nextAsk.value, nextBid.value, oraclePriceData, slot);
204
199
  if (exhaustedSide === 'bid') {
205
200
  nextBid = bidGenerator.next();
206
201
  }
@@ -222,56 +217,141 @@ class DLOB {
222
217
  nodesToFill.push(crossingNode);
223
218
  }
224
219
  }
220
+ if (fallbackBid && !isAmmPaused) {
221
+ askGenerator = this.getLimitAsks(marketIndex, slot, marketType, oraclePriceData);
222
+ const asksCrossingFallback = this.findNodesCrossingFallbackLiquidity(marketType, slot, oraclePriceData, askGenerator, fallbackBid, (askPrice, fallbackPrice) => {
223
+ return askPrice.lte(fallbackPrice);
224
+ });
225
+ for (const askCrossingFallback of asksCrossingFallback) {
226
+ nodesToFill.push(askCrossingFallback);
227
+ }
228
+ }
229
+ if (fallbackAsk && !isAmmPaused) {
230
+ bidGenerator = this.getLimitBids(marketIndex, slot, marketType, oraclePriceData);
231
+ const bidsCrossingFallback = this.findNodesCrossingFallbackLiquidity(marketType, slot, oraclePriceData, bidGenerator, fallbackAsk, (bidPrice, fallbackPrice) => {
232
+ return bidPrice.gte(fallbackPrice);
233
+ });
234
+ for (const bidCrossingFallback of bidsCrossingFallback) {
235
+ nodesToFill.push(bidCrossingFallback);
236
+ }
237
+ }
225
238
  return nodesToFill;
226
239
  }
227
- findFallbackCrossingNodesToFill(marketIndex, fallbackBid, fallbackAsk, slot, marketType, oraclePriceData) {
228
- var _a, _b;
240
+ findMarketNodesToFill(marketIndex, slot, marketType, oraclePriceData, isAmmPaused, fallbackAsk, fallbackBid) {
229
241
  const nodesToFill = new Array();
230
- const askGenerator = this.getAsks(marketIndex, undefined, // dont include vask
231
- slot, marketType, oraclePriceData);
232
- const bidGenerator = this.getBids(marketIndex, undefined, // dont include vbid
233
- slot, marketType, oraclePriceData);
234
- let nextAsk = askGenerator.next();
235
- let nextBid = bidGenerator.next();
236
- // check for asks that cross fallbackBid
237
- while (!nextAsk.done && fallbackBid !== undefined) {
238
- const askNode = nextAsk.value;
239
- if ((0, __1.isVariant)(marketType, 'spot') && ((_a = askNode.order) === null || _a === void 0 ? void 0 : _a.postOnly)) {
240
- nextAsk = askGenerator.next();
242
+ let marketOrderGenerator = this.getMarketAsks(marketIndex, marketType);
243
+ let limitOrderGenerator = this.getLimitBids(marketIndex, slot, marketType, oraclePriceData);
244
+ const marketAsksCrossingBids = this.findMarketNodesCrossingLimitNodes(slot, oraclePriceData, marketOrderGenerator, limitOrderGenerator, (takerPrice, makerPrice) => {
245
+ return takerPrice === undefined || takerPrice.lte(makerPrice);
246
+ });
247
+ for (const marketAskCrossingBid of marketAsksCrossingBids) {
248
+ nodesToFill.push(marketAskCrossingBid);
249
+ }
250
+ if (fallbackBid && !isAmmPaused) {
251
+ marketOrderGenerator = this.getMarketAsks(marketIndex, marketType);
252
+ const marketAsksCrossingFallback = this.findNodesCrossingFallbackLiquidity(marketType, slot, oraclePriceData, marketOrderGenerator, fallbackBid, (takerPrice, fallbackPrice) => {
253
+ return takerPrice === undefined || takerPrice.lte(fallbackPrice);
254
+ });
255
+ for (const marketAskCrossingFallback of marketAsksCrossingFallback) {
256
+ nodesToFill.push(marketAskCrossingFallback);
257
+ }
258
+ }
259
+ marketOrderGenerator = this.getMarketBids(marketIndex, marketType);
260
+ limitOrderGenerator = this.getLimitAsks(marketIndex, slot, marketType, oraclePriceData);
261
+ const marketBidsToFill = this.findMarketNodesCrossingLimitNodes(slot, oraclePriceData, marketOrderGenerator, limitOrderGenerator, (takerPrice, fallbackPrice) => {
262
+ return takerPrice === undefined || takerPrice.gte(fallbackPrice);
263
+ });
264
+ for (const marketBidToFill of marketBidsToFill) {
265
+ nodesToFill.push(marketBidToFill);
266
+ }
267
+ if (fallbackAsk && !isAmmPaused) {
268
+ marketOrderGenerator = this.getMarketBids(marketIndex, marketType);
269
+ const marketBidsCrossingFallback = this.findNodesCrossingFallbackLiquidity(marketType, slot, oraclePriceData, marketOrderGenerator, fallbackAsk, (takerPrice, fallbackPrice) => {
270
+ return takerPrice === undefined || takerPrice.gte(fallbackPrice);
271
+ });
272
+ for (const marketBidCrossingFallback of marketBidsCrossingFallback) {
273
+ nodesToFill.push(marketBidCrossingFallback);
274
+ }
275
+ }
276
+ return nodesToFill;
277
+ }
278
+ findMarketNodesCrossingLimitNodes(slot, oraclePriceData, takerNodeGenerator, makerNodeGenerator, doesCross) {
279
+ const nodesToFill = new Array();
280
+ let nextTakerNode = takerNodeGenerator.next();
281
+ let nextMakerNode = makerNodeGenerator.next();
282
+ while (!nextTakerNode.done && !nextMakerNode.done) {
283
+ const takerNode = nextTakerNode.value;
284
+ const makerNode = nextMakerNode.value;
285
+ const bidUserAuthority = this.userMap.getUserAuthority(makerNode.userAccount.toString());
286
+ const askUserAuthority = this.userMap.getUserAuthority(takerNode.userAccount.toString());
287
+ // Can't match orders from the same authority
288
+ const sameAuthority = bidUserAuthority.equals(askUserAuthority);
289
+ if (sameAuthority) {
290
+ if (Math.random() < 0.5) {
291
+ nextTakerNode = takerNodeGenerator.next();
292
+ }
293
+ else {
294
+ nextMakerNode = makerNodeGenerator.next();
295
+ }
241
296
  continue;
242
297
  }
243
- const askLimitPrice = (0, __1.getOptionalLimitPrice)(askNode.order, oraclePriceData, slot);
244
- // order crosses if there is no limit price or it crosses fallback price
245
- const crosses = askLimitPrice === undefined || askLimitPrice.lte(fallbackBid);
246
- // fallback is available if auction is complete or it's a spot order
247
- const fallbackAvailable = (0, __1.isVariant)(marketType, 'spot') || (0, __1.isAuctionComplete)(askNode.order, slot);
248
- if (crosses && fallbackAvailable) {
249
- nodesToFill.push({
250
- node: askNode,
251
- makerNode: undefined, // filled by fallback
252
- });
298
+ const makerPrice = makerNode.getPrice(oraclePriceData, slot);
299
+ const takerPrice = takerNode.getPrice(oraclePriceData, slot);
300
+ const ordersCross = doesCross(takerPrice, makerPrice);
301
+ if (!ordersCross) {
302
+ // market orders aren't sorted by price, they are sorted by time, so we need to traverse
303
+ // through all of em
304
+ nextTakerNode = takerNodeGenerator.next();
305
+ continue;
306
+ }
307
+ nodesToFill.push({
308
+ node: takerNode,
309
+ makerNode: makerNode,
310
+ });
311
+ const makerOrder = makerNode.order;
312
+ const takerOrder = takerNode.order;
313
+ const makerBaseRemaining = makerOrder.baseAssetAmount.sub(makerOrder.baseAssetAmountFilled);
314
+ const takerBaseRemaining = takerOrder.baseAssetAmount.sub(takerOrder.baseAssetAmountFilled);
315
+ const baseFilled = __1.BN.min(makerBaseRemaining, takerBaseRemaining);
316
+ const newMakerOrder = { ...makerOrder };
317
+ newMakerOrder.baseAssetAmountFilled =
318
+ makerOrder.baseAssetAmountFilled.add(baseFilled);
319
+ this.getListForOrder(newMakerOrder).update(newMakerOrder, makerNode.userAccount);
320
+ if (newMakerOrder.baseAssetAmountFilled.eq(makerOrder.baseAssetAmount)) {
321
+ nextMakerNode = makerNodeGenerator.next();
322
+ }
323
+ const newTakerOrder = { ...takerOrder };
324
+ newTakerOrder.baseAssetAmountFilled =
325
+ takerOrder.baseAssetAmountFilled.add(baseFilled);
326
+ this.getListForOrder(newTakerOrder).update(newTakerOrder, takerNode.userAccount);
327
+ if (newTakerOrder.baseAssetAmountFilled.eq(takerOrder.baseAssetAmount)) {
328
+ nextTakerNode = takerNodeGenerator.next();
253
329
  }
254
- nextAsk = askGenerator.next();
255
330
  }
256
- // check for bids that cross fallbackAsk
257
- while (!nextBid.done && fallbackAsk !== undefined) {
258
- const bidNode = nextBid.value;
259
- if ((0, __1.isVariant)(marketType, 'spot') && ((_b = bidNode.order) === null || _b === void 0 ? void 0 : _b.postOnly)) {
260
- nextBid = bidGenerator.next();
331
+ return nodesToFill;
332
+ }
333
+ findNodesCrossingFallbackLiquidity(marketType, slot, oraclePriceData, nodeGenerator, fallbackPrice, doesCross) {
334
+ var _a;
335
+ const nodesToFill = new Array();
336
+ let nextNode = nodeGenerator.next();
337
+ while (!nextNode.done) {
338
+ const node = nextNode.value;
339
+ if ((0, __1.isVariant)(marketType, 'spot') && ((_a = node.order) === null || _a === void 0 ? void 0 : _a.postOnly)) {
340
+ nextNode = nodeGenerator.next();
261
341
  continue;
262
342
  }
263
- const bidLimitPrice = (0, __1.getOptionalLimitPrice)(bidNode.order, oraclePriceData, slot);
343
+ const nodePrice = (0, __1.getLimitPrice)(node.order, oraclePriceData, slot);
264
344
  // order crosses if there is no limit price or it crosses fallback price
265
- const crosses = bidLimitPrice === undefined || bidLimitPrice.gte(fallbackAsk);
345
+ const crosses = doesCross(nodePrice, fallbackPrice);
266
346
  // fallback is available if auction is complete or it's a spot order
267
- const fallbackAvailable = (0, __1.isVariant)(marketType, 'spot') || (0, __1.isAuctionComplete)(bidNode.order, slot);
347
+ const fallbackAvailable = (0, __1.isVariant)(marketType, 'spot') || (0, __1.isAuctionComplete)(node.order, slot);
268
348
  if (crosses && fallbackAvailable) {
269
349
  nodesToFill.push({
270
- node: bidNode,
350
+ node: node,
271
351
  makerNode: undefined, // filled by fallback
272
352
  });
273
353
  }
274
- nextBid = bidGenerator.next();
354
+ nextNode = nodeGenerator.next();
275
355
  }
276
356
  return nodesToFill;
277
357
  }
@@ -329,55 +409,60 @@ class DLOB {
329
409
  }
330
410
  return nodesToFill;
331
411
  }
332
- getMarketBids(marketIndex, marketType) {
412
+ *getMarketBids(marketIndex, marketType) {
333
413
  const marketTypeStr = (0, __1.getVariant)(marketType);
334
- return this.orderLists
414
+ const generator = this.orderLists
335
415
  .get(marketTypeStr)
336
416
  .get(marketIndex)
337
417
  .market.bid.getGenerator();
418
+ for (const marketBidNode of generator) {
419
+ if (marketBidNode.isBaseFilled()) {
420
+ continue;
421
+ }
422
+ yield marketBidNode;
423
+ }
338
424
  }
339
- getMarketAsks(marketIndex, marketType) {
425
+ *getMarketAsks(marketIndex, marketType) {
340
426
  const marketTypeStr = (0, __1.getVariant)(marketType);
341
- return this.orderLists
427
+ const generator = this.orderLists
342
428
  .get(marketTypeStr)
343
429
  .get(marketIndex)
344
430
  .market.ask.getGenerator();
345
- }
346
- *getAsks(marketIndex, fallbackAsk, slot, marketType, oraclePriceData) {
347
- if ((0, __1.isVariant)(marketType, 'spot') && !oraclePriceData) {
348
- throw new Error('Must provide OraclePriceData to get spot asks');
349
- }
350
- const marketTypeStr = (0, __1.getVariant)(marketType);
351
- const nodeLists = this.orderLists.get(marketTypeStr).get(marketIndex);
352
- const generatorList = [
353
- nodeLists.limit.ask.getGenerator(),
354
- nodeLists.floatingLimit.ask.getGenerator(),
355
- nodeLists.market.ask.getGenerator(),
356
- ];
357
- if (marketTypeStr === 'perp' && fallbackAsk) {
358
- generatorList.push((0, NodeList_1.getVammNodeGenerator)(fallbackAsk));
359
- }
360
- if (generatorList.length === 0) {
361
- throw new Error('No ask generators found');
431
+ for (const marketAskNode of generator) {
432
+ if (marketAskNode.isBaseFilled()) {
433
+ continue;
434
+ }
435
+ yield marketAskNode;
362
436
  }
363
- const askGenerators = generatorList.map((generator) => {
437
+ }
438
+ *getBestNode(generatorList, oraclePriceData, slot, compareFcn) {
439
+ const generators = generatorList.map((generator) => {
364
440
  return {
365
441
  next: generator.next(),
366
442
  generator,
367
443
  };
368
444
  });
369
- let asksExhausted = false;
370
- while (!asksExhausted) {
371
- const bestGenerator = askGenerators.reduce((bestGenerator, currentGenerator) => {
445
+ let sideExhausted = false;
446
+ while (!sideExhausted) {
447
+ const bestGenerator = generators.reduce((bestGenerator, currentGenerator) => {
372
448
  if (currentGenerator.next.done) {
373
449
  return bestGenerator;
374
450
  }
375
451
  if (bestGenerator.next.done) {
376
452
  return currentGenerator;
377
453
  }
378
- const bestAskPrice = bestGenerator.next.value.getPrice(oraclePriceData, slot);
379
- const currentAskPrice = currentGenerator.next.value.getPrice(oraclePriceData, slot);
380
- return bestAskPrice.lt(currentAskPrice)
454
+ const bestValue = bestGenerator.next.value;
455
+ const currentValue = currentGenerator.next.value;
456
+ // always return the market orders first
457
+ if (bestValue.order && (0, __1.isMarketOrder)(bestValue.order)) {
458
+ return bestGenerator;
459
+ }
460
+ if (currentValue.order && (0, __1.isMarketOrder)(currentValue.order)) {
461
+ return currentGenerator;
462
+ }
463
+ const bestPrice = bestValue.getPrice(oraclePriceData, slot);
464
+ const currentPrice = currentValue.getPrice(oraclePriceData, slot);
465
+ return compareFcn(bestPrice, currentPrice)
381
466
  ? bestGenerator
382
467
  : currentGenerator;
383
468
  });
@@ -399,11 +484,25 @@ class DLOB {
399
484
  bestGenerator.next = bestGenerator.generator.next();
400
485
  }
401
486
  else {
402
- asksExhausted = true;
487
+ sideExhausted = true;
403
488
  }
404
489
  }
405
490
  }
406
- *getBids(marketIndex, fallbackBid, slot, marketType, oraclePriceData) {
491
+ *getLimitAsks(marketIndex, slot, marketType, oraclePriceData) {
492
+ if ((0, __1.isVariant)(marketType, 'spot') && !oraclePriceData) {
493
+ throw new Error('Must provide OraclePriceData to get spot asks');
494
+ }
495
+ const marketTypeStr = (0, __1.getVariant)(marketType);
496
+ const nodeLists = this.orderLists.get(marketTypeStr).get(marketIndex);
497
+ const generatorList = [
498
+ nodeLists.limit.ask.getGenerator(),
499
+ nodeLists.floatingLimit.ask.getGenerator(),
500
+ ];
501
+ yield* this.getBestNode(generatorList, oraclePriceData, slot, (bestPrice, currentPrice) => {
502
+ return bestPrice.lt(currentPrice);
503
+ });
504
+ }
505
+ *getLimitBids(marketIndex, slot, marketType, oraclePriceData) {
407
506
  if ((0, __1.isVariant)(marketType, 'spot') && !oraclePriceData) {
408
507
  throw new Error('Must provide OraclePriceData to get spot bids');
409
508
  }
@@ -412,58 +511,44 @@ class DLOB {
412
511
  const generatorList = [
413
512
  nodeLists.limit.bid.getGenerator(),
414
513
  nodeLists.floatingLimit.bid.getGenerator(),
415
- nodeLists.market.bid.getGenerator(),
416
514
  ];
417
- if (marketTypeStr === 'perp' && fallbackBid) {
418
- generatorList.push((0, NodeList_1.getVammNodeGenerator)(fallbackBid));
515
+ yield* this.getBestNode(generatorList, oraclePriceData, slot, (bestPrice, currentPrice) => {
516
+ return bestPrice.gt(currentPrice);
517
+ });
518
+ }
519
+ *getAsks(marketIndex, fallbackAsk, slot, marketType, oraclePriceData) {
520
+ if ((0, __1.isVariant)(marketType, 'spot') && !oraclePriceData) {
521
+ throw new Error('Must provide OraclePriceData to get spot asks');
419
522
  }
420
- if (generatorList.length === 0) {
421
- throw new Error('No bid generators found');
523
+ const generatorList = [
524
+ this.getMarketAsks(marketIndex, marketType),
525
+ this.getLimitAsks(marketIndex, slot, marketType, oraclePriceData),
526
+ ];
527
+ const marketTypeStr = (0, __1.getVariant)(marketType);
528
+ if (marketTypeStr === 'perp' && fallbackAsk) {
529
+ generatorList.push((0, NodeList_1.getVammNodeGenerator)(fallbackAsk));
422
530
  }
423
- const bidGenerators = generatorList.map((generator) => {
424
- return {
425
- next: generator.next(),
426
- generator,
427
- };
531
+ yield* this.getBestNode(generatorList, oraclePriceData, slot, (bestPrice, currentPrice) => {
532
+ return bestPrice.lt(currentPrice);
428
533
  });
429
- let bidsExhausted = false; // there will always be the fallbackBid
430
- while (!bidsExhausted) {
431
- const bestGenerator = bidGenerators.reduce((bestGenerator, currentGenerator) => {
432
- if (currentGenerator.next.done) {
433
- return bestGenerator;
434
- }
435
- if (bestGenerator.next.done) {
436
- return currentGenerator;
437
- }
438
- const bestBidPrice = bestGenerator.next.value.getPrice(oraclePriceData, slot);
439
- const currentBidPrice = currentGenerator.next.value.getPrice(oraclePriceData, slot);
440
- return bestBidPrice.gt(currentBidPrice)
441
- ? bestGenerator
442
- : currentGenerator;
443
- });
444
- if (!bestGenerator.next.done) {
445
- // skip this node if it's already completely filled
446
- if (bestGenerator.next.value.isBaseFilled()) {
447
- bestGenerator.next = bestGenerator.generator.next();
448
- continue;
449
- }
450
- // skip order if user is being liquidated/bankrupt
451
- if (bestGenerator.next.value.userAccount !== undefined) {
452
- const user = this.userMap.get(bestGenerator.next.value.userAccount.toString());
453
- if (user === null || user === void 0 ? void 0 : user.isBeingLiquidated()) {
454
- bestGenerator.next = bestGenerator.generator.next();
455
- continue;
456
- }
457
- }
458
- yield bestGenerator.next.value;
459
- bestGenerator.next = bestGenerator.generator.next();
460
- }
461
- else {
462
- bidsExhausted = true;
463
- }
534
+ }
535
+ *getBids(marketIndex, fallbackBid, slot, marketType, oraclePriceData) {
536
+ if ((0, __1.isVariant)(marketType, 'spot') && !oraclePriceData) {
537
+ throw new Error('Must provide OraclePriceData to get spot bids');
464
538
  }
539
+ const generatorList = [
540
+ this.getMarketBids(marketIndex, marketType),
541
+ this.getLimitBids(marketIndex, slot, marketType, oraclePriceData),
542
+ ];
543
+ const marketTypeStr = (0, __1.getVariant)(marketType);
544
+ if (marketTypeStr === 'perp' && fallbackBid) {
545
+ generatorList.push((0, NodeList_1.getVammNodeGenerator)(fallbackBid));
546
+ }
547
+ yield* this.getBestNode(generatorList, oraclePriceData, slot, (bestPrice, currentPrice) => {
548
+ return bestPrice.gt(currentPrice);
549
+ });
465
550
  }
466
- findCrossingOrders(askNode, bidNode, oraclePriceData, slot) {
551
+ findCrossingLimitOrders(askNode, bidNode, oraclePriceData, slot) {
467
552
  const bidPrice = bidNode.getPrice(oraclePriceData, slot);
468
553
  const askPrice = askNode.getPrice(oraclePriceData, slot);
469
554
  // orders don't cross - we're done walkin gup the book
@@ -488,15 +573,7 @@ class DLOB {
488
573
  exhaustedSide,
489
574
  };
490
575
  }
491
- const { takerNode, makerNode, makerSide } = this.determineMakerAndTaker(askNode, bidNode);
492
- // If maker is market order and auction is complete, cant be maker
493
- if ((0, __1.isMarketOrder)(makerNode.order) &&
494
- (0, __1.isAuctionComplete)(makerNode.order, slot)) {
495
- return {
496
- crossingNodes: [],
497
- exhaustedSide: makerSide,
498
- };
499
- }
576
+ const { takerNode, makerNode } = this.determineMakerAndTaker(askNode, bidNode);
500
577
  const bidBaseRemaining = bidOrder.baseAssetAmount.sub(bidOrder.baseAssetAmountFilled);
501
578
  const askBaseRemaining = askOrder.baseAssetAmount.sub(askOrder.baseAssetAmountFilled);
502
579
  let exhaustedSide;
@@ -570,20 +647,6 @@ class DLOB {
570
647
  makerSide: 'ask',
571
648
  };
572
649
  }
573
- else if ((0, __1.isMarketOrder)(bidNode.order) && (0, __1.isLimitOrder)(askNode.order)) {
574
- return {
575
- takerNode: bidNode,
576
- makerNode: askNode,
577
- makerSide: 'ask',
578
- };
579
- }
580
- else if ((0, __1.isMarketOrder)(askNode.order) && (0, __1.isLimitOrder)(bidNode.order)) {
581
- return {
582
- takerNode: askNode,
583
- makerNode: bidNode,
584
- makerSide: 'bid',
585
- };
586
- }
587
650
  else if (askNode.order.slot.lt(bidNode.order.slot)) {
588
651
  return {
589
652
  takerNode: bidNode,
@@ -61,9 +61,23 @@ export declare class DriftClient {
61
61
  signerPublicKey?: PublicKey;
62
62
  getSignerPublicKey(): PublicKey;
63
63
  getStateAccount(): StateAccount;
64
+ /**
65
+ * Forces a fetch to rpc before returning accounts. Useful for anchor tests.
66
+ */
67
+ forceGetStateAccount(): Promise<StateAccount>;
64
68
  getPerpMarketAccount(marketIndex: number): PerpMarketAccount | undefined;
69
+ /**
70
+ * Forces a fetch to rpc before returning accounts. Useful for anchor tests.
71
+ * @param marketIndex
72
+ */
73
+ forceGetPerpMarketAccount(marketIndex: number): Promise<PerpMarketAccount | undefined>;
65
74
  getPerpMarketAccounts(): PerpMarketAccount[];
66
75
  getSpotMarketAccount(marketIndex: number): SpotMarketAccount | undefined;
76
+ /**
77
+ * Forces a fetch to rpc before returning accounts. Useful for anchor tests.
78
+ * @param marketIndex
79
+ */
80
+ forceGetSpotMarketAccount(marketIndex: number): Promise<SpotMarketAccount | undefined>;
67
81
  getSpotMarketAccounts(): SpotMarketAccount[];
68
82
  getQuoteSpotMarketAccount(): SpotMarketAccount;
69
83
  getOraclePriceDataAndSlot(oraclePublicKey: PublicKey): DataAndSlot<OraclePriceData> | undefined;
@@ -94,6 +108,11 @@ export declare class DriftClient {
94
108
  getUserStatsAccountPublicKey(): PublicKey;
95
109
  getUserAccountPublicKey(): Promise<PublicKey>;
96
110
  getUserAccount(subAccountId?: number): UserAccount | undefined;
111
+ /**
112
+ * Forces a fetch to rpc before returning accounts. Useful for anchor tests.
113
+ * @param subAccountId
114
+ */
115
+ forceGetUserAccount(subAccountId?: number): Promise<UserAccount | undefined>;
97
116
  getUserAccountAndSlot(subAccountId?: number): DataAndSlot<UserAccount> | undefined;
98
117
  getSpotPosition(marketIndex: number): SpotPosition | undefined;
99
118
  getQuoteAssetTokenAmount(): BN;
@@ -177,10 +177,26 @@ class DriftClient {
177
177
  getStateAccount() {
178
178
  return this.accountSubscriber.getStateAccountAndSlot().data;
179
179
  }
180
+ /**
181
+ * Forces a fetch to rpc before returning accounts. Useful for anchor tests.
182
+ */
183
+ async forceGetStateAccount() {
184
+ await this.accountSubscriber.fetch();
185
+ return this.accountSubscriber.getStateAccountAndSlot().data;
186
+ }
180
187
  getPerpMarketAccount(marketIndex) {
181
188
  var _a;
182
189
  return (_a = this.accountSubscriber.getMarketAccountAndSlot(marketIndex)) === null || _a === void 0 ? void 0 : _a.data;
183
190
  }
191
+ /**
192
+ * Forces a fetch to rpc before returning accounts. Useful for anchor tests.
193
+ * @param marketIndex
194
+ */
195
+ async forceGetPerpMarketAccount(marketIndex) {
196
+ var _a;
197
+ await this.accountSubscriber.fetch();
198
+ return (_a = this.accountSubscriber.getMarketAccountAndSlot(marketIndex)) === null || _a === void 0 ? void 0 : _a.data;
199
+ }
184
200
  getPerpMarketAccounts() {
185
201
  return this.accountSubscriber
186
202
  .getMarketAccountsAndSlots()
@@ -189,6 +205,14 @@ class DriftClient {
189
205
  getSpotMarketAccount(marketIndex) {
190
206
  return this.accountSubscriber.getSpotMarketAccountAndSlot(marketIndex).data;
191
207
  }
208
+ /**
209
+ * Forces a fetch to rpc before returning accounts. Useful for anchor tests.
210
+ * @param marketIndex
211
+ */
212
+ async forceGetSpotMarketAccount(marketIndex) {
213
+ await this.accountSubscriber.fetch();
214
+ return this.accountSubscriber.getSpotMarketAccountAndSlot(marketIndex).data;
215
+ }
192
216
  getSpotMarketAccounts() {
193
217
  return this.accountSubscriber
194
218
  .getSpotMarketAccountsAndSlots()
@@ -414,6 +438,14 @@ class DriftClient {
414
438
  getUserAccount(subAccountId) {
415
439
  return this.getUser(subAccountId).getUserAccount();
416
440
  }
441
+ /**
442
+ * Forces a fetch to rpc before returning accounts. Useful for anchor tests.
443
+ * @param subAccountId
444
+ */
445
+ async forceGetUserAccount(subAccountId) {
446
+ await this.getUser(subAccountId).fetchAccounts();
447
+ return this.getUser(subAccountId).getUserAccount();
448
+ }
417
449
  getUserAccountAndSlot(subAccountId) {
418
450
  return this.getUser(subAccountId).getUserAccountAndSlot();
419
451
  }
@@ -7,8 +7,7 @@ export declare function isOrderRiskIncreasing(user: User, order: Order): boolean
7
7
  export declare function isOrderRiskIncreasingInSameDirection(user: User, order: Order): boolean;
8
8
  export declare function isOrderReduceOnly(user: User, order: Order): boolean;
9
9
  export declare function standardizeBaseAssetAmount(baseAssetAmount: BN, stepSize: BN): BN;
10
- export declare function getLimitPrice(order: Order, oraclePriceData: OraclePriceData, slot: number): BN;
11
- export declare function getOptionalLimitPrice(order: Order, oraclePriceData: OraclePriceData, slot: number): BN | undefined;
10
+ export declare function getLimitPrice(order: Order, oraclePriceData: OraclePriceData, slot: number, fallbackPrice?: BN): BN | undefined;
12
11
  export declare function hasLimitPrice(order: Order, slot: number): boolean;
13
12
  export declare function isFillableByVAMM(order: Order, market: PerpMarketAccount, oraclePriceData: OraclePriceData, slot: number, ts: number): boolean;
14
13
  export declare function calculateBaseAssetAmountForAmmToFulfill(order: Order, market: PerpMarketAccount, oraclePriceData: OraclePriceData, slot: number): BN;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.isTriggered = exports.mustBeTriggered = exports.isLimitOrder = exports.isMarketOrder = exports.isOrderExpired = exports.calculateBaseAssetAmountToFillUpToLimitPrice = exports.calculateBaseAssetAmountForAmmToFulfill = exports.isFillableByVAMM = exports.hasLimitPrice = exports.getOptionalLimitPrice = exports.getLimitPrice = exports.standardizeBaseAssetAmount = exports.isOrderReduceOnly = exports.isOrderRiskIncreasingInSameDirection = exports.isOrderRiskIncreasing = void 0;
3
+ exports.isTriggered = exports.mustBeTriggered = exports.isLimitOrder = exports.isMarketOrder = exports.isOrderExpired = exports.calculateBaseAssetAmountToFillUpToLimitPrice = exports.calculateBaseAssetAmountForAmmToFulfill = exports.isFillableByVAMM = exports.hasLimitPrice = exports.getLimitPrice = exports.standardizeBaseAssetAmount = exports.isOrderReduceOnly = exports.isOrderRiskIncreasingInSameDirection = exports.isOrderRiskIncreasing = void 0;
4
4
  const types_1 = require("../types");
5
5
  const numericConstants_1 = require("../constants/numericConstants");
6
6
  const anchor_1 = require("@project-serum/anchor");
@@ -79,7 +79,7 @@ function standardizeBaseAssetAmount(baseAssetAmount, stepSize) {
79
79
  return baseAssetAmount.sub(remainder);
80
80
  }
81
81
  exports.standardizeBaseAssetAmount = standardizeBaseAssetAmount;
82
- function getLimitPrice(order, oraclePriceData, slot) {
82
+ function getLimitPrice(order, oraclePriceData, slot, fallbackPrice) {
83
83
  let limitPrice;
84
84
  if (order.oraclePriceOffset !== 0) {
85
85
  limitPrice = oraclePriceData.price.add(new anchor_1.BN(order.oraclePriceOffset));
@@ -92,14 +92,7 @@ function getLimitPrice(order, oraclePriceData, slot) {
92
92
  limitPrice = order.price;
93
93
  }
94
94
  else {
95
- // check oracle validity?
96
- const oraclePrice1Pct = oraclePriceData.price.div(new anchor_1.BN(100));
97
- if ((0, types_1.isVariant)(order.direction, 'long')) {
98
- limitPrice = oraclePriceData.price.add(oraclePrice1Pct);
99
- }
100
- else {
101
- limitPrice = oraclePriceData.price.sub(oraclePrice1Pct);
102
- }
95
+ limitPrice = fallbackPrice;
103
96
  }
104
97
  }
105
98
  else {
@@ -108,15 +101,6 @@ function getLimitPrice(order, oraclePriceData, slot) {
108
101
  return limitPrice;
109
102
  }
110
103
  exports.getLimitPrice = getLimitPrice;
111
- function getOptionalLimitPrice(order, oraclePriceData, slot) {
112
- if (hasLimitPrice(order, slot)) {
113
- return getLimitPrice(order, oraclePriceData, slot);
114
- }
115
- else {
116
- return undefined;
117
- }
118
- }
119
- exports.getOptionalLimitPrice = getOptionalLimitPrice;
120
104
  function hasLimitPrice(order, slot) {
121
105
  return (order.price.gt(numericConstants_1.ZERO) ||
122
106
  order.oraclePriceOffset != 0 ||
@@ -133,7 +117,7 @@ function calculateBaseAssetAmountForAmmToFulfill(order, market, oraclePriceData,
133
117
  if (mustBeTriggered(order) && !isTriggered(order)) {
134
118
  return numericConstants_1.ZERO;
135
119
  }
136
- const limitPrice = getOptionalLimitPrice(order, oraclePriceData, slot);
120
+ const limitPrice = getLimitPrice(order, oraclePriceData, slot);
137
121
  let baseAssetAmount;
138
122
  const updatedAMM = (0, amm_1.calculateUpdatedAMM)(market.amm, oraclePriceData);
139
123
  if (limitPrice !== undefined) {