@drift-labs/sdk 2.40.0-beta.7 → 2.40.0-beta.9

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 (44) hide show
  1. package/VERSION +1 -1
  2. package/lib/accounts/types.d.ts +5 -1
  3. package/lib/accounts/webSocketAccountSubscriber.d.ts +6 -1
  4. package/lib/accounts/webSocketAccountSubscriber.js +28 -2
  5. package/lib/accounts/webSocketDriftClientAccountSubscriber.d.ts +2 -1
  6. package/lib/accounts/webSocketDriftClientAccountSubscriber.js +6 -5
  7. package/lib/accounts/webSocketProgramAccountSubscriber.d.ts +32 -0
  8. package/lib/accounts/webSocketProgramAccountSubscriber.js +99 -0
  9. package/lib/accounts/webSocketUserAccountSubscriber.d.ts +2 -1
  10. package/lib/accounts/webSocketUserAccountSubscriber.js +3 -2
  11. package/lib/accounts/webSocketUserStatsAccountSubsriber.d.ts +2 -1
  12. package/lib/accounts/webSocketUserStatsAccountSubsriber.js +3 -2
  13. package/lib/auctionSubscriber/auctionSubscriber.d.ts +3 -2
  14. package/lib/auctionSubscriber/auctionSubscriber.js +15 -7
  15. package/lib/auctionSubscriber/types.d.ts +1 -0
  16. package/lib/constants/spotMarkets.js +1 -1
  17. package/lib/driftClient.js +3 -3
  18. package/lib/driftClientConfig.d.ts +1 -0
  19. package/lib/math/superStake.d.ts +51 -4
  20. package/lib/math/superStake.js +173 -21
  21. package/lib/math/utils.d.ts +1 -0
  22. package/lib/math/utils.js +10 -1
  23. package/lib/orderSubscriber/OrderSubscriber.d.ts +1 -3
  24. package/lib/orderSubscriber/OrderSubscriber.js +4 -3
  25. package/lib/orderSubscriber/WebsocketSubscription.d.ts +4 -2
  26. package/lib/orderSubscriber/WebsocketSubscription.js +13 -12
  27. package/lib/orderSubscriber/types.d.ts +1 -0
  28. package/package.json +1 -1
  29. package/src/accounts/types.ts +8 -1
  30. package/src/accounts/webSocketAccountSubscriber.ts +36 -2
  31. package/src/accounts/webSocketDriftClientAccountSubscriber.ts +15 -5
  32. package/src/accounts/webSocketProgramAccountSubscriber.ts +152 -0
  33. package/src/accounts/webSocketUserAccountSubscriber.ts +10 -2
  34. package/src/accounts/webSocketUserStatsAccountSubsriber.ts +10 -2
  35. package/src/auctionSubscriber/auctionSubscriber.ts +30 -21
  36. package/src/auctionSubscriber/types.ts +1 -0
  37. package/src/constants/spotMarkets.ts +1 -1
  38. package/src/driftClient.ts +2 -1
  39. package/src/driftClientConfig.ts +1 -0
  40. package/src/math/superStake.ts +248 -24
  41. package/src/math/utils.ts +12 -0
  42. package/src/orderSubscriber/OrderSubscriber.ts +12 -7
  43. package/src/orderSubscriber/WebsocketSubscription.ts +33 -23
  44. package/src/orderSubscriber/types.ts +1 -0
@@ -3,17 +3,44 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.calculateEstimatedSuperStakeLiquidationPrice = exports.calculateSolEarned = exports.findBestSuperStakeIxs = void 0;
6
+ exports.calculateEstimatedSuperStakeLiquidationPrice = exports.calculateSolEarned = exports.fetchJitoSolMetrics = exports.findBestJitoSolSuperStakeIxs = exports.findBestMSolSuperStakeIxs = exports.findBestSuperStakeIxs = void 0;
7
7
  const web3_js_1 = require("@solana/web3.js");
8
8
  const marinade_1 = require("../marinade");
9
9
  const anchor_1 = require("@coral-xyz/anchor");
10
10
  const types_1 = require("../types");
11
11
  const numericConstants_1 = require("../constants/numericConstants");
12
12
  const node_fetch_1 = __importDefault(require("node-fetch"));
