@drift-labs/sdk 2.142.0-beta.2 → 2.142.0-beta.21

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 (41) hide show
  1. package/VERSION +1 -1
  2. package/lib/browser/accounts/grpcDriftClientAccountSubscriberV2.d.ts +52 -4
  3. package/lib/browser/accounts/grpcDriftClientAccountSubscriberV2.js +315 -38
  4. package/lib/browser/accounts/grpcMultiAccountSubscriber.d.ts +11 -4
  5. package/lib/browser/accounts/grpcMultiAccountSubscriber.js +124 -18
  6. package/lib/browser/adminClient.d.ts +2 -0
  7. package/lib/browser/adminClient.js +17 -0
  8. package/lib/browser/constants/spotMarkets.js +4 -4
  9. package/lib/browser/driftClient.d.ts +25 -2
  10. package/lib/browser/driftClient.js +27 -4
  11. package/lib/browser/events/types.d.ts +3 -3
  12. package/lib/browser/idl/drift.json +72 -0
  13. package/lib/browser/types.d.ts +22 -1
  14. package/lib/node/accounts/grpcDriftClientAccountSubscriberV2.d.ts +52 -4
  15. package/lib/node/accounts/grpcDriftClientAccountSubscriberV2.d.ts.map +1 -1
  16. package/lib/node/accounts/grpcDriftClientAccountSubscriberV2.js +315 -38
  17. package/lib/node/accounts/grpcMultiAccountSubscriber.d.ts +11 -4
  18. package/lib/node/accounts/grpcMultiAccountSubscriber.d.ts.map +1 -1
  19. package/lib/node/accounts/grpcMultiAccountSubscriber.js +124 -18
  20. package/lib/node/adminClient.d.ts +2 -0
  21. package/lib/node/adminClient.d.ts.map +1 -1
  22. package/lib/node/adminClient.js +17 -0
  23. package/lib/node/constants/spotMarkets.js +4 -4
  24. package/lib/node/driftClient.d.ts +25 -2
  25. package/lib/node/driftClient.d.ts.map +1 -1
  26. package/lib/node/driftClient.js +27 -4
  27. package/lib/node/events/types.d.ts +3 -3
  28. package/lib/node/events/types.d.ts.map +1 -1
  29. package/lib/node/idl/drift.json +72 -0
  30. package/lib/node/types.d.ts +22 -1
  31. package/lib/node/types.d.ts.map +1 -1
  32. package/package.json +8 -2
  33. package/scripts/client-test.ts +361 -75
  34. package/src/accounts/grpcDriftClientAccountSubscriberV2.ts +517 -77
  35. package/src/accounts/grpcMultiAccountSubscriber.ts +179 -32
  36. package/src/adminClient.ts +34 -0
  37. package/src/constants/spotMarkets.ts +4 -4
  38. package/src/driftClient.ts +32 -4
  39. package/src/events/types.ts +4 -2
  40. package/src/idl/drift.json +72 -0
  41. package/src/types.ts +25 -2
@@ -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,19 +7,72 @@ 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
- import { DelistedMarketSetting, GrpcConfigs, ResubOpts } from './types';
14
+ import {
15
+ AccountSubscriber,
16
+ DataAndSlot,
17
+ DelistedMarketSetting,
18
+ DriftClientAccountEvents,
19
+ DriftClientAccountSubscriber,
20
+ NotSubscribedError,
21
+ GrpcConfigs,
22
+ ResubOpts,
23
+ } from './types';
12
24
  import { grpcAccountSubscriber } from './grpcAccountSubscriber';
13
25
  import { grpcMultiAccountSubscriber } from './grpcMultiAccountSubscriber';
14
26
  import { PerpMarketAccount, SpotMarketAccount, StateAccount } from '../types';
