@drift-labs/sdk 2.52.0-beta.1 → 2.52.0-beta.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/VERSION +1 -1
  2. package/lib/accounts/webSocketAccountSubscriber.d.ts +1 -1
  3. package/lib/accounts/webSocketAccountSubscriber.js +7 -4
  4. package/lib/accounts/webSocketProgramAccountSubscriber.d.ts +1 -1
  5. package/lib/accounts/webSocketProgramAccountSubscriber.js +7 -4
  6. package/lib/dlob/orderBookLevels.d.ts +22 -0
  7. package/lib/dlob/orderBookLevels.js +115 -1
  8. package/lib/driftClient.js +7 -2
  9. package/lib/events/webSocketLogProvider.js +3 -3
  10. package/lib/factory/bigNum.d.ts +1 -1
  11. package/lib/factory/bigNum.js +5 -2
  12. package/lib/idl/drift.json +13 -1
  13. package/lib/math/amm.d.ts +5 -1
  14. package/lib/math/amm.js +62 -13
  15. package/lib/phoenix/phoenixSubscriber.js +2 -2
  16. package/lib/slot/SlotSubscriber.d.ts +11 -3
  17. package/lib/slot/SlotSubscriber.js +40 -4
  18. package/lib/types.d.ts +1 -1
  19. package/lib/user.d.ts +1 -1
  20. package/lib/user.js +14 -6
  21. package/lib/userMap/userMap.d.ts +3 -0
  22. package/lib/userMap/userMap.js +9 -0
  23. package/package.json +1 -1
  24. package/src/accounts/webSocketAccountSubscriber.ts +7 -4
  25. package/src/accounts/webSocketProgramAccountSubscriber.ts +7 -4
  26. package/src/dlob/orderBookLevels.ts +136 -0
  27. package/src/driftClient.ts +7 -2
  28. package/src/events/webSocketLogProvider.ts +3 -3
  29. package/src/factory/bigNum.ts +11 -2
  30. package/src/idl/drift.json +13 -1
  31. package/src/math/amm.ts +159 -25
  32. package/src/phoenix/phoenixSubscriber.ts +2 -2
  33. package/src/slot/SlotSubscriber.ts +52 -5
  34. package/src/types.ts +1 -1
  35. package/src/user.ts +19 -6
  36. package/src/userMap/userMap.ts +12 -0
  37. package/tests/amm/test.ts +219 -11
  38. package/tests/bn/test.ts +27 -0
  39. package/tests/dlob/helpers.ts +1 -1
  40. package/tests/dlob/test.ts +372 -2
package/VERSION CHANGED
@@ -1 +1 @@
1
- 2.52.0-beta.1
1
+ 2.52.0-beta.3
@@ -24,5 +24,5 @@ export declare class WebSocketAccountSubscriber<T> implements AccountSubscriber<
24
24
  fetch(): Promise<void>;
25
25
  handleRpcResponse(context: Context, accountInfo?: AccountInfo<Buffer>): void;
26
26
  decodeBuffer(buffer: Buffer): T;
27
- unsubscribe(): Promise<void>;
27
+ unsubscribe(onResub?: boolean): Promise<void>;
28
28
  }
@@ -15,7 +15,7 @@ class WebSocketAccountSubscriber {
15
15
  commitment !== null && commitment !== void 0 ? commitment : this.program.provider.opts.commitment;
16
16
  }
