@drift-labs/sdk 2.145.0-beta.2 → 2.145.0

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 (73) hide show
  1. package/VERSION +1 -1
  2. package/lib/browser/accounts/types.d.ts +13 -1
  3. package/lib/browser/accounts/webSocketProgramAccountSubscriberV2.d.ts +53 -0
  4. package/lib/browser/accounts/webSocketProgramAccountSubscriberV2.js +453 -0
  5. package/lib/browser/addresses/pda.d.ts +9 -0
  6. package/lib/browser/addresses/pda.js +60 -1
  7. package/lib/browser/adminClient.d.ts +160 -8
  8. package/lib/browser/adminClient.js +754 -18
  9. package/lib/browser/constituentMap/constituentMap.d.ts +64 -0
  10. package/lib/browser/constituentMap/constituentMap.js +170 -0
  11. package/lib/browser/constituentMap/pollingConstituentAccountSubscriber.d.ts +24 -0
  12. package/lib/browser/constituentMap/pollingConstituentAccountSubscriber.js +60 -0
  13. package/lib/browser/constituentMap/webSocketConstituentAccountSubscriber.d.ts +24 -0
  14. package/lib/browser/constituentMap/webSocketConstituentAccountSubscriber.js +58 -0
  15. package/lib/browser/driftClient.d.ts +89 -2
  16. package/lib/browser/driftClient.js +486 -27
  17. package/lib/browser/driftClientConfig.d.ts +2 -7
  18. package/lib/browser/idl/drift.json +4304 -1380
  19. package/lib/browser/index.d.ts +1 -4
  20. package/lib/browser/index.js +2 -9
  21. package/lib/browser/memcmp.d.ts +3 -1
  22. package/lib/browser/memcmp.js +19 -1
  23. package/lib/browser/types.d.ts +147 -0
  24. package/lib/browser/types.js +13 -1
  25. package/lib/node/accounts/types.d.ts +13 -1
  26. package/lib/node/accounts/types.d.ts.map +1 -1
  27. package/lib/node/accounts/webSocketProgramAccountSubscriberV2.d.ts +54 -0
  28. package/lib/node/accounts/webSocketProgramAccountSubscriberV2.d.ts.map +1 -0
  29. package/lib/node/accounts/webSocketProgramAccountSubscriberV2.js +453 -0
  30. package/lib/node/addresses/pda.d.ts +9 -0
  31. package/lib/node/addresses/pda.d.ts.map +1 -1
  32. package/lib/node/addresses/pda.js +60 -1
  33. package/lib/node/adminClient.d.ts +160 -8
  34. package/lib/node/adminClient.d.ts.map +1 -1
  35. package/lib/node/adminClient.js +754 -18
  36. package/lib/node/constituentMap/constituentMap.d.ts +65 -0
  37. package/lib/node/constituentMap/constituentMap.d.ts.map +1 -0
  38. package/lib/node/constituentMap/constituentMap.js +170 -0
  39. package/lib/node/constituentMap/pollingConstituentAccountSubscriber.d.ts +25 -0
  40. package/lib/node/constituentMap/pollingConstituentAccountSubscriber.d.ts.map +1 -0
  41. package/lib/node/constituentMap/pollingConstituentAccountSubscriber.js +60 -0
  42. package/lib/node/constituentMap/webSocketConstituentAccountSubscriber.d.ts +25 -0
  43. package/lib/node/constituentMap/webSocketConstituentAccountSubscriber.d.ts.map +1 -0
  44. package/lib/node/constituentMap/webSocketConstituentAccountSubscriber.js +58 -0
  45. package/lib/node/driftClient.d.ts +89 -2
  46. package/lib/node/driftClient.d.ts.map +1 -1
  47. package/lib/node/driftClient.js +486 -27
  48. package/lib/node/driftClientConfig.d.ts +2 -7
  49. package/lib/node/driftClientConfig.d.ts.map +1 -1
  50. package/lib/node/idl/drift.json +4304 -1380
  51. package/lib/node/index.d.ts +1 -4
  52. package/lib/node/index.d.ts.map +1 -1
  53. package/lib/node/index.js +2 -9
  54. package/lib/node/memcmp.d.ts +3 -1
  55. package/lib/node/memcmp.d.ts.map +1 -1
  56. package/lib/node/memcmp.js +19 -1
  57. package/lib/node/types.d.ts +147 -0
  58. package/lib/node/types.d.ts.map +1 -1
  59. package/lib/node/types.js +13 -1
  60. package/package.json +1 -1
  61. package/src/accounts/types.ts +20 -0
  62. package/src/accounts/webSocketProgramAccountSubscriberV2.ts +596 -0
  63. package/src/addresses/pda.ts +115 -1
  64. package/src/adminClient.ts +1612 -41
  65. package/src/constituentMap/constituentMap.ts +285 -0
  66. package/src/constituentMap/pollingConstituentAccountSubscriber.ts +97 -0
  67. package/src/constituentMap/webSocketConstituentAccountSubscriber.ts +112 -0
  68. package/src/driftClient.ts +1097 -17
  69. package/src/driftClientConfig.ts +8 -15
  70. package/src/idl/drift.json +4304 -1380
  71. package/src/index.ts +1 -4
  72. package/src/memcmp.ts +23 -1
  73. package/src/types.ts +160 -0
