@drift-labs/sdk 2.49.0-beta.9 → 2.52.0-beta.1

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 (75) hide show
  1. package/VERSION +1 -1
  2. package/lib/accounts/{mockUserAccountSubscriber.d.ts → basicUserAccountSubscriber.d.ts} +6 -2
  3. package/lib/accounts/{mockUserAccountSubscriber.js → basicUserAccountSubscriber.js} +13 -6
  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/adminClient.d.ts +2 -0
  9. package/lib/adminClient.js +17 -0
  10. package/lib/constants/numericConstants.d.ts +3 -0
  11. package/lib/constants/numericConstants.js +4 -1
  12. package/lib/constants/perpMarkets.js +40 -0
  13. package/lib/constants/spotMarkets.js +10 -0
  14. package/lib/decode/user.d.ts +3 -0
  15. package/lib/decode/user.js +329 -0
  16. package/lib/driftClient.d.ts +6 -1
  17. package/lib/driftClient.js +35 -10
  18. package/lib/examples/loadDlob.js +10 -5
  19. package/lib/idl/drift.json +32 -2
  20. package/lib/index.d.ts +3 -1
  21. package/lib/index.js +3 -1
  22. package/lib/math/state.d.ts +5 -0
  23. package/lib/math/state.js +27 -0
  24. package/lib/math/superStake.d.ts +43 -0
  25. package/lib/math/superStake.js +64 -22
  26. package/lib/orderSubscriber/OrderSubscriber.d.ts +3 -0
  27. package/lib/orderSubscriber/OrderSubscriber.js +17 -3
  28. package/lib/orderSubscriber/WebsocketSubscription.d.ts +1 -1
  29. package/lib/orderSubscriber/WebsocketSubscription.js +8 -6
  30. package/lib/orderSubscriber/types.d.ts +4 -1
  31. package/lib/types.d.ts +2 -1
  32. package/lib/user.d.ts +2 -1
  33. package/lib/user.js +13 -5
  34. package/lib/userMap/PollingSubscription.d.ts +15 -0
  35. package/lib/userMap/PollingSubscription.js +28 -0
  36. package/lib/userMap/WebsocketSubscription.d.ts +23 -0
  37. package/lib/userMap/WebsocketSubscription.js +41 -0
  38. package/lib/userMap/userMap.d.ts +16 -18
  39. package/lib/userMap/userMap.js +73 -34
  40. package/lib/userMap/userMapConfig.d.ts +21 -0
  41. package/lib/userMap/userMapConfig.js +2 -0
  42. package/lib/userStats.d.ts +1 -0
  43. package/lib/userStats.js +3 -0
  44. package/package.json +2 -1
  45. package/src/accounts/{mockUserAccountSubscriber.ts → basicUserAccountSubscriber.ts} +12 -6
  46. package/src/accounts/bulkAccountLoader.ts +10 -3
  47. package/src/accounts/oneShotUserAccountSubscriber.ts +64 -0
  48. package/src/accounts/pollingInsuranceFundStakeAccountSubscriber.ts +1 -0
  49. package/src/adminClient.ts +27 -0
  50. package/src/constants/numericConstants.ts +4 -0
  51. package/src/constants/perpMarkets.ts +40 -0
  52. package/src/constants/spotMarkets.ts +10 -0
  53. package/src/decode/user.ts +358 -0
  54. package/src/driftClient.ts +62 -15
  55. package/src/examples/loadDlob.ts +11 -6
  56. package/src/idl/drift.json +33 -3
  57. package/src/index.ts +3 -1
  58. package/src/math/state.ts +26 -0
  59. package/src/math/superStake.ts +108 -20
  60. package/src/orderSubscriber/OrderSubscriber.ts +33 -7
  61. package/src/orderSubscriber/WebsocketSubscription.ts +17 -16
  62. package/src/orderSubscriber/types.ts +15 -2
  63. package/src/types.ts +2 -1
  64. package/src/user.ts +19 -6
  65. package/src/userMap/PollingSubscription.ts +48 -0
  66. package/src/userMap/WebsocketSubscription.ts +76 -0
  67. package/src/userMap/userMap.ts +105 -70
  68. package/src/userMap/userMapConfig.ts +34 -0
  69. package/src/userStats.ts +8 -0
  70. package/tests/amm/test.ts +6 -3
  71. package/tests/decode/test.ts +266 -0
  72. package/tests/decode/userAccountBufferStrings.ts +102 -0
  73. package/tests/dlob/helpers.ts +1 -0
  74. package/tests/dlob/test.ts +10 -8
  75. package/tests/user/helpers.ts +0 -1
