@drift-labs/sdk 2.145.0 → 2.146.0-alpha.13
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.
- package/.env +4 -0
- package/VERSION +1 -1
- package/lib/browser/accounts/grpcMultiUserAccountSubscriber.js +8 -1
- package/lib/browser/accounts/webSocketProgramAccountSubscriberV2.d.ts +99 -7
- package/lib/browser/accounts/webSocketProgramAccountSubscriberV2.js +435 -144
- package/lib/browser/adminClient.d.ts +5 -1
- package/lib/browser/adminClient.js +57 -23
- package/lib/browser/constants/numericConstants.d.ts +2 -0
- package/lib/browser/constants/numericConstants.js +5 -1
- package/lib/browser/constants/perpMarkets.js +0 -2
- package/lib/browser/decode/user.js +4 -0
- package/lib/browser/driftClient.d.ts +25 -10
- package/lib/browser/driftClient.js +238 -41
- package/lib/browser/driftClientConfig.d.ts +7 -2
- package/lib/browser/idl/drift.json +245 -22
- package/lib/browser/index.d.ts +4 -0
- package/lib/browser/index.js +9 -1
- package/lib/browser/marginCalculation.d.ts +86 -0
- package/lib/browser/marginCalculation.js +209 -0
- package/lib/browser/math/margin.d.ts +1 -1
- package/lib/browser/math/margin.js +8 -1
- package/lib/browser/math/position.d.ts +1 -0
- package/lib/browser/math/position.js +10 -2
- package/lib/browser/math/spotPosition.d.ts +1 -1
- package/lib/browser/math/spotPosition.js +3 -2
- package/lib/browser/math/superStake.d.ts +3 -2
- package/lib/browser/types.d.ts +13 -0
- package/lib/browser/types.js +12 -1
- package/lib/browser/user.d.ts +59 -11
- package/lib/browser/user.js +348 -43
- package/lib/node/accounts/grpcMultiUserAccountSubscriber.d.ts.map +1 -1
- package/lib/node/accounts/grpcMultiUserAccountSubscriber.js +8 -1
- package/lib/node/accounts/webSocketProgramAccountSubscriberV2.d.ts +99 -7
- package/lib/node/accounts/webSocketProgramAccountSubscriberV2.d.ts.map +1 -1
- package/lib/node/accounts/webSocketProgramAccountSubscriberV2.js +435 -144
- package/lib/node/adminClient.d.ts +5 -1
- package/lib/node/adminClient.d.ts.map +1 -1
- package/lib/node/adminClient.js +57 -23
- package/lib/node/constants/numericConstants.d.ts +2 -0
- package/lib/node/constants/numericConstants.d.ts.map +1 -1
- package/lib/node/constants/numericConstants.js +5 -1
- package/lib/node/constants/perpMarkets.d.ts.map +1 -1
- package/lib/node/constants/perpMarkets.js +0 -2
- package/lib/node/decode/user.d.ts.map +1 -1
- package/lib/node/decode/user.js +4 -0
- package/lib/node/driftClient.d.ts +25 -10
- package/lib/node/driftClient.d.ts.map +1 -1
- package/lib/node/driftClient.js +238 -41
- package/lib/node/driftClientConfig.d.ts +7 -2
- package/lib/node/driftClientConfig.d.ts.map +1 -1
- package/lib/node/idl/drift.json +245 -22
- package/lib/node/index.d.ts +4 -0
- package/lib/node/index.d.ts.map +1 -1
- package/lib/node/index.js +9 -1
- package/lib/node/marginCalculation.d.ts +87 -0
- package/lib/node/marginCalculation.d.ts.map +1 -0
- package/lib/node/marginCalculation.js +209 -0
- package/lib/node/math/margin.d.ts +1 -1
- package/lib/node/math/margin.d.ts.map +1 -1
- package/lib/node/math/margin.js +8 -1
- package/lib/node/math/position.d.ts +1 -0
- package/lib/node/math/position.d.ts.map +1 -1
- package/lib/node/math/position.js +10 -2
- package/lib/node/math/spotPosition.d.ts +1 -1
- package/lib/node/math/spotPosition.d.ts.map +1 -1
- package/lib/node/math/spotPosition.js +3 -2
- package/lib/node/math/superStake.d.ts +3 -2
- package/lib/node/math/superStake.d.ts.map +1 -1
- package/lib/node/types.d.ts +13 -0
- package/lib/node/types.d.ts.map +1 -1
- package/lib/node/types.js +12 -1
- package/lib/node/user.d.ts +59 -11
- package/lib/node/user.d.ts.map +1 -1
- package/lib/node/user.js +348 -43
- package/package.json +1 -1
- package/scripts/deposit-isolated-positions.ts +110 -0
- package/scripts/single-grpc-client-test.ts +71 -21
- package/scripts/withdraw-isolated-positions.ts +174 -0
- package/src/accounts/grpcMultiUserAccountSubscriber.ts +8 -1
- package/src/accounts/webSocketProgramAccountSubscriberV2.ts +566 -167
- package/src/adminClient.ts +74 -25
- package/src/constants/numericConstants.ts +5 -0
- package/src/constants/perpMarkets.ts +0 -3
- package/src/decode/user.ts +7 -1
- package/src/driftClient.ts +465 -52
- package/src/driftClientConfig.ts +15 -8
- package/src/idl/drift.json +246 -23
- package/src/index.ts +4 -0
- package/src/margin/README.md +143 -0
- package/src/marginCalculation.ts +306 -0
- package/src/math/margin.ts +13 -1
- package/src/math/position.ts +12 -2
- package/src/math/spotPosition.ts +6 -2
- package/src/types.ts +16 -0
- package/src/user.ts +623 -81
- package/tests/amm/test.ts +1 -1
- package/tests/dlob/helpers.ts +6 -3
- package/tests/user/getMarginCalculation.ts +405 -0
- package/tests/user/test.ts +0 -7
|
@@ -3,11 +3,70 @@ 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.
|
|
6
|
+
exports.WebSocketProgramAccountsSubscriberV2 = void 0;
|
|
7
7
|
const web3_js_1 = require("@solana/web3.js");
|
|
8
8
|
const gill_1 = require("gill");
|
|
9
9
|
const bs58_1 = __importDefault(require("bs58"));
|
|
10
|
-
|
|
10
|
+
/**
|
|
11
|
+
* WebSocketProgramAccountsSubscriberV2
|
|
12
|
+
*
|
|
13
|
+
* High-level overview
|
|
14
|
+
* - WebSocket-first subscriber for Solana program accounts that also layers in
|
|
15
|
+
* targeted polling to detect missed updates reliably.
|
|
16
|
+
* - Emits decoded account updates via the provided `onChange` callback.
|
|
17
|
+
* - Designed to focus extra work on the specific accounts the consumer cares
|
|
18
|
+
* about ("monitored accounts") while keeping baseline WS behavior for the
|
|
19
|
+
* full program subscription.
|
|
20
|
+
*
|
|
21
|
+
* Why polling if this is a WebSocket subscriber?
|
|
22
|
+
* - WS infra can stall, drop, or reorder notifications under network stress or
|
|
23
|
+
* provider hiccups. When that happens, critical account changes can be missed.
|
|
24
|
+
* - To mitigate this, the class accepts a set of accounts (provided via constructor) to monitor
|
|
25
|
+
* and uses light polling to verify whether a WS change was missed.
|
|
26
|
+
* - If polling detects a newer slot with different data than the last seen
|
|
27
|
+
* buffer, a centralized resubscription is triggered to restore a clean stream.
|
|
28
|
+
*
|
|
29
|
+
* Initial fetch (on subscribe)
|
|
30
|
+
* - On `subscribe()`, we first perform a single batched fetch of all monitored
|
|
31
|
+
* accounts ("initial monitor fetch").
|
|
32
|
+
* - Purpose: seed the internal `bufferAndSlotMap` and emit the latest state so
|
|
33
|
+
* consumers have up-to-date data immediately, even before WS events arrive.
|
|
34
|
+
* - This step does not decide resubscription; it only establishes ground truth.
|
|
35
|
+
*
|
|
36
|
+
* Continuous polling (only for monitored accounts)
|
|
37
|
+
* - After seeding, each monitored account is put into a monitoring cycle:
|
|
38
|
+
* 1) If no WS notification for an account is observed for `pollingIntervalMs`,
|
|
39
|
+
* we enqueue it for a batched fetch (buffered for a short window).
|
|
40
|
+
* 2) Once an account enters the "currently polling" set, a shared batch poll
|
|
41
|
+
* runs every `pollingIntervalMs` across all such accounts.
|
|
42
|
+
* 3) If WS notifications resume for an account, that account is removed from
|
|
43
|
+
* the polling set and returns to passive monitoring.
|
|
44
|
+
* - Polling compares the newly fetched buffer with the last stored buffer at a
|
|
45
|
+
* later slot. A difference indicates a missed update; we schedule a single
|
|
46
|
+
* resubscription (coalesced across accounts) to re-sync.
|
|
47
|
+
*
|
|
48
|
+
* Accounts the consumer cares about
|
|
49
|
+
* - Provide accounts up-front via the constructor `accountsToMonitor`, or add
|
|
50
|
+
* them dynamically with `addAccountToMonitor()` and remove with
|
|
51
|
+
* `removeAccountFromMonitor()`.
|
|
52
|
+
* - Only these accounts incur additional polling safeguards; other accounts are
|
|
53
|
+
* still processed from the WS stream normally.
|
|
54
|
+
*
|
|
55
|
+
* Resubscription strategy
|
|
56
|
+
* - Missed updates from any monitored account are coalesced and trigger a single
|
|
57
|
+
* resubscription after a short delay. This avoids rapid churn.
|
|
58
|
+
* - If `resubOpts.resubTimeoutMs` is set, an inactivity timer also performs a
|
|
59
|
+
* batch check of monitored accounts. If a missed update is found, the same
|
|
60
|
+
* centralized resubscription flow is used.
|
|
61
|
+
*
|
|
62
|
+
* Tuning knobs
|
|
63
|
+
* - `setPollingInterval(ms)`: adjust how often monitoring/polling runs
|
|
64
|
+
* (default 30s). Shorter = faster detection, higher RPC load.
|
|
65
|
+
* - Debounced immediate poll (~100ms): batches accounts added to polling right after inactivity.
|
|
66
|
+
* - Batch size for `getMultipleAccounts` is limited to 100, requests are chunked
|
|
67
|
+
* and processed concurrently.
|
|
68
|
+
*/
|
|
69
|
+
class WebSocketProgramAccountsSubscriberV2 {
|
|
11
70
|
constructor(subscriptionName, accountDiscriminator, program, decodeBufferFn, options = {
|
|
12
71
|
filters: [],
|
|
13
72
|
}, resubOpts, accountsToMonitor // Optional list of accounts to poll
|
|
@@ -22,11 +81,19 @@ class WebSocketProgramAccountSubscriberV2 {
|
|
|
22
81
|
this.pollingTimeouts = new Map();
|
|
23
82
|
this.lastWsNotificationTime = new Map(); // Track last WS notification time per account
|
|
24
83
|
this.accountsCurrentlyPolling = new Set(); // Track which accounts are being polled
|
|
84
|
+
this.debouncedImmediatePollMs = 100; // configurable short window
|
|
85
|
+
// Centralized resubscription handling
|
|
86
|
+
this.missedChangeDetected = false; // Flag to track if any missed change was detected
|
|
87
|
+
this.accountsWithMissedUpdates = new Set(); // Track which accounts had missed updates
|
|
25
88
|
this.subscriptionName = subscriptionName;
|
|
26
89
|
this.accountDiscriminator = accountDiscriminator;
|
|
27
90
|
this.program = program;
|
|
28
91
|
this.decodeBuffer = decodeBufferFn;
|
|
29
|
-
this.resubOpts = resubOpts
|
|
92
|
+
this.resubOpts = resubOpts !== null && resubOpts !== void 0 ? resubOpts : {
|
|
93
|
+
resubTimeoutMs: 30000,
|
|
94
|
+
usePollingInsteadOfResub: true,
|
|
95
|
+
logResubMessages: false,
|
|
96
|
+
};
|
|
30
97
|
if (((_a = this.resubOpts) === null || _a === void 0 ? void 0 : _a.resubTimeoutMs) < 1000) {
|
|
31
98
|
console.log('resubTimeoutMs should be at least 1000ms to avoid spamming resub');
|
|
32
99
|
}
|
|
@@ -47,52 +114,101 @@ class WebSocketProgramAccountSubscriberV2 {
|
|
|
47
114
|
this.rpc = rpc;
|
|
48
115
|
this.rpcSubscriptions = rpcSubscriptions;
|
|
49
116
|
}
|
|
117
|
+
async handleNotificationLoop(notificationPromise) {
|
|
118
|
+
var _a;
|
|
119
|
+
try {
|
|
120
|
+
const subscriptionIterable = await notificationPromise;
|
|
121
|
+
for await (const notification of subscriptionIterable) {
|
|
122
|
+
try {
|
|
123
|
+
if ((_a = this.resubOpts) === null || _a === void 0 ? void 0 : _a.resubTimeoutMs) {
|
|
124
|
+
this.receivingData = true;
|
|
125
|
+
clearTimeout(this.timeoutId);
|
|
126
|
+
this.handleRpcResponse(notification.context, notification.value.pubkey, notification.value.account.data);
|
|
127
|
+
this.setTimeout();
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
this.handleRpcResponse(notification.context, notification.value.pubkey, notification.value.account.data);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
catch (error) {
|
|
134
|
+
console.error(`Error handling RPC response for pubkey ${notification.value.pubkey}:`, error);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
catch (error) {
|
|
139
|
+
console.error(`[${this.subscriptionName}] Error in notification loop:`, error);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
50
142
|
async subscribe(onChange) {
|
|
51
143
|
var _a, _b;
|
|
144
|
+
/**
|
|
145
|
+
* Start the WebSocket subscription and initialize polling safeguards.
|
|
146
|
+
*
|
|
147
|
+
* Flow
|
|
148
|
+
* - Seeds all monitored accounts with a single batched RPC fetch and emits
|
|
149
|
+
* their current state.
|
|
150
|
+
* - Subscribes to program notifications via WS using gill.
|
|
151
|
+
* - If `resubOpts.resubTimeoutMs` is set, starts an inactivity timer that
|
|
152
|
+
* batch-checks monitored accounts when WS goes quiet.
|
|
153
|
+
* - Begins monitoring for accounts that may need polling when WS
|
|
154
|
+
* notifications are not observed within `pollingIntervalMs`.
|
|
155
|
+
*
|
|
156
|
+
* @param onChange Callback invoked with decoded account data when an update
|
|
157
|
+
* is detected (via WS or batch RPC fetch).
|
|
158
|
+
*/
|
|
159
|
+
const startTime = performance.now();
|
|
52
160
|
if (this.listenerId != null || this.isUnsubscribing) {
|
|
53
161
|
return;
|
|
54
162
|
}
|
|
163
|
+
if ((_a = this.resubOpts) === null || _a === void 0 ? void 0 : _a.logResubMessages) {
|
|
164
|
+
console.log(`[${this.subscriptionName}] initializing subscription. This many monitored accounts: ${this.accountsToMonitor.size}`);
|
|
165
|
+
}
|
|
55
166
|
this.onChange = onChange;
|
|
167
|
+
// initial fetch of monitored data - only fetch and populate, don't check for missed changes
|
|
168
|
+
await this.fetchAndPopulateAllMonitoredAccounts();
|
|
56
169
|
// Create abort controller for proper cleanup
|
|
57
170
|
const abortController = new AbortController();
|
|
58
171
|
this.abortController = abortController;
|
|
172
|
+
this.listenerId = Math.random(); // Unique ID for logging purposes
|
|
173
|
+
if ((_b = this.resubOpts) === null || _b === void 0 ? void 0 : _b.resubTimeoutMs) {
|
|
174
|
+
this.receivingData = true;
|
|
175
|
+
this.setTimeout();
|
|
176
|
+
}
|
|
59
177
|
// Subscribe to program account changes using gill's rpcSubscriptions
|
|
60
178
|
const programId = this.program.programId.toBase58();
|
|
61
179
|
if ((0, gill_1.isAddress)(programId)) {
|
|
62
|
-
const
|
|
180
|
+
const subscriptionPromise = this.rpcSubscriptions
|
|
63
181
|
.programNotifications(programId, {
|
|
64
182
|
commitment: this.options.commitment,
|
|
65
183
|
encoding: 'base64',
|
|
66
|
-
filters: this.options.filters.map((filter) =>
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
184
|
+
filters: this.options.filters.map((filter) => {
|
|
185
|
+
// Convert filter bytes from base58 to base64 if needed
|
|
186
|
+
let bytes = filter.memcmp.bytes;
|
|
187
|
+
if (typeof bytes === 'string' &&
|
|
188
|
+
/^[1-9A-HJ-NP-Za-km-z]+$/.test(bytes)) {
|
|
189
|
+
// Looks like base58 - convert to base64
|
|
190
|
+
const decoded = bs58_1.default.decode(bytes);
|
|
191
|
+
bytes = Buffer.from(decoded).toString('base64');
|
|
192
|
+
}
|
|
193
|
+
return {
|
|
194
|
+
memcmp: {
|
|
195
|
+
offset: BigInt(filter.memcmp.offset),
|
|
196
|
+
bytes: bytes,
|
|
197
|
+
encoding: 'base64',
|
|
198
|
+
},
|
|
199
|
+
};
|
|
200
|
+
}),
|
|
73
201
|
})
|
|
74
202
|
.subscribe({
|
|
75
203
|
abortSignal: abortController.signal,
|
|
76
204
|
});
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
this.handleRpcResponse(notification.context, notification.value.account);
|
|
82
|
-
this.setTimeout();
|
|
83
|
-
}
|
|
84
|
-
else {
|
|
85
|
-
this.handleRpcResponse(notification.context, notification.value.account);
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
this.listenerId = Math.random(); // Unique ID for logging purposes
|
|
90
|
-
if ((_b = this.resubOpts) === null || _b === void 0 ? void 0 : _b.resubTimeoutMs) {
|
|
91
|
-
this.receivingData = true;
|
|
92
|
-
this.setTimeout();
|
|
205
|
+
// Start notification loop without awaiting
|
|
206
|
+
this.handleNotificationLoop(subscriptionPromise);
|
|
207
|
+
// Start monitoring for accounts that may need polling if no WS event is received
|
|
208
|
+
this.startMonitoringForAccounts();
|
|
93
209
|
}
|
|
94
|
-
|
|
95
|
-
this.
|
|
210
|
+
const endTime = performance.now();
|
|
211
|
+
console.log(`[PROFILING] ${this.subscriptionName}.subscribe() completed in ${endTime - startTime}ms`);
|
|
96
212
|
}
|
|
97
213
|
setTimeout() {
|
|
98
214
|
var _a;
|
|
@@ -107,44 +223,46 @@ class WebSocketProgramAccountSubscriberV2 {
|
|
|
107
223
|
}
|
|
108
224
|
if (this.receivingData) {
|
|
109
225
|
if ((_a = this.resubOpts) === null || _a === void 0 ? void 0 : _a.logResubMessages) {
|
|
110
|
-
console.log(`No ws data from ${this.subscriptionName} in ${(_b = this.resubOpts) === null || _b === void 0 ? void 0 : _b.resubTimeoutMs}ms,
|
|
226
|
+
console.log(`No ws data from ${this.subscriptionName} in ${(_b = this.resubOpts) === null || _b === void 0 ? void 0 : _b.resubTimeoutMs}ms, checking for missed changes`);
|
|
227
|
+
}
|
|
228
|
+
// Check for missed changes in monitored accounts
|
|
229
|
+
const missedChangeDetected = await this.fetchAllMonitoredAccounts();
|
|
230
|
+
if (missedChangeDetected) {
|
|
231
|
+
// Signal missed change with a generic identifier since we don't have specific account IDs from this context
|
|
232
|
+
this.signalMissedChange('timeout-check');
|
|
233
|
+
}
|
|
234
|
+
else {
|
|
235
|
+
// No missed changes, continue monitoring
|
|
236
|
+
this.receivingData = false;
|
|
237
|
+
this.setTimeout();
|
|
111
238
|
}
|
|
112
|
-
await this.unsubscribe(true);
|
|
113
|
-
this.receivingData = false;
|
|
114
|
-
await this.subscribe(this.onChange);
|
|
115
239
|
}
|
|
116
240
|
}, (_a = this.resubOpts) === null || _a === void 0 ? void 0 : _a.resubTimeoutMs);
|
|
117
241
|
}
|
|
118
|
-
handleRpcResponse(context, accountInfo) {
|
|
242
|
+
handleRpcResponse(context, accountId, accountInfo) {
|
|
119
243
|
const newSlot = Number(context.slot);
|
|
120
244
|
let newBuffer = undefined;
|
|
121
245
|
if (accountInfo) {
|
|
122
|
-
//
|
|
123
|
-
if (accountInfo
|
|
124
|
-
//
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
else {
|
|
133
|
-
newBuffer = Buffer.from(data, 'base64');
|
|
134
|
-
}
|
|
246
|
+
// Handle different data formats from gill
|
|
247
|
+
if (Array.isArray(accountInfo)) {
|
|
248
|
+
// If it's a tuple [data, encoding]
|
|
249
|
+
const [data, encoding] = accountInfo;
|
|
250
|
+
if (encoding === 'base58') {
|
|
251
|
+
// Convert base58 to buffer using bs58
|
|
252
|
+
newBuffer = Buffer.from(bs58_1.default.decode(data));
|
|
253
|
+
}
|
|
254
|
+
else {
|
|
255
|
+
newBuffer = Buffer.from(data, 'base64');
|
|
135
256
|
}
|
|
136
257
|
}
|
|
137
258
|
}
|
|
138
|
-
|
|
139
|
-
// Note: accountInfo doesn't have a key property, we need to get it from the notification
|
|
140
|
-
// For now, we'll use a placeholder - this needs to be fixed based on the actual gill API
|
|
141
|
-
const accountId = new web3_js_1.PublicKey('11111111111111111111111111111111'); // Placeholder
|
|
142
|
-
const accountIdString = accountId.toBase58();
|
|
259
|
+
const accountIdString = accountId.toString();
|
|
143
260
|
const existingBufferAndSlot = this.bufferAndSlotMap.get(accountIdString);
|
|
144
261
|
// Track WebSocket notification time for this account
|
|
145
262
|
this.lastWsNotificationTime.set(accountIdString, Date.now());
|
|
146
|
-
// If this account was being polled, stop polling it
|
|
147
|
-
if (this.accountsCurrentlyPolling.has(accountIdString)
|
|
263
|
+
// If this account was being polled, stop polling it if the buffer has changed
|
|
264
|
+
if (this.accountsCurrentlyPolling.has(accountIdString) &&
|
|
265
|
+
!(existingBufferAndSlot === null || existingBufferAndSlot === void 0 ? void 0 : existingBufferAndSlot.buffer.equals(newBuffer))) {
|
|
148
266
|
this.accountsCurrentlyPolling.delete(accountIdString);
|
|
149
267
|
// If no more accounts are being polled, stop batch polling
|
|
150
268
|
if (this.accountsCurrentlyPolling.size === 0 &&
|
|
@@ -155,12 +273,7 @@ class WebSocketProgramAccountSubscriberV2 {
|
|
|
155
273
|
}
|
|
156
274
|
if (!existingBufferAndSlot) {
|
|
157
275
|
if (newBuffer) {
|
|
158
|
-
this.
|
|
159
|
-
buffer: newBuffer,
|
|
160
|
-
slot: newSlot,
|
|
161
|
-
});
|
|
162
|
-
const account = this.decodeBuffer(this.accountDiscriminator, newBuffer);
|
|
163
|
-
this.onChange(accountId, account, { slot: newSlot }, newBuffer);
|
|
276
|
+
this.updateBufferAndHandleChange(newBuffer, newSlot, accountIdString);
|
|
164
277
|
}
|
|
165
278
|
return;
|
|
166
279
|
}
|
|
@@ -169,12 +282,7 @@ class WebSocketProgramAccountSubscriberV2 {
|
|
|
169
282
|
}
|
|
170
283
|
const oldBuffer = existingBufferAndSlot.buffer;
|
|
171
284
|
if (newBuffer && (!oldBuffer || !newBuffer.equals(oldBuffer))) {
|
|
172
|
-
this.
|
|
173
|
-
buffer: newBuffer,
|
|
174
|
-
slot: newSlot,
|
|
175
|
-
});
|
|
176
|
-
const account = this.decodeBuffer(this.accountDiscriminator, newBuffer);
|
|
177
|
-
this.onChange(accountId, account, { slot: newSlot }, newBuffer);
|
|
285
|
+
this.updateBufferAndHandleChange(newBuffer, newSlot, accountIdString);
|
|
178
286
|
}
|
|
179
287
|
}
|
|
180
288
|
startMonitoringForAccounts() {
|
|
@@ -193,15 +301,18 @@ class WebSocketProgramAccountSubscriberV2 {
|
|
|
193
301
|
}
|
|
194
302
|
// Set up monitoring timeout - only start polling if no WS notification in 30s
|
|
195
303
|
const timeoutId = setTimeout(async () => {
|
|
304
|
+
var _a;
|
|
196
305
|
// Check if we've received a WS notification for this account recently
|
|
197
|
-
const lastNotificationTime = this.lastWsNotificationTime.get(accountIdString);
|
|
306
|
+
const lastNotificationTime = this.lastWsNotificationTime.get(accountIdString) || 0;
|
|
198
307
|
const currentTime = Date.now();
|
|
199
308
|
if (!lastNotificationTime ||
|
|
200
309
|
currentTime - lastNotificationTime >= this.pollingIntervalMs) {
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
310
|
+
if ((_a = this.resubOpts) === null || _a === void 0 ? void 0 : _a.logResubMessages) {
|
|
311
|
+
console.debug(`[${this.subscriptionName}] No recent WS notification for ${accountIdString}, adding to polling set`);
|
|
312
|
+
}
|
|
313
|
+
// No recent WS notification: add to polling and schedule debounced poll
|
|
314
|
+
this.accountsCurrentlyPolling.add(accountIdString);
|
|
315
|
+
this.scheduleDebouncedImmediatePoll();
|
|
205
316
|
}
|
|
206
317
|
else {
|
|
207
318
|
// We received a WS notification recently, continue monitoring
|
|
@@ -210,15 +321,32 @@ class WebSocketProgramAccountSubscriberV2 {
|
|
|
210
321
|
}, this.pollingIntervalMs);
|
|
211
322
|
this.pollingTimeouts.set(accountIdString, timeoutId);
|
|
212
323
|
}
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
// If this is the first account being polled, start batch polling
|
|
217
|
-
if (this.accountsCurrentlyPolling.size === 1) {
|
|
218
|
-
this.startBatchPolling();
|
|
324
|
+
scheduleDebouncedImmediatePoll() {
|
|
325
|
+
if (this.debouncedImmediatePollTimeout) {
|
|
326
|
+
clearTimeout(this.debouncedImmediatePollTimeout);
|
|
219
327
|
}
|
|
328
|
+
this.debouncedImmediatePollTimeout = setTimeout(async () => {
|
|
329
|
+
var _a;
|
|
330
|
+
try {
|
|
331
|
+
await this.pollAllAccounts();
|
|
332
|
+
// After the immediate poll, ensure continuous batch polling is active
|
|
333
|
+
if (!this.batchPollingTimeout &&
|
|
334
|
+
this.accountsCurrentlyPolling.size > 0) {
|
|
335
|
+
this.startBatchPolling();
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
catch (e) {
|
|
339
|
+
if ((_a = this.resubOpts) === null || _a === void 0 ? void 0 : _a.logResubMessages) {
|
|
340
|
+
console.log(`[${this.subscriptionName}] Error during debounced immediate poll:`, e);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
}, this.debouncedImmediatePollMs);
|
|
220
344
|
}
|
|
221
345
|
startBatchPolling() {
|
|
346
|
+
var _a;
|
|
347
|
+
if ((_a = this.resubOpts) === null || _a === void 0 ? void 0 : _a.logResubMessages) {
|
|
348
|
+
console.debug(`[${this.subscriptionName}] Scheduling batch polling`);
|
|
349
|
+
}
|
|
222
350
|
// Clear existing batch polling timeout
|
|
223
351
|
if (this.batchPollingTimeout) {
|
|
224
352
|
clearTimeout(this.batchPollingTimeout);
|
|
@@ -238,8 +366,32 @@ class WebSocketProgramAccountSubscriberV2 {
|
|
|
238
366
|
if (accountsToPoll.length === 0) {
|
|
239
367
|
return;
|
|
240
368
|
}
|
|
369
|
+
if ((_a = this.resubOpts) === null || _a === void 0 ? void 0 : _a.logResubMessages) {
|
|
370
|
+
console.debug(`[${this.subscriptionName}] Polling all accounts`, accountsToPoll.length, 'accounts');
|
|
371
|
+
}
|
|
372
|
+
// Use the shared batch fetch method
|
|
373
|
+
await this.fetchAccountsBatch(accountsToPoll);
|
|
374
|
+
}
|
|
375
|
+
catch (error) {
|
|
376
|
+
if ((_b = this.resubOpts) === null || _b === void 0 ? void 0 : _b.logResubMessages) {
|
|
377
|
+
console.log(`[${this.subscriptionName}] Error batch polling accounts:`, error);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
/**
|
|
382
|
+
* Fetches and populates all monitored accounts data without checking for missed changes
|
|
383
|
+
* This is used during initial subscription to populate data
|
|
384
|
+
*/
|
|
385
|
+
async fetchAndPopulateAllMonitoredAccounts() {
|
|
386
|
+
var _a;
|
|
387
|
+
try {
|
|
388
|
+
// Get all accounts currently being polled
|
|
389
|
+
const accountsToMonitor = Array.from(this.accountsToMonitor);
|
|
390
|
+
if (accountsToMonitor.length === 0) {
|
|
391
|
+
return;
|
|
392
|
+
}
|
|
241
393
|
// Fetch all accounts in a single batch request
|
|
242
|
-
const accountAddresses =
|
|
394
|
+
const accountAddresses = accountsToMonitor.map((accountId) => accountId);
|
|
243
395
|
const rpcResponse = await this.rpc
|
|
244
396
|
.getMultipleAccounts(accountAddresses, {
|
|
245
397
|
commitment: this.options.commitment,
|
|
@@ -248,8 +400,8 @@ class WebSocketProgramAccountSubscriberV2 {
|
|
|
248
400
|
.send();
|
|
249
401
|
const currentSlot = Number(rpcResponse.context.slot);
|
|
250
402
|
// Process each account response
|
|
251
|
-
for (let i = 0; i <
|
|
252
|
-
const accountIdString =
|
|
403
|
+
for (let i = 0; i < accountsToMonitor.length; i++) {
|
|
404
|
+
const accountIdString = accountsToMonitor[i];
|
|
253
405
|
const accountInfo = rpcResponse.value[i];
|
|
254
406
|
if (!accountInfo) {
|
|
255
407
|
continue;
|
|
@@ -258,24 +410,18 @@ class WebSocketProgramAccountSubscriberV2 {
|
|
|
258
410
|
if (!existingBufferAndSlot) {
|
|
259
411
|
// Account not in our map yet, add it
|
|
260
412
|
let newBuffer = undefined;
|
|
261
|
-
if (accountInfo
|
|
413
|
+
if (accountInfo) {
|
|
262
414
|
if (Array.isArray(accountInfo.data)) {
|
|
263
415
|
const [data, encoding] = accountInfo.data;
|
|
264
416
|
newBuffer = Buffer.from(data, encoding);
|
|
265
417
|
}
|
|
266
418
|
}
|
|
267
419
|
if (newBuffer) {
|
|
268
|
-
this.
|
|
269
|
-
buffer: newBuffer,
|
|
270
|
-
slot: currentSlot,
|
|
271
|
-
});
|
|
272
|
-
const account = this.decodeBuffer(this.accountDiscriminator, newBuffer);
|
|
273
|
-
const accountId = new web3_js_1.PublicKey(accountIdString);
|
|
274
|
-
this.onChange(accountId, account, { slot: currentSlot }, newBuffer);
|
|
420
|
+
this.updateBufferAndHandleChange(newBuffer, currentSlot, accountIdString);
|
|
275
421
|
}
|
|
276
422
|
continue;
|
|
277
423
|
}
|
|
278
|
-
//
|
|
424
|
+
// For initial population, just update the slot if we have newer data
|
|
279
425
|
if (currentSlot > existingBufferAndSlot.slot) {
|
|
280
426
|
let newBuffer = undefined;
|
|
281
427
|
if (accountInfo.data) {
|
|
@@ -289,70 +435,68 @@ class WebSocketProgramAccountSubscriberV2 {
|
|
|
289
435
|
}
|
|
290
436
|
}
|
|
291
437
|
}
|
|
292
|
-
//
|
|
293
|
-
if (newBuffer
|
|
294
|
-
(
|
|
295
|
-
!newBuffer.equals(existingBufferAndSlot.buffer))) {
|
|
296
|
-
if ((_a = this.resubOpts) === null || _a === void 0 ? void 0 : _a.logResubMessages) {
|
|
297
|
-
console.log(`[${this.subscriptionName}] Batch polling detected missed update for account ${accountIdString}, resubscribing`);
|
|
298
|
-
}
|
|
299
|
-
// We missed an update, resubscribe
|
|
300
|
-
await this.unsubscribe(true);
|
|
301
|
-
this.receivingData = false;
|
|
302
|
-
await this.subscribe(this.onChange);
|
|
303
|
-
return;
|
|
438
|
+
// Update with newer data if available
|
|
439
|
+
if (newBuffer) {
|
|
440
|
+
this.updateBufferAndHandleChange(newBuffer, currentSlot, accountIdString);
|
|
304
441
|
}
|
|
305
442
|
}
|
|
306
443
|
}
|
|
307
444
|
}
|
|
308
445
|
catch (error) {
|
|
309
|
-
if ((
|
|
310
|
-
console.log(`[${this.subscriptionName}] Error
|
|
446
|
+
if ((_a = this.resubOpts) === null || _a === void 0 ? void 0 : _a.logResubMessages) {
|
|
447
|
+
console.log(`[${this.subscriptionName}] Error fetching and populating monitored accounts:`, error);
|
|
311
448
|
}
|
|
312
449
|
}
|
|
313
450
|
}
|
|
314
|
-
|
|
451
|
+
/**
|
|
452
|
+
* Fetches all monitored accounts and checks for missed changes
|
|
453
|
+
* Returns true if a missed change was detected and resubscription is needed
|
|
454
|
+
*/
|
|
455
|
+
async fetchAllMonitoredAccounts() {
|
|
315
456
|
var _a, _b;
|
|
316
457
|
try {
|
|
317
|
-
//
|
|
318
|
-
const
|
|
458
|
+
// Get all accounts currently being polled
|
|
459
|
+
const accountsToMonitor = Array.from(this.accountsToMonitor);
|
|
460
|
+
if (accountsToMonitor.length === 0) {
|
|
461
|
+
return false;
|
|
462
|
+
}
|
|
463
|
+
// Fetch all accounts in a single batch request
|
|
464
|
+
const accountAddresses = accountsToMonitor.map((accountId) => accountId);
|
|
319
465
|
const rpcResponse = await this.rpc
|
|
320
|
-
.
|
|
466
|
+
.getMultipleAccounts(accountAddresses, {
|
|
321
467
|
commitment: this.options.commitment,
|
|
322
468
|
encoding: 'base64',
|
|
323
469
|
})
|
|
324
470
|
.send();
|
|
325
471
|
const currentSlot = Number(rpcResponse.context.slot);
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
472
|
+
// Process each account response
|
|
473
|
+
for (let i = 0; i < accountsToMonitor.length; i++) {
|
|
474
|
+
const accountIdString = accountsToMonitor[i];
|
|
475
|
+
const accountInfo = rpcResponse.value[i];
|
|
476
|
+
if (!accountInfo) {
|
|
477
|
+
continue;
|
|
478
|
+
}
|
|
479
|
+
const existingBufferAndSlot = this.bufferAndSlotMap.get(accountIdString);
|
|
480
|
+
if (!existingBufferAndSlot) {
|
|
481
|
+
// Account not in our map yet, add it
|
|
330
482
|
let newBuffer = undefined;
|
|
331
|
-
if (
|
|
332
|
-
if (Array.isArray(
|
|
333
|
-
const [data, encoding] =
|
|
483
|
+
if (accountInfo.data) {
|
|
484
|
+
if (Array.isArray(accountInfo.data)) {
|
|
485
|
+
const [data, encoding] = accountInfo.data;
|
|
334
486
|
newBuffer = Buffer.from(data, encoding);
|
|
335
487
|
}
|
|
336
488
|
}
|
|
337
489
|
if (newBuffer) {
|
|
338
|
-
this.
|
|
339
|
-
buffer: newBuffer,
|
|
340
|
-
slot: currentSlot,
|
|
341
|
-
});
|
|
342
|
-
const account = this.decodeBuffer(this.accountDiscriminator, newBuffer);
|
|
343
|
-
const accountId = new web3_js_1.PublicKey(accountIdString);
|
|
344
|
-
this.onChange(accountId, account, { slot: currentSlot }, newBuffer);
|
|
490
|
+
this.updateBufferAndHandleChange(newBuffer, currentSlot, accountIdString);
|
|
345
491
|
}
|
|
492
|
+
continue;
|
|
346
493
|
}
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
if (rpcResponse.value.data) {
|
|
354
|
-
if (Array.isArray(rpcResponse.value.data)) {
|
|
355
|
-
const [data, encoding] = rpcResponse.value.data;
|
|
494
|
+
// Check if we missed an update
|
|
495
|
+
if (currentSlot > existingBufferAndSlot.slot) {
|
|
496
|
+
let newBuffer = undefined;
|
|
497
|
+
if (accountInfo.data) {
|
|
498
|
+
if (Array.isArray(accountInfo.data)) {
|
|
499
|
+
const [data, encoding] = accountInfo.data;
|
|
356
500
|
if (encoding === 'base58') {
|
|
357
501
|
newBuffer = Buffer.from(bs58_1.default.decode(data));
|
|
358
502
|
}
|
|
@@ -361,25 +505,102 @@ class WebSocketProgramAccountSubscriberV2 {
|
|
|
361
505
|
}
|
|
362
506
|
}
|
|
363
507
|
}
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
508
|
+
// Check if buffer has changed
|
|
509
|
+
if (newBuffer &&
|
|
510
|
+
(!existingBufferAndSlot.buffer ||
|
|
511
|
+
!newBuffer.equals(existingBufferAndSlot.buffer))) {
|
|
512
|
+
if ((_a = this.resubOpts) === null || _a === void 0 ? void 0 : _a.logResubMessages) {
|
|
513
|
+
console.log(`[${this.subscriptionName}] Batch polling detected missed update for account ${accountIdString}, resubscribing`);
|
|
514
|
+
}
|
|
515
|
+
// We missed an update, return true to indicate resubscription is needed
|
|
516
|
+
return true;
|
|
371
517
|
}
|
|
372
|
-
// We missed an update, resubscribe
|
|
373
|
-
await this.unsubscribe(true);
|
|
374
|
-
this.receivingData = false;
|
|
375
|
-
await this.subscribe(this.onChange);
|
|
376
|
-
return;
|
|
377
518
|
}
|
|
378
519
|
}
|
|
520
|
+
// No missed changes detected
|
|
521
|
+
return false;
|
|
379
522
|
}
|
|
380
523
|
catch (error) {
|
|
381
524
|
if ((_b = this.resubOpts) === null || _b === void 0 ? void 0 : _b.logResubMessages) {
|
|
382
|
-
console.log(`[${this.subscriptionName}] Error polling
|
|
525
|
+
console.log(`[${this.subscriptionName}] Error batch polling accounts:`, error);
|
|
526
|
+
}
|
|
527
|
+
return false;
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
async fetchAccountsBatch(accountIds) {
|
|
531
|
+
var _a;
|
|
532
|
+
try {
|
|
533
|
+
// Chunk account IDs into groups of 100 (getMultipleAccounts limit)
|
|
534
|
+
const chunkSize = 100;
|
|
535
|
+
const chunks = [];
|
|
536
|
+
for (let i = 0; i < accountIds.length; i += chunkSize) {
|
|
537
|
+
chunks.push(accountIds.slice(i, i + chunkSize));
|
|
538
|
+
}
|
|
539
|
+
// Process all chunks concurrently
|
|
540
|
+
await Promise.all(chunks.map(async (chunk) => {
|
|
541
|
+
var _a;
|
|
542
|
+
const accountAddresses = chunk.map((accountId) => accountId);
|
|
543
|
+
const rpcResponse = await this.rpc
|
|
544
|
+
.getMultipleAccounts(accountAddresses, {
|
|
545
|
+
commitment: this.options.commitment,
|
|
546
|
+
encoding: 'base64',
|
|
547
|
+
})
|
|
548
|
+
.send();
|
|
549
|
+
const currentSlot = Number(rpcResponse.context.slot);
|
|
550
|
+
// Process each account response in this chunk
|
|
551
|
+
for (let i = 0; i < chunk.length; i++) {
|
|
552
|
+
const accountIdString = chunk[i];
|
|
553
|
+
const accountInfo = rpcResponse.value[i];
|
|
554
|
+
if (!accountInfo) {
|
|
555
|
+
continue;
|
|
556
|
+
}
|
|
557
|
+
const existingBufferAndSlot = this.bufferAndSlotMap.get(accountIdString);
|
|
558
|
+
if (!existingBufferAndSlot) {
|
|
559
|
+
// Account not in our map yet, add it
|
|
560
|
+
let newBuffer = undefined;
|
|
561
|
+
if (accountInfo.data) {
|
|
562
|
+
if (Array.isArray(accountInfo.data)) {
|
|
563
|
+
const [data, encoding] = accountInfo.data;
|
|
564
|
+
newBuffer = Buffer.from(data, encoding);
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
if (newBuffer) {
|
|
568
|
+
this.updateBufferAndHandleChange(newBuffer, currentSlot, accountIdString);
|
|
569
|
+
}
|
|
570
|
+
continue;
|
|
571
|
+
}
|
|
572
|
+
// Check if we missed an update
|
|
573
|
+
if (currentSlot > existingBufferAndSlot.slot) {
|
|
574
|
+
let newBuffer = undefined;
|
|
575
|
+
if (accountInfo.data) {
|
|
576
|
+
if (Array.isArray(accountInfo.data)) {
|
|
577
|
+
const [data, encoding] = accountInfo.data;
|
|
578
|
+
if (encoding === 'base58') {
|
|
579
|
+
newBuffer = Buffer.from(bs58_1.default.decode(data));
|
|
580
|
+
}
|
|
581
|
+
else {
|
|
582
|
+
newBuffer = Buffer.from(data, 'base64');
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
// Check if buffer has changed
|
|
587
|
+
if (newBuffer &&
|
|
588
|
+
(!existingBufferAndSlot.buffer ||
|
|
589
|
+
!newBuffer.equals(existingBufferAndSlot.buffer))) {
|
|
590
|
+
if ((_a = this.resubOpts) === null || _a === void 0 ? void 0 : _a.logResubMessages) {
|
|
591
|
+
console.log(`[${this.subscriptionName}] Batch polling detected missed update for account ${accountIdString}, signaling resubscription`);
|
|
592
|
+
}
|
|
593
|
+
// Signal missed change instead of immediately resubscribing
|
|
594
|
+
this.signalMissedChange(accountIdString);
|
|
595
|
+
return;
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
}));
|
|
600
|
+
}
|
|
601
|
+
catch (error) {
|
|
602
|
+
if ((_a = this.resubOpts) === null || _a === void 0 ? void 0 : _a.logResubMessages) {
|
|
603
|
+
console.log(`[${this.subscriptionName}] Error fetching accounts batch:`, error);
|
|
383
604
|
}
|
|
384
605
|
}
|
|
385
606
|
}
|
|
@@ -393,8 +614,60 @@ class WebSocketProgramAccountSubscriberV2 {
|
|
|
393
614
|
clearTimeout(this.batchPollingTimeout);
|
|
394
615
|
this.batchPollingTimeout = undefined;
|
|
395
616
|
}
|
|
617
|
+
// Clear initial fetch timeout
|
|
618
|
+
// if (this.initialFetchTimeout) {
|
|
619
|
+
// clearTimeout(this.initialFetchTimeout);
|
|
620
|
+
// this.initialFetchTimeout = undefined;
|
|
621
|
+
// }
|
|
622
|
+
// Clear resubscription timeout
|
|
623
|
+
if (this.resubscriptionTimeout) {
|
|
624
|
+
clearTimeout(this.resubscriptionTimeout);
|
|
625
|
+
this.resubscriptionTimeout = undefined;
|
|
626
|
+
}
|
|
396
627
|
// Clear accounts currently polling
|
|
397
628
|
this.accountsCurrentlyPolling.clear();
|
|
629
|
+
// Clear accounts pending initial monitor fetch
|
|
630
|
+
// this.accountsPendingInitialMonitorFetch.clear();
|
|
631
|
+
// Reset missed change flag and clear accounts with missed updates
|
|
632
|
+
this.missedChangeDetected = false;
|
|
633
|
+
this.accountsWithMissedUpdates.clear();
|
|
634
|
+
}
|
|
635
|
+
/**
|
|
636
|
+
* Centralized resubscription handler that only resubscribes once after checking all accounts
|
|
637
|
+
*/
|
|
638
|
+
async handleResubscription() {
|
|
639
|
+
var _a;
|
|
640
|
+
if (this.missedChangeDetected) {
|
|
641
|
+
if ((_a = this.resubOpts) === null || _a === void 0 ? void 0 : _a.logResubMessages) {
|
|
642
|
+
console.log(`[${this.subscriptionName}] Missed change detected for ${this.accountsWithMissedUpdates.size} accounts: ${Array.from(this.accountsWithMissedUpdates).join(', ')}, resubscribing`);
|
|
643
|
+
}
|
|
644
|
+
await this.unsubscribe(true);
|
|
645
|
+
this.receivingData = false;
|
|
646
|
+
await this.subscribe(this.onChange);
|
|
647
|
+
this.missedChangeDetected = false;
|
|
648
|
+
this.accountsWithMissedUpdates.clear();
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
/**
|
|
652
|
+
* Signal that a missed change was detected and schedule resubscription
|
|
653
|
+
*/
|
|
654
|
+
signalMissedChange(accountIdString) {
|
|
655
|
+
if (!this.missedChangeDetected) {
|
|
656
|
+
this.missedChangeDetected = true;
|
|
657
|
+
this.accountsWithMissedUpdates.add(accountIdString);
|
|
658
|
+
// Clear any existing resubscription timeout
|
|
659
|
+
if (this.resubscriptionTimeout) {
|
|
660
|
+
clearTimeout(this.resubscriptionTimeout);
|
|
661
|
+
}
|
|
662
|
+
// Schedule resubscription after a short delay to allow for batch processing
|
|
663
|
+
this.resubscriptionTimeout = setTimeout(async () => {
|
|
664
|
+
await this.handleResubscription();
|
|
665
|
+
}, 100); // 100ms delay to allow for batch processing
|
|
666
|
+
}
|
|
667
|
+
else {
|
|
668
|
+
// If already detected, just add the account to the set
|
|
669
|
+
this.accountsWithMissedUpdates.add(accountIdString);
|
|
670
|
+
}
|
|
398
671
|
}
|
|
399
672
|
unsubscribe(onResub = false) {
|
|
400
673
|
if (!onResub) {
|
|
@@ -415,6 +688,11 @@ class WebSocketProgramAccountSubscriberV2 {
|
|
|
415
688
|
return Promise.resolve();
|
|
416
689
|
}
|
|
417
690
|
// Method to add accounts to the polling list
|
|
691
|
+
/**
|
|
692
|
+
* Add an account to the monitored set.
|
|
693
|
+
* - Monitored accounts are subject to initial fetch and periodic batch polls
|
|
694
|
+
* if WS notifications are not observed within `pollingIntervalMs`.
|
|
695
|
+
*/
|
|
418
696
|
addAccountToMonitor(accountId) {
|
|
419
697
|
const accountIdString = accountId.toBase58();
|
|
420
698
|
this.accountsToMonitor.add(accountIdString);
|
|
@@ -442,6 +720,10 @@ class WebSocketProgramAccountSubscriberV2 {
|
|
|
442
720
|
}
|
|
443
721
|
}
|
|
444
722
|
// Method to set polling interval
|
|
723
|
+
/**
|
|
724
|
+
* Set the monitoring/polling interval for monitored accounts.
|
|
725
|
+
* Shorter intervals detect missed updates sooner but increase RPC load.
|
|
726
|
+
*/
|
|
445
727
|
setPollingInterval(intervalMs) {
|
|
446
728
|
this.pollingIntervalMs = intervalMs;
|
|
447
729
|
// Restart monitoring with new interval if already subscribed
|
|
@@ -449,5 +731,14 @@ class WebSocketProgramAccountSubscriberV2 {
|
|
|
449
731
|
this.startMonitoringForAccounts();
|
|
450
732
|
}
|
|
451
733
|
}
|
|
734
|
+
updateBufferAndHandleChange(newBuffer, newSlot, accountIdString) {
|
|
735
|
+
this.bufferAndSlotMap.set(accountIdString, {
|
|
736
|
+
buffer: newBuffer,
|
|
737
|
+
slot: newSlot,
|
|
738
|
+
});
|
|
739
|
+
const account = this.decodeBuffer(this.accountDiscriminator, newBuffer);
|
|
740
|
+
const accountIdPubkey = new web3_js_1.PublicKey(accountIdString);
|
|
741
|
+
this.onChange(accountIdPubkey, account, { slot: newSlot }, newBuffer);
|
|
742
|
+
}
|
|
452
743
|
}
|
|
453
|
-
exports.
|
|
744
|
+
exports.WebSocketProgramAccountsSubscriberV2 = WebSocketProgramAccountsSubscriberV2;
|