@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
@@ -0,0 +1,444 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.WebSocketDriftClientAccountSubscriberV2 = void 0;
4
+ const types_1 = require("./types");
5
+ const types_2 = require("../types");
6
+ const events_1 = require("events");
7
+ const pda_1 = require("../addresses/pda");
8
+ const web3_js_1 = require("@solana/web3.js");
9
+ const gill_1 = require("gill");
10
+ const oracleClientCache_1 = require("../oracles/oracleClientCache");
11
+ const quoteAssetOracleClient_1 = require("../oracles/quoteAssetOracleClient");
12
+ const config_1 = require("../config");
13
+ const utils_1 = require("./utils");
14
+ const oracleId_1 = require("../oracles/oracleId");
15
+ const types_3 = require("../types");
16
+ const memcmp_1 = require("../memcmp");
17
+ const webSocketProgramAccountsSubscriberV2_1 = require("./webSocketProgramAccountsSubscriberV2");
18
+ const webSocketAccountSubscriberV2_1 = require("./webSocketAccountSubscriberV2");
19
+ const ORACLE_DEFAULT_ID = (0, oracleId_1.getOracleId)(web3_js_1.PublicKey.default, types_3.OracleSource.QUOTE_ASSET);
20
+ class WebSocketDriftClientAccountSubscriberV2 {
21
+ constructor(program, perpMarketIndexes, spotMarketIndexes, oracleInfos, shouldFindAllMarketsAndOracles, delistedMarketSetting, resubOpts, commitment, skipInitialData) {
22
+ this.oracleClientCache = new oracleClientCache_1.OracleClientCache();
23
+ this.skipInitialData = true;
24
+ this.perpMarketAccountLatestData = new Map();
25
+ this.spotMarketAccountLatestData = new Map();
26
+ this.perpOracleMap = new Map();
27
+ this.perpOracleStringMap = new Map();
28
+ this.spotOracleMap = new Map();
29
+ this.spotOracleStringMap = new Map();
30
+ this.oracleSubscribers = new Map();
31
+ this.isSubscribing = false;
32
+ this.chunks = (array, size) => {
33
+ const result = [];
34
+ for (let i = 0; i < array.length; i += size) {
35
+ result.push(array.slice(i, i + size));
36
+ }
37
+ return result;
38
+ };
39
+ this.isSubscribed = false;
40
+ this.program = program;
41
+ this.eventEmitter = new events_1.EventEmitter();
42
+ this.perpMarketIndexes = perpMarketIndexes;
43
+ this.spotMarketIndexes = spotMarketIndexes;
44
+ this.oracleInfos = oracleInfos;
45
+ this.shouldFindAllMarketsAndOracles = shouldFindAllMarketsAndOracles;
46
+ this.delistedMarketSetting = delistedMarketSetting;
47
+ this.resubOpts = resubOpts;
48
+ this.commitment = commitment;
49
+ this.skipInitialData = skipInitialData !== null && skipInitialData !== void 0 ? skipInitialData : false;
50
+ const { rpc, rpcSubscriptions } = (0, gill_1.createSolanaClient)({
51
+ urlOrMoniker: this.program.provider.connection.rpcEndpoint,
52
+ });
53
+ this.rpc = rpc;
54
+ this.rpcSubscriptions = rpcSubscriptions;
55
+ }
56
+ async subscribe() {
57
+ try {
58
+ const startTime = performance.now();
59
+ if (this.isSubscribed) {
60
+ console.log(`[PROFILING] WebSocketDriftClientAccountSubscriberV2.subscribe() skipped - already subscribed`);
61
+ return true;
62
+ }
63
+ if (this.isSubscribing) {
64
+ console.log(`[PROFILING] WebSocketDriftClientAccountSubscriberV2.subscribe() waiting for existing subscription`);
65
+ return await this.subscriptionPromise;
66
+ }
67
+ this.isSubscribing = true;
68
+ // Initialize subscriptionPromiseResolver to a no-op function
69
+ this.subscriptionPromiseResolver = () => { };
70
+ this.subscriptionPromise = new Promise((res) => {
71
+ this.subscriptionPromiseResolver = res;
72
+ });
73
+ const [perpMarketAccountPubkeys, spotMarketAccountPubkeys] = await Promise.all([
74
+ Promise.all(this.perpMarketIndexes.map((marketIndex) => (0, pda_1.getPerpMarketPublicKey)(this.program.programId, marketIndex))),
75
+ Promise.all(this.spotMarketIndexes.map((marketIndex) => (0, pda_1.getSpotMarketPublicKey)(this.program.programId, marketIndex))),
76
+ ]);
77
+ // Profile findAllMarketsAndOracles if needed
78
+ let findAllMarketsDuration = 0;
79
+ if (this.shouldFindAllMarketsAndOracles) {
80
+ const findAllMarketsStartTime = performance.now();
81
+ const { perpMarketIndexes, perpMarketAccounts, spotMarketIndexes, spotMarketAccounts, oracleInfos, } = await (0, config_1.findAllMarketAndOracles)(this.program);
82
+ this.perpMarketIndexes = perpMarketIndexes;
83
+ this.spotMarketIndexes = spotMarketIndexes;
84
+ this.oracleInfos = oracleInfos;
85
+ // front run and set the initial data here to save extra gma call in set initial data
86
+ this.initialPerpMarketAccountData = new Map(perpMarketAccounts.map((market) => [market.marketIndex, market]));
87
+ this.initialSpotMarketAccountData = new Map(spotMarketAccounts.map((market) => [market.marketIndex, market]));
88
+ const findAllMarketsEndTime = performance.now();
89
+ findAllMarketsDuration =
90
+ findAllMarketsEndTime - findAllMarketsStartTime;
91
+ console.log(`[PROFILING] findAllMarketAndOracles completed in ${findAllMarketsDuration.toFixed(2)}ms (${perpMarketAccounts.length} perp markets, ${spotMarketAccounts.length} spot markets)`);
92
+ }
93
+ // Create subscribers
94
+ this.perpMarketAllAccountsSubscriber =
95
+ new webSocketProgramAccountsSubscriberV2_1.WebSocketProgramAccountsSubscriberV2('PerpMarketAccountsSubscriber', 'PerpMarket', this.program, this.program.account.perpMarket.coder.accounts.decodeUnchecked.bind(this.program.account.perpMarket.coder.accounts), {
96
+ filters: [(0, memcmp_1.getPerpMarketAccountsFilter)()],
97
+ commitment: this.commitment,
98
+ }, this.resubOpts, perpMarketAccountPubkeys // because we pass these in, it will monitor these accounts and fetch them right away
99
+ );
100
+ this.spotMarketAllAccountsSubscriber =
101
+ new webSocketProgramAccountsSubscriberV2_1.WebSocketProgramAccountsSubscriberV2('SpotMarketAccountsSubscriber', 'SpotMarket', this.program, this.program.account.spotMarket.coder.accounts.decodeUnchecked.bind(this.program.account.spotMarket.coder.accounts), {
102
+ filters: [(0, memcmp_1.getSpotMarketAccountsFilter)()],
103
+ commitment: this.commitment,
104
+ }, this.resubOpts, spotMarketAccountPubkeys // because we pass these in, it will monitor these accounts and fetch them right away
105
+ );
106
+ // Run all subscriptions in parallel
107
+ await Promise.all([
108
+ // Perp market subscription
109
+ this.perpMarketAllAccountsSubscriber.subscribe((_accountId, data, context, _buffer) => {
110
+ if (this.delistedMarketSetting !== types_1.DelistedMarketSetting.Subscribe &&
111
+ (0, types_2.isVariant)(data.status, 'delisted')) {
112
+ return;
113
+ }
114
+ this.perpMarketAccountLatestData.set(data.marketIndex, {
115
+ data,
116
+ slot: context.slot,
117
+ });
118
+ this.eventEmitter.emit('perpMarketAccountUpdate', data);
119
+ this.eventEmitter.emit('update');
120
+ }),
121
+ // Spot market subscription
122
+ this.spotMarketAllAccountsSubscriber.subscribe((_accountId, data, context, _buffer) => {
123
+ if (this.delistedMarketSetting !== types_1.DelistedMarketSetting.Subscribe &&
124
+ (0, types_2.isVariant)(data.status, 'delisted')) {
125
+ return;
126
+ }
127
+ this.spotMarketAccountLatestData.set(data.marketIndex, {
128
+ data,
129
+ slot: context.slot,
130
+ });
131
+ this.eventEmitter.emit('spotMarketAccountUpdate', data);
132
+ this.eventEmitter.emit('update');
133
+ }),
134
+ // State account subscription
135
+ (async () => {
136
+ const statePublicKey = await (0, pda_1.getDriftStateAccountPublicKey)(this.program.programId);
137
+ this.stateAccountSubscriber = new webSocketAccountSubscriberV2_1.WebSocketAccountSubscriberV2('state', this.program, statePublicKey, undefined, undefined, this.commitment, this.rpcSubscriptions, this.rpc);
138
+ await Promise.all([
139
+ this.stateAccountSubscriber.fetch(),
140
+ this.stateAccountSubscriber.subscribe((data) => {
141
+ this.eventEmitter.emit('stateAccountUpdate', data);
142
+ this.eventEmitter.emit('update');
143
+ }),
144
+ ]);
145
+ })(),
146
+ (async () => {
147
+ await this.setInitialData();
148
+ const subscribeToOraclesStartTime = performance.now();
149
+ await this.subscribeToOracles();
150
+ const subscribeToOraclesEndTime = performance.now();
151
+ const duration = subscribeToOraclesEndTime - subscribeToOraclesStartTime;
152
+ return duration;
153
+ })(),
154
+ ]);
155
+ // const initialPerpMarketDataFromLatestData = new Map(
156
+ // Array.from(this.perpMarketAccountLatestData.values()).map((data) => [
157
+ // data.data.marketIndex,
158
+ // data.data,
159
+ // ])
160
+ // );
161
+ // const initialSpotMarketDataFromLatestData = new Map(
162
+ // Array.from(this.spotMarketAccountLatestData.values()).map((data) => [
163
+ // data.data.marketIndex,
164
+ // data.data,
165
+ // ])
166
+ // );
167
+ // this.initialPerpMarketAccountData = initialPerpMarketDataFromLatestData;
168
+ // this.initialSpotMarketAccountData = initialSpotMarketDataFromLatestData;
169
+ await this.handleDelistedMarketOracles();
170
+ await Promise.all([this.setPerpOracleMap(), this.setSpotOracleMap()]);
171
+ this.eventEmitter.emit('update');
172
+ // delete initial data
173
+ this.removeInitialData();
174
+ const totalDuration = performance.now() - startTime;
175
+ console.log(`[PROFILING] WebSocketDriftClientAccountSubscriberV2.subscribe() completed in ${totalDuration.toFixed(2)}ms`);
176
+ // Resolve the subscription promise
177
+ this.isSubscribed = true;
178
+ this.isSubscribing = false;
179
+ // Before calling subscriptionPromiseResolver, check if it's defined
180
+ if (this.subscriptionPromiseResolver) {
181
+ this.subscriptionPromiseResolver(true);
182
+ }
183
+ return true;
184
+ }
185
+ catch (error) {
186
+ console.error('Subscription failed:', error);
187
+ this.isSubscribing = false;
188
+ this.subscriptionPromiseResolver(false);
189
+ return false;
190
+ }
191
+ }
192
+ async fetch() {
193
+ await this.setInitialData();
194
+ }
195
+ /**
196
+ * This is a no-op method that always returns true.
197
+ * Unlike the previous implementation, we don't need to manually subscribe to individual perp markets
198
+ * because we automatically receive updates for all program account changes via a single websocket subscription.
199
+ * This means any new perp markets will automatically be included without explicit subscription.
200
+ * @param marketIndex The perp market index to add (unused)
201
+ * @returns Promise that resolves to true
202
+ */
203
+ addPerpMarket(_marketIndex) {
204
+ return Promise.resolve(true);
205
+ }
206
+ /**
207
+ * This is a no-op method that always returns true.
208
+ * Unlike the previous implementation, we don't need to manually subscribe to individual spot markets
209
+ * because we automatically receive updates for all program account changes via a single websocket subscription.
210
+ * This means any new spot markets will automatically be included without explicit subscription.
211
+ * @param marketIndex The spot market index to add (unused)
212
+ * @returns Promise that resolves to true
213
+ */
214
+ addSpotMarket(_marketIndex) {
215
+ return Promise.resolve(true);
216
+ }
217
+ // TODO: need more options to skip loading perp market and spot market data. Because of how we fetch within the program account subscribers, I am commenting this all out
218
+ async setInitialData() {
219
+ var _a;
220
+ const connection = this.program.provider.connection;
221
+ // Profile oracle initial data setup
222
+ const oracleSetupStartTime = performance.now();
223
+ const oracleAccountPubkeyChunks = this.chunks(this.oracleInfos.map((oracleInfo) => oracleInfo.publicKey), 100);
224
+ const oracleAccountInfos = (await Promise.all(oracleAccountPubkeyChunks.map((oracleAccountPublicKeysChunk) => connection.getMultipleAccountsInfo(oracleAccountPublicKeysChunk)))).flat();
225
+ this.initialOraclePriceData = new Map(this.oracleInfos.reduce((result, oracleInfo, i) => {
226
+ if (!oracleAccountInfos[i]) {
227
+ return result;
228
+ }
229
+ const oracleClient = this.oracleClientCache.get(oracleInfo.source, connection, this.program);
230
+ const oraclePriceData = oracleClient.getOraclePriceDataFromBuffer(oracleAccountInfos[i].data);
231
+ result.push([
232
+ (0, oracleId_1.getOracleId)(oracleInfo.publicKey, oracleInfo.source),
233
+ oraclePriceData,
234
+ ]);
235
+ return result;
236
+ }, []));
237
+ const oracleSetupEndTime = performance.now();
238
+ const oracleSetupDuration = oracleSetupEndTime - oracleSetupStartTime;
239
+ if ((_a = this.resubOpts) === null || _a === void 0 ? void 0 : _a.logResubMessages) {
240
+ console.log(`[PROFILING] Oracle initial data setup completed in ${oracleSetupDuration.toFixed(2)}ms (${this.initialOraclePriceData.size} oracles)`);
241
+ }
242
+ // emit initial oracle price data
243
+ Array.from(this.initialOraclePriceData.entries()).forEach(([oracleId, oraclePriceData]) => {
244
+ const { publicKey, source } = (0, oracleId_1.getPublicKeyAndSourceFromOracleId)(oracleId);
245
+ this.eventEmitter.emit('oraclePriceUpdate', publicKey, source, oraclePriceData);
246
+ });
247
+ this.eventEmitter.emit('update');
248
+ }
249
+ removeInitialData() {
250
+ this.initialPerpMarketAccountData = new Map();
251
+ this.initialSpotMarketAccountData = new Map();
252
+ this.initialOraclePriceData = new Map();
253
+ }
254
+ async subscribeToOracles() {
255
+ const startTime = performance.now();
256
+ // Filter out default oracles and duplicates to avoid unnecessary subscriptions
257
+ const validOracleInfos = this.oracleInfos.filter((oracleInfo) => !this.oracleSubscribers.has((0, oracleId_1.getOracleId)(oracleInfo.publicKey, oracleInfo.source)));
258
+ await Promise.all(validOracleInfos.map((oracleInfo) => this.subscribeToOracle(oracleInfo)));
259
+ const totalDuration = performance.now() - startTime;
260
+ console.log(`[PROFILING] subscribeToOracles() completed in ${totalDuration.toFixed(2)}ms`);
261
+ return true;
262
+ }
263
+ async subscribeToOracle(oracleInfo) {
264
+ var _a;
265
+ try {
266
+ const oracleId = (0, oracleId_1.getOracleId)(oracleInfo.publicKey, oracleInfo.source);
267
+ const client = this.oracleClientCache.get(oracleInfo.source, this.program.provider.connection, this.program);
268
+ const accountSubscriber = new webSocketAccountSubscriberV2_1.WebSocketAccountSubscriberV2('oracle', this.program, oracleInfo.publicKey, (buffer) => {
269
+ return client.getOraclePriceDataFromBuffer(buffer);
270
+ }, this.resubOpts, this.commitment, this.rpcSubscriptions, this.rpc);
271
+ const initialOraclePriceData = (_a = this.initialOraclePriceData) === null || _a === void 0 ? void 0 : _a.get(oracleId);
272
+ if (initialOraclePriceData) {
273
+ accountSubscriber.setData(initialOraclePriceData);
274
+ }
275
+ await accountSubscriber.subscribe((data) => {
276
+ this.eventEmitter.emit('oraclePriceUpdate', oracleInfo.publicKey, oracleInfo.source, data);
277
+ this.eventEmitter.emit('update');
278
+ });
279
+ this.oracleSubscribers.set(oracleId, accountSubscriber);
280
+ return true;
281
+ }
282
+ catch (error) {
283
+ console.error(`Failed to subscribe to oracle ${oracleInfo.publicKey.toString()}:`, error);
284
+ return false;
285
+ }
286
+ }
287
+ async unsubscribeFromMarketAccounts() {
288
+ await this.perpMarketAllAccountsSubscriber.unsubscribe();
289
+ }
290
+ async unsubscribeFromSpotMarketAccounts() {
291
+ await this.spotMarketAllAccountsSubscriber.unsubscribe();
292
+ }
293
+ async unsubscribeFromOracles() {
294
+ await Promise.all(Array.from(this.oracleSubscribers.values()).map((accountSubscriber) => accountSubscriber.unsubscribe()));
295
+ }
296
+ async unsubscribe() {
297
+ var _a;
298
+ if (!this.isSubscribed) {
299
+ return;
300
+ }
301
+ if (this.subscriptionPromise) {
302
+ await this.subscriptionPromise;
303
+ }
304
+ await Promise.all([
305
+ (_a = this.stateAccountSubscriber) === null || _a === void 0 ? void 0 : _a.unsubscribe(),
306
+ this.unsubscribeFromMarketAccounts(),
307
+ this.unsubscribeFromSpotMarketAccounts(),
308
+ this.unsubscribeFromOracles(),
309
+ ]);
310
+ this.isSubscribed = false;
311
+ this.isSubscribing = false;
312
+ this.subscriptionPromiseResolver = () => { };
313
+ }
314
+ async addOracle(oracleInfo) {
315
+ const oracleId = (0, oracleId_1.getOracleId)(oracleInfo.publicKey, oracleInfo.source);
316
+ if (this.oracleSubscribers.has(oracleId)) {
317
+ return true;
318
+ }
319
+ if (oracleInfo.publicKey.equals(web3_js_1.PublicKey.default)) {
320
+ return true;
321
+ }
322
+ return this.subscribeToOracle(oracleInfo);
323
+ }
324
+ async setPerpOracleMap() {
325
+ const perpMarkets = this.getMarketAccountsAndSlots();
326
+ const addOraclePromises = [];
327
+ for (const perpMarket of perpMarkets) {
328
+ if (!perpMarket || !perpMarket.data) {
329
+ continue;
330
+ }
331
+ const perpMarketAccount = perpMarket.data;
332
+ const perpMarketIndex = perpMarketAccount.marketIndex;
333
+ const oracle = perpMarketAccount.amm.oracle;
334
+ const oracleId = (0, oracleId_1.getOracleId)(oracle, perpMarket.data.amm.oracleSource);
335
+ if (!this.oracleSubscribers.has(oracleId)) {
336
+ addOraclePromises.push(this.addOracle({
337
+ publicKey: oracle,
338
+ source: perpMarket.data.amm.oracleSource,
339
+ }));
340
+ }
341
+ this.perpOracleMap.set(perpMarketIndex, oracle);
342
+ this.perpOracleStringMap.set(perpMarketIndex, oracleId);
343
+ }
344
+ await Promise.all(addOraclePromises);
345
+ }
346
+ async setSpotOracleMap() {
347
+ const spotMarkets = this.getSpotMarketAccountsAndSlots();
348
+ const addOraclePromises = [];
349
+ for (const spotMarket of spotMarkets) {
350
+ if (!spotMarket || !spotMarket.data) {
351
+ continue;
352
+ }
353
+ const spotMarketAccount = spotMarket.data;
354
+ const spotMarketIndex = spotMarketAccount.marketIndex;
355
+ const oracle = spotMarketAccount.oracle;
356
+ const oracleId = (0, oracleId_1.getOracleId)(oracle, spotMarketAccount.oracleSource);
357
+ if (!this.oracleSubscribers.has(oracleId)) {
358
+ addOraclePromises.push(this.addOracle({
359
+ publicKey: oracle,
360
+ source: spotMarketAccount.oracleSource,
361
+ }));
362
+ }
363
+ this.spotOracleMap.set(spotMarketIndex, oracle);
364
+ this.spotOracleStringMap.set(spotMarketIndex, oracleId);
365
+ }
366
+ await Promise.all(addOraclePromises);
367
+ }
368
+ async handleDelistedMarketOracles() {
369
+ if (this.delistedMarketSetting === types_1.DelistedMarketSetting.Subscribe) {
370
+ return;
371
+ }
372
+ const { oracles } = (0, utils_1.findDelistedPerpMarketsAndOracles)(this.getMarketAccountsAndSlots(), this.getSpotMarketAccountsAndSlots());
373
+ for (const oracle of oracles) {
374
+ const oracleId = (0, oracleId_1.getOracleId)(oracle.publicKey, oracle.source);
375
+ if (this.oracleSubscribers.has(oracleId)) {
376
+ await this.oracleSubscribers.get(oracleId).unsubscribe();
377
+ if (this.delistedMarketSetting === types_1.DelistedMarketSetting.Discard) {
378
+ this.oracleSubscribers.delete(oracleId);
379
+ }
380
+ }
381
+ }
382
+ }
383
+ assertIsSubscribed() {
384
+ if (!this.isSubscribed) {
385
+ throw new types_1.NotSubscribedError('You must call `subscribe` before using this function');
386
+ }
387
+ }
388
+ getStateAccountAndSlot() {
389
+ this.assertIsSubscribed();
390
+ return this.stateAccountSubscriber.dataAndSlot;
391
+ }
392
+ getMarketAccountAndSlot(marketIndex) {
393
+ this.assertIsSubscribed();
394
+ return this.perpMarketAccountLatestData.get(marketIndex);
395
+ }
396
+ getMarketAccountsAndSlots() {
397
+ return Array.from(this.perpMarketAccountLatestData.values());
398
+ }
399
+ getSpotMarketAccountAndSlot(marketIndex) {
400
+ this.assertIsSubscribed();
401
+ return this.spotMarketAccountLatestData.get(marketIndex);
402
+ }
403
+ getSpotMarketAccountsAndSlots() {
404
+ return Array.from(this.spotMarketAccountLatestData.values());
405
+ }
406
+ getOraclePriceDataAndSlot(oracleId) {
407
+ var _a;
408
+ this.assertIsSubscribed();
409
+ if (oracleId === ORACLE_DEFAULT_ID) {
410
+ return {
411
+ data: quoteAssetOracleClient_1.QUOTE_ORACLE_PRICE_DATA,
412
+ slot: 0,
413
+ };
414
+ }
415
+ return (_a = this.oracleSubscribers.get(oracleId)) === null || _a === void 0 ? void 0 : _a.dataAndSlot;
416
+ }
417
+ getOraclePriceDataAndSlotForPerpMarket(marketIndex) {
418
+ const perpMarketAccount = this.getMarketAccountAndSlot(marketIndex);
419
+ const oracle = this.perpOracleMap.get(marketIndex);
420
+ const oracleId = this.perpOracleStringMap.get(marketIndex);
421
+ if (!perpMarketAccount || !oracleId) {
422
+ return undefined;
423
+ }
424
+ if (!perpMarketAccount.data.amm.oracle.equals(oracle)) {
425
+ // If the oracle has changed, we need to update the oracle map in background
426
+ this.setPerpOracleMap();
427
+ }
428
+ return this.getOraclePriceDataAndSlot(oracleId);
429
+ }
430
+ getOraclePriceDataAndSlotForSpotMarket(marketIndex) {
431
+ const spotMarketAccount = this.getSpotMarketAccountAndSlot(marketIndex);
432
+ const oracle = this.spotOracleMap.get(marketIndex);
433
+ const oracleId = this.spotOracleStringMap.get(marketIndex);
434
+ if (!spotMarketAccount || !oracleId) {
435
+ return undefined;
436
+ }
437
+ if (!spotMarketAccount.data.oracle.equals(oracle)) {
438
+ // If the oracle has changed, we need to update the oracle map in background
439
+ this.setSpotOracleMap();
440
+ }
441
+ return this.getOraclePriceDataAndSlot(oracleId);
442
+ }
443
+ }
444
+ exports.WebSocketDriftClientAccountSubscriberV2 = WebSocketDriftClientAccountSubscriberV2;
@@ -0,0 +1,145 @@
1
+ /// <reference types="node" />
2
+ /// <reference types="node" />
3
+ import { BufferAndSlot, ProgramAccountSubscriber, ResubOpts } from './types';
4
+ import { Program } from '@coral-xyz/anchor';
5
+ import { Commitment, Context, MemcmpFilter, PublicKey } from '@solana/web3.js';
6
+ import { AccountInfoBase, AccountInfoWithBase58EncodedData, AccountInfoWithBase64EncodedData, type Address } from 'gill';
7
+ /**
8
+ * WebSocketProgramAccountsSubscriberV2
9
+ *
10
+ * High-level overview
11
+ * - WebSocket-first subscriber for Solana program accounts that also layers in
12
+ * targeted polling to detect missed updates reliably.
13
+ * - Emits decoded account updates via the provided `onChange` callback.
14
+ * - Designed to focus extra work on the specific accounts the consumer cares
15
+ * about ("monitored accounts") while keeping baseline WS behavior for the
16
+ * full program subscription.
17
+ *
18
+ * Why polling if this is a WebSocket subscriber?
19
+ * - WS infra can stall, drop, or reorder notifications under network stress or
20
+ * provider hiccups. When that happens, critical account changes can be missed.
21
+ * - To mitigate this, the class accepts a set of accounts (provided via constructor) to monitor
22
+ * and uses light polling to verify whether a WS change was missed.
23
+ * - If polling detects a newer slot with different data than the last seen
24
+ * buffer, a centralized resubscription is triggered to restore a clean stream.
25
+ *
26
+ * Initial fetch (on subscribe)
27
+ * - On `subscribe()`, we first perform a single batched fetch of all monitored
28
+ * accounts ("initial monitor fetch").
29
+ * - Purpose: seed the internal `bufferAndSlotMap` and emit the latest state so
30
+ * consumers have up-to-date data immediately, even before WS events arrive.
31
+ * - This step does not decide resubscription; it only establishes ground truth.
32
+ *
33
+ * Continuous polling (only for monitored accounts)
34
+ * - After seeding, each monitored account is put into a monitoring cycle:
35
+ * 1) If no WS notification for an account is observed for `pollingIntervalMs`,
36
+ * we enqueue it for a batched fetch (buffered for a short window).
37
+ * 2) Once an account enters the "currently polling" set, a shared batch poll
38
+ * runs every `pollingIntervalMs` across all such accounts.
39
+ * 3) If WS notifications resume for an account, that account is removed from
40
+ * the polling set and returns to passive monitoring.
41
+ * - Polling compares the newly fetched buffer with the last stored buffer at a
42
+ * later slot. A difference indicates a missed update; we schedule a single
43
+ * resubscription (coalesced across accounts) to re-sync.
44
+ *
45
+ * Accounts the consumer cares about
46
+ * - Provide accounts up-front via the constructor `accountsToMonitor`, or add
47
+ * them dynamically with `addAccountToMonitor()` and remove with
48
+ * `removeAccountFromMonitor()`.
49
+ * - Only these accounts incur additional polling safeguards; other accounts are
50
+ * still processed from the WS stream normally.
51
+ *
52
+ * Resubscription strategy
53
+ * - Missed updates from any monitored account are coalesced and trigger a single
54
+ * resubscription after a short delay. This avoids rapid churn.
55
+ * - If `resubOpts.resubTimeoutMs` is set, an inactivity timer also performs a
56
+ * batch check of monitored accounts. If a missed update is found, the same
57
+ * centralized resubscription flow is used.
58
+ *
59
+ * Tuning knobs
60
+ * - `setPollingInterval(ms)`: adjust how often monitoring/polling runs
61
+ * (default 30s). Shorter = faster detection, higher RPC load.
62
+ * - Debounced immediate poll (~100ms): batches accounts added to polling right after inactivity.
63
+ * - Batch size for `getMultipleAccounts` is limited to 100, requests are chunked
64
+ * and processed concurrently.
65
+ */
66
+ export declare class WebSocketProgramAccountsSubscriberV2<T> implements ProgramAccountSubscriber<T> {
67
+ subscriptionName: string;
68
+ accountDiscriminator: string;
69
+ bufferAndSlotMap: Map<string, BufferAndSlot>;
70
+ program: Program;
71
+ decodeBuffer: (accountName: string, ix: Buffer) => T;
72
+ onChange: (accountId: PublicKey, data: T, context: Context, buffer: Buffer) => void;
73
+ listenerId?: number;
74
+ resubOpts: ResubOpts;
75
+ isUnsubscribing: boolean;
76
+ timeoutId?: ReturnType<typeof setTimeout>;
77
+ options: {
78
+ filters: MemcmpFilter[];
79
+ commitment?: Commitment;
80
+ };
81
+ receivingData: boolean;
82
+ private rpc;
83
+ private rpcSubscriptions;
84
+ private abortController?;
85
+ private accountsToMonitor;
86
+ private pollingIntervalMs;
87
+ private pollingTimeouts;
88
+ private lastWsNotificationTime;
89
+ private accountsCurrentlyPolling;
90
+ private batchPollingTimeout?;
91
+ private debouncedImmediatePollTimeout?;
92
+ private debouncedImmediatePollMs;
93
+ private missedChangeDetected;
94
+ private resubscriptionTimeout?;
95
+ private accountsWithMissedUpdates;
96
+ constructor(subscriptionName: string, accountDiscriminator: string, program: Program, decodeBufferFn: (accountName: string, ix: Buffer) => T, options?: {
97
+ filters: MemcmpFilter[];
98
+ commitment?: Commitment;
99
+ }, resubOpts?: ResubOpts, accountsToMonitor?: PublicKey[]);
100
+ private handleNotificationLoop;
101
+ subscribe(onChange: (accountId: PublicKey, data: T, context: Context, buffer: Buffer) => void): Promise<void>;
102
+ protected setTimeout(): void;
103
+ handleRpcResponse(context: {
104
+ slot: bigint;
105
+ }, accountId: Address, accountInfo?: AccountInfoBase & (AccountInfoWithBase58EncodedData | AccountInfoWithBase64EncodedData)['data']): void;
106
+ private startMonitoringForAccounts;
107
+ private startMonitoringForAccount;
108
+ private scheduleDebouncedImmediatePoll;
109
+ private startBatchPolling;
110
+ private pollAllAccounts;
111
+ /**
112
+ * Fetches and populates all monitored accounts data without checking for missed changes
113
+ * This is used during initial subscription to populate data
114
+ */
115
+ private fetchAndPopulateAllMonitoredAccounts;
116
+ /**
117
+ * Fetches all monitored accounts and checks for missed changes
118
+ * Returns true if a missed change was detected and resubscription is needed
119
+ */
120
+ private fetchAllMonitoredAccounts;
121
+ private fetchAccountsBatch;
122
+ private clearPollingTimeouts;
123
+ /**
124
+ * Centralized resubscription handler that only resubscribes once after checking all accounts
125
+ */
126
+ private handleResubscription;
127
+ /**
128
+ * Signal that a missed change was detected and schedule resubscription
129
+ */
130
+ private signalMissedChange;
131
+ unsubscribe(onResub?: boolean): Promise<void>;
132
+ /**
133
+ * Add an account to the monitored set.
134
+ * - Monitored accounts are subject to initial fetch and periodic batch polls
135
+ * if WS notifications are not observed within `pollingIntervalMs`.
136
+ */
137
+ addAccountToMonitor(accountId: PublicKey): void;
138
+ removeAccountFromMonitor(accountId: PublicKey): void;
139
+ /**
140
+ * Set the monitoring/polling interval for monitored accounts.
141
+ * Shorter intervals detect missed updates sooner but increase RPC load.
142
+ */
143
+ setPollingInterval(intervalMs: number): void;
144
+ private updateBufferAndHandleChange;
145
+ }