17
17
  async subscribe(onChange) {
18
- if (this.listenerId || this.isUnsubscribing) {
18
+ if (this.listenerId != null || this.isUnsubscribing) {
19
19
  return;
20
20
  }
21
21
  this.onChange = onChange;
@@ -58,7 +58,7 @@ class WebSocketAccountSubscriber {
58
58
  }
59
59
  if (this.receivingData) {
60
60
  console.log(`No ws data from ${this.accountName} in ${this.resubTimeoutMs}ms, resubscribing`);
61
- await this.unsubscribe();
61
+ await this.unsubscribe(true);
62
62
  this.receivingData = false;
63
63
  await this.subscribe(this.onChange);
64
64
  }
@@ -114,11 +114,14 @@ class WebSocketAccountSubscriber {
114
114
  return this.program.account[this.accountName].coder.accounts.decode((0, utils_1.capitalize)(this.accountName), buffer);
115
115
  }
116
116
  }
117
- unsubscribe() {
117
+ unsubscribe(onResub = false) {
118
+ if (!onResub) {
119
+ this.resubTimeoutMs = undefined;
120
+ }
118
121
  this.isUnsubscribing = true;
119
122
  clearTimeout(this.timeoutId);
120
123
  this.timeoutId = undefined;
121
- if (this.listenerId) {
124
+ if (this.listenerId != null) {
122
125
  const promise = this.program.provider.connection
123
126
  .removeAccountChangeListener(this.listenerId)
124
127
  .then(() => {
@@ -29,5 +29,5 @@ export declare class WebSocketProgramAccountSubscriber<T> implements ProgramAcco
29
29
  subscribe(onChange: (accountId: PublicKey, data: T, context: Context) => void): Promise<void>;
30
30
  private setTimeout;
31
31
  handleRpcResponse(context: Context, keyedAccountInfo: KeyedAccountInfo): void;
32
- unsubscribe(): Promise<void>;
32
+ unsubscribe(onResub?: boolean): Promise<void>;
33
33
  }
@@ -17,7 +17,7 @@ class WebSocketProgramAccountSubscriber {
17
17
  }
18
18
  async subscribe(onChange) {
19
19
  var _a;
20
- if (this.listenerId || this.isUnsubscribing) {
20
+ if (this.listenerId != null || this.isUnsubscribing) {
21
21
  return;
22
22
  }
23
23
  this.onChange = onChange;
@@ -47,7 +47,7 @@ class WebSocketProgramAccountSubscriber {
47
47
  }
48
48
  if (this.receivingData) {
49
49
  console.log(`No ws data from ${this.subscriptionName} in ${this.resubTimeoutMs}ms, resubscribing`);
50
- await this.unsubscribe();
50
+ await this.unsubscribe(true);
51
51
  this.receivingData = false;
52
52
  await this.subscribe(this.onChange);
53
53
  }
@@ -93,11 +93,14 @@ class WebSocketProgramAccountSubscriber {
93
93
  this.onChange(keyedAccountInfo.accountId, account, context);
94
94
  }
95
95
  }
96
- unsubscribe() {
96
+ unsubscribe(onResub = false) {
97
+ if (!onResub) {
98
+ this.resubTimeoutMs = undefined;
99
+ }
97
100
  this.isUnsubscribing = true;
98
101
  clearTimeout(this.timeoutId);
99
102
  this.timeoutId = undefined;
100
- if (this.listenerId) {
103
+ if (this.listenerId != null) {
101
104
  const promise = this.program.provider.connection
102
105
  .removeAccountChangeListener(this.listenerId)
103
106
  .then(() => {
@@ -47,4 +47,26 @@ export declare function getVammL2Generator({ marketAccount, oraclePriceData, num
47
47
  topOfBookQuoteAmounts?: BN[];
48
48
  }): L2OrderBookGenerator;
49
49
  export declare function groupL2(l2: L2OrderBook, grouping: BN, depth: number): L2OrderBook;
50
+ /**
51
+ * The purpose of this function is uncross the L2 orderbook by modifying the bid/ask price at the top of the book
52
+ * This will make the liquidity look worse but more intuitive (users familiar with clob get confused w temporarily
53
+ * crossing book)
54
+ *
55
+ * Things to note about how it works:
56
+ * - it will not uncross the user's liquidity
57
+ * - it does the uncrossing by "shifting" the crossing liquidity to the nearest uncrossed levels. Thus the output liquidity maintains the same total size.
58
+ *
59
+ * @param bids
60
+ * @param asks
61
+ * @param oraclePrice
62
+ * @param oracleTwap5Min
63
+ * @param markTwap5Min
64
+ * @param grouping
65
+ * @param userBids
66
+ * @param userAsks
67
+ */
68
+ export declare function uncrossL2(bids: L2Level[], asks: L2Level[], oraclePrice: BN, oracleTwap5Min: BN, markTwap5Min: BN, grouping: BN, userBids: Set<string>, userAsks: Set<string>): {
69
+ bids: L2Level[];
70
+ asks: L2Level[];
71
+ };
50
72
  export {};
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.groupL2 = exports.getVammL2Generator = exports.createL2Levels = exports.mergeL2LevelGenerators = exports.getL2GeneratorFromDLOBNodes = exports.DEFAULT_TOP_OF_BOOK_QUOTE_AMOUNTS = void 0;
3
+ exports.uncrossL2 = exports.groupL2 = exports.getVammL2Generator = exports.createL2Levels = exports.mergeL2LevelGenerators = exports.getL2GeneratorFromDLOBNodes = exports.DEFAULT_TOP_OF_BOOK_QUOTE_AMOUNTS = void 0;
4
4
  const __1 = require("..");
5
5
  const assert_1 = require("../assert/assert");
6
6
  exports.DEFAULT_TOP_OF_BOOK_QUOTE_AMOUNTS = [
@@ -245,3 +245,117 @@ function groupL2Levels(levels, grouping, direction, depth) {
245
245
  }
246
246
  return groupedLevels;
247
247
  }
248
+ /**
249
+ * The purpose of this function is uncross the L2 orderbook by modifying the bid/ask price at the top of the book
250
+ * This will make the liquidity look worse but more intuitive (users familiar with clob get confused w temporarily
251
+ * crossing book)
252
+ *
253
+ * Things to note about how it works:
254
+ * - it will not uncross the user's liquidity
255
+ * - it does the uncrossing by "shifting" the crossing liquidity to the nearest uncrossed levels. Thus the output liquidity maintains the same total size.
256
+ *
257
+ * @param bids
258
+ * @param asks
259
+ * @param oraclePrice
260
+ * @param oracleTwap5Min
261
+ * @param markTwap5Min
262
+ * @param grouping
263
+ * @param userBids
264
+ * @param userAsks
265
+ */
266
+ function uncrossL2(bids, asks, oraclePrice, oracleTwap5Min, markTwap5Min, grouping, userBids, userAsks) {
267
+ // If there are no bids or asks, there is nothing to center
268
+ if (bids.length === 0 || asks.length === 0) {
269
+ return { bids, asks };
270
+ }
271
+ // If the top of the book is already centered, there is nothing to do
272
+ if (bids[0].price.lt(asks[0].price)) {
273
+ return { bids, asks };
274
+ }
275
+ const newBids = [];
276
+ const newAsks = [];
277
+ const updateLevels = (newPrice, oldLevel, levels) => {
278
+ if (levels.length > 0 && levels[levels.length - 1].price.eq(newPrice)) {
279
+ levels[levels.length - 1].size = levels[levels.length - 1].size.add(oldLevel.size);
280
+ for (const [source, size] of Object.entries(oldLevel.sources)) {
281
+ if (levels[levels.length - 1].sources[source]) {
282
+ levels[levels.length - 1].sources = {
283
+ ...levels[levels.length - 1].sources,
284
+ [source]: levels[levels.length - 1].sources[source].add(size),
285
+ };
286
+ }
287
+ else {
288
+ levels[levels.length - 1].sources[source] = size;
289
+ }
290
+ }
291
+ }
292
+ else {
293
+ levels.push({
294
+ price: newPrice,
295
+ size: oldLevel.size,
296
+ sources: oldLevel.sources,
297
+ });
298
+ }
299
+ };
300
+ // This is the best estimate of the premium in the market vs oracle to filter crossing around
301
+ const referencePrice = oraclePrice.add(markTwap5Min.sub(oracleTwap5Min));
302
+ let bidIndex = 0;
303
+ let askIndex = 0;
304
+ while (bidIndex < bids.length || askIndex < asks.length) {
305
+ const nextBid = bids[bidIndex];
306
+ const nextAsk = asks[askIndex];
307
+ if (!nextBid) {
308
+ newAsks.push(nextAsk);
309
+ askIndex++;
310
+ continue;
311
+ }
312
+ if (!nextAsk) {
313
+ newBids.push(nextBid);
314
+ bidIndex++;
315
+ continue;
316
+ }
317
+ if (nextBid.price.gt(nextAsk.price)) {
318
+ if (userBids.has(nextBid.price.toString())) {
319
+ newBids.push(nextBid);
320
+ bidIndex++;
321
+ continue;
322
+ }
323
+ if (userAsks.has(nextAsk.price.toString())) {
324
+ newAsks.push(nextAsk);
325
+ askIndex++;
326
+ continue;
327
+ }
328
+ if (nextBid.price.gt(referencePrice) &&
329
+ nextAsk.price.gt(referencePrice)) {
330
+ const newBidPrice = nextAsk.price.sub(grouping);
331
+ updateLevels(newBidPrice, nextBid, newBids);
332
+ bidIndex++;
333
+ }
334
+ else if (nextAsk.price.lt(referencePrice) &&
335
+ nextBid.price.lt(referencePrice)) {
336
+ const newAskPrice = nextBid.price.add(grouping);
337
+ updateLevels(newAskPrice, nextAsk, newAsks);
338
+ askIndex++;
339
+ }
340
+ else {
341
+ const newBidPrice = referencePrice.sub(grouping);
342
+ const newAskPrice = referencePrice.add(grouping);
343
+ updateLevels(newBidPrice, nextBid, newBids);
344
+ updateLevels(newAskPrice, nextAsk, newAsks);
345
+ bidIndex++;
346
+ askIndex++;
347
+ }
348
+ }
349
+ else {
350
+ newAsks.push(nextAsk);
351
+ askIndex++;
352
+ newBids.push(nextBid);
353
+ bidIndex++;
354
+ }
355
+ }
356
+ return {
357
+ bids: newBids,
358
+ asks: newAsks,
359
+ };
360
+ }
361
+ exports.uncrossL2 = uncrossL2;
@@ -2216,8 +2216,13 @@ class DriftClient {
2216
2216
  async getSwapIx({ outMarketIndex, inMarketIndex, amountIn, inTokenAccount, outTokenAccount, limitPrice, reduceOnly, userAccountPublicKey, }) {
2217
2217
  const userAccountPublicKeyToUse = userAccountPublicKey || (await this.getUserAccountPublicKey());
2218
2218
  const userAccounts = [];
2219
- if (this.hasUser() && this.getUser().getUserAccountAndSlot()) {
2220
- userAccounts.push(this.getUser().getUserAccountAndSlot().data);
2219
+ try {
2220
+ if (this.hasUser() && this.getUser().getUserAccountAndSlot()) {
2221
+ userAccounts.push(this.getUser().getUserAccountAndSlot().data);
2222
+ }
2223
+ }
2224
+ catch (err) {
2225
+ // ignore
2221
2226
  }
2222
2227
  const remainingAccounts = this.getRemainingAccounts({
2223
2228
  userAccounts,
@@ -17,7 +17,7 @@ class WebSocketLogProvider {
17
17
  }
18
18
  }
19
19
  async subscribe(callback) {
20
- if (this.subscriptionId) {
20
+ if (this.subscriptionId != null) {
21
21
  return true;
22
22
  }
23
23
  this.callback = callback;
@@ -48,14 +48,14 @@ class WebSocketLogProvider {
48
48
  }, this.commitment);
49
49
  }
50
50
  isSubscribed() {
51
- return this.subscriptionId !== undefined;
51
+ return this.subscriptionId != null;
52
52
  }
53
53
  async unsubscribe(external = false) {
54
54
  this.isUnsubscribing = true;
55
55
  this.externalUnsubscribe = external;
56
56
  clearTimeout(this.timeoutId);
57
57
  this.timeoutId = undefined;
58
- if (this.subscriptionId !== undefined) {
58
+ if (this.subscriptionId != null) {
59
59
  try {
60
60
  await this.connection.removeOnLogsListener(this.subscriptionId);
61
61
  this.subscriptionId = undefined;
@@ -86,7 +86,7 @@ export declare class BigNum {
86
86
  * @returns
87
87
  */
88
88
  toNotional(useTradePrecision?: boolean, precisionOverride?: number): string;
89
- toMillified(precision?: number, rounded?: boolean): string;
89
+ toMillified(precision?: number, rounded?: boolean, type?: 'financial' | 'scientific'): string;
90
90
  toJSON(): {
91
91
  val: string;
92
92
  precision: string;
@@ -330,7 +330,7 @@ class BigNum {
330
330
  }
331
331
  return `${prefix}${val.replace('-', '')}`;
332
332
  }
333
- toMillified(precision = 3, rounded = false) {
333
+ toMillified(precision = 3, rounded = false, type = 'financial') {
334
334
  if (rounded) {
335
335
  return this.toRounded(precision).toMillified(precision);
336
336
  }
@@ -346,7 +346,10 @@ class BigNum {
346
346
  if (leftSide.length <= 3) {
347
347
  return this.shift(new anchor_1.BN(precision)).toPrecision(precision, true);
348
348
  }
349
- const unitTicks = ['', 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y'];
349
+ const unitTicks = type === 'financial'
350
+ ? ['', 'K', 'M', 'B', 'T', 'Q']
351
+ : ['', 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y'];
352
+ // TODO -- handle nubers which are larger than the max unit tick
350
353
  const unitNumber = Math.floor((leftSide.length - 1) / 3);
351
354
  const unit = unitTicks[unitNumber];
352
355
  let leadDigits = leftSide.slice(0, precision);
@@ -7343,12 +7343,24 @@
7343
7343
  "name": "totalFeeEarnedPerLp",
7344
7344
  "type": "u64"
7345
7345
  },
7346
+ {
7347
+ "name": "netUnsettledFundingPnl",
7348
+ "type": "i64"
7349
+ },
7350
+ {
7351
+ "name": "quoteAssetAmountWithUnsettledLp",
7352
+ "type": "i64"
7353
+ },
7354
+ {
7355
+ "name": "referencePriceOffset",
7356
+ "type": "i32"
7357
+ },
7346
7358
  {
7347
7359
  "name": "padding",
7348
7360
  "type": {
7349
7361
  "array": [
7350
7362
  "u8",
7351
- 32
7363
+ 12
7352
7364
  ]
7353
7365
  }
7354
7366
  }
package/lib/math/amm.d.ts CHANGED
@@ -34,7 +34,9 @@ export 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, stepSize?: BN): [BN, BN];
37
+ export declare function calculateInventoryLiquidityRatio(baseAssetAmountWithAmm: BN, baseAssetReserve: BN, minBaseAssetReserve: BN, maxBaseAssetReserve: BN): BN;
37
38
  export declare function calculateInventoryScale(baseAssetAmountWithAmm: BN, baseAssetReserve: BN, minBaseAssetReserve: BN, maxBaseAssetReserve: BN, directionalSpread: number, maxSpread: number): number;
39
+ export declare function calculateReferencePriceOffset(reservePrice: BN, last24hAvgFundingRate: BN, liquidityFraction: BN, oracleTwapFast: BN, markTwapFast: BN, oracleTwapSlow: BN, markTwapSlow: BN, maxOffsetPct: number): BN;
38
40
  export declare function calculateEffectiveLeverage(baseSpread: number, quoteAssetReserve: BN, terminalQuoteAssetReserve: BN, pegMultiplier: BN, netBaseAssetAmount: BN, reservePrice: BN, totalFeeMinusDistributions: BN): number;
39
41
  export declare function calculateMaxSpread(marginRatioInitial: number): number;
40
42
  export declare function calculateVolSpreadBN(lastOracleConfPct: BN, reservePrice: BN, markStd: BN, oracleStd: BN, longIntensity: BN, shortIntensity: BN, volume24H: BN): [BN, BN];
@@ -55,11 +57,13 @@ export declare function calculateSpreadBN(baseSpread: number, lastOracleReserveP
55
57
  halfRevenueRetreatAmount: number;
56
58
  longSpreadwRevRetreat: number;
57
59
  shortSpreadwRevRetreat: number;
60
+ longSpreadwOffsetShrink: number;
61
+ shortSpreadwOffsetShrink: number;
58
62
  totalSpread: number;
59
63
  longSpread: number;
60
64
  shortSpread: number;
61
65
  };
62
- export declare function calculateSpread(amm: AMM, oraclePriceData: OraclePriceData, now?: BN): [number, number];
66
+ export declare function calculateSpread(amm: AMM, oraclePriceData: OraclePriceData, now?: BN, reservePrice?: BN): [number, number];
63
67
  export declare function calculateSpreadReserves(amm: AMM, oraclePriceData: OraclePriceData, now?: BN): {
64
68
  baseAssetReserve: any;
65
69
  quoteAssetReserve: any;
package/lib/math/amm.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.calculateMaxBaseAssetAmountFillable = exports.calculateQuoteAssetAmountSwapped = exports.calculateMaxBaseAssetAmountToTrade = exports.calculateTerminalPrice = exports.getSwapDirection = exports.calculateSwapOutput = exports.calculateSpreadReserves = exports.calculateSpread = exports.calculateSpreadBN = exports.calculateVolSpreadBN = exports.calculateMaxSpread = exports.calculateEffectiveLeverage = exports.calculateInventoryScale = exports.calculateMarketOpenBidAsk = exports.calculateAmmReservesAfterSwap = exports.calculatePrice = exports.calculateBidAskPrice = exports.calculateUpdatedAMMSpreadReserves = exports.calculateUpdatedAMM = exports.calculateNewAmm = exports.calculateOptimalPegAndBudget = exports.calculatePegFromTargetPrice = void 0;
3
+ exports.calculateMaxBaseAssetAmountFillable = exports.calculateQuoteAssetAmountSwapped = exports.calculateMaxBaseAssetAmountToTrade = exports.calculateTerminalPrice = exports.getSwapDirection = exports.calculateSwapOutput = exports.calculateSpreadReserves = exports.calculateSpread = exports.calculateSpreadBN = exports.calculateVolSpreadBN = exports.calculateMaxSpread = exports.calculateEffectiveLeverage = exports.calculateReferencePriceOffset = exports.calculateInventoryScale = exports.calculateInventoryLiquidityRatio = exports.calculateMarketOpenBidAsk = exports.calculateAmmReservesAfterSwap = exports.calculatePrice = exports.calculateBidAskPrice = exports.calculateUpdatedAMMSpreadReserves = exports.calculateUpdatedAMM = exports.calculateNewAmm = exports.calculateOptimalPegAndBudget = exports.calculatePegFromTargetPrice = void 0;
4
4
  const anchor_1 = require("@coral-xyz/anchor");
5
5
  const numericConstants_1 = require("../constants/numericConstants");
6
6
  const types_1 = require("../types");
@@ -201,11 +201,7 @@ function calculateMarketOpenBidAsk(baseAssetReserve, minBaseAssetReserve, maxBas
201
201
  return [openBids, openAsks];
202
202
  }
203
203
  exports.calculateMarketOpenBidAsk = calculateMarketOpenBidAsk;
204
- function calculateInventoryScale(baseAssetAmountWithAmm, baseAssetReserve, minBaseAssetReserve, maxBaseAssetReserve, directionalSpread, maxSpread) {
205
- if (baseAssetAmountWithAmm.eq(numericConstants_1.ZERO)) {
206
- return 1;
207
- }
208
- const MAX_BID_ASK_INVENTORY_SKEW_FACTOR = numericConstants_1.BID_ASK_SPREAD_PRECISION.mul(new anchor_1.BN(10));
204
+ function calculateInventoryLiquidityRatio(baseAssetAmountWithAmm, baseAssetReserve, minBaseAssetReserve, maxBaseAssetReserve) {
209
205
  // inventory skew
210
206
  const [openBids, openAsks] = calculateMarketOpenBidAsk(baseAssetReserve, minBaseAssetReserve, maxBaseAssetReserve);
211
207
  const minSideLiquidity = anchor_1.BN.min(openBids.abs(), openAsks.abs());
@@ -213,6 +209,15 @@ function calculateInventoryScale(baseAssetAmountWithAmm, baseAssetReserve, minBa
213
209
  .mul(numericConstants_1.PERCENTAGE_PRECISION)
214
210
  .div(anchor_1.BN.max(minSideLiquidity, numericConstants_1.ONE))
215
211
  .abs(), numericConstants_1.PERCENTAGE_PRECISION);
212
+ return inventoryScaleBN;
213
+ }
214
+ exports.calculateInventoryLiquidityRatio = calculateInventoryLiquidityRatio;
215
+ function calculateInventoryScale(baseAssetAmountWithAmm, baseAssetReserve, minBaseAssetReserve, maxBaseAssetReserve, directionalSpread, maxSpread) {
216
+ if (baseAssetAmountWithAmm.eq(numericConstants_1.ZERO)) {
217
+ return 1;
218
+ }
219
+ const MAX_BID_ASK_INVENTORY_SKEW_FACTOR = numericConstants_1.BID_ASK_SPREAD_PRECISION.mul(new anchor_1.BN(10));
220
+ const inventoryScaleBN = calculateInventoryLiquidityRatio(baseAssetAmountWithAmm, baseAssetReserve, minBaseAssetReserve, maxBaseAssetReserve);
216
221
  const inventoryScaleMaxBN = anchor_1.BN.max(MAX_BID_ASK_INVENTORY_SKEW_FACTOR, new anchor_1.BN(maxSpread)
217
222
  .mul(numericConstants_1.BID_ASK_SPREAD_PRECISION)
218
223
  .div(new anchor_1.BN(Math.max(directionalSpread, 1))));
@@ -220,6 +225,36 @@ function calculateInventoryScale(baseAssetAmountWithAmm, baseAssetReserve, minBa
220
225
  return inventoryScaleCapped;
221
226
  }
222
227
  exports.calculateInventoryScale = calculateInventoryScale;
228
+ function calculateReferencePriceOffset(reservePrice, last24hAvgFundingRate, liquidityFraction, oracleTwapFast, markTwapFast, oracleTwapSlow, markTwapSlow, maxOffsetPct) {
229
+ if (last24hAvgFundingRate.eq(numericConstants_1.ZERO)) {
230
+ return numericConstants_1.ZERO;
231
+ }
232
+ const maxOffsetInPrice = new anchor_1.BN(maxOffsetPct)
233
+ .mul(reservePrice)
234
+ .div(numericConstants_1.PERCENTAGE_PRECISION);
235
+ // Calculate quote denominated market premium
236
+ const markPremiumMinute = (0, __1.clampBN)(markTwapFast.sub(oracleTwapFast), maxOffsetInPrice.mul(new anchor_1.BN(-1)), maxOffsetInPrice);
237
+ const markPremiumHour = (0, __1.clampBN)(markTwapSlow.sub(oracleTwapSlow), maxOffsetInPrice.mul(new anchor_1.BN(-1)), maxOffsetInPrice);
238
+ // Convert last24hAvgFundingRate to quote denominated premium
239
+ const markPremiumDay = (0, __1.clampBN)(last24hAvgFundingRate.div(numericConstants_1.FUNDING_RATE_BUFFER_PRECISION).mul(new anchor_1.BN(24)), maxOffsetInPrice.mul(new anchor_1.BN(-1)), maxOffsetInPrice);
240
+ // Take average clamped premium as the price-based offset
241
+ const markPremiumAvg = markPremiumMinute
242
+ .add(markPremiumHour)
243
+ .add(markPremiumDay)
244
+ .div(new anchor_1.BN(3));
245
+ const markPremiumAvgPct = markPremiumAvg
246
+ .mul(numericConstants_1.PRICE_PRECISION)
247
+ .div(reservePrice);
248
+ const inventoryPct = (0, __1.clampBN)(liquidityFraction.mul(new anchor_1.BN(maxOffsetPct)).div(numericConstants_1.PERCENTAGE_PRECISION), maxOffsetInPrice.mul(new anchor_1.BN(-1)), maxOffsetInPrice);
249
+ // Only apply when inventory is consistent with recent and 24h market premium
250
+ let offsetPct = markPremiumAvgPct.add(inventoryPct);
251
+ if (!(0, __1.sigNum)(inventoryPct).eq((0, __1.sigNum)(markPremiumAvgPct))) {
252
+ offsetPct = numericConstants_1.ZERO;
253
+ }
254
+ const clampedOffsetPct = (0, __1.clampBN)(offsetPct, new anchor_1.BN(-maxOffsetPct), new anchor_1.BN(maxOffsetPct));
255
+ return clampedOffsetPct;
256
+ }
257
+ exports.calculateReferencePriceOffset = calculateReferencePriceOffset;
223
258
  function calculateEffectiveLeverage(baseSpread, quoteAssetReserve, terminalQuoteAssetReserve, pegMultiplier, netBaseAssetAmount, reservePrice, totalFeeMinusDistributions) {
224
259
  // vAMM skew
225
260
  const netBaseAssetValue = quoteAssetReserve
@@ -283,6 +318,8 @@ function calculateSpreadBN(baseSpread, lastOracleReservePriceSpreadPct, lastOrac
283
318
  halfRevenueRetreatAmount: 0,
284
319
  longSpreadwRevRetreat: 0,
285
320
  shortSpreadwRevRetreat: 0,
321
+ longSpreadwOffsetShrink: 0,
322
+ shortSpreadwOffsetShrink: 0,
286
323
  totalSpread: 0,
287
324
  longSpread: 0,
288
325
  shortSpread: 0,
@@ -380,11 +417,13 @@ function calculateSpreadBN(baseSpread, lastOracleReservePriceSpreadPct, lastOrac
380
417
  return [longSpread, shortSpread];
381
418
  }
382
419
  exports.calculateSpreadBN = calculateSpreadBN;
383
- function calculateSpread(amm, oraclePriceData, now) {
420
+ function calculateSpread(amm, oraclePriceData, now, reservePrice) {
384
421
  if (amm.baseSpread == 0 || amm.curveUpdateIntensity == 0) {
385
422
  return [amm.baseSpread / 2, amm.baseSpread / 2];
386
423
  }
387
- const reservePrice = calculatePrice(amm.baseAssetReserve, amm.quoteAssetReserve, amm.pegMultiplier);
424
+ if (!reservePrice) {
425
+ reservePrice = calculatePrice(amm.baseAssetReserve, amm.quoteAssetReserve, amm.pegMultiplier);
426
+ }
388
427
  const targetPrice = (oraclePriceData === null || oraclePriceData === void 0 ? void 0 : oraclePriceData.price) || reservePrice;
389
428
  const confInterval = oraclePriceData.confidence || numericConstants_1.ZERO;
390
429
  const targetMarkSpreadPct = reservePrice
@@ -410,10 +449,15 @@ function calculateSpreadReserves(amm, oraclePriceData, now) {
410
449
  quoteAssetReserve: amm.quoteAssetReserve,
411
450
  };
412
451
  }
413
- const spreadFraction = anchor_1.BN.max(new anchor_1.BN(spread / 2), numericConstants_1.ONE);
452
+ let spreadFraction = new anchor_1.BN(spread / 2);
453
+ // make non-zero
454
+ if (spreadFraction.eq(numericConstants_1.ZERO)) {
455
+ spreadFraction = spread >= 0 ? new anchor_1.BN(1) : new anchor_1.BN(-1);
456
+ }
414
457
  const quoteAssetReserveDelta = amm.quoteAssetReserve.div(numericConstants_1.BID_ASK_SPREAD_PRECISION.div(spreadFraction));
415
458
  let quoteAssetReserve;
416
- if ((0, types_1.isVariant)(direction, 'long')) {
459
+ if ((spread >= 0 && (0, types_1.isVariant)(direction, 'long')) ||
460
+ (spread <= 0 && (0, types_1.isVariant)(direction, 'short'))) {
417
461
  quoteAssetReserve = amm.quoteAssetReserve.add(quoteAssetReserveDelta);
418
462
  }
419
463
  else {
@@ -425,9 +469,14 @@ function calculateSpreadReserves(amm, oraclePriceData, now) {
425
469
  quoteAssetReserve,
426
470
  };
427
471
  }
428
- const [longSpread, shortSpread] = calculateSpread(amm, oraclePriceData, now);
429
- const askReserves = calculateSpreadReserve(longSpread, types_1.PositionDirection.LONG, amm);
430
- const bidReserves = calculateSpreadReserve(shortSpread, types_1.PositionDirection.SHORT, amm);
472
+ const reservePrice = calculatePrice(amm.baseAssetReserve, amm.quoteAssetReserve, amm.pegMultiplier);
473
+ // always allow 10 bps of price offset, up to a fifth of the market's max_spread
474
+ const maxOffset = Math.max(amm.maxSpread / 5, numericConstants_1.PERCENTAGE_PRECISION.toNumber() / 1000);
475
+ const liquidityFraction = calculateInventoryLiquidityRatio(amm.baseAssetAmountWithAmm, amm.baseAssetReserve, amm.minBaseAssetReserve, amm.maxBaseAssetReserve);
476
+ const referencePriceOffset = calculateReferencePriceOffset(reservePrice, amm.last24HAvgFundingRate, liquidityFraction, amm.historicalOracleData.lastOraclePriceTwap5Min, amm.lastMarkPriceTwap5Min, amm.historicalOracleData.lastOraclePriceTwap, amm.lastMarkPriceTwap, maxOffset);
477
+ const [longSpread, shortSpread] = calculateSpread(amm, oraclePriceData, now, reservePrice);
478
+ const askReserves = calculateSpreadReserve(longSpread + referencePriceOffset.toNumber(), types_1.PositionDirection.LONG, amm);
479
+ const bidReserves = calculateSpreadReserve(shortSpread + referencePriceOffset.toNumber(), types_1.PositionDirection.SHORT, amm);
431
480
  return [bidReserves, askReserves];
432
481
  }
433
482
  exports.calculateSpreadReserves = calculateSpreadReserves;
@@ -104,9 +104,9 @@ class PhoenixSubscriber {
104
104
  const ladder = (0, phoenix_sdk_1.getMarketUiLadder)(this.market, this.lastSlot, this.lastUnixTimestamp, 20);
105
105
  for (let i = 0; i < ladder[side].length; i++) {
106
106
  const { price, quantity } = ladder[side][i];
107
- const size = new anchor_1.BN(Math.floor(quantity * basePrecision));
107
+ const size = new anchor_1.BN(quantity).mul(new anchor_1.BN(basePrecision));
108
108
  yield {
109
- price: new anchor_1.BN(Math.floor(price * pricePrecision)),
109
+ price: new anchor_1.BN(price).mul(new anchor_1.BN(pricePrecision)),
110
110
  size,
111
111
  sources: {
112
112
  phoenix: size,
@@ -1,8 +1,11 @@
1
1
  /// <reference types="node" />
2
+ /// <reference types="node" />
2
3
  import { Connection } from '@solana/web3.js';
3
4
  import { EventEmitter } from 'events';
4
5
  import StrictEventEmitter from 'strict-event-emitter-types/types/src';
5
- type SlotSubscriberConfig = {};
6
+ type SlotSubscriberConfig = {
7
+ resubTimeoutMs?: number;
8
+ };
6
9
  export interface SlotSubscriberEvents {
7
10
  newSlot: (newSlot: number) => void;
8
11
  }
@@ -11,9 +14,14 @@ export declare class SlotSubscriber {
11
14
  currentSlot: number;
12
15
  subscriptionId: number;
13
16
  eventEmitter: StrictEventEmitter<EventEmitter, SlotSubscriberEvents>;
14
- constructor(connection: Connection, _config?: SlotSubscriberConfig);
17
+ timeoutId?: NodeJS.Timeout;
18
+ resubTimeoutMs?: number;
19
+ isUnsubscribing: boolean;
20
+ receivingData: boolean;
21
+ constructor(connection: Connection, config?: SlotSubscriberConfig);
15
22
  subscribe(): Promise<void>;
23
+ private setTimeout;
16
24
  getSlot(): number;
17
- unsubscribe(): Promise<void>;
25
+ unsubscribe(onResub?: boolean): Promise<void>;
18
26
  }
19
27
  export {};
@@ -3,28 +3,64 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.SlotSubscriber = void 0;
4
4
  const events_1 = require("events");
5
5
  class SlotSubscriber {
6
- constructor(connection, _config) {
6
+ constructor(connection, config) {
7
7
  this.connection = connection;
8
+ this.isUnsubscribing = false;
9
+ this.receivingData = false;
8
10
  this.eventEmitter = new events_1.EventEmitter();
11
+ this.resubTimeoutMs = config === null || config === void 0 ? void 0 : config.resubTimeoutMs;
9
12
  }
10
13
  async subscribe() {
11
- if (this.subscriptionId) {
14
+ if (this.subscriptionId != null) {
12
15
  return;
13
16
  }
14
17
  this.currentSlot = await this.connection.getSlot('confirmed');
15
18
  this.subscriptionId = this.connection.onSlotChange((slotInfo) => {
16
19
  if (!this.currentSlot || this.currentSlot < slotInfo.slot) {
20
+ if (this.resubTimeoutMs && !this.isUnsubscribing) {
21
+ this.receivingData = true;
22
+ clearTimeout(this.timeoutId);
23
+ this.setTimeout();
24
+ }
17
25
  this.currentSlot = slotInfo.slot;
18
26
  this.eventEmitter.emit('newSlot', slotInfo.slot);
19
27
  }
20
28
  });
29
+ if (this.resubTimeoutMs) {
30
+ this.setTimeout();
31
+ }
32
+ }
33
+ setTimeout() {
34
+ this.timeoutId = setTimeout(async () => {
35
+ if (this.isUnsubscribing) {
36
+ // If we are in the process of unsubscribing, do not attempt to resubscribe
37
+ return;
38
+ }
39
+ if (this.receivingData) {
40
+ console.log(`No new slot in ${this.resubTimeoutMs}ms, slot subscriber resubscribing`);
41
+ await this.unsubscribe(true);
42
+ this.receivingData = false;
43
+ await this.subscribe();
44
+ }
45
+ }, this.resubTimeoutMs);
21
46
  }
22
47
  getSlot() {
23
48
  return this.currentSlot;
24
49
  }
25
- async unsubscribe() {
26
- if (this.subscriptionId) {
50
+ async unsubscribe(onResub = false) {
51
+ if (!onResub) {
52
+ this.resubTimeoutMs = undefined;
53
+ }
54
+ this.isUnsubscribing = true;
55
+ clearTimeout(this.timeoutId);
56
+ this.timeoutId = undefined;
57
+ if (this.subscriptionId != null) {
27
58
  await this.connection.removeSlotChangeListener(this.subscriptionId);
59
+ this.subscriptionId = undefined;
60
+ this.isUnsubscribing = false;
61
+ }
62
+ else {
63
+ this.isUnsubscribing = false;
28
64
  }
29
65
  }
30
66
  }
package/lib/types.d.ts CHANGED
@@ -767,7 +767,7 @@ export type AMM = {
767
767
  pegMultiplier: BN;
768
768
  cumulativeFundingRateLong: BN;
769
769
  cumulativeFundingRateShort: BN;
770
- last24hAvgFundingRate: BN;
770
+ last24HAvgFundingRate: BN;
771
771
  lastFundingRateShort: BN;
772
772
  lastFundingRateLong: BN;
773
773
  totalLiquidationFee: BN;
package/lib/user.d.ts CHANGED
@@ -353,7 +353,7 @@ export declare class User {
353
353
  * @param quoteAmount
354
354
  * @returns feeForQuote : Precision QUOTE_PRECISION
355
355
  */
356
- calculateFeeForQuoteAmount(quoteAmount: BN): BN;
356
+ calculateFeeForQuoteAmount(quoteAmount: BN, marketIndex?: number): BN;
357
357
  /**
358
358
  * Calculates a user's max withdrawal amounts for a spot market. If reduceOnly is true,
359
359
  * it will return the max withdrawal amount without opening a liability for the user