@drift-labs/sdk 2.142.0-beta.9 → 2.142.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 (58) hide show
  1. package/VERSION +1 -1
  2. package/lib/browser/accounts/grpcDriftClientAccountSubscriberV2.d.ts +46 -5
  3. package/lib/browser/accounts/grpcDriftClientAccountSubscriberV2.js +242 -41
  4. package/lib/browser/accounts/grpcMultiAccountSubscriber.d.ts +6 -3
  5. package/lib/browser/accounts/grpcMultiAccountSubscriber.js +112 -19
  6. package/lib/browser/adminClient.d.ts +4 -0
  7. package/lib/browser/adminClient.js +34 -0
  8. package/lib/browser/constants/perpMarkets.js +35 -0
  9. package/lib/browser/constants/spotMarkets.js +4 -4
  10. package/lib/browser/driftClient.d.ts +35 -5
  11. package/lib/browser/driftClient.js +41 -14
  12. package/lib/browser/events/parse.d.ts +2 -0
  13. package/lib/browser/events/parse.js +94 -1
  14. package/lib/browser/events/types.d.ts +22 -3
  15. package/lib/browser/idl/drift.json +105 -6
  16. package/lib/browser/math/amm.d.ts +1 -0
  17. package/lib/browser/math/amm.js +28 -4
  18. package/lib/browser/types.d.ts +20 -0
  19. package/lib/node/accounts/grpcDriftClientAccountSubscriberV2.d.ts +46 -5
  20. package/lib/node/accounts/grpcDriftClientAccountSubscriberV2.d.ts.map +1 -1
  21. package/lib/node/accounts/grpcDriftClientAccountSubscriberV2.js +242 -41
  22. package/lib/node/accounts/grpcMultiAccountSubscriber.d.ts +6 -3
  23. package/lib/node/accounts/grpcMultiAccountSubscriber.d.ts.map +1 -1
  24. package/lib/node/accounts/grpcMultiAccountSubscriber.js +112 -19
  25. package/lib/node/adminClient.d.ts +4 -0
  26. package/lib/node/adminClient.d.ts.map +1 -1
  27. package/lib/node/adminClient.js +34 -0
  28. package/lib/node/constants/perpMarkets.d.ts.map +1 -1
  29. package/lib/node/constants/perpMarkets.js +35 -0
  30. package/lib/node/constants/spotMarkets.js +4 -4
  31. package/lib/node/driftClient.d.ts +35 -5
  32. package/lib/node/driftClient.d.ts.map +1 -1
  33. package/lib/node/driftClient.js +41 -14
  34. package/lib/node/events/parse.d.ts +2 -0
  35. package/lib/node/events/parse.d.ts.map +1 -1
  36. package/lib/node/events/parse.js +94 -1
  37. package/lib/node/events/types.d.ts +22 -3
  38. package/lib/node/events/types.d.ts.map +1 -1
  39. package/lib/node/idl/drift.json +105 -6
  40. package/lib/node/math/amm.d.ts +1 -0
  41. package/lib/node/math/amm.d.ts.map +1 -1
  42. package/lib/node/math/amm.js +28 -4
  43. package/lib/node/types.d.ts +20 -0
  44. package/lib/node/types.d.ts.map +1 -1
  45. package/package.json +2 -1
  46. package/scripts/client-test.ts +294 -135
  47. package/src/accounts/grpcDriftClientAccountSubscriberV2.ts +398 -72
  48. package/src/accounts/grpcMultiAccountSubscriber.ts +163 -31
  49. package/src/adminClient.ts +74 -0
  50. package/src/constants/perpMarkets.ts +37 -0
  51. package/src/constants/spotMarkets.ts +4 -4
  52. package/src/driftClient.ts +65 -14
  53. package/src/events/parse.ts +115 -0
  54. package/src/events/types.ts +26 -2
  55. package/src/idl/drift.json +105 -6
  56. package/src/math/amm.ts +52 -8
  57. package/src/types.ts +22 -0
  58. package/tests/events/parseLogsForCuUsage.ts +139 -0
