@drift-labs/sdk 2.136.0-beta.0 → 2.136.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 (77) hide show
  1. package/VERSION +1 -1
  2. package/lib/browser/accounts/types.d.ts +2 -0
  3. package/lib/browser/accounts/webSocketAccountSubscriberV2.d.ts +76 -3
  4. package/lib/browser/accounts/webSocketAccountSubscriberV2.js +211 -39
  5. package/lib/browser/accounts/webSocketDriftClientAccountSubscriberV2.d.ts +87 -0
  6. package/lib/browser/accounts/webSocketDriftClientAccountSubscriberV2.js +444 -0
  7. package/lib/browser/accounts/webSocketProgramAccountsSubscriberV2.d.ts +145 -0
  8. package/lib/browser/accounts/webSocketProgramAccountsSubscriberV2.js +744 -0
  9. package/lib/browser/accounts/websocketProgramUserAccountSubscriber.d.ts +22 -0
  10. package/lib/browser/accounts/websocketProgramUserAccountSubscriber.js +54 -0
  11. package/lib/browser/driftClient.js +22 -18
  12. package/lib/browser/driftClientConfig.d.ts +7 -2
  13. package/lib/browser/factory/bigNum.d.ts +2 -2
  14. package/lib/browser/factory/bigNum.js +20 -5
  15. package/lib/browser/index.d.ts +4 -0
  16. package/lib/browser/index.js +9 -1
  17. package/lib/browser/memcmp.d.ts +2 -0
  18. package/lib/browser/memcmp.js +19 -1
  19. package/lib/browser/oracles/oracleId.d.ts +5 -0
  20. package/lib/browser/oracles/oracleId.js +46 -1
  21. package/lib/browser/user.js +12 -5
  22. package/lib/browser/userConfig.d.ts +3 -0
  23. package/lib/node/accounts/types.d.ts +2 -0
  24. package/lib/node/accounts/types.d.ts.map +1 -1
  25. package/lib/node/accounts/webSocketAccountSubscriberV2.d.ts +76 -3
  26. package/lib/node/accounts/webSocketAccountSubscriberV2.d.ts.map +1 -1
  27. package/lib/node/accounts/webSocketAccountSubscriberV2.js +211 -39
  28. package/lib/node/accounts/webSocketDriftClientAccountSubscriberV2.d.ts +88 -0
  29. package/lib/node/accounts/webSocketDriftClientAccountSubscriberV2.d.ts.map +1 -0
  30. package/lib/node/accounts/webSocketDriftClientAccountSubscriberV2.js +444 -0
  31. package/lib/node/accounts/webSocketProgramAccountsSubscriberV2.d.ts +146 -0
  32. package/lib/node/accounts/webSocketProgramAccountsSubscriberV2.d.ts.map +1 -0
  33. package/lib/node/accounts/webSocketProgramAccountsSubscriberV2.js +744 -0
  34. package/lib/node/accounts/websocketProgramUserAccountSubscriber.d.ts +23 -0
  35. package/lib/node/accounts/websocketProgramUserAccountSubscriber.d.ts.map +1 -0
  36. package/lib/node/accounts/websocketProgramUserAccountSubscriber.js +54 -0
  37. package/lib/node/driftClient.d.ts.map +1 -1
  38. package/lib/node/driftClient.js +22 -18
  39. package/lib/node/driftClientConfig.d.ts +7 -2
  40. package/lib/node/driftClientConfig.d.ts.map +1 -1
  41. package/lib/node/factory/bigNum.d.ts +2 -2
  42. package/lib/node/factory/bigNum.d.ts.map +1 -1
  43. package/lib/node/factory/bigNum.js +20 -5
  44. package/lib/node/index.d.ts +4 -0
  45. package/lib/node/index.d.ts.map +1 -1
  46. package/lib/node/index.js +9 -1
  47. package/lib/node/memcmp.d.ts +2 -0
  48. package/lib/node/memcmp.d.ts.map +1 -1
  49. package/lib/node/memcmp.js +19 -1
  50. package/lib/node/oracles/oracleId.d.ts +5 -0
  51. package/lib/node/oracles/oracleId.d.ts.map +1 -1
  52. package/lib/node/oracles/oracleId.js +46 -1
  53. package/lib/node/user.d.ts.map +1 -1
  54. package/lib/node/user.js +12 -5
  55. package/lib/node/userConfig.d.ts +3 -0
  56. package/lib/node/userConfig.d.ts.map +1 -1
  57. package/package.json +1 -1
  58. package/src/accounts/README_WebSocketAccountSubscriberV2.md +41 -0
  59. package/src/accounts/types.ts +3 -0
  60. package/src/accounts/webSocketAccountSubscriberV2.ts +243 -42
  61. package/src/accounts/webSocketDriftClientAccountSubscriberV2.ts +745 -0
  62. package/src/accounts/webSocketProgramAccountsSubscriberV2.ts +995 -0
  63. package/src/accounts/websocketProgramUserAccountSubscriber.ts +94 -0
  64. package/src/driftClient.ts +13 -7
  65. package/src/driftClientConfig.ts +15 -8
  66. package/src/factory/bigNum.ts +22 -5
  67. package/src/index.ts +4 -0
  68. package/src/memcmp.ts +17 -0
  69. package/src/oracles/oracleId.ts +34 -0
  70. package/src/user.ts +21 -9
  71. package/src/userConfig.ts +3 -0
  72. package/lib/browser/accounts/webSocketProgramAccountSubscriberV2.d.ts +0 -53
  73. package/lib/browser/accounts/webSocketProgramAccountSubscriberV2.js +0 -453
  74. package/lib/node/accounts/webSocketProgramAccountSubscriberV2.d.ts +0 -54
  75. package/lib/node/accounts/webSocketProgramAccountSubscriberV2.d.ts.map +0 -1
  76. package/lib/node/accounts/webSocketProgramAccountSubscriberV2.js +0 -453
  77. package/src/accounts/webSocketProgramAccountSubscriberV2.ts +0 -596