@@ -0,0 +1,26 @@
1
+ import { StateAccount } from '../types';
2
+ import { BN, LAMPORTS_PRECISION, PERCENTAGE_PRECISION, ZERO } from '../';
3
+
4
+ export function calculateInitUserFee(stateAccount: StateAccount): BN {
5
+ const maxInitFee = new BN(stateAccount.maxInitializeUserFee)
6
+ .mul(LAMPORTS_PRECISION)
7
+ .divn(100);
8
+ const targetUtilization = PERCENTAGE_PRECISION.muln(8).divn(10);
9
+
10
+ const accountSpaceUtilization = stateAccount.numberOfSubAccounts
11
+ .addn(1)
12
+ .mul(PERCENTAGE_PRECISION)
13
+ .div(getMaxNumberOfSubAccounts(stateAccount));
14
+
15
+ if (accountSpaceUtilization.gt(targetUtilization)) {
16
+ return maxInitFee
17
+ .mul(accountSpaceUtilization.sub(targetUtilization))
18
+ .div(PERCENTAGE_PRECISION.sub(targetUtilization));
19
+ } else {
20
+ return ZERO;
21
+ }
22
+ }
23
+
24
+ export function getMaxNumberOfSubAccounts(stateAccount: StateAccount): BN {
25
+ return new BN(stateAccount.maxNumberOfSubAccounts).muln(100);
26
+ }
@@ -14,6 +14,38 @@ import { LAMPORTS_PRECISION, ZERO } from '../constants/numericConstants';
14
14
  import fetch from 'node-fetch';
15
15
  import { checkSameDate } from './utils';
16
16
 
