@drift-labs/sdk 2.0.14 → 2.0.16

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/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  <div align="center">
2
2
  <img height="120x" src="https://uploads-ssl.webflow.com/611580035ad59b20437eb024/616f97a42f5637c4517d0193_Logo%20(1)%20(1).png" />
3
3
 
4
- <h1 style="margin-top:20px;">Drift Protocol v1</h1>
4
+ <h1 style="margin-top:20px;">Drift Protocol v2</h1>
5
5
 
6
6
  <p>
7
7
  <a href="https://www.npmjs.com/package/@drift-labs/sdk"><img alt="SDK npm package" src="https://img.shields.io/npm/v/@drift-labs/sdk" /></a>
@@ -11,13 +11,13 @@
11
11
  </p>
12
12
  </div>
13
13
 
14
- # Drift Protocol v1
14
+ # Drift Protocol v2
15
15
 
16
16
  This repository provides open source access to Drift's Typescript SDK, Solana Programs, and more.
17
17
 
18
18
  # SDK Guide
19
19
 
20
- The technical documentation for the SDK can be found [here](https://drift-labs.github.io/protocol-v1/sdk/), and you can visit Drift's general purpose documentation [here](https://docs.drift.trade/drift-sdk-documentation).
20
+ The technical documentation for the SDK can be found [here](https://drift-labs.github.io/protocol-v2/sdk/), and you can visit Drift's general purpose documentation [here](https://docs.drift.trade/sdk-documentation).
21
21
 
22
22
  ## Installation
23
23
 
@@ -201,9 +201,6 @@ class PollingDriftClientAccountSubscriber {
201
201
  return false;
202
202
  }
203
203
  async unsubscribe() {
204
- if (!this.isSubscribed) {
205
- return;
206
- }
207
204
  for (const [_, accountToPoll] of this.accountsToPoll) {
208
205
  this.accountLoader.removeAccount(accountToPoll.publicKey, accountToPoll.callbackId);
209
206
  }
@@ -83,9 +83,6 @@ class PollingUserStatsAccountSubscriber {
83
83
  return success;
84
84
  }
85
85
  async unsubscribe() {
86
- if (!this.isSubscribed) {
87
- return;
88
- }
89
86
  for (const [_, accountToPoll] of this.accountsToPoll) {
90
87
  this.accountLoader.removeAccount(accountToPoll.publicKey, accountToPoll.callbackId);
91
88
  }
@@ -27,6 +27,8 @@ export declare class AdminClient extends DriftClient {
27
27
  updatePerpMarketMaxSpread(perpMarketIndex: number, maxSpread: number): Promise<TransactionSignature>;
28
28
  updatePerpFeeStructure(feeStructure: FeeStructure): Promise<TransactionSignature>;
29
29
  updateSpotFeeStructure(feeStructure: FeeStructure): Promise<TransactionSignature>;
30
+ updateInitialPctToLiquidate(initialPctToLiquidate: number): Promise<TransactionSignature>;
31
+ updateLiquidationDuration(liquidationDuration: number): Promise<TransactionSignature>;
30
32
  updateOracleGuardRails(oracleGuardRails: OracleGuardRails): Promise<TransactionSignature>;
31
33
  updateStateSettlementDuration(settlementDuration: number): Promise<TransactionSignature>;
32
34
  updateWithdrawGuardThreshold(spotMarketIndex: number, withdrawGuardThreshold: BN): Promise<TransactionSignature>;
@@ -324,6 +324,22 @@ class AdminClient extends driftClient_1.DriftClient {
324
324
  },
325
325
  });
326
326
  }
327
+ async updateInitialPctToLiquidate(initialPctToLiquidate) {
328
+ return await this.program.rpc.updateInitialPctToLiquidate(initialPctToLiquidate, {
329
+ accounts: {
330
+ admin: this.wallet.publicKey,
331
+ state: await this.getStatePublicKey(),
332
+ },
333
+ });
334
+ }
335
+ async updateLiquidationDuration(liquidationDuration) {
336
+ return await this.program.rpc.updateLiquidationDuration(liquidationDuration, {
337
+ accounts: {
338
+ admin: this.wallet.publicKey,
339
+ state: await this.getStatePublicKey(),
340
+ },
341
+ });
342
+ }
327
343
  async updateOracleGuardRails(oracleGuardRails) {
328
344
  return await this.program.rpc.updateOracleGuardRails(oracleGuardRails, {
329
345
  accounts: {
@@ -50,7 +50,10 @@ export declare const PRICE_TO_QUOTE_PRECISION: BN;
50
50
  export declare const AMM_TIMES_PEG_TO_QUOTE_PRECISION_RATIO: BN;
51
51
  export declare const MARGIN_PRECISION: BN;
52
52
  export declare const BID_ASK_SPREAD_PRECISION: BN;
53
+ export declare const LIQUIDATION_PCT_PRECISION: BN;
53
54
  export declare const ONE_YEAR: BN;
54
55
  export declare const QUOTE_SPOT_MARKET_INDEX = 0;
55
56
  export declare const LAMPORTS_PRECISION: BN;
56
57
  export declare const LAMPORTS_EXP: BN;
58
+ export declare const OPEN_ORDER_MARGIN_REQUIREMENT: BN;
59
+ export declare const DEFAULT_REVENUE_SINCE_LAST_FUNDING_SPREAD_RETREAT: BN;
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.BID_ASK_SPREAD_PRECISION = exports.MARGIN_PRECISION = exports.AMM_TIMES_PEG_TO_QUOTE_PRECISION_RATIO = exports.PRICE_TO_QUOTE_PRECISION = exports.PRICE_DIV_PEG = exports.AMM_TO_QUOTE_PRECISION_RATIO = exports.BASE_PRECISION_EXP = exports.BASE_PRECISION = exports.AMM_RESERVE_PRECISION = exports.PEG_PRECISION = exports.FUNDING_RATE_BUFFER_PRECISION = exports.FUNDING_RATE_PRECISION = exports.PRICE_PRECISION = exports.QUOTE_PRECISION = exports.LIQUIDATION_FEE_PRECISION = exports.SPOT_MARKET_IMF_PRECISION = exports.SPOT_MARKET_IMF_PRECISION_EXP = exports.SPOT_MARKET_BALANCE_PRECISION = exports.SPOT_MARKET_BALANCE_PRECISION_EXP = exports.SPOT_MARKET_WEIGHT_PRECISION = exports.SPOT_MARKET_UTILIZATION_PRECISION = exports.SPOT_MARKET_UTILIZATION_PRECISION_EXP = exports.SPOT_MARKET_CUMULATIVE_INTEREST_PRECISION = exports.SPOT_MARKET_CUMULATIVE_INTEREST_PRECISION_EXP = exports.SPOT_MARKET_RATE_PRECISION = exports.SPOT_MARKET_RATE_PRECISION_EXP = exports.AMM_RESERVE_PRECISION_EXP = exports.PEG_PRECISION_EXP = exports.FUNDING_RATE_PRECISION_EXP = exports.PRICE_PRECISION_EXP = exports.FUNDING_RATE_BUFFER_PRECISION_EXP = exports.QUOTE_PRECISION_EXP = exports.CONCENTRATION_PRECISION = exports.PERCENTAGE_PRECISION = exports.PERCENTAGE_PRECISION_EXP = exports.MAX_LEVERAGE = exports.TEN_MILLION = exports.BN_MAX = exports.TEN_THOUSAND = exports.TEN = exports.NINE = exports.EIGHT = exports.SEVEN = exports.SIX = exports.FIVE = exports.FOUR = exports.THREE = exports.TWO = exports.ONE = exports.ZERO = void 0;
4
- exports.LAMPORTS_EXP = exports.LAMPORTS_PRECISION = exports.QUOTE_SPOT_MARKET_INDEX = exports.ONE_YEAR = void 0;
4
+ exports.DEFAULT_REVENUE_SINCE_LAST_FUNDING_SPREAD_RETREAT = exports.OPEN_ORDER_MARGIN_REQUIREMENT = exports.LAMPORTS_EXP = exports.LAMPORTS_PRECISION = exports.QUOTE_SPOT_MARKET_INDEX = exports.ONE_YEAR = exports.LIQUIDATION_PCT_PRECISION = void 0;
5
5
  const web3_js_1 = require("@solana/web3.js");
6
6
  const __1 = require("../");
7
7
  exports.ZERO = new __1.BN(0);
@@ -54,7 +54,10 @@ exports.PRICE_TO_QUOTE_PRECISION = exports.PRICE_PRECISION.div(exports.QUOTE_PRE
54
54
  exports.AMM_TIMES_PEG_TO_QUOTE_PRECISION_RATIO = exports.AMM_RESERVE_PRECISION.mul(exports.PEG_PRECISION).div(exports.QUOTE_PRECISION); // 10^9
55
55
  exports.MARGIN_PRECISION = exports.TEN_THOUSAND;
56
56
  exports.BID_ASK_SPREAD_PRECISION = new __1.BN(1000000); // 10^6
57
+ exports.LIQUIDATION_PCT_PRECISION = exports.TEN_THOUSAND;
57
58
  exports.ONE_YEAR = new __1.BN(31536000);
58
59
  exports.QUOTE_SPOT_MARKET_INDEX = 0;
59
60
  exports.LAMPORTS_PRECISION = new __1.BN(web3_js_1.LAMPORTS_PER_SOL);
60
61
  exports.LAMPORTS_EXP = new __1.BN(Math.log10(web3_js_1.LAMPORTS_PER_SOL));
62
+ exports.OPEN_ORDER_MARGIN_REQUIREMENT = exports.QUOTE_PRECISION.div(new __1.BN(100));
63
+ exports.DEFAULT_REVENUE_SINCE_LAST_FUNDING_SPREAD_RETREAT = new __1.BN(-25).mul(exports.QUOTE_PRECISION);
@@ -29,7 +29,6 @@ export declare type NodeToFill = {
29
29
  export declare type NodeToTrigger = {
30
30
  node: TriggerOrderNode;
31
31
  };
32
- declare type Side = 'ask' | 'bid' | 'both' | 'nocross';
33
32
  export declare class DLOB {
34
33
  openOrders: Map<MarketTypeStr, Set<string>>;
35
34
  orderLists: Map<MarketTypeStr, Map<number, MarketNodeLists>>;
@@ -59,7 +58,7 @@ export declare class DLOB {
59
58
  findNodesToFill(marketIndex: number, fallbackBid: BN | undefined, fallbackAsk: BN | undefined, slot: number, ts: number, marketType: MarketType, oraclePriceData: OraclePriceData): NodeToFill[];
60
59
  findLimitOrderNodesToFill(marketIndex: number, slot: number, marketType: MarketType, oraclePriceData: OraclePriceData, isAmmPaused: boolean, fallbackAsk: BN | undefined, fallbackBid: BN | undefined): NodeToFill[];
61
60
  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[];
61
+ findMarketNodesCrossingLimitNodes(marketIndex: number, slot: number, marketType: MarketType, oraclePriceData: OraclePriceData, takerNodeGenerator: Generator<DLOBNode>, makerNodeGeneratorFn: (marketIndex: number, slot: number, marketType: MarketType, oraclePriceData: OraclePriceData) => Generator<DLOBNode>, doesCross: (takerPrice: BN | undefined, makerPrice: BN) => boolean): NodeToFill[];
63
62
  findNodesCrossingFallbackLiquidity(marketType: MarketType, slot: number, oraclePriceData: OraclePriceData, nodeGenerator: Generator<DLOBNode>, fallbackPrice: BN, doesCross: (nodePrice: BN | undefined, fallbackPrice: BN) => boolean): NodeToFill[];
64
63
  findExpiredNodesToFill(marketIndex: number, ts: number, marketType: MarketType): NodeToFill[];
65
64
  findJitAuctionNodesToFill(marketIndex: number, slot: number, marketType: MarketType): NodeToFill[];
@@ -70,14 +69,10 @@ export declare class DLOB {
70
69
  getLimitBids(marketIndex: number, slot: number, marketType: MarketType, oraclePriceData: OraclePriceData): Generator<DLOBNode>;
71
70
  getAsks(marketIndex: number, fallbackAsk: BN | undefined, slot: number, marketType: MarketType, oraclePriceData: OraclePriceData): Generator<DLOBNode>;
72
71
  getBids(marketIndex: number, fallbackBid: BN | undefined, slot: number, marketType: MarketType, oraclePriceData: OraclePriceData): Generator<DLOBNode>;
73
- findCrossingLimitOrders(askNode: DLOBNode, bidNode: DLOBNode, oraclePriceData: OraclePriceData, slot: number): {
74
- crossingNodes: NodeToFill[];
75
- exhaustedSide: Side;
76
- };
72
+ findCrossingLimitOrders(marketIndex: number, slot: number, marketType: MarketType, oraclePriceData: OraclePriceData): NodeToFill[];
77
73
  determineMakerAndTaker(askNode: DLOBNode, bidNode: DLOBNode): {
78
74
  takerNode: DLOBNode;
79
75
  makerNode: DLOBNode;
80
- makerSide: Side;
81
76
  };
82
77
  getBestAsk(marketIndex: number, fallbackAsk: BN | undefined, slot: number, marketType: MarketType, oraclePriceData: OraclePriceData): BN;
83
78
  getBestBid(marketIndex: number, fallbackBid: BN | undefined, slot: number, marketType: MarketType, oraclePriceData: OraclePriceData): BN;
package/lib/dlob/DLOB.js CHANGED
@@ -189,36 +189,12 @@ class DLOB {
189
189
  }
190
190
  findLimitOrderNodesToFill(marketIndex, slot, marketType, oraclePriceData, isAmmPaused, fallbackAsk, fallbackBid) {
191
191
  const nodesToFill = new Array();
192
- let askGenerator = this.getLimitAsks(marketIndex, slot, marketType, oraclePriceData);
193
- let bidGenerator = this.getLimitBids(marketIndex, slot, marketType, oraclePriceData);
194
- let nextAsk = askGenerator.next();
195
- let nextBid = bidGenerator.next();
196
- // First try to find orders that cross
197
- while (!nextAsk.done && !nextBid.done) {
198
- const { crossingNodes, exhaustedSide } = this.findCrossingLimitOrders(nextAsk.value, nextBid.value, oraclePriceData, slot);
199
- if (exhaustedSide === 'bid') {
200
- nextBid = bidGenerator.next();
201
- }
202
- else if (exhaustedSide === 'ask') {
203
- nextAsk = askGenerator.next();
204
- }
205
- else if (exhaustedSide === 'both') {
206
- nextBid = bidGenerator.next();
207
- nextAsk = askGenerator.next();
208
- }
209
- else if (exhaustedSide === 'nocross') {
210
- break;
211
- }
212
- else {
213
- console.error(`invalid exhaustedSide: ${exhaustedSide}`);
214
- break;
215
- }
216
- for (const crossingNode of crossingNodes) {
217
- nodesToFill.push(crossingNode);
218
- }
192
+ const crossingNodes = this.findCrossingLimitOrders(marketIndex, slot, marketType, oraclePriceData);
193
+ for (const crossingNode of crossingNodes) {
194
+ nodesToFill.push(crossingNode);
219
195
  }
220
196
  if (fallbackBid && !isAmmPaused) {
221
- askGenerator = this.getLimitAsks(marketIndex, slot, marketType, oraclePriceData);
197
+ const askGenerator = this.getLimitAsks(marketIndex, slot, marketType, oraclePriceData);
222
198
  const asksCrossingFallback = this.findNodesCrossingFallbackLiquidity(marketType, slot, oraclePriceData, askGenerator, fallbackBid, (askPrice, fallbackPrice) => {
223
199
  return askPrice.lte(fallbackPrice);
224
200
  });
@@ -227,7 +203,7 @@ class DLOB {
227
203
  }
228
204
  }
229
205
  if (fallbackAsk && !isAmmPaused) {
230
- bidGenerator = this.getLimitBids(marketIndex, slot, marketType, oraclePriceData);
206
+ const bidGenerator = this.getLimitBids(marketIndex, slot, marketType, oraclePriceData);
231
207
  const bidsCrossingFallback = this.findNodesCrossingFallbackLiquidity(marketType, slot, oraclePriceData, bidGenerator, fallbackAsk, (bidPrice, fallbackPrice) => {
232
208
  return bidPrice.gte(fallbackPrice);
233
209
  });
@@ -240,8 +216,7 @@ class DLOB {
240
216
  findMarketNodesToFill(marketIndex, slot, marketType, oraclePriceData, isAmmPaused, fallbackAsk, fallbackBid) {
241
217
  const nodesToFill = new Array();
242
218
  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) => {
219
+ const marketAsksCrossingBids = this.findMarketNodesCrossingLimitNodes(marketIndex, slot, marketType, oraclePriceData, marketOrderGenerator, this.getLimitBids.bind(this), (takerPrice, makerPrice) => {
245
220
  return takerPrice === undefined || takerPrice.lte(makerPrice);
246
221
  });
247
222
  for (const marketAskCrossingBid of marketAsksCrossingBids) {
@@ -257,8 +232,7 @@ class DLOB {
257
232
  }
258
233
  }
259
234
  marketOrderGenerator = this.getMarketBids(marketIndex, marketType);
260
- limitOrderGenerator = this.getLimitAsks(marketIndex, slot, marketType, oraclePriceData);
261
- const marketBidsToFill = this.findMarketNodesCrossingLimitNodes(slot, oraclePriceData, marketOrderGenerator, limitOrderGenerator, (takerPrice, fallbackPrice) => {
235
+ const marketBidsToFill = this.findMarketNodesCrossingLimitNodes(marketIndex, slot, marketType, oraclePriceData, marketOrderGenerator, this.getLimitAsks.bind(this), (takerPrice, fallbackPrice) => {
262
236
  return takerPrice === undefined || takerPrice.gte(fallbackPrice);
263
237
  });
264
238
  for (const marketBidToFill of marketBidsToFill) {
@@ -275,57 +249,46 @@ class DLOB {
275
249
  }
276
250
  return nodesToFill;
277
251
  }
278
- findMarketNodesCrossingLimitNodes(slot, oraclePriceData, takerNodeGenerator, makerNodeGenerator, doesCross) {
252
+ findMarketNodesCrossingLimitNodes(marketIndex, slot, marketType, oraclePriceData, takerNodeGenerator, makerNodeGeneratorFn, doesCross) {
279
253
  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();
254
+ for (const takerNode of takerNodeGenerator) {
255
+ const makerNodeGenerator = makerNodeGeneratorFn(marketIndex, slot, marketType, oraclePriceData);
256
+ for (const makerNode of makerNodeGenerator) {
257
+ const bidUserAuthority = this.userMap.getUserAuthority(makerNode.userAccount.toString());
258
+ const askUserAuthority = this.userMap.getUserAuthority(takerNode.userAccount.toString());
259
+ // Can't match orders from the same authority
260
+ const sameAuthority = bidUserAuthority.equals(askUserAuthority);
261
+ if (sameAuthority) {
262
+ continue;
292
263
  }
293
- else {
294
- nextMakerNode = makerNodeGenerator.next();
264
+ const makerPrice = makerNode.getPrice(oraclePriceData, slot);
265
+ const takerPrice = takerNode.getPrice(oraclePriceData, slot);
266
+ const ordersCross = doesCross(takerPrice, makerPrice);
267
+ if (!ordersCross) {
268
+ // market orders aren't sorted by price, they are sorted by time, so we need to traverse
269
+ // through all of em
270
+ break;
271
+ }
272
+ nodesToFill.push({
273
+ node: takerNode,
274
+ makerNode: makerNode,
275
+ });
276
+ const makerOrder = makerNode.order;
277
+ const takerOrder = takerNode.order;
278
+ const makerBaseRemaining = makerOrder.baseAssetAmount.sub(makerOrder.baseAssetAmountFilled);
279
+ const takerBaseRemaining = takerOrder.baseAssetAmount.sub(takerOrder.baseAssetAmountFilled);
280
+ const baseFilled = __1.BN.min(makerBaseRemaining, takerBaseRemaining);
281
+ const newMakerOrder = { ...makerOrder };
282
+ newMakerOrder.baseAssetAmountFilled =
283
+ makerOrder.baseAssetAmountFilled.add(baseFilled);
284
+ this.getListForOrder(newMakerOrder).update(newMakerOrder, makerNode.userAccount);
285
+ const newTakerOrder = { ...takerOrder };
286
+ newTakerOrder.baseAssetAmountFilled =
287
+ takerOrder.baseAssetAmountFilled.add(baseFilled);
288
+ this.getListForOrder(newTakerOrder).update(newTakerOrder, takerNode.userAccount);
289
+ if (newTakerOrder.baseAssetAmountFilled.eq(takerOrder.baseAssetAmount)) {
290
+ break;
295
291
  }
296
- continue;
297
- }
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();
329
292
  }
330
293
  }
331
294
  return nodesToFill;
@@ -548,117 +511,72 @@ class DLOB {
548
511
  return bestPrice.gt(currentPrice);
549
512
  });
550
513
  }
551
- findCrossingLimitOrders(askNode, bidNode, oraclePriceData, slot) {
552
- const bidPrice = bidNode.getPrice(oraclePriceData, slot);
553
- const askPrice = askNode.getPrice(oraclePriceData, slot);
554
- // orders don't cross - we're done walkin gup the book
555
- if (bidPrice.lt(askPrice)) {
556
- return {
557
- crossingNodes: [],
558
- exhaustedSide: 'nocross',
559
- };
560
- }
561
- const bidOrder = bidNode.order;
562
- const askOrder = askNode.order;
563
- const bidUserAuthority = this.userMap.getUserAuthority(bidNode.userAccount.toString());
564
- const askUserAuthority = this.userMap.getUserAuthority(askNode.userAccount.toString());
565
- // Can't match orders from the same authority
566
- const sameAuthority = bidUserAuthority.equals(askUserAuthority);
567
- if (sameAuthority || (bidOrder.postOnly && askOrder.postOnly)) {
568
- // don't have a principle way to pick which one to exhaust,
569
- // exhaust each one 50% of the time so we can try each one against other orders
570
- const exhaustedSide = Math.random() < 0.5 ? 'bid' : 'ask';
571
- return {
572
- crossingNodes: [],
573
- exhaustedSide,
574
- };
575
- }
576
- const { takerNode, makerNode } = this.determineMakerAndTaker(askNode, bidNode);
577
- const bidBaseRemaining = bidOrder.baseAssetAmount.sub(bidOrder.baseAssetAmountFilled);
578
- const askBaseRemaining = askOrder.baseAssetAmount.sub(askOrder.baseAssetAmountFilled);
579
- let exhaustedSide;
580
- if (bidBaseRemaining.eq(askBaseRemaining)) {
581
- exhaustedSide = 'both';
582
- }
583
- else if (bidBaseRemaining.gt(askBaseRemaining)) {
584
- exhaustedSide = 'ask';
585
- }
586
- else {
587
- exhaustedSide = 'bid';
588
- }
589
- // update the orders on DLOB as if they were fill - so we don't try to match them on the next iteration
590
- // NOTE: if something goes wrong during the actual fill (transaction fails, i.e. due to orders already being filled)
591
- // then we risk having a mismatch between this local DLOB and the actual DLOB state on the blockchain. This isn't
592
- // a problem in the current implementation because we construct a new DLOB from the blockchain state every time, rather
593
- // than updating the existing DLOB based on events.
594
- if (exhaustedSide === 'ask') {
595
- // bid partially filled
596
- const newBidOrder = { ...bidOrder };
597
- newBidOrder.baseAssetAmountFilled =
598
- bidOrder.baseAssetAmountFilled.add(askBaseRemaining);
599
- this.getListForOrder(newBidOrder).update(newBidOrder, bidNode.userAccount);
600
- // ask completely filled
601
- const newAskOrder = { ...askOrder };
602
- newAskOrder.baseAssetAmountFilled = askOrder.baseAssetAmount;
603
- this.getListForOrder(newAskOrder).update(newAskOrder, askNode.userAccount);
604
- }
605
- else if (exhaustedSide === 'bid') {
606
- // ask partially filled
607
- const newAskOrder = { ...askOrder };
608
- newAskOrder.baseAssetAmountFilled =
609
- askOrder.baseAssetAmountFilled.add(bidBaseRemaining);
610
- this.getListForOrder(newAskOrder).update(newAskOrder, askNode.userAccount);
611
- // bid completely filled
612
- const newBidOrder = { ...bidOrder };
613
- newBidOrder.baseAssetAmountFilled = bidOrder.baseAssetAmount;
614
- this.getListForOrder(newBidOrder).update(newBidOrder, bidNode.userAccount);
615
- }
616
- else {
617
- // both completely filled
618
- const newBidOrder = { ...bidOrder };
619
- newBidOrder.baseAssetAmountFilled = bidOrder.baseAssetAmount;
620
- this.getListForOrder(newBidOrder).update(newBidOrder, bidNode.userAccount);
621
- const newAskOrder = { ...askOrder };
622
- newAskOrder.baseAssetAmountFilled = askOrder.baseAssetAmount;
623
- this.getListForOrder(newAskOrder).update(newAskOrder, askNode.userAccount);
624
- }
625
- return {
626
- crossingNodes: [
627
- {
514
+ findCrossingLimitOrders(marketIndex, slot, marketType, oraclePriceData) {
515
+ const nodesToFill = new Array();
516
+ for (const askNode of this.getLimitAsks(marketIndex, slot, marketType, oraclePriceData)) {
517
+ for (const bidNode of this.getLimitBids(marketIndex, slot, marketType, oraclePriceData)) {
518
+ const bidPrice = bidNode.getPrice(oraclePriceData, slot);
519
+ const askPrice = askNode.getPrice(oraclePriceData, slot);
520
+ // orders don't cross - we're done walking the book
521
+ if (bidPrice.lt(askPrice)) {
522
+ return nodesToFill;
523
+ }
524
+ const bidOrder = bidNode.order;
525
+ const askOrder = askNode.order;
526
+ const bidUserAuthority = this.userMap.getUserAuthority(bidNode.userAccount.toString());
527
+ const askUserAuthority = this.userMap.getUserAuthority(askNode.userAccount.toString());
528
+ // Can't match orders from the same authority
529
+ const sameAuthority = bidUserAuthority.equals(askUserAuthority);
530
+ if (sameAuthority || (bidOrder.postOnly && askOrder.postOnly)) {
531
+ continue;
532
+ }
533
+ const { takerNode, makerNode } = this.determineMakerAndTaker(askNode, bidNode);
534
+ const bidBaseRemaining = bidOrder.baseAssetAmount.sub(bidOrder.baseAssetAmountFilled);
535
+ const askBaseRemaining = askOrder.baseAssetAmount.sub(askOrder.baseAssetAmountFilled);
536
+ const baseFilled = __1.BN.min(bidBaseRemaining, askBaseRemaining);
537
+ const newBidOrder = { ...bidOrder };
538
+ newBidOrder.baseAssetAmountFilled =
539
+ bidOrder.baseAssetAmountFilled.add(baseFilled);
540
+ this.getListForOrder(newBidOrder).update(newBidOrder, bidNode.userAccount);
541
+ // ask completely filled
542
+ const newAskOrder = { ...askOrder };
543
+ newAskOrder.baseAssetAmountFilled =
544
+ askOrder.baseAssetAmountFilled.add(baseFilled);
545
+ this.getListForOrder(newAskOrder).update(newAskOrder, askNode.userAccount);
546
+ nodesToFill.push({
628
547
  node: takerNode,
629
548
  makerNode: makerNode,
630
- },
631
- ],
632
- exhaustedSide,
633
- };
549
+ });
550
+ if (newAskOrder.baseAssetAmount.eq(newAskOrder.baseAssetAmountFilled)) {
551
+ break;
552
+ }
553
+ }
554
+ }
555
+ return nodesToFill;
634
556
  }
635
557
  determineMakerAndTaker(askNode, bidNode) {
636
558
  if (bidNode.order.postOnly) {
637
559
  return {
638
560
  takerNode: askNode,
639
561
  makerNode: bidNode,
640
- makerSide: 'bid',
641
562
  };
642
563
  }
643
564
  else if (askNode.order.postOnly) {
644
565
  return {
645
566
  takerNode: bidNode,
646
567
  makerNode: askNode,
647
- makerSide: 'ask',
648
568
  };
649
569
  }
650
570
  else if (askNode.order.slot.lt(bidNode.order.slot)) {
651
571
  return {
652
572
  takerNode: bidNode,
653
573
  makerNode: askNode,
654
- makerSide: 'ask',
655
574
  };
656
575
  }
657
576
  else {
658
577
  return {
659
578
  takerNode: askNode,
660
579
  makerNode: bidNode,
661
- makerSide: 'bid',
662
580
  };
663
581
  }
664
582
  }
@@ -1432,7 +1432,7 @@
1432
1432
  },
1433
1433
  {
1434
1434
  "name": "spotMarket",
1435
- "isMut": false,
1435
+ "isMut": true,
1436
1436
  "isSigner": false
1437
1437
  },
1438
1438
  {
@@ -3233,6 +3233,48 @@
3233
3233
  }
3234
3234
  ]
3235
3235
  },
3236
+ {
3237
+ "name": "updateInitialPctToLiquidate",
3238
+ "accounts": [
3239
+ {
3240
+ "name": "admin",
3241
+ "isMut": false,
3242
+ "isSigner": true
3243
+ },
3244
+ {
3245
+ "name": "state",
3246
+ "isMut": true,
3247
+ "isSigner": false
3248
+ }
3249
+ ],
3250
+ "args": [
3251
+ {
3252
+ "name": "initialPctToLiquidate",
3253
+ "type": "u16"
3254
+ }
3255
+ ]
3256
+ },
3257
+ {
3258
+ "name": "updateLiquidationDuration",
3259
+ "accounts": [
3260
+ {
3261
+ "name": "admin",
3262
+ "isMut": false,
3263
+ "isSigner": true
3264
+ },
3265
+ {
3266
+ "name": "state",
3267
+ "isMut": true,
3268
+ "isSigner": false
3269
+ }
3270
+ ],
3271
+ "args": [
3272
+ {
3273
+ "name": "liquidationDuration",
3274
+ "type": "u8"
3275
+ }
3276
+ ]
3277
+ },
3236
3278
  {
3237
3279
  "name": "updateOracleGuardRails",
3238
3280
  "accounts": [
@@ -4320,12 +4362,20 @@
4320
4362
  "defined": "ExchangeStatus"
4321
4363
  }
4322
4364
  },
4365
+ {
4366
+ "name": "liquidationDuration",
4367
+ "type": "u8"
4368
+ },
4369
+ {
4370
+ "name": "initialPctToLiquidate",
4371
+ "type": "u16"
4372
+ },
4323
4373
  {
4324
4374
  "name": "padding",
4325
4375
  "type": {
4326
4376
  "array": [
4327
4377
  "u8",
4328
- 17
4378
+ 14
4329
4379
  ]
4330
4380
  }
4331
4381
  }
@@ -4420,8 +4470,8 @@
4420
4470
  "type": "u64"
4421
4471
  },
4422
4472
  {
4423
- "name": "liquidationStartTs",
4424
- "type": "i64"
4473
+ "name": "liquidationStartSlot",
4474
+ "type": "u64"
4425
4475
  },
4426
4476
  {
4427
4477
  "name": "nextOrderId",
@@ -5596,11 +5646,11 @@
5596
5646
  "type": "u64"
5597
5647
  },
5598
5648
  {
5599
- "name": "lastNetBaseAssetAmountPerLp",
5649
+ "name": "lastBaseAssetAmountPerLp",
5600
5650
  "type": "i64"
5601
5651
  },
5602
5652
  {
5603
- "name": "lastNetQuoteAssetAmountPerLp",
5653
+ "name": "lastQuoteAssetAmountPerLp",
5604
5654
  "type": "i64"
5605
5655
  },
5606
5656
  {
@@ -6016,6 +6066,9 @@
6016
6066
  },
6017
6067
  {
6018
6068
  "name": "OrderFillWithSerum"
6069
+ },
6070
+ {
6071
+ "name": "NoBorrowLiquidity"
6019
6072
  }
6020
6073
  ]
6021
6074
  }
@@ -8454,11 +8507,16 @@
8454
8507
  },
8455
8508
  {
8456
8509
  "code": 6219,
8510
+ "name": "UnableToGetLimitPrice",
8511
+ "msg": "Unable To Get Limit Price"
8512
+ },
8513
+ {
8514
+ "code": 6220,
8457
8515
  "name": "InvalidLiquidation",
8458
8516
  "msg": "Invalid Liquidation"
8459
8517
  },
8460
8518
  {
8461
- "code": 6220,
8519
+ "code": 6221,
8462
8520
  "name": "SpotFulfillmentConfigDisabled",
8463
8521
  "msg": "Spot Fulfullment Config Disabled"
8464
8522
  }
package/lib/math/amm.d.ts CHANGED
@@ -34,15 +34,16 @@ export declare type AssetType = 'quote' | 'base';
34
34
  */
35
35
  export declare function calculateAmmReservesAfterSwap(amm: Pick<AMM, 'pegMultiplier' | 'quoteAssetReserve' | 'sqrtK' | 'baseAssetReserve'>, inputAssetType: AssetType, swapAmount: BN, swapDirection: SwapDirection): [BN, BN];
36
36
  export declare function calculateMarketOpenBidAsk(baseAssetReserve: BN, minBaseAssetReserve: BN, maxBaseAssetReserve: BN): [BN, BN];
37
- export declare function calculateInventoryScale(netBaseAssetAmount: BN, baseAssetReserve: BN, minBaseAssetReserve: BN, maxBaseAssetReserve: BN): number;
37
+ export declare function calculateInventoryScale(baseAssetAmountWithAmm: BN, baseAssetReserve: BN, minBaseAssetReserve: BN, maxBaseAssetReserve: BN, directionalSpread: number, maxSpread: number): number;
38
38
  export declare function calculateEffectiveLeverage(baseSpread: number, quoteAssetReserve: BN, terminalQuoteAssetReserve: BN, pegMultiplier: BN, netBaseAssetAmount: BN, reservePrice: BN, totalFeeMinusDistributions: BN): number;
39
39
  export declare function calculateMaxSpread(marginRatioInitial: number): number;
40
- export declare function calculateSpreadBN(baseSpread: number, lastOracleReservePriceSpreadPct: BN, lastOracleConfPct: BN, maxSpread: number, quoteAssetReserve: BN, terminalQuoteAssetReserve: BN, pegMultiplier: BN, netBaseAssetAmount: BN, reservePrice: BN, totalFeeMinusDistributions: BN, baseAssetReserve: BN, minBaseAssetReserve: BN, maxBaseAssetReserve: BN): [number, number];
41
- export declare function calculateSpread(amm: AMM, direction: PositionDirection, oraclePriceData: OraclePriceData): number;
42
- export declare function calculateSpreadReserves(amm: AMM, direction: PositionDirection, oraclePriceData: OraclePriceData): {
43
- baseAssetReserve: BN;
44
- quoteAssetReserve: BN;
45
- };
40
+ export declare function calculateVolSpreadBN(lastOracleConfPct: BN, reservePrice: BN, markStd: BN, oracleStd: BN, longIntensity: BN, shortIntensity: BN, volume24H: BN): [BN, BN];
41
+ export declare function calculateSpreadBN(baseSpread: number, lastOracleReservePriceSpreadPct: BN, lastOracleConfPct: BN, maxSpread: number, quoteAssetReserve: BN, terminalQuoteAssetReserve: BN, pegMultiplier: BN, baseAssetAmountWithAmm: BN, reservePrice: BN, totalFeeMinusDistributions: BN, netRevenueSinceLastFunding: BN, baseAssetReserve: BN, minBaseAssetReserve: BN, maxBaseAssetReserve: BN, markStd: BN, oracleStd: BN, longIntensity: BN, shortIntensity: BN, volume24H: BN): [number, number];
42
+ export declare function calculateSpread(amm: AMM, oraclePriceData: OraclePriceData): [number, number];
43
+ export declare function calculateSpreadReserves(amm: AMM, oraclePriceData: OraclePriceData): {
44
+ baseAssetReserve: any;
45
+ quoteAssetReserve: any;
46
+ }[];
46
47
  /**
47
48
  * Helper function calculating constant product curve output. Agnostic to whether input asset is quote or base
48
49
  *