@@ -1,4 +1,5 @@
1
- import { WebSocketDriftClientAccountSubscriber } from './webSocketDriftClientAccountSubscriber';
1
+ import StrictEventEmitter from 'strict-event-emitter-types';
2
+ import { EventEmitter } from 'events';
2
3
  import { OracleInfo, OraclePriceData } from '../oracles/types';
3
4
  import { Program } from '@coral-xyz/anchor';
4
5
  import { PublicKey } from '@solana/web3.js';
@@ -6,11 +7,17 @@ import { findAllMarketAndOracles } from '../config';
6
7
  import {
7
8
  getDriftStateAccountPublicKey,
8
9
  getPerpMarketPublicKey,
10
+ getPerpMarketPublicKeySync,
9
11
  getSpotMarketPublicKey,
12
+ getSpotMarketPublicKeySync,
10
13
  } from '../addresses/pda';
11
14
  import {
15
+ AccountSubscriber,
12
16
  DataAndSlot,
13
17
  DelistedMarketSetting,
18
+ DriftClientAccountEvents,
19
+ DriftClientAccountSubscriber,
20
+ NotSubscribedError,
14
21
  GrpcConfigs,
15
22
  ResubOpts,
16
23
  } from './types';
@@ -21,14 +28,51 @@ import {
21
28
  getOracleId,
22
29
  getPublicKeyAndSourceFromOracleId,
23
30
  } from '../oracles/oracleId';
31
+ import { OracleClientCache } from '../oracles/oracleClientCache';
32
+ import { findDelistedPerpMarketsAndOracles } from './utils';
24
33
 