@@ -0,0 +1,596 @@
1
+ import { BufferAndSlot, ProgramAccountSubscriber, ResubOpts } from './types';
2
+ import { AnchorProvider, Program } from '@coral-xyz/anchor';
3
+ import { Commitment, Context, MemcmpFilter, PublicKey } from '@solana/web3.js';
4
+ import {
5
+ AccountInfoBase,
6
+ AccountInfoWithBase58EncodedData,
7
+ AccountInfoWithBase64EncodedData,
8
+ createSolanaClient,
9
+ isAddress,
10
+ type Address,
11
+ type Commitment as GillCommitment,
12
+ } from 'gill';
13
+ import bs58 from 'bs58';
14
+
15
+ export class WebSocketProgramAccountSubscriberV2<T>
16
+ implements ProgramAccountSubscriber<T>
17
+ {
18
+ subscriptionName: string;
19
+ accountDiscriminator: string;
20
+ bufferAndSlot?: BufferAndSlot;
21
+ bufferAndSlotMap: Map<string, BufferAndSlot> = new Map();
22
+ program: Program;
23
+ decodeBuffer: (accountName: string, ix: Buffer) => T;
24
+ onChange: (
25
+ accountId: PublicKey,
26
+ data: T,
27
+ context: Context,
28
+ buffer: Buffer
29
+ ) => void;
30
+ listenerId?: number;
31
+ resubOpts?: ResubOpts;
32
+ isUnsubscribing = false;
33
+ timeoutId?: ReturnType<typeof setTimeout>;
34
+ options: { filters: MemcmpFilter[]; commitment?: Commitment };
35
+
36
+ receivingData = false;
37
+
38
+ // Gill client components
39
+ private rpc: ReturnType<typeof createSolanaClient>['rpc'];
40
+ private rpcSubscriptions: ReturnType<
41
+ typeof createSolanaClient
42
+ >['rpcSubscriptions'];
43
+ private abortController?: AbortController;
44
+
45
+ // Polling logic for specific accounts
46
+ private accountsToMonitor: Set<string> = new Set();
47
+ private pollingIntervalMs: number = 30000; // 30 seconds
48
+ private pollingTimeouts: Map<string, ReturnType<typeof setTimeout>> =
49
+ new Map();
50
+ private lastWsNotificationTime: Map<string, number> = new Map(); // Track last WS notification time per account
51
+ private accountsCurrentlyPolling: Set<string> = new Set(); // Track which accounts are being polled
52
+ private batchPollingTimeout?: ReturnType<typeof setTimeout>; // Single timeout for batch polling
53
+
54
+ public constructor(
55
+ subscriptionName: string,
56
+ accountDiscriminator: string,
57
+ program: Program,
58
+ decodeBufferFn: (accountName: string, ix: Buffer) => T,
59
+ options: { filters: MemcmpFilter[]; commitment?: Commitment } = {
60
+ filters: [],
61
+ },
62
+ resubOpts?: ResubOpts,
63
+ accountsToMonitor?: PublicKey[] // Optional list of accounts to poll
64
+ ) {
65
+ this.subscriptionName = subscriptionName;
66
+ this.accountDiscriminator = accountDiscriminator;
67
+ this.program = program;
68
+ this.decodeBuffer = decodeBufferFn;
69
+ this.resubOpts = resubOpts;
70
+ if (this.resubOpts?.resubTimeoutMs < 1000) {
71
+ console.log(
72
+ 'resubTimeoutMs should be at least 1000ms to avoid spamming resub'
73
+ );
74
+ }
75
+ this.options = options;
76
+ this.receivingData = false;
77
+
78
+ // Initialize accounts to monitor
79
+ if (accountsToMonitor) {
80
+ accountsToMonitor.forEach((account) => {
81
+ this.accountsToMonitor.add(account.toBase58());
82
+ });
83
+ }
84
+
85
+ // Initialize gill client using the same RPC URL as the program provider
86
+ const rpcUrl = (this.program.provider as AnchorProvider).connection
87
+ .rpcEndpoint;
88
+ const { rpc, rpcSubscriptions } = createSolanaClient({
89
+ urlOrMoniker: rpcUrl,
90
+ });
91
+ this.rpc = rpc;
92
+ this.rpcSubscriptions = rpcSubscriptions;
93
+ }
94
+
95
+ async subscribe(
96
+ onChange: (
97
+ accountId: PublicKey,
98
+ data: T,
99
+ context: Context,
100
+ buffer: Buffer
101
+ ) => void
102
+ ): Promise<void> {
103
+ if (this.listenerId != null || this.isUnsubscribing) {
104
+ return;
105
+ }
106
+
107
+ this.onChange = onChange;
108
+
109
+ // Create abort controller for proper cleanup
110
+ const abortController = new AbortController();
111
+ this.abortController = abortController;
112
+
113
+ // Subscribe to program account changes using gill's rpcSubscriptions
114
+ const programId = this.program.programId.toBase58();
115
+ if (isAddress(programId)) {
116
+ const subscription = await this.rpcSubscriptions
117
+ .programNotifications(programId, {
118
+ commitment: this.options.commitment as GillCommitment,
119
+ encoding: 'base64',
120
+ filters: this.options.filters.map((filter) => ({
121
+ memcmp: {
122
+ offset: BigInt(filter.memcmp.offset),
123
+ bytes: filter.memcmp.bytes as any,
124
+ encoding: 'base64' as const,
125
+ },
126
+ })),
127
+ })
128
+ .subscribe({
129
+ abortSignal: abortController.signal,
130
+ });
131
+
132
+ for await (const notification of subscription) {
133
+ if (this.resubOpts?.resubTimeoutMs) {
134
+ this.receivingData = true;
135
+ clearTimeout(this.timeoutId);
136
+ this.handleRpcResponse(
137
+ notification.context,
138
+ notification.value.account
139
+ );
140
+ this.setTimeout();
141
+ } else {
142
+ this.handleRpcResponse(
143
+ notification.context,
144
+ notification.value.account
145
+ );
146
+ }
147
+ }
148
+ }
149
+
150
+ this.listenerId = Math.random(); // Unique ID for logging purposes
151
+
152
+ if (this.resubOpts?.resubTimeoutMs) {
153
+ this.receivingData = true;
154
+ this.setTimeout();
155
+ }
156
+
157
+ // Start monitoring for accounts that may need polling if no WS event is received
158
+ this.startMonitoringForAccounts();
159
+ }
160
+
161
+ protected setTimeout(): void {
162
+ if (!this.onChange) {
163
+ throw new Error('onChange callback function must be set');
164
+ }
165
+ this.timeoutId = setTimeout(
166
+ async () => {
167
+ if (this.isUnsubscribing) {
168
+ // If we are in the process of unsubscribing, do not attempt to resubscribe
169
+ return;
170
+ }
171
+
172
+ if (this.receivingData) {
173
+ if (this.resubOpts?.logResubMessages) {
174
+ console.log(
175
+ `No ws data from ${this.subscriptionName} in ${this.resubOpts?.resubTimeoutMs}ms, resubscribing`
176
+ );
177
+ }
178
+ await this.unsubscribe(true);
179
+ this.receivingData = false;
180
+ await this.subscribe(this.onChange);
181
+ }
182
+ },
183
+ this.resubOpts?.resubTimeoutMs
184
+ );
185
+ }
186
+
187
+ handleRpcResponse(
188
+ context: { slot: bigint },
189
+ accountInfo?: AccountInfoBase &
190
+ (AccountInfoWithBase58EncodedData | AccountInfoWithBase64EncodedData)
191
+ ): void {
192
+ const newSlot = Number(context.slot);
193
+ let newBuffer: Buffer | undefined = undefined;
194
+
195
+ if (accountInfo) {
196
+ // Extract data from gill response
197
+ if (accountInfo.data) {
198
+ // Handle different data formats from gill
199
+ if (Array.isArray(accountInfo.data)) {
200
+ // If it's a tuple [data, encoding]
201
+ const [data, encoding] = accountInfo.data;
202
+
203
+ if (encoding === ('base58' as any)) {
204
+ // Convert base58 to buffer using bs58
205
+ newBuffer = Buffer.from(bs58.decode(data));
206
+ } else {
207
+ newBuffer = Buffer.from(data, 'base64');
208
+ }
209
+ }
210
+ }
211
+ }
212
+
213
+ // Convert gill's account key to PublicKey
214
+ // Note: accountInfo doesn't have a key property, we need to get it from the notification
215
+ // For now, we'll use a placeholder - this needs to be fixed based on the actual gill API
216
+ const accountId = new PublicKey('11111111111111111111111111111111'); // Placeholder
217
+ const accountIdString = accountId.toBase58();
218
+
219
+ const existingBufferAndSlot = this.bufferAndSlotMap.get(accountIdString);
220
+
221
+ // Track WebSocket notification time for this account
222
+ this.lastWsNotificationTime.set(accountIdString, Date.now());
223
+
224
+ // If this account was being polled, stop polling it
225
+ if (this.accountsCurrentlyPolling.has(accountIdString)) {
226
+ this.accountsCurrentlyPolling.delete(accountIdString);
227
+
228
+ // If no more accounts are being polled, stop batch polling
229
+ if (
230
+ this.accountsCurrentlyPolling.size === 0 &&
231
+ this.batchPollingTimeout
232
+ ) {
233
+ clearTimeout(this.batchPollingTimeout);
234
+ this.batchPollingTimeout = undefined;
235
+ }
236
+ }
237
+
238
+ if (!existingBufferAndSlot) {
239
+ if (newBuffer) {
240
+ this.bufferAndSlotMap.set(accountIdString, {
241
+ buffer: newBuffer,
242
+ slot: newSlot,
243
+ });
244
+ const account = this.decodeBuffer(this.accountDiscriminator, newBuffer);
245
+ this.onChange(accountId, account, { slot: newSlot }, newBuffer);
246
+ }
247
+ return;
248
+ }
249
+
250
+ if (newSlot < existingBufferAndSlot.slot) {
251
+ return;
252
+ }
253
+
254
+ const oldBuffer = existingBufferAndSlot.buffer;
255
+ if (newBuffer && (!oldBuffer || !newBuffer.equals(oldBuffer))) {
256
+ this.bufferAndSlotMap.set(accountIdString, {
257
+ buffer: newBuffer,
258
+ slot: newSlot,
259
+ });
260
+ const account = this.decodeBuffer(this.accountDiscriminator, newBuffer);
261
+ this.onChange(accountId, account, { slot: newSlot }, newBuffer);
262
+ }
263
+ }
264
+
265
+ private startMonitoringForAccounts(): void {
266
+ // Clear any existing polling timeouts
267
+ this.clearPollingTimeouts();
268
+
269
+ // Start monitoring for each account in the accountsToMonitor set
270
+ this.accountsToMonitor.forEach((accountIdString) => {
271
+ this.startMonitoringForAccount(accountIdString);
272
+ });
273
+ }
274
+
275
+ private startMonitoringForAccount(accountIdString: string): void {
276
+ // Clear existing timeout for this account
277
+ const existingTimeout = this.pollingTimeouts.get(accountIdString);
278
+ if (existingTimeout) {
279
+ clearTimeout(existingTimeout);
280
+ }
281
+
282
+ // Set up monitoring timeout - only start polling if no WS notification in 30s
283
+ const timeoutId = setTimeout(async () => {
284
+ // Check if we've received a WS notification for this account recently
285
+ const lastNotificationTime =
286
+ this.lastWsNotificationTime.get(accountIdString);
287
+ const currentTime = Date.now();
288
+
289
+ if (
290
+ !lastNotificationTime ||
291
+ currentTime - lastNotificationTime >= this.pollingIntervalMs
292
+ ) {
293
+ // No recent WS notification, start polling
294
+ await this.pollAccount(accountIdString);
295
+ // Schedule next poll
296
+ this.startPollingForAccount(accountIdString);
297
+ } else {
298
+ // We received a WS notification recently, continue monitoring
299
+ this.startMonitoringForAccount(accountIdString);
300
+ }
301
+ }, this.pollingIntervalMs);
302
+
303
+ this.pollingTimeouts.set(accountIdString, timeoutId);
304
+ }
305
+
306
+ private startPollingForAccount(accountIdString: string): void {
307
+ // Add account to polling set
308
+ this.accountsCurrentlyPolling.add(accountIdString);
309
+
310
+ // If this is the first account being polled, start batch polling
311
+ if (this.accountsCurrentlyPolling.size === 1) {
312
+ this.startBatchPolling();
313
+ }
314
+ }
315
+
316
+ private startBatchPolling(): void {
317
+ // Clear existing batch polling timeout
318
+ if (this.batchPollingTimeout) {
319
+ clearTimeout(this.batchPollingTimeout);
320
+ }
321
+
322
+ // Set up batch polling interval
323
+ this.batchPollingTimeout = setTimeout(async () => {
324
+ await this.pollAllAccounts();
325
+ // Schedule next batch poll
326
+ this.startBatchPolling();
327
+ }, this.pollingIntervalMs);
328
+ }
329
+
330
+ private async pollAllAccounts(): Promise<void> {
331
+ try {
332
+ // Get all accounts currently being polled
333
+ const accountsToPoll = Array.from(this.accountsCurrentlyPolling);
334
+ if (accountsToPoll.length === 0) {
335
+ return;
336
+ }
337
+
338
+ // Fetch all accounts in a single batch request
339
+ const accountAddresses = accountsToPoll.map(
340
+ (accountId) => accountId as Address
341
+ );
342
+ const rpcResponse = await this.rpc
343
+ .getMultipleAccounts(accountAddresses, {
344
+ commitment: this.options.commitment as GillCommitment,
345
+ encoding: 'base64',
346
+ })
347
+ .send();
348
+
349
+ const currentSlot = Number(rpcResponse.context.slot);
350
+
351
+ // Process each account response
352
+ for (let i = 0; i < accountsToPoll.length; i++) {
353
+ const accountIdString = accountsToPoll[i];
354
+ const accountInfo = rpcResponse.value[i];
355
+
356
+ if (!accountInfo) {
357
+ continue;
358
+ }
359
+
360
+ const existingBufferAndSlot =
361
+ this.bufferAndSlotMap.get(accountIdString);
362
+
363
+ if (!existingBufferAndSlot) {
364
+ // Account not in our map yet, add it
365
+ let newBuffer: Buffer | undefined = undefined;
366
+ if (accountInfo.data) {
367
+ if (Array.isArray(accountInfo.data)) {
368
+ const [data, encoding] = accountInfo.data;
369
+ newBuffer = Buffer.from(data, encoding);
370
+ }
371
+ }
372
+
373
+ if (newBuffer) {
374
+ this.bufferAndSlotMap.set(accountIdString, {
375
+ buffer: newBuffer,
376
+ slot: currentSlot,
377
+ });
378
+ const account = this.decodeBuffer(
379
+ this.accountDiscriminator,
380
+ newBuffer
381
+ );
382
+ const accountId = new PublicKey(accountIdString);
383
+ this.onChange(accountId, account, { slot: currentSlot }, newBuffer);
384
+ }
385
+ continue;
386
+ }
387
+
388
+ // Check if we missed an update
389
+ if (currentSlot > existingBufferAndSlot.slot) {
390
+ let newBuffer: Buffer | undefined = undefined;
391
+ if (accountInfo.data) {
392
+ if (Array.isArray(accountInfo.data)) {
393
+ const [data, encoding] = accountInfo.data;
394
+ if (encoding === ('base58' as any)) {
395
+ newBuffer = Buffer.from(bs58.decode(data));
396
+ } else {
397
+ newBuffer = Buffer.from(data, 'base64');
398
+ }
399
+ }
400
+ }
401
+
402
+ // Check if buffer has changed
403
+ if (
404
+ newBuffer &&
405
+ (!existingBufferAndSlot.buffer ||
406
+ !newBuffer.equals(existingBufferAndSlot.buffer))
407
+ ) {
408
+ if (this.resubOpts?.logResubMessages) {
409
+ console.log(
410
+ `[${this.subscriptionName}] Batch polling detected missed update for account ${accountIdString}, resubscribing`
411
+ );
412
+ }
413
+ // We missed an update, resubscribe
414
+ await this.unsubscribe(true);
415
+ this.receivingData = false;
416
+ await this.subscribe(this.onChange);
417
+ return;
418
+ }
419
+ }
420
+ }
421
+ } catch (error) {
422
+ if (this.resubOpts?.logResubMessages) {
423
+ console.log(
424
+ `[${this.subscriptionName}] Error batch polling accounts:`,
425
+ error
426
+ );
427
+ }
428
+ }
429
+ }
430
+
431
+ private async pollAccount(accountIdString: string): Promise<void> {
432
+ try {
433
+ // Fetch current account data using gill's rpc
434
+ const accountAddress = accountIdString as Address;
435
+ const rpcResponse = await this.rpc
436
+ .getAccountInfo(accountAddress, {
437
+ commitment: this.options.commitment as GillCommitment,
438
+ encoding: 'base64',
439
+ })
440
+ .send();
441
+
442
+ const currentSlot = Number(rpcResponse.context.slot);
443
+ const existingBufferAndSlot = this.bufferAndSlotMap.get(accountIdString);
444
+
445
+ if (!existingBufferAndSlot) {
446
+ // Account not in our map yet, add it
447
+ if (rpcResponse.value) {
448
+ let newBuffer: Buffer | undefined = undefined;
449
+ if (rpcResponse.value.data) {
450
+ if (Array.isArray(rpcResponse.value.data)) {
451
+ const [data, encoding] = rpcResponse.value.data;
452
+ newBuffer = Buffer.from(data, encoding);
453
+ }
454
+ }
455
+
456
+ if (newBuffer) {
457
+ this.bufferAndSlotMap.set(accountIdString, {
458
+ buffer: newBuffer,
459
+ slot: currentSlot,
460
+ });
461
+ const account = this.decodeBuffer(
462
+ this.accountDiscriminator,
463
+ newBuffer
464
+ );
465
+ const accountId = new PublicKey(accountIdString);
466
+ this.onChange(accountId, account, { slot: currentSlot }, newBuffer);
467
+ }
468
+ }
469
+ return;
470
+ }
471
+
472
+ // Check if we missed an update
473
+ if (currentSlot > existingBufferAndSlot.slot) {
474
+ let newBuffer: Buffer | undefined = undefined;
475
+ if (rpcResponse.value) {
476
+ if (rpcResponse.value.data) {
477
+ if (Array.isArray(rpcResponse.value.data)) {
478
+ const [data, encoding] = rpcResponse.value.data;
479
+ if (encoding === ('base58' as any)) {
480
+ newBuffer = Buffer.from(bs58.decode(data));
481
+ } else {
482
+ newBuffer = Buffer.from(data, 'base64');
483
+ }
484
+ }
485
+ }
486
+ }
487
+
488
+ // Check if buffer has changed
489
+ if (
490
+ newBuffer &&
491
+ (!existingBufferAndSlot.buffer ||
492
+ !newBuffer.equals(existingBufferAndSlot.buffer))
493
+ ) {
494
+ if (this.resubOpts?.logResubMessages) {
495
+ console.log(
496
+ `[${this.subscriptionName}] Polling detected missed update for account ${accountIdString}, resubscribing`
497
+ );
498
+ }
499
+ // We missed an update, resubscribe
500
+ await this.unsubscribe(true);
501
+ this.receivingData = false;
502
+ await this.subscribe(this.onChange);
503
+ return;
504
+ }
505
+ }
506
+ } catch (error) {
507
+ if (this.resubOpts?.logResubMessages) {
508
+ console.log(
509
+ `[${this.subscriptionName}] Error polling account ${accountIdString}:`,
510
+ error
511
+ );
512
+ }
513
+ }
514
+ }
515
+
516
+ private clearPollingTimeouts(): void {
517
+ this.pollingTimeouts.forEach((timeoutId) => {
518
+ clearTimeout(timeoutId);
519
+ });
520
+ this.pollingTimeouts.clear();
521
+
522
+ // Clear batch polling timeout
523
+ if (this.batchPollingTimeout) {
524
+ clearTimeout(this.batchPollingTimeout);
525
+ this.batchPollingTimeout = undefined;
526
+ }
527
+
528
+ // Clear accounts currently polling
529
+ this.accountsCurrentlyPolling.clear();
530
+ }
531
+
532
+ unsubscribe(onResub = false): Promise<void> {
533
+ if (!onResub) {
534
+ this.resubOpts.resubTimeoutMs = undefined;
535
+ }
536
+ this.isUnsubscribing = true;
537
+ clearTimeout(this.timeoutId);
538
+ this.timeoutId = undefined;
539
+
540
+ // Clear polling timeouts
541
+ this.clearPollingTimeouts();
542
+
543
+ // Abort the WebSocket subscription
544
+ if (this.abortController) {
545
+ this.abortController.abort('unsubscribing');
546
+ this.abortController = undefined;
547
+ }
548
+
549
+ this.listenerId = undefined;
550
+ this.isUnsubscribing = false;
551
+
552
+ return Promise.resolve();
553
+ }
554
+
555
+ // Method to add accounts to the polling list
556
+ addAccountToMonitor(accountId: PublicKey): void {
557
+ const accountIdString = accountId.toBase58();
558
+ this.accountsToMonitor.add(accountIdString);
559
+
560
+ // If already subscribed, start monitoring for this account
561
+ if (this.listenerId != null && !this.isUnsubscribing) {
562
+ this.startMonitoringForAccount(accountIdString);
563
+ }
564
+ }
565
+
566
+ // Method to remove accounts from the polling list
567
+ removeAccountFromMonitor(accountId: PublicKey): void {
568
+ const accountIdString = accountId.toBase58();
569
+ this.accountsToMonitor.delete(accountIdString);
570
+
571
+ // Clear monitoring timeout for this account
572
+ const timeoutId = this.pollingTimeouts.get(accountIdString);
573
+ if (timeoutId) {
574
+ clearTimeout(timeoutId);
575
+ this.pollingTimeouts.delete(accountIdString);
576
+ }
577
+
578
+ // Remove from currently polling set if it was being polled
579
+ this.accountsCurrentlyPolling.delete(accountIdString);
580
+
581
+ // If no more accounts are being polled, stop batch polling
582
+ if (this.accountsCurrentlyPolling.size === 0 && this.batchPollingTimeout) {
583
+ clearTimeout(this.batchPollingTimeout);
584
+ this.batchPollingTimeout = undefined;
585
+ }
586
+ }
587
+
588
+ // Method to set polling interval
589
+ setPollingInterval(intervalMs: number): void {
590
+ this.pollingIntervalMs = intervalMs;
591
+ // Restart monitoring with new interval if already subscribed
592
+ if (this.listenerId != null && !this.isUnsubscribing) {
593
+ this.startMonitoringForAccounts();
594
+ }
595
+ }
596
+ }
@@ -1,7 +1,11 @@
1
1
  import { PublicKey } from '@solana/web3.js';