15
- import { getOracleId } from '../oracles/oracleId';
16
-
17
- export class grpcDriftClientAccountSubscriberV2 extends WebSocketDriftClientAccountSubscriber {
27
+ import {
28
+ getOracleId,
29
+ getPublicKeyAndSourceFromOracleId,
30
+ } from '../oracles/oracleId';
31
+ import { OracleClientCache } from '../oracles/oracleClientCache';
32
+ import { findDelistedPerpMarketsAndOracles } from './utils';
33
+
34
+ export class grpcDriftClientAccountSubscriberV2
35
+ implements DriftClientAccountSubscriber
36
+ {
18
37
  private grpcConfigs: GrpcConfigs;
19
38
  private perpMarketsSubscriber?: grpcMultiAccountSubscriber<PerpMarketAccount>;
20
39
  private spotMarketsSubscriber?: grpcMultiAccountSubscriber<SpotMarketAccount>;
21
- private oracleMultiSubscriber?: grpcMultiAccountSubscriber<OraclePriceData>;
40
+ private oracleMultiSubscriber?: grpcMultiAccountSubscriber<
41
+ OraclePriceData,
42
+ OracleInfo
43
+ >;
44
+ private perpMarketIndexToAccountPubkeyMap = new Map<number, string>();
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;
22
76
 
23
77
  constructor(
24
78
  grpcConfigs: GrpcConfigs,
@@ -30,16 +84,156 @@ export class grpcDriftClientAccountSubscriberV2 extends WebSocketDriftClientAcco
30
84
  delistedMarketSetting: DelistedMarketSetting,
31
85
  resubOpts?: ResubOpts
32
86
  ) {
33
- super(
34
- program,
35
- perpMarketIndexes,
36
- spotMarketIndexes,
37
- oracleInfos,
38
- shouldFindAllMarketsAndOracles,
39
- delistedMarketSetting,
40
- resubOpts
41
- );
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();
42
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;
43
237
  }
44
238
 
45
239
  public async subscribe(): Promise<boolean> {
@@ -123,11 +317,174 @@ export class grpcDriftClientAccountSubscriberV2 extends WebSocketDriftClientAcco
123
317
  return true;
124
318
  }
125
319
 
126
- override async subscribeToPerpMarketAccounts(): Promise<boolean> {
127
- const perpMarketPubkeys = await Promise.all(
128
- this.perpMarketIndexes.map((marketIndex) =>
129
- getPerpMarketPublicKey(this.program.programId, marketIndex)
130
- )
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(
351
+ marketIndex: number
352
+ ): DataAndSlot<PerpMarketAccount> | undefined {
353
+ return this.perpMarketsSubscriber?.getAccountData(
354
+ this.perpMarketIndexToAccountPubkeyMap.get(marketIndex)
355
+ );
356
+ }
357
+
358
+ getSpotMarketAccountAndSlot(
359
+ marketIndex: number
360
+ ): DataAndSlot<SpotMarketAccount> | undefined {
361
+ return this.spotMarketsSubscriber?.getAccountData(
362
+ this.spotMarketIndexToAccountPubkeyMap.get(marketIndex)
363
+ );
364
+ }
365
+
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() {
412
+ const perpMarketsMap = this.perpMarketsSubscriber?.getAccountDataMap();
413
+ const perpMarkets = Array.from(perpMarketsMap.values());
414
+ const addOraclePromises = [];
415
+ for (const perpMarket of perpMarkets) {
416
+ if (!perpMarket || !perpMarket.data) {
417
+ continue;
418
+ }
419
+ const perpMarketAccount = perpMarket.data;
420
+ const perpMarketIndex = perpMarketAccount.marketIndex;
421
+ const oracle = perpMarketAccount.amm.oracle;
422
+ const oracleId = getOracleId(oracle, perpMarket.data.amm.oracleSource);
423
+ if (!this.oracleMultiSubscriber?.getAccountDataMap().has(oracleId)) {
424
+ addOraclePromises.push(
425
+ this.addOracle({
426
+ publicKey: oracle,
427
+ source: perpMarket.data.amm.oracleSource,
428
+ })
429
+ );
430
+ }
431
+ this.perpOracleMap.set(perpMarketIndex, oracle);
432
+ this.perpOracleStringMap.set(perpMarketIndex, oracleId);
433
+ }
434
+ await Promise.all(addOraclePromises);
435
+ }
436
+
437
+ async setSpotOracleMap() {
438
+ const spotMarketsMap = this.spotMarketsSubscriber?.getAccountDataMap();
439
+ const spotMarkets = Array.from(spotMarketsMap.values());
440
+ const addOraclePromises = [];
441
+ for (const spotMarket of spotMarkets) {
442
+ if (!spotMarket || !spotMarket.data) {
443
+ continue;
444
+ }
445
+ const spotMarketAccount = spotMarket.data;
446
+ const spotMarketIndex = spotMarketAccount.marketIndex;
447
+ const oracle = spotMarketAccount.oracle;
448
+ const oracleId = getOracleId(oracle, spotMarketAccount.oracleSource);
449
+ if (!this.oracleMultiSubscriber?.getAccountDataMap().has(oracleId)) {
450
+ addOraclePromises.push(
451
+ this.addOracle({
452
+ publicKey: oracle,
453
+ source: spotMarketAccount.oracleSource,
454
+ })
455
+ );
456
+ }
457
+ this.spotOracleMap.set(spotMarketIndex, oracle);
458
+ this.spotOracleStringMap.set(spotMarketIndex, oracleId);
459
+ }
460
+ await Promise.all(addOraclePromises);
461
+ }
462
+
463
+ async subscribeToPerpMarketAccounts(): Promise<boolean> {
464
+ if (this.resubOpts?.logResubMessages) {
465
+ console.log(
466
+ '[grpcDriftClientAccountSubscriberV2] subscribeToPerpMarketAccounts'
467
+ );
468
+ }
469
+ const perpMarketIndexToAccountPubkeys: Array<[number, PublicKey]> =
470
+ await Promise.all(
471
+ this.perpMarketIndexes.map(async (marketIndex) => [
472
+ marketIndex,
473
+ await getPerpMarketPublicKey(this.program.programId, marketIndex),
474
+ ])
475
+ );
476
+ for (const [
477
+ marketIndex,
478
+ accountPubkey,
479
+ ] of perpMarketIndexToAccountPubkeys) {
480
+ this.perpMarketIndexToAccountPubkeyMap.set(
481
+ marketIndex,
482
+ accountPubkey.toBase58()
483
+ );
484
+ }
485
+
486
+ const perpMarketPubkeys = perpMarketIndexToAccountPubkeys.map(
487
+ ([_, accountPubkey]) => accountPubkey
131
488
  );
132
489
 
133
490
  this.perpMarketsSubscriber =
@@ -151,6 +508,11 @@ export class grpcDriftClientAccountSubscriberV2 extends WebSocketDriftClientAcco
151
508
  }
152
509
  }
153
510
  );
511
+
512
+ for (const data of this.initialPerpMarketAccountData.values()) {
513
+ this.perpMarketsSubscriber.setAccountData(data.pubkey.toBase58(), data);
514
+ }
515
+
154
516
  await this.perpMarketsSubscriber.subscribe(
155
517
  perpMarketPubkeys,
156
518
  (_accountId, data) => {
@@ -165,11 +527,31 @@ export class grpcDriftClientAccountSubscriberV2 extends WebSocketDriftClientAcco
165
527
  return true;
166
528
  }
167
529
 
168
- override async subscribeToSpotMarketAccounts(): Promise<boolean> {
169
- const spotMarketPubkeys = await Promise.all(
170
- this.spotMarketIndexes.map((marketIndex) =>
171
- getSpotMarketPublicKey(this.program.programId, marketIndex)
172
- )
530
+ async subscribeToSpotMarketAccounts(): Promise<boolean> {
531
+ if (this.resubOpts?.logResubMessages) {
532
+ console.log(
533
+ '[grpcDriftClientAccountSubscriberV2] subscribeToSpotMarketAccounts'
534
+ );
535
+ }
536
+ const spotMarketIndexToAccountPubkeys: Array<[number, PublicKey]> =
537
+ await Promise.all(
538
+ this.spotMarketIndexes.map(async (marketIndex) => [
539
+ marketIndex,
540
+ await getSpotMarketPublicKey(this.program.programId, marketIndex),
541
+ ])
542
+ );
543
+ for (const [
544
+ marketIndex,
545
+ accountPubkey,
546
+ ] of spotMarketIndexToAccountPubkeys) {
547
+ this.spotMarketIndexToAccountPubkeyMap.set(
548
+ marketIndex,
549
+ accountPubkey.toBase58()
550
+ );
551
+ }
552
+
553
+ const spotMarketPubkeys = spotMarketIndexToAccountPubkeys.map(
554
+ ([_, accountPubkey]) => accountPubkey
173
555
  );
174
556
 
175
557
  this.spotMarketsSubscriber =
@@ -193,6 +575,11 @@ export class grpcDriftClientAccountSubscriberV2 extends WebSocketDriftClientAcco
193
575
  }
194
576
  }
195
577
  );
578
+
579
+ for (const data of this.initialSpotMarketAccountData.values()) {
580
+ this.spotMarketsSubscriber.setAccountData(data.pubkey.toBase58(), data);
581
+ }
582
+
196
583
  await this.spotMarketsSubscriber.subscribe(
197
584
  spotMarketPubkeys,
198
585
  (_accountId, data) => {
@@ -207,67 +594,84 @@ export class grpcDriftClientAccountSubscriberV2 extends WebSocketDriftClientAcco
207
594
  return true;
208
595
  }
209
596
 
210
- override async subscribeToOracles(): Promise<boolean> {
211
- // Build list of unique oracle pubkeys and a lookup for sources
212
- 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[]>();
213
602
  for (const info of this.oracleInfos) {
214
- const id = getOracleId(info.publicKey, info.source);
215
- if (
216
- !uniqueOraclePubkeys.has(id) &&
217
- !info.publicKey.equals((PublicKey as any).default)
218
- ) {
219
- uniqueOraclePubkeys.set(id, info);
603
+ const pubkey = info.publicKey.toBase58();
604
+ if (!oraclePubkeyToInfosMap.has(pubkey)) {
605
+ oraclePubkeyToInfosMap.set(pubkey, []);
220
606
  }
607
+ oraclePubkeyToInfosMap.get(pubkey).push(info);
221
608
  }
222
609
 
223
- const oraclePubkeys = Array.from(uniqueOraclePubkeys.values()).map(
224
- (i) => i.publicKey
225
- );
226
- const pubkeyToSource = new Map<string, OracleInfo['source']>(
227
- Array.from(uniqueOraclePubkeys.values()).map((i) => [
228
- i.publicKey.toBase58(),
229
- i.source,
230
- ])
610
+ const oraclePubkeys = Array.from(
611
+ new Set(this.oracleInfos.map((info) => info.publicKey))
231
612
  );
232
613
 
233
- this.oracleMultiSubscriber =
234
- await grpcMultiAccountSubscriber.create<OraclePriceData>(
235
- this.grpcConfigs,
236
- 'oracle',
237
- this.program,
238
- (buffer: Buffer, pubkey?: string) => {
239
- if (!pubkey) {
240
- throw new Error('Oracle pubkey missing in decode');
241
- }
242
- const source = pubkeyToSource.get(pubkey);
243
- const client = this.oracleClientCache.get(
244
- source,
245
- this.program.provider.connection,
246
- this.program
247
- );
248
- return client.getOraclePriceDataFromBuffer(buffer);
249
- },
250
- this.resubOpts,
251
- undefined,
252
- async () => {
253
- try {
254
- if (this.resubOpts?.logResubMessages) {
255
- console.log(
256
- '[grpcDriftClientAccountSubscriberV2] oracle subscriber unsubscribed; resubscribing'
257
- );
258
- }
259
- await this.subscribeToOracles();
260
- } catch (e) {
261
- 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
+ );
262
642
  }
643
+ await this.subscribeToOracles();
644
+ } catch (e) {
645
+ console.error('Oracle resubscribe failed:', e);
263
646
  }
264
- );
647
+ },
648
+ oraclePubkeyToInfosMap
649
+ );
650
+
651
+ for (const data of this.initialOraclePriceData.entries()) {
652
+ const { publicKey } = getPublicKeyAndSourceFromOracleId(data[0]);
653
+ this.oracleMultiSubscriber.setAccountData(publicKey.toBase58(), data[1]);
654
+ this.oracleIdToOracleDataMap.set(data[0], {
655
+ data: data[1],
656
+ slot: 0,
657
+ });
658
+ }
265
659
 
266
660
  await this.oracleMultiSubscriber.subscribe(
267
661
  oraclePubkeys,
268
- (accountId, data) => {
269
- const source = pubkeyToSource.get(accountId.toBase58());
270
- 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
+
271
675
  this.eventEmitter.emit('update');
272
676
  }
273
677
  );
@@ -275,20 +679,56 @@ export class grpcDriftClientAccountSubscriberV2 extends WebSocketDriftClientAcco
275
679
  return true;
276
680
  }
277
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
+ for (const perpMarketIndex of perpMarketIndexes) {
695
+ await this.perpMarketsSubscriber.removeAccounts([
696
+ new PublicKey(
697
+ this.perpMarketIndexToAccountPubkeyMap.get(perpMarketIndex) || ''
698
+ ),
699
+ ]);
700
+ if (this.delistedMarketSetting === DelistedMarketSetting.Discard) {
701
+ this.perpMarketIndexToAccountPubkeyMap.delete(perpMarketIndex);
702
+ }
703
+ }
704
+
705
+ for (const oracle of oracles) {
706
+ await this.oracleMultiSubscriber.removeAccounts([oracle.publicKey]);
707
+ }
708
+ }
709
+
710
+ removeInitialData() {
711
+ this.initialPerpMarketAccountData = new Map();
712
+ this.initialSpotMarketAccountData = new Map();
713
+ this.initialOraclePriceData = new Map();
714
+ }
715
+
278
716
  async unsubscribeFromOracles(): Promise<void> {
279
717
  if (this.oracleMultiSubscriber) {
280
718
  await this.oracleMultiSubscriber.unsubscribe();
281
719
  this.oracleMultiSubscriber = undefined;
282
720
  return;
283
721
  }
284
- await super.unsubscribeFromOracles();
285
722
  }
286
723
 
287
- override async unsubscribe(): Promise<void> {
724
+ async unsubscribe(): Promise<void> {
288
725
  if (this.isSubscribed) {
289
726
  return;
290
727
  }
291
728
 
292
729
  await this.stateAccountSubscriber.unsubscribe();
730
+ await this.unsubscribeFromOracles();
731
+ await this.perpMarketsSubscriber?.unsubscribe();
732
+ await this.spotMarketsSubscriber?.unsubscribe();
293
733
  }
294
734
  }