17
+ export type BSOL_STATS_API_RESPONSE = {
18
+ success: boolean;
19
+ stats?: {
20
+ conversion: {
21
+ bsol_to_sol: number;
22
+ sol_to_bsol: number;
23
+ };
24
+ apy: {
25
+ base: number;
26
+ blze: number;
27
+ total: number;
28
+ lending: number;
29
+ liquidity: number;
30
+ };
31
+ };
32
+ };
33
+
34
+ export type BSOL_EMISSIONS_API_RESPONSE = {
35
+ success: boolean;
36
+ emissions?: {
37
+ lend: number;
38
+ };
39
+ };
40
+
41
+ export async function fetchBSolMetrics() {
42
+ return await fetch('https://stake.solblaze.org/api/v1/stats');
43
+ }
44
+
45
+ export async function fetchBSolDriftEmissions() {
46
+ return await fetch('https://stake.solblaze.org/api/v1/drift_emissions');
47
+ }
48
+
17
49
  export async function findBestSuperStakeIxs({
18
50
  marketIndex,
19
51
  amount,
@@ -56,6 +88,16 @@ export async function findBestSuperStakeIxs({
56
88
  userAccountPublicKey,
57
89
  onlyDirectRoutes,
58
90
  });
91
+ } else if (marketIndex === 8) {
92
+ return findBestLstSuperStakeIxs({
93
+ amount,
94
+ lstMint: driftClient.getSpotMarketAccount(8).mint,
95
+ lstMarketIndex: 8,
96
+ jupiterClient,
97
+ driftClient,
98
+ userAccountPublicKey,
99
+ onlyDirectRoutes,
100
+ });
59
101
  } else {
60
102
  throw new Error(`Unsupported superstake market index: ${marketIndex}`);
61
103
  }
@@ -153,16 +195,53 @@ export async function findBestJitoSolSuperStakeIxs({
153
195
  lookupTables: AddressLookupTableAccount[];
154
196
  method: 'jupiter' | 'marinade';
155
197
  price: number;
198
+ }> {
199
+ return await findBestLstSuperStakeIxs({
200
+ amount,
201
+ jupiterClient,
202
+ driftClient,
203
+ userAccountPublicKey,
204
+ onlyDirectRoutes,
205
+ lstMint: driftClient.getSpotMarketAccount(6).mint,
206
+ lstMarketIndex: 6,
207
+ });
208
+ }
209
+
210
+ /**
211
+ * Finds best Jupiter Swap instructions for a generic lstMint
212
+ *
213
+ * Without doing any extra steps like checking if you can get a better rate by staking directly with that LST platform
214
+ */
215
+ export async function findBestLstSuperStakeIxs({
216
+ amount,
217
+ lstMint,
218
+ jupiterClient,
219
+ driftClient,
220
+ userAccountPublicKey,
221
+ onlyDirectRoutes,
222
+ lstMarketIndex,
223
+ }: {
224
+ amount: BN;
225
+ lstMint: PublicKey;
226
+ lstMarketIndex: number;
227
+ jupiterClient: JupiterClient;
228
+ driftClient: DriftClient;
229
+ userAccountPublicKey?: PublicKey;
230
+ onlyDirectRoutes?: boolean;
231
+ }): Promise<{
232
+ ixs: TransactionInstruction[];
233
+ lookupTables: AddressLookupTableAccount[];
234
+ method: 'jupiter' | 'marinade';
235
+ price: number;
156
236
  }> {
157
237
  const solMint = driftClient.getSpotMarketAccount(1).mint;
158
- const JitoSolMint = driftClient.getSpotMarketAccount(6).mint;
159
238
 
160
239
  let jupiterPrice;
161
240
  let bestRoute;
162
241
  try {
163
242
  const jupiterRoutes = await jupiterClient.getRoutes({
164
243
  inputMint: solMint,
165
- outputMint: JitoSolMint,
244
+ outputMint: lstMint,
166
245
  amount,
167
246
  onlyDirectRoutes,
168
247
  });
@@ -176,7 +255,7 @@ export async function findBestJitoSolSuperStakeIxs({
176
255
 
177
256
  const { ixs, lookupTables } = await driftClient.getJupiterSwapIx({
178
257
  inMarketIndex: 1,
179
- outMarketIndex: 6,
258
+ outMarketIndex: lstMarketIndex,
180
259
  route: bestRoute,
181
260
  jupiterClient,
182
261
  amount,
@@ -322,10 +401,27 @@ export async function calculateSolEarned({
322
401
  }
323
402
  };
324
403
 
404
+ const getBSolPrice = async (timestamps: number[]) => {
405
+ // Currently there's only one bSOL price, no timestamped data
406
+ // So just use the same price for every timestamp for now
407
+ const response = await fetchBSolMetrics();
408
+ if (response.status === 200) {
409
+ const data = (await response.json()) as BSOL_STATS_API_RESPONSE;
410
+ const bSolRatio = data?.stats?.conversion?.bsol_to_sol;
411
+ if (bSolRatio) {
412
+ timestamps.forEach((timestamp) => lstRatios.set(timestamp, bSolRatio));
413
+ }
414
+ }
415
+ };
416
+
417
+ // This block kind of assumes the record are all from the same market
418
+ // Otherwise the following code that checks the record.marketIndex would break
325
419
  if (marketIndex === 2) {
326
420
  await Promise.all(timestamps.map(getMsolPrice));
327
421
  } else if (marketIndex === 6) {
328
422
  lstRatios = await getJitoSolHistoricalPriceMap(timestamps);
423
+ } else if (marketIndex === 8) {
424
+ await getBSolPrice(timestamps);
329
425
  }
330
426
 
331
427
  let solEarned = ZERO;
@@ -336,23 +432,15 @@ export async function calculateSolEarned({
336
432
  } else {
337
433
  solEarned = solEarned.add(record.amount);
338
434
  }
339
- } else if (record.marketIndex === 2) {
340
- const msolRatio = lstRatios.get(record.ts.toNumber());
341
- const msolRatioBN = new BN(msolRatio * LAMPORTS_PER_SOL);
342
-
343
- const solAmount = record.amount.mul(msolRatioBN).div(LAMPORTS_PRECISION);
344
- if (isVariant(record.direction, 'deposit')) {
345
- solEarned = solEarned.sub(solAmount);
346
- } else {
347
- solEarned = solEarned.add(solAmount);
348
- }
349
- } else if (record.marketIndex === 6) {
350
- const jitoSolRatio = lstRatios.get(record.ts.toNumber());
351
- const jitoSolRatioBN = new BN(jitoSolRatio * LAMPORTS_PER_SOL);
352
-
353
- const solAmount = record.amount
354
- .mul(jitoSolRatioBN)
355
- .div(LAMPORTS_PRECISION);
435
+ } else if (
436
+ record.marketIndex === 2 ||
437
+ record.marketIndex === 6 ||
438
+ record.marketIndex === 8
439
+ ) {
440
+ const lstRatio = lstRatios.get(record.ts.toNumber());
441
+ const lstRatioBN = new BN(lstRatio * LAMPORTS_PER_SOL);
442
+
443
+ const solAmount = record.amount.mul(lstRatioBN).div(LAMPORTS_PRECISION);
356
444
  if (isVariant(record.direction, 'deposit')) {
357
445
  solEarned = solEarned.sub(solAmount);
358
446
  } else {
@@ -10,6 +10,7 @@ import { WebsocketSubscription } from './WebsocketSubscription';
10
10
  import StrictEventEmitter from 'strict-event-emitter-types';
11
11
  import { EventEmitter } from 'events';
12
12
  import { BN } from '../index';
13
+ import { decodeUser } from '../decode/user';
13
14
 
14
15
  export class OrderSubscriber {
15
16
  driftClient: DriftClient;
@@ -22,6 +23,7 @@ export class OrderSubscriber {
22
23
  fetchPromiseResolver: () => void;
23
24
 
24
25
  mostRecentSlot: number;
26
+ decodeFn: (name: string, data: Buffer) => UserAccount;
25
27
 
26
28
  constructor(config: OrderSubscriberConfig) {
27
29
  this.driftClient = config.driftClient;
@@ -39,6 +41,14 @@ export class OrderSubscriber {
39
41
  resubTimeoutMs: config.subscriptionConfig.resubTimeoutMs,
40
42
  });
41
43
  }
44
+ if (config.fastDecode ?? true) {
45
+ this.decodeFn = (name, data) => decodeUser(data);
46
+ } else {
47
+ this.decodeFn =
48
+ this.driftClient.program.account.user.coder.accounts.decodeUnchecked.bind(
49
+ this.driftClient.program.account.user.coder.accounts
50
+ );
51
+ }
42
52
  this.eventEmitter = new EventEmitter();
43
53
  }
44
54
 
@@ -94,12 +104,16 @@ export class OrderSubscriber {
94
104
  programAccount.account.data,
95
105
  slot
96
106
  );
107
+ // give event loop a chance to breathe
108
+ await new Promise((resolve) => setTimeout(resolve, 0));
97
109
  }
98
110
 
99
111
  for (const key of this.usersAccounts.keys()) {
100
112
  if (!programAccountSet.has(key)) {
101
113
  this.usersAccounts.delete(key);
102
114
  }
115
+ // give event loop a chance to breathe
116
+ await new Promise((resolve) => setTimeout(resolve, 0));
103
117
  }
104
118
  } catch (e) {
105
119
  console.error(e);
@@ -119,6 +133,13 @@ export class OrderSubscriber {
119
133
  this.mostRecentSlot = slot;
120
134
  }
121
135
 
136
+ this.eventEmitter.emit(
137
+ 'updateReceived',
138
+ new PublicKey(key),
139
+ slot,
140
+ dataType
141
+ );
142
+
122
143
  const slotAndUserAccount = this.usersAccounts.get(key);
123
144
  if (!slotAndUserAccount || slotAndUserAccount.slot <= slot) {
124
145
  let userAccount: UserAccount;
@@ -139,15 +160,19 @@ export class OrderSubscriber {
139
160
  return;
140
161
  }
141
162
 
142
- userAccount =
143
- this.driftClient.program.account.user.coder.accounts.decodeUnchecked(
144
- 'User',
145
- buffer
146
- ) as UserAccount;
163
+ userAccount = this.decodeFn('User', buffer) as UserAccount;
147
164
  } else {
148
165
  userAccount = data as UserAccount;
149
166
  }
150
167
 
168
+ this.eventEmitter.emit(
169
+ 'userUpdated',
170
+ userAccount,
171
+ new PublicKey(key),
172
+ slot,
173
+ dataType
174
+ );
175
+
151
176
  const newOrders = userAccount.orders.filter(
152
177
  (order) =>
153
178
  order.slot.toNumber() > (slotAndUserAccount?.slot ?? 0) &&
@@ -155,11 +180,12 @@ export class OrderSubscriber {
155
180
  );
156
181
  if (newOrders.length > 0) {
157
182
  this.eventEmitter.emit(
158
- 'onUpdate',
183
+ 'orderCreated',
159
184
  userAccount,
160
185
  newOrders,
161
186
  new PublicKey(key),
162
- slot
187
+ slot,
188
+ dataType
163
189
  );
164
190
  }
165
191
  if (userAccount.hasOpenOrder) {
@@ -10,7 +10,7 @@ export class WebsocketSubscription {
10
10
  private skipInitialLoad: boolean;
11
11
  private resubTimeoutMs?: number;
12
12
 
13
- private subscriber: WebSocketProgramAccountSubscriber<UserAccount>;
13
+ private subscriber?: WebSocketProgramAccountSubscriber<UserAccount>;
14
14
 
15
15
  constructor({
16
16
  orderSubscriber,
@@ -30,22 +30,22 @@ export class WebsocketSubscription {
30
30
  }
31
31
 
32
32
  public async subscribe(): Promise<void> {
33
- if (!this.subscriber) {
34
- this.subscriber = new WebSocketProgramAccountSubscriber<UserAccount>(
35
- 'OrderSubscriber',
36
- 'User',
37
- this.orderSubscriber.driftClient.program,
38
- this.orderSubscriber.driftClient.program.account.user.coder.accounts.decode.bind(
39
- this.orderSubscriber.driftClient.program.account.user.coder.accounts
40
- ),
41
- {
42
- filters: [getUserFilter(), getNonIdleUserFilter()],
43
- commitment: this.commitment,
44
- },
45
- this.resubTimeoutMs
46
- );
33
+ if (this.subscriber) {
34
+ return;
47
35
  }
48
36
 
37
+ this.subscriber = new WebSocketProgramAccountSubscriber<UserAccount>(
38
+ 'OrderSubscriber',
39
+ 'User',
40
+ this.orderSubscriber.driftClient.program,
41
+ this.orderSubscriber.decodeFn,
42
+ {
43
+ filters: [getUserFilter(), getNonIdleUserFilter()],
44
+ commitment: this.commitment,
45
+ },
46
+ this.resubTimeoutMs
47
+ );
48
+
49
49
  await this.subscriber.subscribe(
50
50
  (accountId: PublicKey, account: UserAccount, context: Context) => {
51
51
  const userKey = accountId.toBase58();
@@ -65,6 +65,7 @@ export class WebsocketSubscription {
65
65
 
66
66
  public async unsubscribe(): Promise<void> {
67
67
  if (!this.subscriber) return;
68
- this.subscriber.unsubscribe();
68
+ await this.subscriber.unsubscribe();
69
+ this.subscriber = undefined;
69
70
  }
70
71
  }
@@ -16,13 +16,26 @@ export type OrderSubscriberConfig = {
16
16
  resubTimeoutMs?: number;
17
17
  commitment?: Commitment;
18
18
  };
19
+ fastDecode?: boolean;
19
20
  };
20
21
 
21
22
  export interface OrderSubscriberEvents {
22
- onUpdate: (
23
+ orderCreated: (
23
24
  account: UserAccount,
24
25
  updatedOrders: Order[],
25
26
  pubkey: PublicKey,
26
- slot: number
27
+ slot: number,
28
+ dataType: 'raw' | 'decoded'
29
+ ) => void;
30
+ userUpdated: (
31
+ account: UserAccount,
32
+ pubkey: PublicKey,
33
+ slot: number,
34
+ dataType: 'raw' | 'decoded'
35
+ ) => void;
36
+ updateReceived: (
37
+ pubkey: PublicKey,
38
+ slot: number,
39
+ dataType: 'raw' | 'decoded'
27
40
  ) => void;
28
41
  }
package/src/types.ts CHANGED
@@ -552,6 +552,7 @@ export type StateAccount = {
552
552
  lpCooldownTime: BN;
553
553
  initialPctToLiquidate: number;
554
554
  liquidationDuration: number;
555
+ maxInitializeUserFee: number;
555
556
  };
556
557
 
557
558
  export type PerpMarketAccount = {
@@ -869,8 +870,8 @@ export type Order = {
869
870
  marketIndex: number;
870
871
  price: BN;
871
872
  baseAssetAmount: BN;
872
- baseAssetAmountFilled: BN;
873
873
  quoteAssetAmount: BN;
874
+ baseAssetAmountFilled: BN;
874
875
  quoteAssetAmountFilled: BN;
875
876
  direction: PositionDirection;
876
877
  reduceOnly: boolean;
package/src/user.ts CHANGED
@@ -422,7 +422,8 @@ export class User {
422
422
  public getPerpPositionWithLPSettle(
423
423
  marketIndex: number,
424
424
  originalPosition?: PerpPosition,
425
- burnLpShares = false
425
+ burnLpShares = false,
426
+ includeRemainderInBaseAmount = false
426
427
  ): [PerpPosition, BN, BN] {
427
428
  originalPosition =
428
429
  originalPosition ??
@@ -600,7 +601,16 @@ export class User {
600
601
  position.lastCumulativeFundingRate = ZERO;
601
602
  }
602
603
 
603
- return [position, remainderBaa, pnl];
604
+ const remainderBeforeRemoval = new BN(position.remainderBaseAssetAmount);
605
+
606
+ if (includeRemainderInBaseAmount) {
607
+ position.baseAssetAmount = position.baseAssetAmount.add(
608
+ remainderBeforeRemoval
609
+ );
610
+ position.remainderBaseAssetAmount = 0;
611
+ }
612
+
613
+ return [position, remainderBeforeRemoval, pnl];
604
614
  }
605
615
 
606
616
  /**
@@ -1559,16 +1569,19 @@ export class User {
1559
1569
  );
1560
1570
  }
1561
1571
 
1572
+ getNetUsdValue(): BN {
1573
+ const netSpotValue = this.getNetSpotMarketValue();
1574
+ const unrealizedPnl = this.getUnrealizedPNL(true, undefined, undefined);
1575
+ return netSpotValue.add(unrealizedPnl);
1576
+ }
1577
+
1562
1578
  /**
1563
1579
  * Calculates the all time P&L of the user.
1564
1580
  *
1565
1581
  * Net withdraws + Net spot market value + Net unrealized P&L -
1566
1582
  */
1567
1583
  getTotalAllTimePnl(): BN {
1568
- const netBankValue = this.getNetSpotMarketValue();
1569
- const unrealizedPnl = this.getUnrealizedPNL(true, undefined, undefined);
1570
-
1571
- const netUsdValue = netBankValue.add(unrealizedPnl);
1584
+ const netUsdValue = this.getNetUsdValue();
1572
1585
  const totalDeposits = this.getUserAccount().totalDeposits;
1573
1586
  const totalWithdraws = this.getUserAccount().totalWithdraws;
1574
1587
 
@@ -0,0 +1,48 @@
1
+ import { UserMap } from './userMap';
2
+
3
+ export class PollingSubscription {
4
+ private userMap: UserMap;
5
+ private frequency: number;
6
+ private skipInitialLoad: boolean;
7
+
8
+ intervalId?: ReturnType<typeof setTimeout>;
9
+
10
+ constructor({
11
+ userMap,
12
+ frequency,
13
+ skipInitialLoad = false,
14
+ }: {
15
+ userMap: UserMap;
16
+ frequency: number;
17
+ skipInitialLoad?: boolean;
18
+ includeIdle?: boolean;
19
+ }) {
20
+ this.userMap = userMap;
21
+ this.frequency = frequency;
22
+ this.skipInitialLoad = skipInitialLoad;
23
+ }
24
+
25
+ public async subscribe(): Promise<void> {
26
+ if (this.intervalId) {
27
+ return;
28
+ }
29
+
30
+ if (this.frequency > 0) {
31
+ this.intervalId = setInterval(
32
+ this.userMap.sync.bind(this.userMap),
33
+ this.frequency
34
+ );
35
+ }
36
+
37
+ if (!this.skipInitialLoad) {
38
+ await this.userMap.sync();
39
+ }
40
+ }
41
+
42
+ public async unsubscribe(): Promise<void> {
43
+ if (this.intervalId) {
44
+ clearInterval(this.intervalId);
45
+ this.intervalId = undefined;
46
+ }
47
+ }
48
+ }
@@ -0,0 +1,76 @@
1
+ import { UserMap } from './userMap';
2
+ import { getNonIdleUserFilter, getUserFilter } from '../memcmp';
3
+ import { WebSocketProgramAccountSubscriber } from '../accounts/webSocketProgramAccountSubscriber';
4
+ import { UserAccount } from '../types';
5
+ import { Commitment, Context, PublicKey } from '@solana/web3.js';
6
+
7
+ export class WebsocketSubscription {
8
+ private userMap: UserMap;
9
+ private commitment: Commitment;
10
+ private skipInitialLoad: boolean;
11
+ private resubTimeoutMs?: number;
12
+ private includeIdle?: boolean;
13
+ private decodeFn: (name: string, data: Buffer) => UserAccount;
14
+
15
+ private subscriber: WebSocketProgramAccountSubscriber<UserAccount>;
16
+
17
+ constructor({
18
+ userMap,
19
+ commitment,
20
+ skipInitialLoad = false,
21
+ resubTimeoutMs,
22
+ includeIdle = false,
23
+ decodeFn,
24
+ }: {
25
+ userMap: UserMap;
26
+ commitment: Commitment;
27
+ skipInitialLoad?: boolean;
28
+ resubTimeoutMs?: number;
29
+ includeIdle?: boolean;
30
+ decodeFn: (name: string, data: Buffer) => UserAccount;
31
+ }) {
32
+ this.userMap = userMap;
33
+ this.commitment = commitment;
34
+ this.skipInitialLoad = skipInitialLoad;
35
+ this.resubTimeoutMs = resubTimeoutMs;
36
+ this.includeIdle = includeIdle || false;
37
+ this.decodeFn = decodeFn;
38
+ }
39
+
40
+ public async subscribe(): Promise<void> {
41
+ if (!this.subscriber) {
42
+ const filters = [getUserFilter()];
43
+ if (!this.includeIdle) {
44
+ filters.push(getNonIdleUserFilter());
45
+ }
46
+ this.subscriber = new WebSocketProgramAccountSubscriber<UserAccount>(
47
+ 'UserMap',
48
+ 'User',
49
+ this.userMap.driftClient.program,
50
+ this.decodeFn,
51
+ {
52
+ filters,
53
+ commitment: this.commitment,
54
+ },
55
+ this.resubTimeoutMs
56
+ );
57
+ }
58
+
59
+ await this.subscriber.subscribe(
60
+ (accountId: PublicKey, account: UserAccount, context: Context) => {
61
+ const userKey = accountId.toBase58();
62
+ this.userMap.updateUserAccount(userKey, account, context.slot);
63
+ }
64
+ );
65
+
66
+ if (!this.skipInitialLoad) {
67
+ await this.userMap.sync();
68
+ }
69
+ }
70
+
71
+ public async unsubscribe(): Promise<void> {
72
+ if (!this.subscriber) return;
73
+ await this.subscriber.unsubscribe();
74
+ this.subscriber = undefined;
75
+ }
76
+ }