@drift-labs/sdk 2.52.0-beta.0 → 2.52.0-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/VERSION +1 -1
  2. package/lib/accounts/basicUserAccountSubscriber.d.ts +4 -0
  3. package/lib/accounts/basicUserAccountSubscriber.js +4 -0
  4. package/lib/accounts/bulkAccountLoader.js +7 -1
  5. package/lib/accounts/oneShotUserAccountSubscriber.d.ts +17 -0
  6. package/lib/accounts/oneShotUserAccountSubscriber.js +48 -0
  7. package/lib/accounts/pollingInsuranceFundStakeAccountSubscriber.js +1 -0
  8. package/lib/accounts/webSocketAccountSubscriber.d.ts +1 -1
  9. package/lib/accounts/webSocketAccountSubscriber.js +7 -4
  10. package/lib/accounts/webSocketProgramAccountSubscriber.d.ts +1 -1
  11. package/lib/accounts/webSocketProgramAccountSubscriber.js +7 -4
  12. package/lib/adminClient.d.ts +1 -0
  13. package/lib/adminClient.js +9 -0
  14. package/lib/constants/numericConstants.d.ts +3 -0
  15. package/lib/constants/numericConstants.js +4 -1
  16. package/lib/dlob/orderBookLevels.d.ts +22 -0
  17. package/lib/dlob/orderBookLevels.js +115 -1
  18. package/lib/driftClient.d.ts +1 -0
  19. package/lib/driftClient.js +14 -5
  20. package/lib/events/webSocketLogProvider.js +3 -3
  21. package/lib/factory/bigNum.d.ts +1 -1
  22. package/lib/factory/bigNum.js +5 -2
  23. package/lib/idl/drift.json +13 -1
  24. package/lib/index.d.ts +1 -0
  25. package/lib/index.js +1 -0
  26. package/lib/math/amm.d.ts +5 -1
  27. package/lib/math/amm.js +62 -13
  28. package/lib/phoenix/phoenixSubscriber.js +2 -2
  29. package/lib/slot/SlotSubscriber.d.ts +11 -3
  30. package/lib/slot/SlotSubscriber.js +40 -4
  31. package/lib/types.d.ts +1 -1
  32. package/lib/userMap/userMap.d.ts +3 -0
  33. package/lib/userMap/userMap.js +10 -1
  34. package/lib/userStats.d.ts +1 -0
  35. package/lib/userStats.js +3 -0
  36. package/package.json +1 -1
  37. package/src/accounts/basicUserAccountSubscriber.ts +4 -0
  38. package/src/accounts/bulkAccountLoader.ts +10 -3
  39. package/src/accounts/oneShotUserAccountSubscriber.ts +64 -0
  40. package/src/accounts/pollingInsuranceFundStakeAccountSubscriber.ts +1 -0
  41. package/src/accounts/webSocketAccountSubscriber.ts +7 -4
  42. package/src/accounts/webSocketProgramAccountSubscriber.ts +7 -4
  43. package/src/adminClient.ts +13 -0
  44. package/src/constants/numericConstants.ts +4 -0
  45. package/src/dlob/orderBookLevels.ts +136 -0
  46. package/src/driftClient.ts +21 -10
  47. package/src/events/webSocketLogProvider.ts +3 -3
  48. package/src/factory/bigNum.ts +11 -2
  49. package/src/idl/drift.json +13 -1
  50. package/src/index.ts +1 -0
  51. package/src/math/amm.ts +159 -25
  52. package/src/phoenix/phoenixSubscriber.ts +2 -2
  53. package/src/slot/SlotSubscriber.ts +52 -5
  54. package/src/types.ts +1 -1
  55. package/src/userMap/userMap.ts +17 -3
  56. package/src/userStats.ts +8 -0
  57. package/tests/amm/test.ts +219 -11
  58. package/tests/bn/test.ts +27 -0
  59. package/tests/dlob/helpers.ts +1 -1
  60. package/tests/dlob/test.ts +372 -2
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;
@@ -22,6 +22,7 @@ export declare class UserMap implements UserMapInterface {
22
22
  private subscription;
23
23
  private stateAccountUpdateCallback;
24
24
  private decode;
25
+ private mostRecentSlot;
25
26
  private syncPromise?;
26
27
  private syncPromiseResolver;
27
28
  /**
@@ -68,4 +69,6 @@ export declare class UserMap implements UserMapInterface {
68
69
  sync(): Promise<void>;
69
70
  unsubscribe(): Promise<void>;
70
71
  updateUserAccount(key: string, userAccount: UserAccount, slot: number): Promise<void>;
72
+ updateLatestSlot(slot: number): void;
73
+ getSlot(): number;
71
74
  }
@@ -21,6 +21,7 @@ class UserMap {
21
21
  this.lastNumberOfSubAccounts = state.numberOfSubAccounts;
22
22
  }
23
23
  };
24
+ this.mostRecentSlot = 0;
24
25
  this.driftClient = config.driftClient;
25
26
  if (config.connection) {
26
27
  this.connection = config.connection;
@@ -73,7 +74,7 @@ class UserMap {
73
74
  userAccountPublicKey,
74
75
  accountSubscription: {
75
76
  type: 'custom',
76
- userAccountSubscriber: new __1.BasicUserAccountSubscriber(userAccountPublicKey, userAccount, slot),
77
+ userAccountSubscriber: new __1.OneShotUserAccountSubscriber(this.driftClient.program, userAccountPublicKey, userAccount, slot, this.commitment),
77
78
  },
78
79
  });
79
80
  await user.subscribe(userAccount);
@@ -218,6 +219,7 @@ class UserMap {
218
219
  await this.connection._rpcRequest('getProgramAccounts', rpcRequestArgs);
219
220
  const rpcResponseAndContext = rpcJSONResponse.result;
220
221
  const slot = rpcResponseAndContext.context.slot;
222
+ this.updateLatestSlot(slot);
221
223
  const programAccountBufferMap = new Map();
222
224
  for (const programAccount of rpcResponseAndContext.value) {
223
225
  programAccountBufferMap.set(programAccount.pubkey.toString(),
@@ -263,6 +265,7 @@ class UserMap {
263
265
  }
264
266
  }
265
267
  async updateUserAccount(key, userAccount, slot) {
268
+ this.updateLatestSlot(slot);
266
269
  if (!this.userMap.has(key)) {
267
270
  this.addPubkey(new web3_js_1.PublicKey(key), userAccount, slot);
268
271
  }
@@ -271,5 +274,11 @@ class UserMap {
271
274
  user.accountSubscriber.updateData(userAccount, slot);
272
275
  }
273
276
  }
277
+ updateLatestSlot(slot) {
278
+ this.mostRecentSlot = Math.max(slot, this.mostRecentSlot);
279
+ }
280
+ getSlot() {
281
+ return this.mostRecentSlot;
282
+ }
274
283
  }
275
284
  exports.UserMap = UserMap;
@@ -15,4 +15,5 @@ export declare class UserStats {
15
15
  getAccountAndSlot(): DataAndSlot<UserStatsAccount>;
16
16
  getAccount(): UserStatsAccount;
17
17
  getReferrerInfo(): ReferrerInfo | undefined;
18
+ static getOldestActionTs(account: UserStatsAccount): number;
18
19
  }
package/lib/userStats.js CHANGED
@@ -48,5 +48,8 @@ class UserStats {
48
48
  };
49
49
  }
50
50
  }
51
+ static getOldestActionTs(account) {
52
+ return Math.min(account.lastFillerVolume30DTs.toNumber(), account.lastMakerVolume30DTs.toNumber(), account.lastTakerVolume30DTs.toNumber());
53
+ }
51
54
  }
52
55
  exports.UserStats = UserStats;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@drift-labs/sdk",
3
- "version": "2.52.0-beta.0",
3
+ "version": "2.52.0-beta.2",
4
4
  "main": "lib/index.js",
5
5
  "types": "lib/index.d.ts",
6
6
  "author": "crispheaney",
@@ -4,6 +4,10 @@ import StrictEventEmitter from 'strict-event-emitter-types';
4
4
  import { EventEmitter } from 'events';
5
5
  import { UserAccount } from '../types';
6
6
 
7
+ /**
8
+ * Basic implementation of UserAccountSubscriber. It will only take in UserAccount
9
+ * data during initialization and will not fetch or subscribe to updates.
10
+ */
7
11
  export class BasicUserAccountSubscriber implements UserAccountSubscriber {
8
12
  isSubscribed: boolean;
9
13
  eventEmitter: StrictEventEmitter<EventEmitter, UserAccountEvents>;
@@ -78,6 +78,7 @@ export class BulkAccountLoader {
78
78
  if (existingAccountToLoad) {
79
79
  existingAccountToLoad.callbacks.delete(callbackId);
80
80
  if (existingAccountToLoad.callbacks.size === 0) {
81
+ this.bufferAndSlotMap.delete(publicKey.toString());
81
82
  this.accountsToLoad.delete(existingAccountToLoad.publicKey.toString());
82
83
  }
83
84
  }
@@ -153,9 +154,11 @@ export class BulkAccountLoader {
153
154
  const requests = new Array<{ methodName: string; args: any }>();
154
155
  for (const accountsToLoadChunk of accountsToLoadChunks) {
155
156
  const args = [
156
- accountsToLoadChunk.map((accountToLoad) => {
157
- return accountToLoad.publicKey.toBase58();
158
- }),
157
+ accountsToLoadChunk
158
+ .filter((accountToLoad) => accountToLoad.callbacks.size > 0)
159
+ .map((accountToLoad) => {
160
+ return accountToLoad.publicKey.toBase58();
161
+ }),
159
162
  { commitment: this.commitment },
160
163
  ];
161
164
 
@@ -190,6 +193,10 @@ export class BulkAccountLoader {
190
193
 
191
194
  const accountsToLoad = accountsToLoadChunks[i];
192
195
  accountsToLoad.forEach((accountToLoad, j) => {
196
+ if (accountToLoad.callbacks.size === 0) {
197
+ return;
198
+ }
199
+
193
200
  const key = accountToLoad.publicKey.toBase58();
194
201
  const oldRPCResponse = this.bufferAndSlotMap.get(key);
195
202
 
@@ -0,0 +1,64 @@
1
+ import { Commitment, PublicKey } from '@solana/web3.js';
2
+ import { UserAccount } from '../types';
3
+ import { BasicUserAccountSubscriber } from './basicUserAccountSubscriber';
4
+ import { Program } from '@coral-xyz/anchor';
5
+
6
+ /**
7
+ * Simple implementation of UserAccountSubscriber. It will fetch the UserAccount
8
+ * date on subscribe (or call to fetch) if no account data is provided on init.
9
+ * Expect to use only 1 RPC call unless you call fetch repeatedly.
10
+ */
11
+ export class OneShotUserAccountSubscriber extends BasicUserAccountSubscriber {
12
+ program: Program;
13
+ commitment: Commitment;
14
+
15
+ public constructor(
16
+ program: Program,
17
+ userAccountPublicKey: PublicKey,
18
+ data?: UserAccount,
19
+ slot?: number,
20
+ commitment?: Commitment
21
+ ) {
22
+ super(userAccountPublicKey, data, slot);
23
+ this.program = program;
24
+ this.commitment = commitment ?? 'confirmed';
25
+ }
26
+
27
+ async subscribe(userAccount?: UserAccount): Promise<boolean> {
28
+ if (userAccount) {
29
+ this.user = { data: userAccount, slot: this.user.slot };
30
+ return true;
31
+ }
32
+
33
+ await this.fetchIfUnloaded();
34
+ if (this.doesAccountExist()) {
35
+ this.eventEmitter.emit('update');
36
+ }
37
+ return true;
38
+ }
39
+
40
+ async fetchIfUnloaded(): Promise<void> {
41
+ if (this.user.data === undefined) {
42
+ await this.fetch();
43
+ }
44
+ }
45
+
46
+ async fetch(): Promise<void> {
47
+ try {
48
+ const dataAndContext = await this.program.account.user.fetchAndContext(
49
+ this.userAccountPublicKey,
50
+ this.commitment
51
+ );
52
+ if (dataAndContext.context.slot > (this.user?.slot ?? 0)) {
53
+ this.user = {
54
+ data: dataAndContext.data as UserAccount,
55
+ slot: dataAndContext.context.slot,
56
+ };
57
+ }
58
+ } catch (e) {
59
+ console.error(
60
+ `OneShotUserAccountSubscriber.fetch() UserAccount does not exist: ${e.message}`
61
+ );
62
+ }
63
+ }
64
+ }
@@ -54,6 +54,7 @@ export class PollingInsuranceFundStakeAccountSubscriber
54
54
 
55
55
  await this.addToAccountLoader();
56
56
 
57
+ await this.fetchIfUnloaded();
57
58
  if (this.doesAccountExist()) {
58
59
  this.eventEmitter.emit('update');
59
60
  }
@@ -40,7 +40,7 @@ export class WebSocketAccountSubscriber<T> implements AccountSubscriber<T> {
40
40
  }
41
41
 
42
42
  async subscribe(onChange: (data: T) => void): Promise<void> {
43
- if (this.listenerId || this.isUnsubscribing) {
43
+ if (this.listenerId != null || this.isUnsubscribing) {
44
44
  return;
45
45
  }
46
46
 
@@ -95,7 +95,7 @@ export class WebSocketAccountSubscriber<T> implements AccountSubscriber<T> {
95
95
  console.log(
96
96
  `No ws data from ${this.accountName} in ${this.resubTimeoutMs}ms, resubscribing`
97
97
  );
98
- await this.unsubscribe();
98
+ await this.unsubscribe(true);
99
99
  this.receivingData = false;
100
100
  await this.subscribe(this.onChange);
101
101
  }
@@ -164,12 +164,15 @@ export class WebSocketAccountSubscriber<T> implements AccountSubscriber<T> {
164
164
  }
165
165
  }
166
166
 
167
- unsubscribe(): Promise<void> {
167
+ unsubscribe(onResub = false): Promise<void> {
168
+ if (!onResub) {
169
+ this.resubTimeoutMs = undefined;
170
+ }
168
171
  this.isUnsubscribing = true;
169
172
  clearTimeout(this.timeoutId);
170
173
  this.timeoutId = undefined;
171
174
 
172
- if (this.listenerId) {
175
+ if (this.listenerId != null) {
173
176
  const promise = this.program.provider.connection
174
177
  .removeAccountChangeListener(this.listenerId)
175
178
  .then(() => {
@@ -49,7 +49,7 @@ export class WebSocketProgramAccountSubscriber<T>
49
49
  async subscribe(
50
50
  onChange: (accountId: PublicKey, data: T, context: Context) => void
51
51
  ): Promise<void> {
52
- if (this.listenerId || this.isUnsubscribing) {
52
+ if (this.listenerId != null || this.isUnsubscribing) {
53
53
  return;
54
54
  }
55
55
 
@@ -91,7 +91,7 @@ export class WebSocketProgramAccountSubscriber<T>
91
91
  console.log(
92
92
  `No ws data from ${this.subscriptionName} in ${this.resubTimeoutMs}ms, resubscribing`
93
93
  );
94
- await this.unsubscribe();
94
+ await this.unsubscribe(true);
95
95
  this.receivingData = false;
96
96
  await this.subscribe(this.onChange);
97
97
  }
@@ -145,12 +145,15 @@ export class WebSocketProgramAccountSubscriber<T>
145
145
  }
146
146
  }
147
147
 
148
- unsubscribe(): Promise<void> {
148
+ unsubscribe(onResub = false): Promise<void> {
149
+ if (!onResub) {
150
+ this.resubTimeoutMs = undefined;
151
+ }
149
152
  this.isUnsubscribing = true;
150
153
  clearTimeout(this.timeoutId);
151
154
  this.timeoutId = undefined;
152
155
 
153
- if (this.listenerId) {
156
+ if (this.listenerId != null) {
154
157
  const promise = this.program.provider.connection
155
158
  .removeAccountChangeListener(this.listenerId)
156
159
  .then(() => {
@@ -1181,6 +1181,19 @@ export class AdminClient extends DriftClient {
1181
1181
  });
1182
1182
  }
1183
1183
 
1184
+ public async updatePhoenixFulfillmentConfigStatus(
1185
+ phoenixFulfillmentConfig: PublicKey,
1186
+ status: SpotFulfillmentConfigStatus
1187
+ ): Promise<TransactionSignature> {
1188
+ return await this.program.rpc.phoenixFulfillmentConfigStatus(status, {
1189
+ accounts: {
1190
+ admin: this.wallet.publicKey,
1191
+ state: await this.getStatePublicKey(),
1192
+ phoenixFulfillmentConfig,
1193
+ },
1194
+ });
1195
+ }
1196
+
1184
1197
  public async updateSpotMarketExpiry(
1185
1198
  spotMarketIndex: number,
1186
1199
  expiryTs: BN
@@ -99,3 +99,7 @@ export const OPEN_ORDER_MARGIN_REQUIREMENT = QUOTE_PRECISION.div(new BN(100));
99
99
  export const DEFAULT_REVENUE_SINCE_LAST_FUNDING_SPREAD_RETREAT = new BN(
100
100
  -25
101
101
  ).mul(QUOTE_PRECISION);
102
+
103
+ export const ACCOUNT_AGE_DELETION_CUTOFF_SECONDS = 60 * 60 * 24 * 13; // 13 days
104
+ export const IDLE_TIME_SLOTS = 9000;
105
+ export const SLOT_TIME_ESTIMATE_MS = 400;