package/VERSION CHANGED
@@ -1 +1 @@
1
- 2.136.0-beta.0
1
+ 2.136.0-beta.2
@@ -139,6 +139,8 @@ export type DataAndSlot<T> = {
139
139
  export type ResubOpts = {
140
140
  resubTimeoutMs?: number;
141
141
  logResubMessages?: boolean;
142
+ usePollingInsteadOfResub?: boolean;
143
+ pollingIntervalMs?: number;
142
144
  };
143
145
  export interface UserStatsAccountEvents {
144
146
  userStatsAccountUpdate: (payload: UserStatsAccount) => void;
@@ -2,8 +2,56 @@
2
2
  /// <reference types="node" />
3
3
  import { DataAndSlot, AccountSubscriber, ResubOpts, BufferAndSlot } from './types';
4
4
  import { Program } from '@coral-xyz/anchor';
5
- import { AccountInfoBase, AccountInfoWithBase58EncodedData, AccountInfoWithBase64EncodedData, type Commitment } from 'gill';
5
+ import { AccountInfoBase, AccountInfoWithBase64EncodedData, AccountInfoWithBase58EncodedData, Rpc, RpcSubscriptions, SolanaRpcSubscriptionsApi, type Commitment } from 'gill';
6
6
  import { PublicKey } from '@solana/web3.js';
7
+ /**
8
+ * WebSocketAccountSubscriberV2
9
+ *
10
+ * High-level overview
11
+ * - WebSocket-first subscriber for a single Solana account with optional
12
+ * polling safeguards when the WS feed goes quiet.
13
+ * - Emits decoded updates via `onChange` and maintains the latest
14
+ * `{buffer, slot}` and decoded `{data, slot}` internally.
15
+ *
16
+ * Why polling if this is a WebSocket subscriber?
17
+ * - Under real-world conditions, WS notifications can stall or get dropped.
18
+ * - When `resubOpts.resubTimeoutMs` elapses without WS data, you can either:
19
+ * - resubscribe to the WS stream (default), or
20
+ * - enable `resubOpts.usePollingInsteadOfResub` to start polling this single
21
+ * account via RPC to check for missed changes.
22
+ * - Polling compares the fetched buffer to the last known buffer. If different
23
+ * at an equal-or-later slot, it indicates a missed update and we resubscribe
24
+ * to WS to restore a clean stream.
25
+ *
26
+ * Initial fetch (on subscribe)
27
+ * - On `subscribe()`, we do a one-time RPC `fetch()` to seed internal state and
28
+ * emit the latest account state, ensuring consumers start from ground truth
29
+ * even before WS events arrive.
30
+ *
31
+ * Continuous polling (opt-in)
32
+ * - If `usePollingInsteadOfResub` is set, the inactivity timeout triggers a
33
+ * polling loop that periodically `fetch()`es the account and checks for
34
+ * changes. On change, polling stops and we resubscribe to WS.
35
+ * - If not set (default), the inactivity timeout immediately triggers a WS
36
+ * resubscription (no polling loop).
37
+ *
38
+ * Account focus
39
+ * - This class tracks exactly one account — the one passed to the constructor —
40
+ * which is by definition the account the consumer cares about. The extra
41
+ * logic is narrowly scoped to this account to minimize overhead.
42
+ *
43
+ * Tuning knobs
44
+ * - `resubOpts.resubTimeoutMs`: WS inactivity threshold before fallback.
45
+ * - `resubOpts.usePollingInsteadOfResub`: toggle polling vs immediate resub.
46
+ * - `resubOpts.pollingIntervalMs`: polling cadence (default 30s).
47
+ * - `resubOpts.logResubMessages`: verbose logs for diagnostics.
48
+ * - `commitment`: WS/RPC commitment used for reads and notifications.
49
+ * - `decodeBufferFn`: optional custom decode; defaults to Anchor coder.
50
+ *
51
+ * Implementation notes
52
+ * - Uses `gill` for both WS (`rpcSubscriptions`) and RPC (`rpc`) to match the
53
+ * program provider’s RPC endpoint. Handles base58/base64 encoded data.
54
+ */
7
55
  export declare class WebSocketAccountSubscriberV2<T> implements AccountSubscriber<T> {
8
56
  dataAndSlot?: DataAndSlot<T>;
9
57
  bufferAndSlot?: BufferAndSlot;
@@ -14,19 +62,44 @@ export declare class WebSocketAccountSubscriberV2<T> implements AccountSubscribe
14
62
  decodeBufferFn: (buffer: Buffer) => T;
15
63
  onChange: (data: T) => void;
16
64
  listenerId?: number;
17
- resubOpts?: ResubOpts;
65
+ resubOpts: ResubOpts;
18
66
  commitment?: Commitment;
19
67
  isUnsubscribing: boolean;
20
68
  timeoutId?: ReturnType<typeof setTimeout>;
69
+ pollingTimeoutId?: ReturnType<typeof setTimeout>;
21
70
  receivingData: boolean;
22
71
  private rpc;
23
72
  private rpcSubscriptions;
24
73
  private abortController?;
25
- constructor(accountName: string, program: Program, accountPublicKey: PublicKey, decodeBuffer?: (buffer: Buffer) => T, resubOpts?: ResubOpts, commitment?: Commitment);
74
+ /**
75
+ * Create a single-account WebSocket subscriber with optional polling fallback.
76
+ *
77
+ * @param accountName Name of the Anchor account type (used for default decode).
78
+ * @param program Anchor `Program` used for decoding and provider access.
79
+ * @param accountPublicKey Public key of the account to track.
80
+ * @param decodeBuffer Optional custom decode function; if omitted, uses
81
+ * program coder to decode `accountName`.
82
+ * @param resubOpts Resubscription/polling options. See class docs.
83
+ * @param commitment Commitment for WS and RPC operations.
84
+ * @param rpcSubscriptions Optional override/injection for testing.
85
+ * @param rpc Optional override/injection for testing.
86
+ */
87
+ constructor(accountName: string, program: Program, accountPublicKey: PublicKey, decodeBuffer?: (buffer: Buffer) => T, resubOpts?: ResubOpts, commitment?: Commitment, rpcSubscriptions?: RpcSubscriptions<SolanaRpcSubscriptionsApi> & string, rpc?: Rpc<any>);
26
88
  private handleNotificationLoop;
27
89
  subscribe(onChange: (data: T) => void): Promise<void>;
28
90
  setData(data: T, slot?: number): void;
29
91
  protected setTimeout(): void;
92
+ /**
93
+ * Start the polling loop (single-account).
94
+ * - Periodically calls `fetch()` and compares buffers to detect changes.
95
+ * - On detected change, stops polling and resubscribes to WS.
96
+ */
97
+ private startPolling;
98
+ private stopPolling;
99
+ /**
100
+ * Fetch the current account state via RPC and process it through the same
101
+ * decoding and update pathway as WS notifications.
102
+ */
30
103
  fetch(): Promise<void>;
31
104
  handleRpcResponse(context: {
32
105
  slot: bigint;
@@ -7,17 +7,81 @@ exports.WebSocketAccountSubscriberV2 = void 0;
7
7
  const utils_1 = require("./utils");
8
8
  const gill_1 = require("gill");
9
9
  const bs58_1 = __importDefault(require("bs58"));
10
+ /**
11
+ * WebSocketAccountSubscriberV2
12
+ *
13
+ * High-level overview
14
+ * - WebSocket-first subscriber for a single Solana account with optional
15
+ * polling safeguards when the WS feed goes quiet.
16
+ * - Emits decoded updates via `onChange` and maintains the latest
17
+ * `{buffer, slot}` and decoded `{data, slot}` internally.
18
+ *
19
+ * Why polling if this is a WebSocket subscriber?
20
+ * - Under real-world conditions, WS notifications can stall or get dropped.
21
+ * - When `resubOpts.resubTimeoutMs` elapses without WS data, you can either:
22
+ * - resubscribe to the WS stream (default), or
23
+ * - enable `resubOpts.usePollingInsteadOfResub` to start polling this single
24
+ * account via RPC to check for missed changes.
25
+ * - Polling compares the fetched buffer to the last known buffer. If different
26
+ * at an equal-or-later slot, it indicates a missed update and we resubscribe
27
+ * to WS to restore a clean stream.
28
+ *
29
+ * Initial fetch (on subscribe)
30
+ * - On `subscribe()`, we do a one-time RPC `fetch()` to seed internal state and
31
+ * emit the latest account state, ensuring consumers start from ground truth
32
+ * even before WS events arrive.
33
+ *
34
+ * Continuous polling (opt-in)
35
+ * - If `usePollingInsteadOfResub` is set, the inactivity timeout triggers a
36
+ * polling loop that periodically `fetch()`es the account and checks for
37
+ * changes. On change, polling stops and we resubscribe to WS.
38
+ * - If not set (default), the inactivity timeout immediately triggers a WS
39
+ * resubscription (no polling loop).
40
+ *
41
+ * Account focus
42
+ * - This class tracks exactly one account — the one passed to the constructor —
43
+ * which is by definition the account the consumer cares about. The extra
44
+ * logic is narrowly scoped to this account to minimize overhead.
45
+ *
46
+ * Tuning knobs
47
+ * - `resubOpts.resubTimeoutMs`: WS inactivity threshold before fallback.
48
+ * - `resubOpts.usePollingInsteadOfResub`: toggle polling vs immediate resub.
49
+ * - `resubOpts.pollingIntervalMs`: polling cadence (default 30s).
50
+ * - `resubOpts.logResubMessages`: verbose logs for diagnostics.
51
+ * - `commitment`: WS/RPC commitment used for reads and notifications.
52
+ * - `decodeBufferFn`: optional custom decode; defaults to Anchor coder.
53
+ *
54
+ * Implementation notes
55
+ * - Uses `gill` for both WS (`rpcSubscriptions`) and RPC (`rpc`) to match the
56
+ * program provider’s RPC endpoint. Handles base58/base64 encoded data.
57
+ */
10
58
  class WebSocketAccountSubscriberV2 {
11
- constructor(accountName, program, accountPublicKey, decodeBuffer, resubOpts, commitment) {
12
- var _a;
59
+ /**
60
+ * Create a single-account WebSocket subscriber with optional polling fallback.
61
+ *
62
+ * @param accountName Name of the Anchor account type (used for default decode).
63
+ * @param program Anchor `Program` used for decoding and provider access.
64
+ * @param accountPublicKey Public key of the account to track.
65
+ * @param decodeBuffer Optional custom decode function; if omitted, uses
66
+ * program coder to decode `accountName`.
67
+ * @param resubOpts Resubscription/polling options. See class docs.
68
+ * @param commitment Commitment for WS and RPC operations.
69
+ * @param rpcSubscriptions Optional override/injection for testing.
70
+ * @param rpc Optional override/injection for testing.
71
+ */
72
+ constructor(accountName, program, accountPublicKey, decodeBuffer, resubOpts, commitment, rpcSubscriptions, rpc) {
13
73
  this.isUnsubscribing = false;
14
74
  this.accountName = accountName;
15
75
  this.logAccountName = `${accountName}-${accountPublicKey.toBase58()}-ws-acct-subscriber-v2`;
16
76
  this.program = program;
17
77
  this.accountPublicKey = accountPublicKey;
18
78
  this.decodeBufferFn = decodeBuffer;
19
- this.resubOpts = resubOpts;
20
- if (((_a = this.resubOpts) === null || _a === void 0 ? void 0 : _a.resubTimeoutMs) < 1000) {
79
+ this.resubOpts = resubOpts !== null && resubOpts !== void 0 ? resubOpts : {
80
+ resubTimeoutMs: 30000,
81
+ usePollingInsteadOfResub: true,
82
+ logResubMessages: false,
83
+ };
84
+ if (this.resubOpts.resubTimeoutMs < 1000) {
21
85
  console.log(`resubTimeoutMs should be at least 1000ms to avoid spamming resub ${this.logAccountName}`);
22
86
  }
23
87
  this.receivingData = false;
@@ -27,32 +91,59 @@ class WebSocketAccountSubscriberV2 {
27
91
  this.commitment =
28
92
  commitment !== null && commitment !== void 0 ? commitment : this.program.provider.opts.commitment;
29
93
  // Initialize gill client using the same RPC URL as the program provider
30
- const rpcUrl = this.program.provider.connection
31
- .rpcEndpoint;
32
- const { rpc, rpcSubscriptions } = (0, gill_1.createSolanaClient)({
33
- urlOrMoniker: rpcUrl,
34
- });
35
- this.rpc = rpc;
36
- this.rpcSubscriptions = rpcSubscriptions;
94
+ this.rpc = rpc
95
+ ? rpc
96
+ : (() => {
97
+ const rpcUrl = this.program.provider.connection
98
+ .rpcEndpoint;
99
+ const { rpc } = (0, gill_1.createSolanaClient)({
100
+ urlOrMoniker: rpcUrl,
101
+ });
102
+ return rpc;
103
+ })();
104
+ this.rpcSubscriptions = rpcSubscriptions
105
+ ? rpcSubscriptions
106
+ : (() => {
107
+ const rpcUrl = this.program.provider.connection
108
+ .rpcEndpoint;
109
+ const { rpcSubscriptions } = (0, gill_1.createSolanaClient)({
110
+ urlOrMoniker: rpcUrl,
111
+ });
112
+ return rpcSubscriptions;
113
+ })();
37
114
  }
38
- async handleNotificationLoop(subscription) {
39
- var _a;
115
+ async handleNotificationLoop(subscriptionPromise) {
116
+ const subscription = await subscriptionPromise;
40
117
  for await (const notification of subscription) {
41
- if ((_a = this.resubOpts) === null || _a === void 0 ? void 0 : _a.resubTimeoutMs) {
42
- this.receivingData = true;
43
- clearTimeout(this.timeoutId);
44
- this.handleRpcResponse(notification.context, notification.value);
45
- this.setTimeout();
46
- }
47
- else {
48
- this.handleRpcResponse(notification.context, notification.value);
118
+ // If we're currently polling and receive a WebSocket event, stop polling
119
+ if (this.pollingTimeoutId) {
120
+ if (this.resubOpts.logResubMessages) {
121
+ console.log(`[${this.logAccountName}] Received WebSocket event while polling, stopping polling`);
122
+ }
123
+ this.stopPolling();
49
124
  }
125
+ this.receivingData = true;
126
+ clearTimeout(this.timeoutId);
127
+ this.handleRpcResponse(notification.context, notification.value);
128
+ this.setTimeout();
50
129
  }
51
130
  }
52
131
  async subscribe(onChange) {
53
- var _a, _b;
132
+ /**
133
+ * Start the WebSocket subscription and (optionally) setup inactivity
134
+ * fallback.
135
+ *
136
+ * Flow
137
+ * - If we do not have initial state, perform a one-time `fetch()` to seed
138
+ * internal buffers and emit current data.
139
+ * - Subscribe to account notifications via WS.
140
+ * - If `resubOpts.resubTimeoutMs` is set, schedule an inactivity timeout.
141
+ * When it fires:
142
+ * - if `usePollingInsteadOfResub` is true, start polling loop;
143
+ * - otherwise, resubscribe to WS immediately.
144
+ */
54
145
  if (this.listenerId != null || this.isUnsubscribing) {
55
- if ((_a = this.resubOpts) === null || _a === void 0 ? void 0 : _a.logResubMessages) {
146
+ if (this.resubOpts.logResubMessages) {
56
147
  console.log(`[${this.logAccountName}] Subscribe returning early - listenerId=${this.listenerId}, isUnsubscribing=${this.isUnsubscribing}`);
57
148
  }
58
149
  return;
@@ -65,14 +156,14 @@ class WebSocketAccountSubscriberV2 {
65
156
  const abortController = new AbortController();
66
157
  this.abortController = abortController;
67
158
  this.listenerId = Math.random(); // Unique ID for logging purposes
68
- if ((_b = this.resubOpts) === null || _b === void 0 ? void 0 : _b.resubTimeoutMs) {
159
+ if (this.resubOpts.resubTimeoutMs) {
69
160
  this.receivingData = true;
70
161
  this.setTimeout();
71
162
  }
72
163
  // Subscribe to account changes using gill's rpcSubscriptions
73
164
  const pubkey = this.accountPublicKey.toBase58();
74
165
  if ((0, gill_1.isAddress)(pubkey)) {
75
- const subscription = await this.rpcSubscriptions
166
+ const subscriptionPromise = this.rpcSubscriptions
76
167
  .accountNotifications(pubkey, {
77
168
  commitment: this.commitment,
78
169
  encoding: 'base64',
@@ -80,8 +171,11 @@ class WebSocketAccountSubscriberV2 {
80
171
  .subscribe({
81
172
  abortSignal: abortController.signal,
82
173
  });
83
- // Start notification loop without awaiting
84
- this.handleNotificationLoop(subscription);
174
+ // Start notification loop with the subscription promise
175
+ this.handleNotificationLoop(subscriptionPromise);
176
+ }
177
+ else {
178
+ throw new Error('Invalid account public key');
85
179
  }
86
180
  }
87
181
  setData(data, slot) {
@@ -95,37 +189,108 @@ class WebSocketAccountSubscriberV2 {
95
189
  };
96
190
  }
97
191
  setTimeout() {
98
- var _a;
192
+ /**
193
+ * Schedule inactivity handling. If WS is quiet for
194
+ * `resubOpts.resubTimeoutMs` and `receivingData` is true, trigger either
195
+ * a polling loop or a resubscribe depending on options.
196
+ */
99
197
  if (!this.onChange) {
100
198
  throw new Error('onChange callback function must be set');
101
199
  }
102
200
  this.timeoutId = setTimeout(async () => {
103
- var _a, _b, _c, _d;
104
201
  if (this.isUnsubscribing) {
105
202
  // If we are in the process of unsubscribing, do not attempt to resubscribe
106
- if ((_a = this.resubOpts) === null || _a === void 0 ? void 0 : _a.logResubMessages) {
203
+ if (this.resubOpts.logResubMessages) {
107
204
  console.log(`[${this.logAccountName}] Timeout fired but isUnsubscribing=true, skipping resubscribe`);
108
205
  }
109
206
  return;
110
207
  }
111
208
  if (this.receivingData) {
112
- if ((_b = this.resubOpts) === null || _b === void 0 ? void 0 : _b.logResubMessages) {
113
- console.log(`No ws data from ${this.logAccountName} in ${this.resubOpts.resubTimeoutMs}ms, resubscribing - listenerId=${this.listenerId}, isUnsubscribing=${this.isUnsubscribing}`);
209
+ if (this.resubOpts.usePollingInsteadOfResub) {
210
+ // Use polling instead of resubscribing
211
+ if (this.resubOpts.logResubMessages) {
212
+ console.log(`[${this.logAccountName}] No ws data in ${this.resubOpts.resubTimeoutMs}ms, starting polling - listenerId=${this.listenerId}`);
213
+ }
214
+ this.startPolling();
114
215
  }
115
- await this.unsubscribe(true);
116
- this.receivingData = false;
117
- await this.subscribe(this.onChange);
118
- if ((_c = this.resubOpts) === null || _c === void 0 ? void 0 : _c.logResubMessages) {
119
- console.log(`[${this.logAccountName}] Resubscribe completed - receivingData=${this.receivingData}, listenerId=${this.listenerId}, isUnsubscribing=${this.isUnsubscribing}`);
216
+ else {
217
+ // Original resubscribe behavior
218
+ if (this.resubOpts.logResubMessages) {
219
+ console.log(`No ws data from ${this.logAccountName} in ${this.resubOpts.resubTimeoutMs}ms, resubscribing - listenerId=${this.listenerId}, isUnsubscribing=${this.isUnsubscribing}`);
220
+ }
221
+ await this.unsubscribe(true);
222
+ this.receivingData = false;
223
+ await this.subscribe(this.onChange);
224
+ if (this.resubOpts.logResubMessages) {
225
+ console.log(`[${this.logAccountName}] Resubscribe completed - receivingData=${this.receivingData}, listenerId=${this.listenerId}, isUnsubscribing=${this.isUnsubscribing}`);
226
+ }
120
227
  }
121
228
  }
122
229
  else {
123
- if ((_d = this.resubOpts) === null || _d === void 0 ? void 0 : _d.logResubMessages) {
230
+ if (this.resubOpts.logResubMessages) {
124
231
  console.log(`[${this.logAccountName}] Timeout fired but receivingData=false, skipping resubscribe`);
125
232
  }
126
233
  }
127
- }, (_a = this.resubOpts) === null || _a === void 0 ? void 0 : _a.resubTimeoutMs);
234
+ }, this.resubOpts.resubTimeoutMs);
235
+ }
236
+ /**
237
+ * Start the polling loop (single-account).
238
+ * - Periodically calls `fetch()` and compares buffers to detect changes.
239
+ * - On detected change, stops polling and resubscribes to WS.
240
+ */
241
+ startPolling() {
242
+ const pollingInterval = this.resubOpts.pollingIntervalMs || 30000; // Default to 30s
243
+ const poll = async () => {
244
+ var _a, _b;
245
+ if (this.isUnsubscribing) {
246
+ return;
247
+ }
248
+ try {
249
+ // Store current data and buffer before polling
250
+ const currentBuffer = (_a = this.bufferAndSlot) === null || _a === void 0 ? void 0 : _a.buffer;
251
+ // Fetch latest account data
252
+ await this.fetch();
253
+ // Check if we got new data by comparing buffers
254
+ const newBuffer = (_b = this.bufferAndSlot) === null || _b === void 0 ? void 0 : _b.buffer;
255
+ const hasNewData = newBuffer && (!currentBuffer || !newBuffer.equals(currentBuffer));
256
+ if (hasNewData) {
257
+ // New data received, stop polling and resubscribe to websocket
258
+ if (this.resubOpts.logResubMessages) {
259
+ console.log(`[${this.logAccountName}] Polling detected account data change, resubscribing to websocket`);
260
+ }
261
+ await this.unsubscribe(true);
262
+ this.receivingData = false;
263
+ await this.subscribe(this.onChange);
264
+ }
265
+ else {
266
+ // No new data, continue polling
267
+ if (this.resubOpts.logResubMessages) {
268
+ console.log(`[${this.logAccountName}] Polling found no account changes, continuing to poll every ${pollingInterval}ms`);
269
+ }
270
+ this.pollingTimeoutId = setTimeout(poll, pollingInterval);
271
+ }
272
+ }
273
+ catch (error) {
274
+ if (this.resubOpts.logResubMessages) {
275
+ console.error(`[${this.logAccountName}] Error during polling:`, error);
276
+ }
277
+ // On error, continue polling
278
+ this.pollingTimeoutId = setTimeout(poll, pollingInterval);
279
+ }
280
+ };
281
+ // Start polling immediately
282
+ poll();
283
+ }
284
+ stopPolling() {
285
+ if (this.pollingTimeoutId) {
286
+ clearTimeout(this.pollingTimeoutId);
287
+ this.pollingTimeoutId = undefined;
288
+ }
128
289
  }
290
+ /**
291
+ * Fetch the current account state via RPC and process it through the same
292
+ * decoding and update pathway as WS notifications.
293
+ */
129
294
  async fetch() {
130
295
  // Use gill's rpc for fetching account info
131
296
  const accountAddress = this.accountPublicKey.toBase58();
@@ -204,12 +369,19 @@ class WebSocketAccountSubscriberV2 {
204
369
  }
205
370
  }
206
371
  unsubscribe(onResub = false) {
372
+ /**
373
+ * Stop timers, polling, and WS subscription.
374
+ * - When called during a resubscribe (`onResub=true`), we preserve
375
+ * `resubOpts.resubTimeoutMs` for the restarted subscription.
376
+ */
207
377
  if (!onResub && this.resubOpts) {
208
378
  this.resubOpts.resubTimeoutMs = undefined;
209
379
  }
210
380
  this.isUnsubscribing = true;
211
381
  clearTimeout(this.timeoutId);
212
382
  this.timeoutId = undefined;
383
+ // Stop polling if active
384
+ this.stopPolling();
213
385
  // Abort the WebSocket subscription
214
386
  if (this.abortController) {
215
387
  this.abortController.abort('unsubscribing');
@@ -0,0 +1,87 @@
1
+ /// <reference types="node" />
2
+ import { AccountSubscriber, DataAndSlot, DelistedMarketSetting, DriftClientAccountEvents, DriftClientAccountSubscriber, ResubOpts } from './types';
3
+ import { PerpMarketAccount, SpotMarketAccount, StateAccount } from '../types';
4
+ import { Program } from '@coral-xyz/anchor';
5
+ import StrictEventEmitter from 'strict-event-emitter-types';
6
+ import { EventEmitter } from 'events';
7
+ import { PublicKey } from '@solana/web3.js';
8
+ import { Commitment } from 'gill';
9
+ import { OracleInfo, OraclePriceData } from '../oracles/types';
10
+ import { OracleClientCache } from '../oracles/oracleClientCache';
11
+ import { WebSocketProgramAccountsSubscriberV2 } from './webSocketProgramAccountsSubscriberV2';
12
+ import { WebSocketAccountSubscriberV2 } from './webSocketAccountSubscriberV2';
13
+ export declare class WebSocketDriftClientAccountSubscriberV2 implements DriftClientAccountSubscriber {
14
+ isSubscribed: boolean;
15
+ program: Program;
16
+ commitment?: Commitment;
17
+ perpMarketIndexes: number[];
18
+ spotMarketIndexes: number[];
19
+ oracleInfos: OracleInfo[];
20
+ oracleClientCache: OracleClientCache;
21
+ resubOpts?: ResubOpts;
22
+ shouldFindAllMarketsAndOracles: boolean;
23
+ skipInitialData: boolean;
24
+ eventEmitter: StrictEventEmitter<EventEmitter, DriftClientAccountEvents>;
25
+ stateAccountSubscriber?: WebSocketAccountSubscriberV2<StateAccount>;
26
+ perpMarketAllAccountsSubscriber: WebSocketProgramAccountsSubscriberV2<PerpMarketAccount>;
27
+ perpMarketAccountLatestData: Map<number, DataAndSlot<PerpMarketAccount>>;
28
+ spotMarketAllAccountsSubscriber: WebSocketProgramAccountsSubscriberV2<SpotMarketAccount>;
29
+ spotMarketAccountLatestData: Map<number, DataAndSlot<SpotMarketAccount>>;
30
+ perpOracleMap: Map<number, PublicKey>;
31
+ perpOracleStringMap: Map<number, string>;
32
+ spotOracleMap: Map<number, PublicKey>;
33
+ spotOracleStringMap: Map<number, string>;
34
+ oracleSubscribers: Map<string, AccountSubscriber<OraclePriceData>>;
35
+ delistedMarketSetting: DelistedMarketSetting;
36
+ initialPerpMarketAccountData: Map<number, PerpMarketAccount>;
37
+ initialSpotMarketAccountData: Map<number, SpotMarketAccount>;
38
+ initialOraclePriceData: Map<string, OraclePriceData>;
39
+ protected isSubscribing: boolean;
40
+ protected subscriptionPromise: Promise<boolean>;
41
+ protected subscriptionPromiseResolver: (val: boolean) => void;
42
+ private rpc;
43
+ private rpcSubscriptions;
44
+ constructor(program: Program, perpMarketIndexes: number[], spotMarketIndexes: number[], oracleInfos: OracleInfo[], shouldFindAllMarketsAndOracles: boolean, delistedMarketSetting: DelistedMarketSetting, resubOpts?: ResubOpts, commitment?: Commitment, skipInitialData?: boolean);
45
+ subscribe(): Promise<boolean>;
46
+ chunks: <T>(array: readonly T[], size: number) => T[][];
47
+ fetch(): Promise<void>;
48
+ /**
49
+ * This is a no-op method that always returns true.
50
+ * Unlike the previous implementation, we don't need to manually subscribe to individual perp markets
51
+ * because we automatically receive updates for all program account changes via a single websocket subscription.
52
+ * This means any new perp markets will automatically be included without explicit subscription.
53
+ * @param marketIndex The perp market index to add (unused)
54
+ * @returns Promise that resolves to true
55
+ */
56
+ addPerpMarket(_marketIndex: number): Promise<boolean>;
57
+ /**
58
+ * This is a no-op method that always returns true.
59
+ * Unlike the previous implementation, we don't need to manually subscribe to individual spot markets
60
+ * because we automatically receive updates for all program account changes via a single websocket subscription.
61
+ * This means any new spot markets will automatically be included without explicit subscription.
62
+ * @param marketIndex The spot market index to add (unused)
63
+ * @returns Promise that resolves to true
64
+ */
65
+ addSpotMarket(_marketIndex: number): Promise<boolean>;
66
+ setInitialData(): Promise<void>;
67
+ removeInitialData(): void;
68
+ subscribeToOracles(): Promise<boolean>;
69
+ subscribeToOracle(oracleInfo: OracleInfo): Promise<boolean>;
70
+ unsubscribeFromMarketAccounts(): Promise<void>;
71
+ unsubscribeFromSpotMarketAccounts(): Promise<void>;
72
+ unsubscribeFromOracles(): Promise<void>;
73
+ unsubscribe(): Promise<void>;
74
+ addOracle(oracleInfo: OracleInfo): Promise<boolean>;
75
+ setPerpOracleMap(): Promise<void>;
76
+ setSpotOracleMap(): Promise<void>;
77
+ handleDelistedMarketOracles(): Promise<void>;
78
+ assertIsSubscribed(): void;
79
+ getStateAccountAndSlot(): DataAndSlot<StateAccount>;
80
+ getMarketAccountAndSlot(marketIndex: number): DataAndSlot<PerpMarketAccount> | undefined;
81
+ getMarketAccountsAndSlots(): DataAndSlot<PerpMarketAccount>[];
82
+ getSpotMarketAccountAndSlot(marketIndex: number): DataAndSlot<SpotMarketAccount> | undefined;
83
+ getSpotMarketAccountsAndSlots(): DataAndSlot<SpotMarketAccount>[];
84
+ getOraclePriceDataAndSlot(oracleId: string): DataAndSlot<OraclePriceData> | undefined;
85
+ getOraclePriceDataAndSlotForPerpMarket(marketIndex: number): DataAndSlot<OraclePriceData> | undefined;
86
+ getOraclePriceDataAndSlotForSpotMarket(marketIndex: number): DataAndSlot<OraclePriceData> | undefined;
87
+ }