25
- export class grpcDriftClientAccountSubscriberV2 extends WebSocketDriftClientAccountSubscriber {
34
+ export class grpcDriftClientAccountSubscriberV2
35
+ implements DriftClientAccountSubscriber
36
+ {
26
37
  private grpcConfigs: GrpcConfigs;
27
38
  private perpMarketsSubscriber?: grpcMultiAccountSubscriber<PerpMarketAccount>;
28
39
  private spotMarketsSubscriber?: grpcMultiAccountSubscriber<SpotMarketAccount>;
29
- private oracleMultiSubscriber?: grpcMultiAccountSubscriber<OraclePriceData>;
40
+ private oracleMultiSubscriber?: grpcMultiAccountSubscriber<
41
+ OraclePriceData,
42
+ OracleInfo
43
+ >;
30
44
  private perpMarketIndexToAccountPubkeyMap = new Map<number, string>();
31
45
  private spotMarketIndexToAccountPubkeyMap = new Map<number, string>();
46
+ private delistedMarketSetting: DelistedMarketSetting;
47
+
48
+ public eventEmitter: StrictEventEmitter<
49
+ EventEmitter,
50
+ DriftClientAccountEvents
51
+ >;
52
+ public isSubscribed: boolean;
53
+ public isSubscribing: boolean;
54
+ public program: Program;
55
+ public perpMarketIndexes: number[];
56
+ public spotMarketIndexes: number[];
57
+ public shouldFindAllMarketsAndOracles: boolean;
58
+ public oracleInfos: OracleInfo[];
59
+ public initialPerpMarketAccountData: Map<number, PerpMarketAccount>;
60
+ public initialSpotMarketAccountData: Map<number, SpotMarketAccount>;
61
+ public initialOraclePriceData: Map<string, OraclePriceData>;
62
+ public perpOracleMap = new Map<number, PublicKey>();
63
+ public perpOracleStringMap = new Map<number, string>();
64
+ public spotOracleMap = new Map<number, PublicKey>();
65
+ public spotOracleStringMap = new Map<number, string>();
66
+ private oracleIdToOracleDataMap = new Map<
67
+ string,
68
+ DataAndSlot<OraclePriceData>
69
+ >();
70
+ public stateAccountSubscriber?: AccountSubscriber<StateAccount>;
71
+ oracleClientCache = new OracleClientCache();
72
+ private resubOpts?: ResubOpts;
73
+
74
+ private subscriptionPromise: Promise<boolean>;
75
+ protected subscriptionPromiseResolver: (val: boolean) => void;
32
76
 
33
77
  constructor(
34
78
  grpcConfigs: GrpcConfigs,
@@ -40,16 +84,156 @@ export class grpcDriftClientAccountSubscriberV2 extends WebSocketDriftClientAcco
40
84
  delistedMarketSetting: DelistedMarketSetting,
41
85
  resubOpts?: ResubOpts
42
86
  ) {
43
- super(
44
- program,
45
- perpMarketIndexes,
46
- spotMarketIndexes,
47
- oracleInfos,
48
- shouldFindAllMarketsAndOracles,
49
- delistedMarketSetting,
50
- resubOpts
51
- );
87
+ this.eventEmitter = new EventEmitter();
88
+ this.isSubscribed = false;
89
+ this.isSubscribing = false;
90
+ this.program = program;
91
+ this.perpMarketIndexes = perpMarketIndexes;
92
+ this.spotMarketIndexes = spotMarketIndexes;
93
+ this.shouldFindAllMarketsAndOracles = shouldFindAllMarketsAndOracles;
94
+ this.oracleInfos = oracleInfos;
95
+ this.initialPerpMarketAccountData = new Map();
96
+ this.initialSpotMarketAccountData = new Map();
97
+ this.initialOraclePriceData = new Map();
98
+ this.perpOracleMap = new Map();
99
+ this.perpOracleStringMap = new Map();
100
+ this.spotOracleMap = new Map();
101
+ this.spotOracleStringMap = new Map();
52
102
  this.grpcConfigs = grpcConfigs;
103
+ this.resubOpts = resubOpts;
104
+ this.delistedMarketSetting = delistedMarketSetting;
105
+ }
106
+
107
+ chunks = <T>(array: readonly T[], size: number): T[][] => {
108
+ return new Array(Math.ceil(array.length / size))
109
+ .fill(null)
110
+ .map((_, index) => index * size)
111
+ .map((begin) => array.slice(begin, begin + size));
112
+ };
113
+
114
+ async setInitialData(): Promise<void> {
115
+ const connection = this.program.provider.connection;
116
+
117
+ if (
118
+ !this.initialPerpMarketAccountData ||
119
+ this.initialPerpMarketAccountData.size === 0
120
+ ) {
121
+ const perpMarketPublicKeys = this.perpMarketIndexes.map((marketIndex) =>
122
+ getPerpMarketPublicKeySync(this.program.programId, marketIndex)
123
+ );
124
+ const perpMarketPublicKeysChunks = this.chunks(perpMarketPublicKeys, 75);
125
+ const perpMarketAccountInfos = (
126
+ await Promise.all(
127
+ perpMarketPublicKeysChunks.map((perpMarketPublicKeysChunk) =>
128
+ connection.getMultipleAccountsInfo(perpMarketPublicKeysChunk)
129
+ )
130
+ )
131
+ ).flat();
132
+ this.initialPerpMarketAccountData = new Map(
133
+ perpMarketAccountInfos
134
+ .filter((accountInfo) => !!accountInfo)
135
+ .map((accountInfo) => {
136
+ const perpMarket = this.program.coder.accounts.decode(
137
+ 'PerpMarket',
138
+ accountInfo.data
139
+ );
140
+ return [perpMarket.marketIndex, perpMarket];
141
+ })
142
+ );
143
+ }
144
+
145
+ if (
146
+ !this.initialSpotMarketAccountData ||
147
+ this.initialSpotMarketAccountData.size === 0
148
+ ) {
149
+ const spotMarketPublicKeys = this.spotMarketIndexes.map((marketIndex) =>
150
+ getSpotMarketPublicKeySync(this.program.programId, marketIndex)
151
+ );
152
+ const spotMarketPublicKeysChunks = this.chunks(spotMarketPublicKeys, 75);
153
+ const spotMarketAccountInfos = (
154
+ await Promise.all(
155
+ spotMarketPublicKeysChunks.map((spotMarketPublicKeysChunk) =>
156
+ connection.getMultipleAccountsInfo(spotMarketPublicKeysChunk)
157
+ )
158
+ )
159
+ ).flat();
160
+ this.initialSpotMarketAccountData = new Map(
161
+ spotMarketAccountInfos
162
+ .filter((accountInfo) => !!accountInfo)
163
+ .map((accountInfo) => {
164
+ const spotMarket = this.program.coder.accounts.decode(
165
+ 'SpotMarket',
166
+ accountInfo.data
167
+ );
168
+ return [spotMarket.marketIndex, spotMarket];
169
+ })
170
+ );
171
+ }
172
+
173
+ const oracleAccountPubkeyChunks = this.chunks(
174
+ this.oracleInfos.map((oracleInfo) => oracleInfo.publicKey),
175
+ 75
176
+ );
177
+ const oracleAccountInfos = (
178
+ await Promise.all(
179
+ oracleAccountPubkeyChunks.map((oracleAccountPublicKeysChunk) =>
180
+ connection.getMultipleAccountsInfo(oracleAccountPublicKeysChunk)
181
+ )
182
+ )
183
+ ).flat();
184
+ this.initialOraclePriceData = new Map(
185
+ this.oracleInfos.reduce((result, oracleInfo, i) => {
186
+ if (!oracleAccountInfos[i]) {
187
+ return result;
188
+ }
189
+ const oracleClient = this.oracleClientCache.get(
190
+ oracleInfo.source,
191
+ connection,
192
+ this.program
193
+ );
194
+ const oraclePriceData = oracleClient.getOraclePriceDataFromBuffer(
195
+ oracleAccountInfos[i].data
196
+ );
197
+ result.push([
198
+ getOracleId(oracleInfo.publicKey, oracleInfo.source),
199
+ oraclePriceData,
200
+ ]);
201
+ return result;
202
+ }, [])
203
+ );
204
+ }
205
+
206
+ async addPerpMarket(_marketIndex: number): Promise<boolean> {
207
+ if (!this.perpMarketIndexes.includes(_marketIndex)) {
208
+ this.perpMarketIndexes = this.perpMarketIndexes.concat(_marketIndex);
209
+ }
210
+ return true;
211
+ }
212
+
213
+ async addSpotMarket(_marketIndex: number): Promise<boolean> {
214
+ return true;
215
+ }
216
+
217
+ async addOracle(oracleInfo: OracleInfo): Promise<boolean> {
218
+ if (this.resubOpts?.logResubMessages) {
219
+ console.log('[grpcDriftClientAccountSubscriberV2] addOracle');
220
+ }
221
+ if (oracleInfo.publicKey.equals(PublicKey.default)) {
222
+ return true;
223
+ }
224
+
225
+ const exists = this.oracleInfos.some(
226
+ (o) =>
227
+ o.source === oracleInfo.source &&
228
+ o.publicKey.equals(oracleInfo.publicKey)
229
+ );
230
+ if (!exists) {
231
+ this.oracleInfos = this.oracleInfos.concat(oracleInfo);
232
+ }
233
+
234
+ this.oracleMultiSubscriber?.addAccounts([oracleInfo.publicKey]);
235
+
236
+ return true;
53
237
  }
54
238
 
55
239
  public async subscribe(): Promise<boolean> {
@@ -133,7 +317,37 @@ export class grpcDriftClientAccountSubscriberV2 extends WebSocketDriftClientAcco
133
317
  return true;
134
318
  }
135
319
 
136
- override getMarketAccountAndSlot(
320
+ public async fetch(): Promise<void> {
321
+ await this.stateAccountSubscriber?.fetch();
322
+ await this.perpMarketsSubscriber?.fetch();
323
+ await this.spotMarketsSubscriber?.fetch();
324
+ await this.oracleMultiSubscriber?.fetch();
325
+ }
326
+
327
+ private assertIsSubscribed(): void {
328
+ if (!this.isSubscribed) {
329
+ throw new NotSubscribedError(
330
+ 'You must call `subscribe` before using this function'
331
+ );
332
+ }
333
+ }
334
+
335
+ public getStateAccountAndSlot(): DataAndSlot<StateAccount> {
336
+ this.assertIsSubscribed();
337
+ return this.stateAccountSubscriber.dataAndSlot;
338
+ }
339
+
340
+ public getMarketAccountsAndSlots(): DataAndSlot<PerpMarketAccount>[] {
341
+ const map = this.perpMarketsSubscriber?.getAccountDataMap();
342
+ return Array.from(map?.values() ?? []);
343
+ }
344
+
345
+ public getSpotMarketAccountsAndSlots(): DataAndSlot<SpotMarketAccount>[] {
346
+ const map = this.spotMarketsSubscriber?.getAccountDataMap();
347
+ return Array.from(map?.values() ?? []);
348
+ }
349
+
350
+ getMarketAccountAndSlot(
137
351
  marketIndex: number
138
352
  ): DataAndSlot<PerpMarketAccount> | undefined {
139
353
  return this.perpMarketsSubscriber?.getAccountData(
@@ -141,7 +355,7 @@ export class grpcDriftClientAccountSubscriberV2 extends WebSocketDriftClientAcco
141
355
  );
142
356
  }
143
357
 
144
- override getSpotMarketAccountAndSlot(
358
+ getSpotMarketAccountAndSlot(
145
359
  marketIndex: number
146
360
  ): DataAndSlot<SpotMarketAccount> | undefined {
147
361
  return this.spotMarketsSubscriber?.getAccountData(
@@ -149,7 +363,52 @@ export class grpcDriftClientAccountSubscriberV2 extends WebSocketDriftClientAcco
149
363
  );
150
364
  }
151
365
 
152
- override async setPerpOracleMap() {
366
+ public getOraclePriceDataAndSlot(
367
+ oracleId: string
368
+ ): DataAndSlot<OraclePriceData> | undefined {
369
+ this.assertIsSubscribed();
370
+ // we need to rely on a map we store in this class because the grpcMultiAccountSubscriber does not track a mapping or oracle ID.
371
+ // DO NOT call getAccountData on the oracleMultiSubscriber, it will not return the correct data in certain cases(BONK spot and perp market subscribed too at once).
372
+ return this.oracleIdToOracleDataMap.get(oracleId);
373
+ }
374
+
375
+ public getOraclePriceDataAndSlotForPerpMarket(
376
+ marketIndex: number
377
+ ): DataAndSlot<OraclePriceData> | undefined {
378
+ const perpMarketAccount = this.getMarketAccountAndSlot(marketIndex);
379
+ const oracle = this.perpOracleMap.get(marketIndex);
380
+ const oracleId = this.perpOracleStringMap.get(marketIndex);
381
+ if (!perpMarketAccount || !oracleId) {
382
+ return undefined;
383
+ }
384
+
385
+ if (!perpMarketAccount.data.amm.oracle.equals(oracle)) {
386
+ // If the oracle has changed, we need to update the oracle map in background
387
+ this.setPerpOracleMap();
388
+ }
389
+
390
+ return this.getOraclePriceDataAndSlot(oracleId);
391
+ }
392
+
393
+ public getOraclePriceDataAndSlotForSpotMarket(
394
+ marketIndex: number
395
+ ): DataAndSlot<OraclePriceData> | undefined {
396
+ const spotMarketAccount = this.getSpotMarketAccountAndSlot(marketIndex);
397
+ const oracle = this.spotOracleMap.get(marketIndex);
398
+ const oracleId = this.spotOracleStringMap.get(marketIndex);
399
+ if (!spotMarketAccount || !oracleId) {
400
+ return undefined;
401
+ }
402
+
403
+ if (!spotMarketAccount.data.oracle.equals(oracle)) {
404
+ // If the oracle has changed, we need to update the oracle map in background
405
+ this.setSpotOracleMap();
406
+ }
407
+
408
+ return this.getOraclePriceDataAndSlot(oracleId);
409
+ }
410
+
411
+ async setPerpOracleMap() {
153
412
  const perpMarketsMap = this.perpMarketsSubscriber?.getAccountDataMap();
154
413
  const perpMarkets = Array.from(perpMarketsMap.values());
155
414
  const addOraclePromises = [];
@@ -161,7 +420,7 @@ export class grpcDriftClientAccountSubscriberV2 extends WebSocketDriftClientAcco
161
420
  const perpMarketIndex = perpMarketAccount.marketIndex;
162
421
  const oracle = perpMarketAccount.amm.oracle;
163
422
  const oracleId = getOracleId(oracle, perpMarket.data.amm.oracleSource);
164
- if (!this.oracleSubscribers.has(oracleId)) {
423
+ if (!this.oracleMultiSubscriber?.getAccountDataMap().has(oracleId)) {
165
424
  addOraclePromises.push(
166
425
  this.addOracle({
167
426
  publicKey: oracle,
@@ -175,7 +434,7 @@ export class grpcDriftClientAccountSubscriberV2 extends WebSocketDriftClientAcco
175
434
  await Promise.all(addOraclePromises);
176
435
  }
177
436
 
178
- override async setSpotOracleMap() {
437
+ async setSpotOracleMap() {
179
438
  const spotMarketsMap = this.spotMarketsSubscriber?.getAccountDataMap();
180
439
  const spotMarkets = Array.from(spotMarketsMap.values());
181
440
  const addOraclePromises = [];
@@ -187,7 +446,7 @@ export class grpcDriftClientAccountSubscriberV2 extends WebSocketDriftClientAcco
187
446
  const spotMarketIndex = spotMarketAccount.marketIndex;
188
447
  const oracle = spotMarketAccount.oracle;
189
448
  const oracleId = getOracleId(oracle, spotMarketAccount.oracleSource);
190
- if (!this.oracleSubscribers.has(oracleId)) {
449
+ if (!this.oracleMultiSubscriber?.getAccountDataMap().has(oracleId)) {
191
450
  addOraclePromises.push(
192
451
  this.addOracle({
193
452
  publicKey: oracle,
@@ -201,7 +460,12 @@ export class grpcDriftClientAccountSubscriberV2 extends WebSocketDriftClientAcco
201
460
  await Promise.all(addOraclePromises);
202
461
  }
203
462
 
204
- override async subscribeToPerpMarketAccounts(): Promise<boolean> {
463
+ async subscribeToPerpMarketAccounts(): Promise<boolean> {
464
+ if (this.resubOpts?.logResubMessages) {
465
+ console.log(
466
+ '[grpcDriftClientAccountSubscriberV2] subscribeToPerpMarketAccounts'
467
+ );
468
+ }
205
469
  const perpMarketIndexToAccountPubkeys: Array<[number, PublicKey]> =
206
470
  await Promise.all(
207
471
  this.perpMarketIndexes.map(async (marketIndex) => [
@@ -263,7 +527,12 @@ export class grpcDriftClientAccountSubscriberV2 extends WebSocketDriftClientAcco
263
527
  return true;
264
528
  }
265
529
 
266
- override async subscribeToSpotMarketAccounts(): Promise<boolean> {
530
+ async subscribeToSpotMarketAccounts(): Promise<boolean> {
531
+ if (this.resubOpts?.logResubMessages) {
532
+ console.log(
533
+ '[grpcDriftClientAccountSubscriberV2] subscribeToSpotMarketAccounts'
534
+ );
535
+ }
267
536
  const spotMarketIndexToAccountPubkeys: Array<[number, PublicKey]> =
268
537
  await Promise.all(
269
538
  this.spotMarketIndexes.map(async (marketIndex) => [
@@ -325,72 +594,84 @@ export class grpcDriftClientAccountSubscriberV2 extends WebSocketDriftClientAcco
325
594
  return true;
326
595
  }
327
596
 
328
- override async subscribeToOracles(): Promise<boolean> {
329
- // Build list of unique oracle pubkeys and a lookup for sources
330
- const uniqueOraclePubkeys = new Map<string, OracleInfo>();
597
+ async subscribeToOracles(): Promise<boolean> {
598
+ if (this.resubOpts?.logResubMessages) {
599
+ console.log('grpcDriftClientAccountSubscriberV2 subscribeToOracles');
600
+ }
601
+ const oraclePubkeyToInfosMap = new Map<string, OracleInfo[]>();
331
602
  for (const info of this.oracleInfos) {
332
- const id = getOracleId(info.publicKey, info.source);
333
- if (
334
- !uniqueOraclePubkeys.has(id) &&
335
- !info.publicKey.equals((PublicKey as any).default)
336
- ) {
337
- uniqueOraclePubkeys.set(id, info);
603
+ const pubkey = info.publicKey.toBase58();
604
+ if (!oraclePubkeyToInfosMap.has(pubkey)) {
605
+ oraclePubkeyToInfosMap.set(pubkey, []);
338
606
  }
607
+ oraclePubkeyToInfosMap.get(pubkey).push(info);
339
608
  }
340
609
 
341
- const oraclePubkeys = Array.from(uniqueOraclePubkeys.values()).map(
342
- (i) => i.publicKey
343
- );
344
- const pubkeyToSource = new Map<string, OracleInfo['source']>(
345
- Array.from(uniqueOraclePubkeys.values()).map((i) => [
346
- i.publicKey.toBase58(),
347
- i.source,
348
- ])
610
+ const oraclePubkeys = Array.from(
611
+ new Set(this.oracleInfos.map((info) => info.publicKey))
349
612
  );
350
613
 
351
- this.oracleMultiSubscriber =
352
- await grpcMultiAccountSubscriber.create<OraclePriceData>(
353
- this.grpcConfigs,
354
- 'oracle',
355
- this.program,
356
- (buffer: Buffer, pubkey?: string) => {
357
- if (!pubkey) {
358
- throw new Error('Oracle pubkey missing in decode');
359
- }
360
- const source = pubkeyToSource.get(pubkey);
361
- const client = this.oracleClientCache.get(
362
- source,
363
- this.program.provider.connection,
364
- this.program
365
- );
366
- return client.getOraclePriceDataFromBuffer(buffer);
367
- },
368
- this.resubOpts,
369
- undefined,
370
- async () => {
371
- try {
372
- if (this.resubOpts?.logResubMessages) {
373
- console.log(
374
- '[grpcDriftClientAccountSubscriberV2] oracle subscriber unsubscribed; resubscribing'
375
- );
376
- }
377
- await this.subscribeToOracles();
378
- } catch (e) {
379
- console.error('Oracle resubscribe failed:', e);
614
+ this.oracleMultiSubscriber = await grpcMultiAccountSubscriber.create<
615
+ OraclePriceData,
616
+ OracleInfo
617
+ >(
618
+ this.grpcConfigs,
619
+ 'oracle',
620
+ this.program,
621
+ (buffer: Buffer, pubkey?: string, accountProps?: OracleInfo) => {
622
+ if (!pubkey) {
623
+ throw new Error('Oracle pubkey missing in decode');
624
+ }
625
+
626
+ const client = this.oracleClientCache.get(
627
+ accountProps.source,
628
+ this.program.provider.connection,
629
+ this.program
630
+ );
631
+ const price = client.getOraclePriceDataFromBuffer(buffer);
632
+ return price;
633
+ },
634
+ this.resubOpts,
635
+ undefined,
636
+ async () => {
637
+ try {
638
+ if (this.resubOpts?.logResubMessages) {
639
+ console.log(
640
+ '[grpcDriftClientAccountSubscriberV2] oracle subscriber unsubscribed; resubscribing'
641
+ );
380
642
  }
643
+ await this.subscribeToOracles();
644
+ } catch (e) {
645
+ console.error('Oracle resubscribe failed:', e);
381
646
  }
382
- );
647
+ },
648
+ oraclePubkeyToInfosMap
649
+ );
383
650
 
384
651
  for (const data of this.initialOraclePriceData.entries()) {
385
652
  const { publicKey } = getPublicKeyAndSourceFromOracleId(data[0]);
386
653
  this.oracleMultiSubscriber.setAccountData(publicKey.toBase58(), data[1]);
654
+ this.oracleIdToOracleDataMap.set(data[0], {
655
+ data: data[1],
656
+ slot: 0,
657
+ });
387
658
  }
388
659
 
389
660
  await this.oracleMultiSubscriber.subscribe(
390
661
  oraclePubkeys,
391
- (accountId, data) => {
392
- const source = pubkeyToSource.get(accountId.toBase58());
393
- this.eventEmitter.emit('oraclePriceUpdate', accountId, source, data);
662
+ (accountId, data, context, _b, accountProps) => {
663
+ const oracleId = getOracleId(accountId, accountProps.source);
664
+ this.oracleIdToOracleDataMap.set(oracleId, {
665
+ data,
666
+ slot: context.slot,
667
+ });
668
+ this.eventEmitter.emit(
669
+ 'oraclePriceUpdate',
670
+ accountId,
671
+ accountProps.source,
672
+ data
673
+ );
674
+
394
675
  this.eventEmitter.emit('update');
395
676
  }
396
677
  );
@@ -398,20 +679,65 @@ export class grpcDriftClientAccountSubscriberV2 extends WebSocketDriftClientAcco
398
679
  return true;
399
680
  }
400
681
 
682
+ async handleDelistedMarkets(): Promise<void> {
683
+ if (this.delistedMarketSetting === DelistedMarketSetting.Subscribe) {
684
+ return;
685
+ }
686
+
687
+ const { perpMarketIndexes, oracles } = findDelistedPerpMarketsAndOracles(
688
+ Array.from(
689
+ this.perpMarketsSubscriber?.getAccountDataMap().values() || []
690
+ ),
691
+ Array.from(this.spotMarketsSubscriber?.getAccountDataMap().values() || [])
692
+ );
693
+
694
+ // Build array of perp market pubkeys to remove
695
+ const perpMarketPubkeysToRemove = perpMarketIndexes
696
+ .map((marketIndex) => {
697
+ const pubkeyString =
698
+ this.perpMarketIndexToAccountPubkeyMap.get(marketIndex);
699
+ return pubkeyString ? new PublicKey(pubkeyString) : null;
700
+ })
701
+ .filter((pubkey) => pubkey !== null) as PublicKey[];
702
+
703
+ // Build array of oracle pubkeys to remove
704
+ const oraclePubkeysToRemove = oracles.map((oracle) => oracle.publicKey);
705
+
706
+ // Remove accounts in batches - perp markets
707
+ if (perpMarketPubkeysToRemove.length > 0) {
708
+ await this.perpMarketsSubscriber.removeAccounts(
709
+ perpMarketPubkeysToRemove
710
+ );
711
+ }
712
+
713
+ // Remove accounts in batches - oracles
714
+ if (oraclePubkeysToRemove.length > 0) {
715
+ await this.oracleMultiSubscriber.removeAccounts(oraclePubkeysToRemove);
716
+ }
717
+ }
718
+
719
+ removeInitialData() {
720
+ this.initialPerpMarketAccountData = new Map();
721
+ this.initialSpotMarketAccountData = new Map();
722
+ this.initialOraclePriceData = new Map();
723
+ }
724
+
401
725
  async unsubscribeFromOracles(): Promise<void> {
402
726
  if (this.oracleMultiSubscriber) {
403
727
  await this.oracleMultiSubscriber.unsubscribe();
404
728
  this.oracleMultiSubscriber = undefined;
405
729
  return;
406
730
  }
407
- await super.unsubscribeFromOracles();
408
731
  }
409
732
 
410
- override async unsubscribe(): Promise<void> {
733
+ async unsubscribe(): Promise<void> {
411
734
  if (this.isSubscribed) {
412
735
  return;
413
736
  }
414
737
 
415
738
  await this.stateAccountSubscriber.unsubscribe();
739
+ await this.unsubscribeFromOracles();
740
+ await this.perpMarketsSubscriber?.unsubscribe();
741
+ await this.spotMarketsSubscriber?.unsubscribe();
416
742
  }
417
743
  }