2
2
  import * as anchor from '@coral-xyz/anchor';
3
3
  import { BN } from '@coral-xyz/anchor';
4
- import { TOKEN_2022_PROGRAM_ID, TOKEN_PROGRAM_ID } from '@solana/spl-token';
4
+ import {
5
+ getAssociatedTokenAddress,
6
+ TOKEN_2022_PROGRAM_ID,
7
+ TOKEN_PROGRAM_ID,
8
+ } from '@solana/spl-token';
5
9
  import { SpotMarketAccount, TokenProgramFlag } from '../types';
6
10
 
7
11
  export async function getDriftStateAccountPublicKeyAndNonce(
@@ -420,3 +424,113 @@ export function getRevenueShareEscrowAccountPublicKey(
420
424
  programId
421
425
  )[0];
422
426
  }
427
+
428
+ export function getLpPoolPublicKey(
429
+ programId: PublicKey,
430
+ lpPoolId: number
431
+ ): PublicKey {
432
+ return PublicKey.findProgramAddressSync(
433
+ [
434
+ Buffer.from(anchor.utils.bytes.utf8.encode('lp_pool')),
435
+ new anchor.BN(lpPoolId).toArrayLike(Buffer, 'le', 1),
436
+ ],
437
+ programId
438
+ )[0];
439
+ }
440
+
441
+ export function getLpPoolTokenVaultPublicKey(
442
+ programId: PublicKey,
443
+ lpPool: PublicKey
444
+ ): PublicKey {
445
+ return PublicKey.findProgramAddressSync(
446
+ [
447
+ Buffer.from(anchor.utils.bytes.utf8.encode('LP_POOL_TOKEN_VAULT')),
448
+ lpPool.toBuffer(),
449
+ ],
450
+ programId
451
+ )[0];
452
+ }
453
+ export function getAmmConstituentMappingPublicKey(
454
+ programId: PublicKey,
455
+ lpPoolPublicKey: PublicKey
456
+ ): PublicKey {
457
+ return PublicKey.findProgramAddressSync(
458
+ [
459
+ Buffer.from(anchor.utils.bytes.utf8.encode('AMM_MAP')),
460
+ lpPoolPublicKey.toBuffer(),
461
+ ],
462
+ programId
463
+ )[0];
464
+ }
465
+
466
+ export function getConstituentTargetBasePublicKey(
467
+ programId: PublicKey,
468
+ lpPoolPublicKey: PublicKey
469
+ ): PublicKey {
470
+ return PublicKey.findProgramAddressSync(
471
+ [
472
+ Buffer.from(
473
+ anchor.utils.bytes.utf8.encode('constituent_target_base_seed')
474
+ ),
475
+ lpPoolPublicKey.toBuffer(),
476
+ ],
477
+ programId
478
+ )[0];
479
+ }
480
+
481
+ export function getConstituentPublicKey(
482
+ programId: PublicKey,
483
+ lpPoolPublicKey: PublicKey,
484
+ spotMarketIndex: number
485
+ ): PublicKey {
486
+ return PublicKey.findProgramAddressSync(
487
+ [
488
+ Buffer.from(anchor.utils.bytes.utf8.encode('CONSTITUENT')),
489
+ lpPoolPublicKey.toBuffer(),
490
+ new anchor.BN(spotMarketIndex).toArrayLike(Buffer, 'le', 2),
491
+ ],
492
+ programId
493
+ )[0];
494
+ }
495
+
496
+ export function getConstituentVaultPublicKey(
497
+ programId: PublicKey,
498
+ lpPoolPublicKey: PublicKey,
499
+ spotMarketIndex: number
500
+ ): PublicKey {
501
+ return PublicKey.findProgramAddressSync(
502
+ [
503
+ Buffer.from(anchor.utils.bytes.utf8.encode('CONSTITUENT_VAULT')),
504
+ lpPoolPublicKey.toBuffer(),
505
+ new anchor.BN(spotMarketIndex).toArrayLike(Buffer, 'le', 2),
506
+ ],
507
+ programId
508
+ )[0];
509
+ }
510
+
511
+ export function getAmmCachePublicKey(programId: PublicKey): PublicKey {
512
+ return PublicKey.findProgramAddressSync(
513
+ [Buffer.from(anchor.utils.bytes.utf8.encode('amm_cache_seed'))],
514
+ programId
515
+ )[0];
516
+ }
517
+
518
+ export function getConstituentCorrelationsPublicKey(
519
+ programId: PublicKey,
520
+ lpPoolPublicKey: PublicKey
521
+ ): PublicKey {
522
+ return PublicKey.findProgramAddressSync(
523
+ [
524
+ Buffer.from(anchor.utils.bytes.utf8.encode('constituent_correlations')),
525
+ lpPoolPublicKey.toBuffer(),
526
+ ],
527
+ programId
528
+ )[0];
529
+ }
530
+
531
+ export async function getLpPoolTokenTokenAccountPublicKey(
532
+ lpPoolTokenMint: PublicKey,
533
+ authority: PublicKey
534
+ ): Promise<PublicKey> {
535
+ return await getAssociatedTokenAddress(lpPoolTokenMint, authority, true);
536
+ }