13
- async function findBestSuperStakeIxs({ amount, jupiterClient, driftClient, userAccountPublicKey, marinadePrice, forceMarinade, onlyDirectRoutes, }) {
14
- if (!marinadePrice) {
13
+ const utils_1 = require("./utils");
14
+ async function findBestSuperStakeIxs({ marketIndex, amount, jupiterClient, driftClient, userAccountPublicKey, price, forceMarinade, onlyDirectRoutes, }) {
15
+ if (marketIndex === 2) {
16
+ return findBestMSolSuperStakeIxs({
17
+ amount,
18
+ jupiterClient,
19
+ driftClient,
20
+ userAccountPublicKey,
21
+ price,
22
+ forceMarinade,
23
+ onlyDirectRoutes,
24
+ });
25
+ }
26
+ else if (marketIndex === 6) {
27
+ return findBestJitoSolSuperStakeIxs({
28
+ amount,
29
+ jupiterClient,
30
+ driftClient,
31
+ userAccountPublicKey,
32
+ onlyDirectRoutes,
33
+ });
34
+ }
35
+ else {
36
+ throw new Error(`Unsupported superstake market index: ${marketIndex}`);
37
+ }
38
+ }
39
+ exports.findBestSuperStakeIxs = findBestSuperStakeIxs;
40
+ async function findBestMSolSuperStakeIxs({ amount, jupiterClient, driftClient, userAccountPublicKey, price, forceMarinade, onlyDirectRoutes, }) {
41
+ if (!price) {
15
42
  const marinadeProgram = (0, marinade_1.getMarinadeFinanceProgram)(driftClient.provider);
16
- marinadePrice = await (0, marinade_1.getMarinadeMSolPrice)(marinadeProgram);
43
+ price = await (0, marinade_1.getMarinadeMSolPrice)(marinadeProgram);
17
44
  }
18
45
  const solMint = driftClient.getSpotMarketAccount(1).mint;
19
46
  const mSOLMint = driftClient.getSpotMarketAccount(2).mint;
@@ -32,7 +59,7 @@ async function findBestSuperStakeIxs({ amount, jupiterClient, driftClient, userA
32
59
  catch (e) {
33
60
  console.error('Error getting jupiter price', e);
34
61
  }
35
- if (!jupiterPrice || marinadePrice <= jupiterPrice || forceMarinade) {
62
+ if (!jupiterPrice || price <= jupiterPrice || forceMarinade) {
36
63
  const ixs = await driftClient.getStakeForMSOLIx({
37
64
  amount,
38
65
  userAccountPublicKey,
@@ -41,7 +68,7 @@ async function findBestSuperStakeIxs({ amount, jupiterClient, driftClient, userA
41
68
  method: 'marinade',
42
69
  ixs,
43
70
  lookupTables: [],
44
- price: marinadePrice,
71
+ price: price,
45
72
  };
46
73
  }
47
74
  else {
@@ -61,25 +88,137 @@ async function findBestSuperStakeIxs({ amount, jupiterClient, driftClient, userA
61
88
  };
62
89
  }
63
90
  }
64
- exports.findBestSuperStakeIxs = findBestSuperStakeIxs;
65
- async function calculateSolEarned({ user, depositRecords, }) {
91
+ exports.findBestMSolSuperStakeIxs = findBestMSolSuperStakeIxs;
92
+ async function findBestJitoSolSuperStakeIxs({ amount, jupiterClient, driftClient, userAccountPublicKey, onlyDirectRoutes, }) {
93
+ const solMint = driftClient.getSpotMarketAccount(1).mint;
94
+ const JitoSolMint = driftClient.getSpotMarketAccount(6).mint;
95
+ let jupiterPrice;
96
+ let bestRoute;
97
+ try {
98
+ const jupiterRoutes = await jupiterClient.getRoutes({
99
+ inputMint: solMint,
100
+ outputMint: JitoSolMint,
101
+ amount,
102
+ onlyDirectRoutes,
103
+ });
104
+ bestRoute = jupiterRoutes[0];
105
+ jupiterPrice = bestRoute.inAmount / bestRoute.outAmount;
106
+ }
107
+ catch (e) {
108
+ console.error('Error getting jupiter price', e);
109
+ throw e;
110
+ }
111
+ const { ixs, lookupTables } = await driftClient.getJupiterSwapIx({
112
+ inMarketIndex: 1,
113
+ outMarketIndex: 6,
114
+ route: bestRoute,
115
+ jupiterClient,
116
+ amount,
117
+ userAccountPublicKey,
118
+ });
119
+ return {
120
+ method: 'jupiter',
121
+ ixs,
122
+ lookupTables,
123
+ price: jupiterPrice,
124
+ };
125
+ }
126
+ exports.findBestJitoSolSuperStakeIxs = findBestJitoSolSuperStakeIxs;
127
+ const JITO_SOL_START_DATE = '2022-10-31T00:00:00Z';
128
+ async function fetchJitoSolMetrics() {
129
+ const res = await (0, node_fetch_1.default)('https://kobe.mainnet.jito.network/', {
130
+ body: JSON.stringify({
131
+ operationName: 'QueryRoot',
132
+ variables: {
133
+ request: {
134
+ bucketType: 'DAILY',
135
+ rangeFilter: {
136
+ start: JITO_SOL_START_DATE,
137
+ end: new Date().toISOString(),
138
+ },
139
+ sortBy: {
140
+ order: 'ASC',
141
+ field: 'BLOCK_TIME',
142
+ },
143
+ },
144
+ },
145
+ query: `
146
+ query QueryRoot($request: GetStakePoolStatsRequest!) {
147
+ getStakePoolStats(req: $request) {
148
+ tvl {
149
+ data
150
+ date
151
+ }
152
+ apy {
153
+ data
154
+ date
155
+ }
156
+ supply {
157
+ data
158
+ date
159
+ }
160
+ }
161
+ }
162
+ `,
163
+ }),
164
+ method: 'POST',
165
+ });
166
+ const data = await res.json();
167
+ return data;
168
+ }
169
+ exports.fetchJitoSolMetrics = fetchJitoSolMetrics;
170
+ const getJitoSolHistoricalPriceMap = async (timestamps) => {
171
+ try {
172
+ const data = await fetchJitoSolMetrics();
173
+ const jitoSolHistoricalPriceMap = new Map();
174
+ const jitoSolHistoricalPriceInSol = [];
175
+ for (let i = 0; i < data.data.getStakePoolStats.supply.length; i++) {
176
+ const priceInSol = data.data.getStakePoolStats.tvl[i].data /
177
+ new anchor_1.BN(10).pow(numericConstants_1.NINE) /
178
+ data.data.getStakePoolStats.supply[i].data;
179
+ jitoSolHistoricalPriceInSol.push({
180
+ price: priceInSol,
181
+ ts: data.data.getStakePoolStats.tvl[i].date,
182
+ });
183
+ }
184
+ for (const timestamp of timestamps) {
185
+ const date = new Date(timestamp * 1000);
186
+ const dateString = date.toISOString();
187
+ const price = jitoSolHistoricalPriceInSol.find((p) => (0, utils_1.checkSameDate)(p.ts, dateString));
188
+ if (price) {
189
+ jitoSolHistoricalPriceMap.set(timestamp, price.price);
190
+ }
191
+ }
192
+ return jitoSolHistoricalPriceMap;
193
+ }
194
+ catch (err) {
195
+ console.error(err);
196
+ return undefined;
197
+ }
198
+ };
199
+ async function calculateSolEarned({ marketIndex, user, depositRecords, }) {
66
200
  const now = Date.now() / 1000;
67
201
  const timestamps = [
68
202
  now,
69
203
  ...depositRecords.map((r) => r.ts.toNumber()),
70
204
  ];
71
- const msolRatios = new Map();
72
- const getPrice = async (timestamp) => {
205
+ let lstRatios = new Map();
206
+ const getMsolPrice = async (timestamp) => {
73
207
  const date = new Date(timestamp * 1000); // Convert Unix timestamp to milliseconds
74
208
  const swaggerApiDateTime = date.toISOString(); // Format date as swagger API date-time
75
209
  const url = `https://api.marinade.finance/msol/price_sol?time=${swaggerApiDateTime}`;
76
210
  const response = await (0, node_fetch_1.default)(url);
77
211
  if (response.status === 200) {
78
212
  const data = await response.json();
79
- msolRatios.set(timestamp, data);
213
+ lstRatios.set(timestamp, data);
80
214
  }
81
215
  };
82
- await Promise.all(timestamps.map(getPrice));
216
+ if (marketIndex === 2) {
217
+ await Promise.all(timestamps.map(getMsolPrice));
218
+ }
219
+ else if (marketIndex === 6) {
220
+ lstRatios = await getJitoSolHistoricalPriceMap(timestamps);
221
+ }
83
222
  let solEarned = numericConstants_1.ZERO;
84
223
  for (const record of depositRecords) {
85
224
  if (record.marketIndex === 1) {
@@ -91,7 +230,7 @@ async function calculateSolEarned({ user, depositRecords, }) {
91
230
  }
92
231
  }
93
232
  else if (record.marketIndex === 2) {
94
- const msolRatio = msolRatios.get(record.ts.toNumber());
233
+ const msolRatio = lstRatios.get(record.ts.toNumber());
95
234
  const msolRatioBN = new anchor_1.BN(msolRatio * web3_js_1.LAMPORTS_PER_SOL);
96
235
  const solAmount = record.amount.mul(msolRatioBN).div(numericConstants_1.LAMPORTS_PRECISION);
97
236
  if ((0, types_1.isVariant)(record.direction, 'deposit')) {
@@ -101,21 +240,34 @@ async function calculateSolEarned({ user, depositRecords, }) {
101
240
  solEarned = solEarned.add(solAmount);
102
241
  }
103
242
  }
243
+ else if (record.marketIndex === 6) {
244
+ const jitoSolRatio = lstRatios.get(record.ts.toNumber());
245
+ const jitoSolRatioBN = new anchor_1.BN(jitoSolRatio * web3_js_1.LAMPORTS_PER_SOL);
246
+ const solAmount = record.amount
247
+ .mul(jitoSolRatioBN)
248
+ .div(numericConstants_1.LAMPORTS_PRECISION);
249
+ if ((0, types_1.isVariant)(record.direction, 'deposit')) {
250
+ solEarned = solEarned.sub(solAmount);
251
+ }
252
+ else {
253
+ solEarned = solEarned.add(solAmount);
254
+ }
255
+ }
104
256
  }
105
- const currentMSOLTokenAmount = await user.getTokenAmount(2);
257
+ const currentLstTokenAmount = await user.getTokenAmount(marketIndex);
258
+ const currentLstRatio = lstRatios.get(now);
259
+ const currentLstRatioBN = new anchor_1.BN(currentLstRatio * web3_js_1.LAMPORTS_PER_SOL);
260
+ solEarned = solEarned.add(currentLstTokenAmount.mul(currentLstRatioBN).div(numericConstants_1.LAMPORTS_PRECISION));
106
261
  const currentSOLTokenAmount = await user.getTokenAmount(1);
107
- const currentMSOLRatio = msolRatios.get(now);
108
- const currentMSOLRatioBN = new anchor_1.BN(currentMSOLRatio * web3_js_1.LAMPORTS_PER_SOL);
109
- solEarned = solEarned.add(currentMSOLTokenAmount.mul(currentMSOLRatioBN).div(numericConstants_1.LAMPORTS_PRECISION));
110
262
  solEarned = solEarned.add(currentSOLTokenAmount);
111
263
  return solEarned;
112
264
  }
113
265
  exports.calculateSolEarned = calculateSolEarned;
114
- // calculate estimated liquidation price (in mSOL/SOL) based on target amounts
115
- function calculateEstimatedSuperStakeLiquidationPrice(msolDepositAmount, msolMaintenanceAssetWeight, solBorrowAmount, solMaintenanceLiabilityWeight, msolPriceRatio) {
266
+ // calculate estimated liquidation price (in LST/SOL) based on target amounts
267
+ function calculateEstimatedSuperStakeLiquidationPrice(lstDepositAmount, lstMaintenanceAssetWeight, solBorrowAmount, solMaintenanceLiabilityWeight, lstPriceRatio) {
116
268
  const liquidationDivergence = (solMaintenanceLiabilityWeight * solBorrowAmount) /
117
- (msolMaintenanceAssetWeight * msolDepositAmount * msolPriceRatio);
118
- const liquidationPrice = msolPriceRatio * liquidationDivergence;
269
+ (lstMaintenanceAssetWeight * lstDepositAmount * lstPriceRatio);
270
+ const liquidationPrice = lstPriceRatio * liquidationDivergence;
119
271
  return liquidationPrice;
120
272
  }
121
273
  exports.calculateEstimatedSuperStakeLiquidationPrice = calculateEstimatedSuperStakeLiquidationPrice;
@@ -12,3 +12,4 @@ export declare const sigNum: (x: BN) => BN;
12
12
  * @returns: timeRemainingUntilUpdate (in seconds)
13
13
  */
14
14
  export declare function timeRemainingUntilUpdate(now: BN, lastUpdateTs: BN, updatePeriod: BN): BN;
15
+ export declare const checkSameDate: (dateString1: string, dateString2: string) => boolean;
package/lib/math/utils.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.timeRemainingUntilUpdate = exports.sigNum = exports.divCeil = exports.squareRootBN = exports.clampBN = void 0;
3
+ exports.checkSameDate = exports.timeRemainingUntilUpdate = exports.sigNum = exports.divCeil = exports.squareRootBN = exports.clampBN = void 0;
4
4
  const __1 = require("../");
5
5
  function clampBN(x, min, max) {
6
6
  return __1.BN.max(min, __1.BN.min(x, max));
@@ -76,3 +76,12 @@ function timeRemainingUntilUpdate(now, lastUpdateTs, updatePeriod) {
76
76
  return timeRemainingUntilUpdate;
77
77
  }
78
78
  exports.timeRemainingUntilUpdate = timeRemainingUntilUpdate;
79
+ const checkSameDate = (dateString1, dateString2) => {
80
+ const date1 = new Date(dateString1);
81
+ const date2 = new Date(dateString2);
82
+ const isSameDate = date1.getDate() === date2.getDate() &&
83
+ date1.getMonth() === date2.getMonth() &&
84
+ date1.getFullYear() === date2.getFullYear();
85
+ return isSameDate;
86
+ };
87
+ exports.checkSameDate = checkSameDate;
@@ -1,8 +1,6 @@
1
1
  /// <reference types="node" />
2
- /// <reference types="node" />
3
2
  import { DriftClient } from '../driftClient';
4
3
  import { UserAccount } from '../types';
5
- import { Buffer } from 'buffer';
6
4
  import { DLOB } from '../dlob/DLOB';
7
5
  import { OrderSubscriberConfig, OrderSubscriberEvents } from './types';
8
6
  import { PollingSubscription } from './PollingSubscription';
@@ -22,7 +20,7 @@ export declare class OrderSubscriber {
22
20
  constructor(config: OrderSubscriberConfig);
23
21
  subscribe(): Promise<void>;
24
22
  fetch(): Promise<void>;
25
- tryUpdateUserAccount(key: string, buffer: Buffer, slot: number): void;
23
+ tryUpdateUserAccount(key: string, userAccount: UserAccount, slot: number): void;
26
24
  getDLOB(slot: number): Promise<DLOB>;
27
25
  unsubscribe(): Promise<void>;
28
26
  }
@@ -22,6 +22,7 @@ class OrderSubscriber {
22
22
  this.subscription = new WebsocketSubscription_1.WebsocketSubscription({
23
23
  orderSubscriber: this,
24
24
  skipInitialLoad: config.subscriptionConfig.skipInitialLoad,
25
+ resubTimeoutMs: config.subscriptionConfig.resubTimeoutMs,
25
26
  });
26
27
  }
27
28
  this.eventEmitter = new events_1.EventEmitter();
@@ -57,7 +58,8 @@ class OrderSubscriber {
57
58
  // @ts-ignore
58
59
  const buffer = buffer_1.Buffer.from(programAccount.account.data[0], programAccount.account.data[1]);
59
60
  programAccountSet.add(key);
60
- this.tryUpdateUserAccount(key, buffer, slot);
61
+ const userAccount = this.driftClient.program.account.user.coder.accounts.decode('User', buffer);
62
+ this.tryUpdateUserAccount(key, userAccount, slot);
61
63
  }
62
64
  for (const key of this.usersAccounts.keys()) {
63
65
  if (!programAccountSet.has(key)) {
@@ -73,10 +75,9 @@ class OrderSubscriber {
73
75
  this.fetchPromise = undefined;
74
76
  }
75
77
  }
76
- tryUpdateUserAccount(key, buffer, slot) {
78
+ tryUpdateUserAccount(key, userAccount, slot) {
77
79
  const slotAndUserAccount = this.usersAccounts.get(key);
78
80
  if (!slotAndUserAccount || slotAndUserAccount.slot < slot) {
79
- const userAccount = this.driftClient.program.account.user.coder.accounts.decode('User', buffer);
80
81
  const newOrders = userAccount.orders.filter((order) => {
81
82
  var _a;
82
83
  return order.slot.toNumber() > ((_a = slotAndUserAccount === null || slotAndUserAccount === void 0 ? void 0 : slotAndUserAccount.slot) !== null && _a !== void 0 ? _a : 0) &&
@@ -2,10 +2,12 @@ import { OrderSubscriber } from './OrderSubscriber';
2
2
  export declare class WebsocketSubscription {
3
3
  private orderSubscriber;
4
4
  private skipInitialLoad;
5
- private websocketId;
6
- constructor({ orderSubscriber, skipInitialLoad, }: {
5
+ private resubTimeoutMs?;
6
+ private subscriber;
7
+ constructor({ orderSubscriber, skipInitialLoad, resubTimeoutMs, }: {
7
8
  orderSubscriber: OrderSubscriber;
8
9
  skipInitialLoad?: boolean;
10
+ resubTimeoutMs?: number;
9
11
  });
10
12
  subscribe(): Promise<void>;
11
13
  unsubscribe(): Promise<void>;
@@ -2,29 +2,30 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.WebsocketSubscription = void 0;
4
4
  const memcmp_1 = require("../memcmp");
5
+ const webSocketProgramAccountSubscriber_1 = require("../accounts/webSocketProgramAccountSubscriber");
5
6
  class WebsocketSubscription {
6
- constructor({ orderSubscriber, skipInitialLoad = false, }) {
7
+ constructor({ orderSubscriber, skipInitialLoad = false, resubTimeoutMs, }) {
7
8
  this.orderSubscriber = orderSubscriber;
8
9
  this.skipInitialLoad = skipInitialLoad;
10
+ this.resubTimeoutMs = resubTimeoutMs;
9
11
  }
10
12
  async subscribe() {
11
- if (this.websocketId) {
12
- return;
13
+ if (!this.subscriber) {
14
+ this.subscriber = new webSocketProgramAccountSubscriber_1.WebSocketProgramAccountSubscriber('OrderSubscriber', 'User', this.orderSubscriber.driftClient.program, this.orderSubscriber.driftClient.program.account.user.coder.accounts.decode.bind(this.orderSubscriber.driftClient.program.account.user.coder.accounts), {
15
+ filters: [(0, memcmp_1.getUserFilter)(), (0, memcmp_1.getNonIdleUserFilter)()],
16
+ commitment: this.orderSubscriber.driftClient.opts.commitment,
17
+ }, this.resubTimeoutMs);
13
18
  }
14
- this.websocketId =
15
- this.orderSubscriber.driftClient.connection.onProgramAccountChange(this.orderSubscriber.driftClient.program.programId, (keyAccountInfo, context) => {
16
- const userKey = keyAccountInfo.accountId.toBase58();
17
- this.orderSubscriber.tryUpdateUserAccount(userKey, keyAccountInfo.accountInfo.data, context.slot);
18
- }, this.orderSubscriber.driftClient.opts.commitment, [(0, memcmp_1.getUserFilter)(), (0, memcmp_1.getNonIdleUserFilter)()]);
19
+ await this.subscriber.subscribe((accountId, account, context) => {
20
+ const userKey = accountId.toBase58();
21
+ this.orderSubscriber.tryUpdateUserAccount(userKey, account, context.slot);
22
+ });
19
23
  if (!this.skipInitialLoad) {
20
24
  await this.orderSubscriber.fetch();
21
25
  }
22
26
  }
23
27
  async unsubscribe() {
24
- if (this.websocketId) {
25
- await this.orderSubscriber.driftClient.connection.removeProgramAccountChangeListener(this.websocketId);
26
- this.websocketId = undefined;
27
- }
28
+ this.subscriber.unsubscribe();
28
29
  }
29
30
  }
30
31
  exports.WebsocketSubscription = WebsocketSubscription;
@@ -9,6 +9,7 @@ export type OrderSubscriberConfig = {
9
9
  } | {
10
10
  type: 'websocket';
11
11
  skipInitialLoad?: boolean;
12
+ resubTimeoutMs?: number;
12
13
  };
13
14
  };
14
15
  export interface OrderSubscriberEvents {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@drift-labs/sdk",
3
- "version": "2.40.0-beta.7",
3
+ "version": "2.40.0-beta.9",
4
4
  "main": "lib/index.js",
5
5
  "types": "lib/index.d.ts",
6
6
  "author": "crispheaney",
@@ -8,7 +8,7 @@ import {
8
8
  } from '../types';
9
9
  import StrictEventEmitter from 'strict-event-emitter-types';
10
10
  import { EventEmitter } from 'events';
11
- import { PublicKey } from '@solana/web3.js';
11
+ import { Context, PublicKey } from '@solana/web3.js';
12
12
  import { Account } from '@solana/spl-token';
13
13
  import { OracleInfo, OraclePriceData } from '..';
14
14
 
@@ -21,6 +21,13 @@ export interface AccountSubscriber<T> {
21
21
  setData(userAccount: T, slot?: number): void;
22
22
  }
23
23
 
24
+ export interface ProgramAccountSubscriber<T> {
25
+ subscribe(
26
+ onChange: (accountId: PublicKey, data: T, context: Context) => void
27
+ ): Promise<void>;
28
+ unsubscribe(): Promise<void>;
29
+ }
30
+
24
31
  export class NotSubscribedError extends Error {
25
32
  name = 'NotSubscribedError';
26
33
  }
@@ -13,17 +13,24 @@ export class WebSocketAccountSubscriber<T> implements AccountSubscriber<T> {
13
13
  decodeBufferFn: (buffer: Buffer) => T;
14
14
  onChange: (data: T) => void;
15
15
  listenerId?: number;
16
+ resubTimeoutMs?: number;
17
+ timeoutId?: NodeJS.Timeout;
18
+
19
+ receivingData: boolean;
16
20
 
17
21
  public constructor(
18
22
  accountName: string,
19
23
  program: Program,
20
24
  accountPublicKey: PublicKey,
21
- decodeBuffer?: (buffer: Buffer) => T
25
+ decodeBuffer?: (buffer: Buffer) => T,
26
+ resubTimeoutMs?: number
22
27
  ) {
23
28
  this.accountName = accountName;
24
29
  this.program = program;
25
30
  this.accountPublicKey = accountPublicKey;
26
31
  this.decodeBufferFn = decodeBuffer;
32
+ this.resubTimeoutMs = resubTimeoutMs;
33
+ this.receivingData = false;
27
34
  }
28
35
 
29
36
  async subscribe(onChange: (data: T) => void): Promise<void> {
@@ -39,10 +46,21 @@ export class WebSocketAccountSubscriber<T> implements AccountSubscriber<T> {
39
46
  this.listenerId = this.program.provider.connection.onAccountChange(
40
47
  this.accountPublicKey,
41
48
  (accountInfo, context) => {
42
- this.handleRpcResponse(context, accountInfo);
49
+ if (this.resubTimeoutMs) {
50
+ this.receivingData = true;
51
+ clearTimeout(this.timeoutId);
52
+ this.handleRpcResponse(context, accountInfo);
53
+ this.setTimeout();
54
+ } else {
55
+ this.handleRpcResponse(context, accountInfo);
56
+ }
43
57
  },
44
58
  (this.program.provider as AnchorProvider).opts.commitment
45
59
  );
60
+
61
+ if (this.resubTimeoutMs) {
62
+ this.setTimeout();
63
+ }
46
64
  }
47
65
 
48
66
  setData(data: T, slot?: number): void {
@@ -57,6 +75,22 @@ export class WebSocketAccountSubscriber<T> implements AccountSubscriber<T> {
57
75
  };
58
76
  }
59
77
 
78
+ private setTimeout(): void {
79
+ if (!this.onChange) {
80
+ throw new Error('onChange callback function must be set');
81
+ }
82
+ this.timeoutId = setTimeout(async () => {
83
+ if (this.receivingData) {
84
+ console.log(
85
+ `No ws data from ${this.accountName} in ${this.resubTimeoutMs}ms, resubscribing`
86
+ );
87
+ await this.unsubscribe();
88
+ this.receivingData = false;
89
+ await this.subscribe(this.onChange);
90
+ }
91
+ }, this.resubTimeoutMs);
92
+ }
93
+
60
94
  async fetch(): Promise<void> {
61
95
  const rpcResponse =
62
96
  await this.program.provider.connection.getAccountInfoAndContext(
@@ -31,6 +31,7 @@ export class WebSocketDriftClientAccountSubscriber
31
31
  oracleInfos: OracleInfo[];
32
32
  oracleClientCache = new OracleClientCache();
33
33
 
34
+ resubTimeoutMs?: number;
34
35
  shouldFindAllMarketsAndOracles: boolean;
35
36
 
36
37
  eventEmitter: StrictEventEmitter<EventEmitter, DriftClientAccountEvents>;
@@ -54,7 +55,8 @@ export class WebSocketDriftClientAccountSubscriber
54
55
  perpMarketIndexes: number[],
55
56
  spotMarketIndexes: number[],
56
57
  oracleInfos: OracleInfo[],
57
- shouldFindAllMarketsAndOracles: boolean
58
+ shouldFindAllMarketsAndOracles: boolean,
59
+ resubTimeoutMs?: number
58
60
  ) {
59
61
  this.isSubscribed = false;
60
62
  this.program = program;
@@ -63,6 +65,7 @@ export class WebSocketDriftClientAccountSubscriber
63
65
  this.spotMarketIndexes = spotMarketIndexes;
64
66
  this.oracleInfos = oracleInfos;
65
67
  this.shouldFindAllMarketsAndOracles = shouldFindAllMarketsAndOracles;
68
+ this.resubTimeoutMs = resubTimeoutMs;
66
69
  }
67
70
 
68
71
  public async subscribe(): Promise<boolean> {
@@ -96,7 +99,9 @@ export class WebSocketDriftClientAccountSubscriber
96
99
  this.stateAccountSubscriber = new WebSocketAccountSubscriber(
97
100
  'state',
98
101
  this.program,
99
- statePublicKey
102
+ statePublicKey,
103
+ undefined,
104
+ this.resubTimeoutMs
100
105
  );
101
106
  await this.stateAccountSubscriber.subscribe((data: StateAccount) => {
102
107
  this.eventEmitter.emit('stateAccountUpdate', data);
@@ -136,7 +141,9 @@ export class WebSocketDriftClientAccountSubscriber
136
141
  const accountSubscriber = new WebSocketAccountSubscriber<PerpMarketAccount>(
137
142
  'perpMarket',
138
143
  this.program,
139
- perpMarketPublicKey
144
+ perpMarketPublicKey,
145
+ undefined,
146
+ this.resubTimeoutMs
140
147
  );
141
148
  await accountSubscriber.subscribe((data: PerpMarketAccount) => {
142
149
  this.eventEmitter.emit('perpMarketAccountUpdate', data);
@@ -161,7 +168,9 @@ export class WebSocketDriftClientAccountSubscriber
161
168
  const accountSubscriber = new WebSocketAccountSubscriber<SpotMarketAccount>(
162
169
  'spotMarket',
163
170
  this.program,
164
- marketPublicKey
171
+ marketPublicKey,
172
+ undefined,
173
+ this.resubTimeoutMs
165
174
  );
166
175
  await accountSubscriber.subscribe((data: SpotMarketAccount) => {
167
176
  this.eventEmitter.emit('spotMarketAccountUpdate', data);
@@ -192,7 +201,8 @@ export class WebSocketDriftClientAccountSubscriber
192
201
  oracleInfo.publicKey,
193
202
  (buffer: Buffer) => {
194
203
  return client.getOraclePriceDataFromBuffer(buffer);
195
- }
204
+ },
205
+ this.resubTimeoutMs
196
206
  );
197
207
 
198
208
  await accountSubscriber.subscribe((data: OraclePriceData) => {