@drift-labs/sdk 2.142.0 → 2.143.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/VERSION CHANGED
@@ -1 +1 @@
1
- 2.142.0-beta.27
1
+ 2.143.0-beta.1
@@ -106,9 +106,10 @@ class grpcDriftClientAccountSubscriberV2 {
106
106
  }
107
107
  const exists = this.oracleInfos.some((o) => o.source === oracleInfo.source &&
108
108
  o.publicKey.equals(oracleInfo.publicKey));
109
- if (!exists) {
110
- this.oracleInfos = this.oracleInfos.concat(oracleInfo);
109
+ if (exists) {
110
+ return true; // Already exists, don't add duplicate
111
111
  }
112
+ this.oracleInfos = this.oracleInfos.concat(oracleInfo);
112
113
  (_c = this.oracleMultiSubscriber) === null || _c === void 0 ? void 0 : _c.addAccounts([oracleInfo.publicKey]);
113
114
  return true;
114
115
  }
@@ -412,10 +413,31 @@ class grpcDriftClientAccountSubscriberV2 {
412
413
  // Remove accounts in batches - perp markets
413
414
  if (perpMarketPubkeysToRemove.length > 0) {
414
415
  await this.perpMarketsSubscriber.removeAccounts(perpMarketPubkeysToRemove);
416
+ // Clean up the mapping for removed perp markets
417
+ for (const pubkey of perpMarketPubkeysToRemove) {
418
+ const pubkeyString = pubkey.toBase58();
419
+ for (const [marketIndex, accountPubkey,] of this.perpMarketIndexToAccountPubkeyMap.entries()) {
420
+ if (accountPubkey === pubkeyString) {
421
+ this.perpMarketIndexToAccountPubkeyMap.delete(marketIndex);
422
+ this.perpOracleMap.delete(marketIndex);
423
+ this.perpOracleStringMap.delete(marketIndex);
424
+ break;
425
+ }
426
+ }
427
+ }
415
428
  }
416
429
  // Remove accounts in batches - oracles
417
430
  if (oraclePubkeysToRemove.length > 0) {
418
431
  await this.oracleMultiSubscriber.removeAccounts(oraclePubkeysToRemove);
432
+ // Clean up oracle data for removed oracles by finding their sources
433
+ for (const pubkey of oraclePubkeysToRemove) {
434
+ // Find the oracle source by checking oracleInfos
435
+ const oracleInfo = this.oracleInfos.find((info) => info.publicKey.equals(pubkey));
436
+ if (oracleInfo) {
437
+ const oracleId = (0, oracleId_1.getOracleId)(pubkey, oracleInfo.source);
438
+ this.oracleIdToOracleDataMap.delete(oracleId);
439
+ }
440
+ }
419
441
  }
420
442
  }
421
443
  removeInitialData() {
@@ -431,14 +453,24 @@ class grpcDriftClientAccountSubscriberV2 {
431
453
  }
432
454
  }
433
455
  async unsubscribe() {
434
- var _a, _c;
435
- if (this.isSubscribed) {
456
+ var _a, _c, _d;
457
+ if (!this.isSubscribed) {
436
458
  return;
437
459
  }
438
- await this.stateAccountSubscriber.unsubscribe();
460
+ this.isSubscribed = false;
461
+ this.isSubscribing = false;
462
+ await ((_a = this.stateAccountSubscriber) === null || _a === void 0 ? void 0 : _a.unsubscribe());
439
463
  await this.unsubscribeFromOracles();
440
- await ((_a = this.perpMarketsSubscriber) === null || _a === void 0 ? void 0 : _a.unsubscribe());
441
- await ((_c = this.spotMarketsSubscriber) === null || _c === void 0 ? void 0 : _c.unsubscribe());
464
+ await ((_c = this.perpMarketsSubscriber) === null || _c === void 0 ? void 0 : _c.unsubscribe());
465
+ await ((_d = this.spotMarketsSubscriber) === null || _d === void 0 ? void 0 : _d.unsubscribe());
466
+ // Clean up all maps to prevent memory leaks
467
+ this.perpMarketIndexToAccountPubkeyMap.clear();
468
+ this.spotMarketIndexToAccountPubkeyMap.clear();
469
+ this.oracleIdToOracleDataMap.clear();
470
+ this.perpOracleMap.clear();
471
+ this.perpOracleStringMap.clear();
472
+ this.spotOracleMap.clear();
473
+ this.spotOracleStringMap.clear();
442
474
  }
443
475
  }
444
476
  exports.grpcDriftClientAccountSubscriberV2 = grpcDriftClientAccountSubscriberV2;
@@ -1 +1 @@
1
- {"version":3,"file":"grpcDriftClientAccountSubscriberV2.d.ts","sourceRoot":"","sources":["../../../src/accounts/grpcDriftClientAccountSubscriberV2.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"grpcDriftClientAccountSubscriberV2.d.ts","sourceRoot":"","sources":["../../../src/accounts/grpcDriftClientAccountSubscriberV2.ts"],"names":[],"mappings":";AAAA,OAAO,kBAAkB,MAAM,4BAA4B,CAAC;AAC5D,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAC/D,OAAO,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAC5C,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAS5C,OAAO,EACN,iBAAiB,EACjB,WAAW,EACX,qBAAqB,EACrB,wBAAwB,EACxB,4BAA4B,EAE5B,WAAW,EACX,SAAS,EACT,MAAM,SAAS,CAAC;AAGjB,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAK9E,OAAO,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AAGjE,qBAAa,kCACZ,YAAW,4BAA4B;IAEvC,OAAO,CAAC,WAAW,CAAc;IACjC,OAAO,CAAC,qBAAqB,CAAC,CAAgD;IAC9E,OAAO,CAAC,qBAAqB,CAAC,CAAgD;IAC9E,OAAO,CAAC,qBAAqB,CAAC,CAG5B;IACF,OAAO,CAAC,iCAAiC,CAA6B;IACtE,OAAO,CAAC,iCAAiC,CAA6B;IACtE,OAAO,CAAC,qBAAqB,CAAwB;IAE9C,YAAY,EAAE,kBAAkB,CACtC,YAAY,EACZ,wBAAwB,CACxB,CAAC;IACK,YAAY,EAAE,OAAO,CAAC;IACtB,aAAa,EAAE,OAAO,CAAC;IACvB,OAAO,EAAE,OAAO,CAAC;IACjB,iBAAiB,EAAE,MAAM,EAAE,CAAC;IAC5B,iBAAiB,EAAE,MAAM,EAAE,CAAC;IAC5B,8BAA8B,EAAE,OAAO,CAAC;IACxC,WAAW,EAAE,UAAU,EAAE,CAAC;IAC1B,4BAA4B,EAAE,GAAG,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC;IAC7D,4BAA4B,EAAE,GAAG,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC;IAC7D,sBAAsB,EAAE,GAAG,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;IACrD,aAAa,yBAAgC;IAC7C,mBAAmB,sBAA6B;IAChD,aAAa,yBAAgC;IAC7C,mBAAmB,sBAA6B;IACvD,OAAO,CAAC,uBAAuB,CAG3B;IACG,sBAAsB,CAAC,EAAE,iBAAiB,CAAC,YAAY,CAAC,CAAC;IAChE,iBAAiB,oBAA2B;IAC5C,OAAO,CAAC,SAAS,CAAC,CAAY;IAE9B,OAAO,CAAC,mBAAmB,CAAmB;IAC9C,SAAS,CAAC,2BAA2B,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,IAAI,CAAC;gBAG7D,WAAW,EAAE,WAAW,EACxB,OAAO,EAAE,OAAO,EAChB,iBAAiB,EAAE,MAAM,EAAE,EAC3B,iBAAiB,EAAE,MAAM,EAAE,EAC3B,WAAW,EAAE,UAAU,EAAE,EACzB,8BAA8B,EAAE,OAAO,EACvC,qBAAqB,EAAE,qBAAqB,EAC5C,SAAS,CAAC,EAAE,SAAS;IAsBtB,MAAM,aAAc,SAAS,CAAC,EAAE,QAAQ,MAAM,KAAG,CAAC,EAAE,EAAE,CAKpD;IAEI,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC;IA4F/B,aAAa,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAOrD,aAAa,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAIrD,SAAS,CAAC,UAAU,EAAE,UAAU,GAAG,OAAO,CAAC,OAAO,CAAC;IAuB5C,SAAS,IAAI,OAAO,CAAC,OAAO,CAAC;IAiF7B,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAOnC,OAAO,CAAC,kBAAkB;IAQnB,sBAAsB,IAAI,WAAW,CAAC,YAAY,CAAC;IAKnD,yBAAyB,IAAI,WAAW,CAAC,iBAAiB,CAAC,EAAE;IAK7D,6BAA6B,IAAI,WAAW,CAAC,iBAAiB,CAAC,EAAE;IAKxE,uBAAuB,CACtB,WAAW,EAAE,MAAM,GACjB,WAAW,CAAC,iBAAiB,CAAC,GAAG,SAAS;IAM7C,2BAA2B,CAC1B,WAAW,EAAE,MAAM,GACjB,WAAW,CAAC,iBAAiB,CAAC,GAAG,SAAS;IAMtC,yBAAyB,CAC/B,QAAQ,EAAE,MAAM,GACd,WAAW,CAAC,eAAe,CAAC,GAAG,SAAS;IAOpC,sCAAsC,CAC5C,WAAW,EAAE,MAAM,GACjB,WAAW,CAAC,eAAe,CAAC,GAAG,SAAS;IAgBpC,sCAAsC,CAC5C,WAAW,EAAE,MAAM,GACjB,WAAW,CAAC,eAAe,CAAC,GAAG,SAAS;IAgBrC,gBAAgB;IA0BhB,gBAAgB;IA0BhB,6BAA6B,IAAI,OAAO,CAAC,OAAO,CAAC;IAmEjD,6BAA6B,IAAI,OAAO,CAAC,OAAO,CAAC;IAmEjD,kBAAkB,IAAI,OAAO,CAAC,OAAO,CAAC;IAqFtC,qBAAqB,IAAI,OAAO,CAAC,IAAI,CAAC;IA+D5C,iBAAiB;IAMX,sBAAsB,IAAI,OAAO,CAAC,IAAI,CAAC;IAQvC,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC;CAsBlC"}
@@ -106,9 +106,10 @@ class grpcDriftClientAccountSubscriberV2 {
106
106
  }
107
107
  const exists = this.oracleInfos.some((o) => o.source === oracleInfo.source &&
108
108
  o.publicKey.equals(oracleInfo.publicKey));
109
- if (!exists) {
110
- this.oracleInfos = this.oracleInfos.concat(oracleInfo);
109
+ if (exists) {
110
+ return true; // Already exists, don't add duplicate
111
111
  }
112
+ this.oracleInfos = this.oracleInfos.concat(oracleInfo);
112
113
  (_c = this.oracleMultiSubscriber) === null || _c === void 0 ? void 0 : _c.addAccounts([oracleInfo.publicKey]);
113
114
  return true;
114
115
  }
@@ -412,10 +413,31 @@ class grpcDriftClientAccountSubscriberV2 {
412
413
  // Remove accounts in batches - perp markets
413
414
  if (perpMarketPubkeysToRemove.length > 0) {
414
415
  await this.perpMarketsSubscriber.removeAccounts(perpMarketPubkeysToRemove);
416
+ // Clean up the mapping for removed perp markets
417
+ for (const pubkey of perpMarketPubkeysToRemove) {
418
+ const pubkeyString = pubkey.toBase58();
419
+ for (const [marketIndex, accountPubkey,] of this.perpMarketIndexToAccountPubkeyMap.entries()) {
420
+ if (accountPubkey === pubkeyString) {
421
+ this.perpMarketIndexToAccountPubkeyMap.delete(marketIndex);
422
+ this.perpOracleMap.delete(marketIndex);
423
+ this.perpOracleStringMap.delete(marketIndex);
424
+ break;
425
+ }
426
+ }
427
+ }
415
428
  }
416
429
  // Remove accounts in batches - oracles
417
430
  if (oraclePubkeysToRemove.length > 0) {
418
431
  await this.oracleMultiSubscriber.removeAccounts(oraclePubkeysToRemove);
432
+ // Clean up oracle data for removed oracles by finding their sources
433
+ for (const pubkey of oraclePubkeysToRemove) {
434
+ // Find the oracle source by checking oracleInfos
435
+ const oracleInfo = this.oracleInfos.find((info) => info.publicKey.equals(pubkey));
436
+ if (oracleInfo) {
437
+ const oracleId = (0, oracleId_1.getOracleId)(pubkey, oracleInfo.source);
438
+ this.oracleIdToOracleDataMap.delete(oracleId);
439
+ }
440
+ }
419
441
  }
420
442
  }
421
443
  removeInitialData() {
@@ -431,14 +453,24 @@ class grpcDriftClientAccountSubscriberV2 {
431
453
  }
432
454
  }
433
455
  async unsubscribe() {
434
- var _a, _c;
435
- if (this.isSubscribed) {
456
+ var _a, _c, _d;
457
+ if (!this.isSubscribed) {
436
458
  return;
437
459
  }
438
- await this.stateAccountSubscriber.unsubscribe();
460
+ this.isSubscribed = false;
461
+ this.isSubscribing = false;
462
+ await ((_a = this.stateAccountSubscriber) === null || _a === void 0 ? void 0 : _a.unsubscribe());
439
463
  await this.unsubscribeFromOracles();
440
- await ((_a = this.perpMarketsSubscriber) === null || _a === void 0 ? void 0 : _a.unsubscribe());
441
- await ((_c = this.spotMarketsSubscriber) === null || _c === void 0 ? void 0 : _c.unsubscribe());
464
+ await ((_c = this.perpMarketsSubscriber) === null || _c === void 0 ? void 0 : _c.unsubscribe());
465
+ await ((_d = this.spotMarketsSubscriber) === null || _d === void 0 ? void 0 : _d.unsubscribe());
466
+ // Clean up all maps to prevent memory leaks
467
+ this.perpMarketIndexToAccountPubkeyMap.clear();
468
+ this.spotMarketIndexToAccountPubkeyMap.clear();
469
+ this.oracleIdToOracleDataMap.clear();
470
+ this.perpOracleMap.clear();
471
+ this.perpOracleStringMap.clear();
472
+ this.spotOracleMap.clear();
473
+ this.spotOracleStringMap.clear();
442
474
  }
443
475
  }
444
476
  exports.grpcDriftClientAccountSubscriberV2 = grpcDriftClientAccountSubscriberV2;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@drift-labs/sdk",
3
- "version": "2.142.0",
3
+ "version": "2.143.0-beta.1",
4
4
  "main": "lib/node/index.js",
5
5
  "types": "lib/node/index.d.ts",
6
6
  "module": "./lib/browser/index.js",
@@ -0,0 +1,226 @@
1
+ import { DriftClient } from '../src/driftClient';
2
+ import { grpcDriftClientAccountSubscriberV2 } from '../src/accounts/grpcDriftClientAccountSubscriberV2';
3
+ import { Connection, Keypair, PublicKey } from '@solana/web3.js';
4
+ import { DriftClientConfig } from '../src/driftClientConfig';
5
+ import {
6
+ DRIFT_PROGRAM_ID,
7
+ PerpMarketAccount,
8
+ SpotMarketAccount,
9
+ Wallet,
10
+ OracleInfo,
11
+ decodeName,
12
+ } from '../src';
13
+ import { CommitmentLevel } from '@triton-one/yellowstone-grpc';
14
+ import dotenv from 'dotenv';
15
+ import {
16
+ AnchorProvider,
17
+ Idl,
18
+ Program,
19
+ ProgramAccount,
20
+ } from '@coral-xyz/anchor';
21
+ import driftIDL from '../src/idl/drift.json';
22
+
23
+ const GRPC_ENDPOINT = process.env.GRPC_ENDPOINT;
24
+ const TOKEN = process.env.TOKEN;
25
+ const RPC_ENDPOINT = process.env.RPC_ENDPOINT;
26
+
27
+ async function initializeSingleGrpcClient() {
28
+ console.log('šŸš€ Initializing single gRPC Drift Client...');
29
+
30
+ const connection = new Connection(RPC_ENDPOINT);
31
+ const wallet = new Wallet(new Keypair());
32
+ dotenv.config({ path: '../' });
33
+
34
+ const programId = new PublicKey(DRIFT_PROGRAM_ID);
35
+ const provider = new AnchorProvider(
36
+ connection,
37
+ // @ts-ignore
38
+ wallet,
39
+ {
40
+ commitment: 'processed',
41
+ }
42
+ );
43
+
44
+ const program = new Program(driftIDL as Idl, programId, provider);
45
+
46
+ // Get perp market accounts
47
+ const allPerpMarketProgramAccounts =
48
+ (await program.account.perpMarket.all()) as ProgramAccount<PerpMarketAccount>[];
49
+ const perpMarketProgramAccounts = allPerpMarketProgramAccounts.filter((val) =>
50
+ [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15].includes(
51
+ val.account.marketIndex
52
+ )
53
+ );
54
+ const perpMarketIndexes = perpMarketProgramAccounts.map(
55
+ (val) => val.account.marketIndex
56
+ );
57
+
58
+ // Get spot market accounts
59
+ const allSpotMarketProgramAccounts =
60
+ (await program.account.spotMarket.all()) as ProgramAccount<SpotMarketAccount>[];
61
+ const spotMarketProgramAccounts = allSpotMarketProgramAccounts.filter((val) =>
62
+ [0, 1, 2, 3, 4, 5].includes(val.account.marketIndex)
63
+ );
64
+ const spotMarketIndexes = spotMarketProgramAccounts.map(
65
+ (val) => val.account.marketIndex
66
+ );
67
+
68
+ // Get oracle infos
69
+ const seen = new Set<string>();
70
+ const oracleInfos: OracleInfo[] = [];
71
+ for (const acct of perpMarketProgramAccounts) {
72
+ const key = `${acct.account.amm.oracle.toBase58()}-${
73
+ Object.keys(acct.account.amm.oracleSource)[0]
74
+ }`;
75
+ if (!seen.has(key)) {
76
+ seen.add(key);
77
+ oracleInfos.push({
78
+ publicKey: acct.account.amm.oracle,
79
+ source: acct.account.amm.oracleSource,
80
+ });
81
+ }
82
+ }
83
+ for (const acct of spotMarketProgramAccounts) {
84
+ const key = `${acct.account.oracle.toBase58()}-${
85
+ Object.keys(acct.account.oracleSource)[0]
86
+ }`;
87
+ if (!seen.has(key)) {
88
+ seen.add(key);
89
+ oracleInfos.push({
90
+ publicKey: acct.account.oracle,
91
+ source: acct.account.oracleSource,
92
+ });
93
+ }
94
+ }
95
+
96
+ console.log(`šŸ“Š Markets: ${perpMarketIndexes.length} perp, ${spotMarketIndexes.length} spot`);
97
+ console.log(`šŸ”® Oracles: ${oracleInfos.length}`);
98
+
99
+ const baseAccountSubscription = {
100
+ type: 'grpc' as const,
101
+ grpcConfigs: {
102
+ endpoint: GRPC_ENDPOINT,
103
+ token: TOKEN,
104
+ commitmentLevel: CommitmentLevel.PROCESSED,
105
+ channelOptions: {
106
+ 'grpc.keepalive_time_ms': 10_000,
107
+ 'grpc.keepalive_timeout_ms': 1_000,
108
+ 'grpc.keepalive_permit_without_calls': 1,
109
+ },
110
+ },
111
+ };
112
+
113
+ const config: DriftClientConfig = {
114
+ connection,
115
+ wallet,
116
+ programID: new PublicKey(DRIFT_PROGRAM_ID),
117
+ accountSubscription: {
118
+ ...baseAccountSubscription,
119
+ driftClientAccountSubscriber: grpcDriftClientAccountSubscriberV2,
120
+ },
121
+ perpMarketIndexes,
122
+ spotMarketIndexes,
123
+ oracleInfos,
124
+ };
125
+
126
+ const client = new DriftClient(config);
127
+
128
+ // Set up event listeners
129
+ const eventCounts = {
130
+ stateAccountUpdate: 0,
131
+ perpMarketAccountUpdate: 0,
132
+ spotMarketAccountUpdate: 0,
133
+ oraclePriceUpdate: 0,
134
+ update: 0,
135
+ };
136
+
137
+ console.log('šŸŽ§ Setting up event listeners...');
138
+
139
+ client.eventEmitter.on('stateAccountUpdate', (_data) => {
140
+ eventCounts.stateAccountUpdate++;
141
+ });
142
+
143
+ client.eventEmitter.on('perpMarketAccountUpdate', (_data) => {
144
+ eventCounts.perpMarketAccountUpdate++;
145
+ });
146
+
147
+ client.eventEmitter.on('spotMarketAccountUpdate', (_data) => {
148
+ eventCounts.spotMarketAccountUpdate++;
149
+ });
150
+
151
+ client.eventEmitter.on('oraclePriceUpdate', (_publicKey, _source, _data) => {
152
+ eventCounts.oraclePriceUpdate++;
153
+ });
154
+
155
+ client.accountSubscriber.eventEmitter.on('update', () => {
156
+ eventCounts.update++;
157
+ });
158
+
159
+ // Subscribe
160
+ console.log('šŸ”— Subscribing to accounts...');
161
+ await client.subscribe();
162
+
163
+ console.log('āœ… Client subscribed successfully!');
164
+ console.log('šŸš€ Starting high-load testing (50 reads/sec per perp market)...');
165
+
166
+ // High-frequency load testing - 50 reads per second per perp market
167
+ const loadTestInterval = setInterval(async () => {
168
+ try {
169
+ // Test getPerpMarketAccount for each perp market (50 times per second per market)
170
+ for (const marketIndex of perpMarketIndexes) {
171
+ const perpMarketAccount = client.getPerpMarketAccount(marketIndex);
172
+ console.log("perpMarketAccount name: ", decodeName(perpMarketAccount.name));
173
+ console.log("perpMarketAccount data: ", JSON.stringify({
174
+ marketIndex: perpMarketAccount.marketIndex,
175
+ name: decodeName(perpMarketAccount.name),
176
+ baseAssetReserve: perpMarketAccount.amm.baseAssetReserve.toString(),
177
+ quoteAssetReserve: perpMarketAccount.amm.quoteAssetReserve.toString()
178
+ }));
179
+ }
180
+
181
+ // Test getMMOracleDataForPerpMarket for each perp market (50 times per second per market)
182
+ for (const marketIndex of perpMarketIndexes) {
183
+ try {
184
+ const oracleData = client.getMMOracleDataForPerpMarket(marketIndex);
185
+ console.log("oracleData price: ", oracleData.price.toString());
186
+ console.log("oracleData: ", JSON.stringify({
187
+ price: oracleData.price.toString(),
188
+ confidence: oracleData.confidence?.toString(),
189
+ slot: oracleData.slot?.toString()
190
+ }));
191
+ } catch (error) {
192
+ // Ignore errors for load testing
193
+ }
194
+ }
195
+ } catch (error) {
196
+ console.error('Load test error:', error);
197
+ }
198
+ }, 20); // 50 times per second = 1000ms / 50 = 20ms interval
199
+
200
+ // Log periodic stats
201
+ const statsInterval = setInterval(() => {
202
+ console.log('\nšŸ“ˆ Event Counts:', eventCounts);
203
+ console.log(`ā±ļø Client subscribed: ${client.isSubscribed}`);
204
+ console.log(`šŸ”— Account subscriber subscribed: ${client.accountSubscriber.isSubscribed}`);
205
+ console.log(`šŸ”„ Load: ${perpMarketIndexes.length * 50 * 2} reads/sec (${perpMarketIndexes.length} markets Ɨ 50 getPerpMarketAccount + 50 getMMOracleDataForPerpMarket)`);
206
+ }, 5000);
207
+
208
+ // Handle shutdown signals - just exit without cleanup since they never unsubscribe
209
+ process.on('SIGINT', () => {
210
+ console.log('\nšŸ›‘ Shutting down...');
211
+ clearInterval(loadTestInterval);
212
+ clearInterval(statsInterval);
213
+ process.exit(0);
214
+ });
215
+
216
+ process.on('SIGTERM', () => {
217
+ console.log('\nšŸ›‘ Shutting down...');
218
+ clearInterval(loadTestInterval);
219
+ clearInterval(statsInterval);
220
+ process.exit(0);
221
+ });
222
+
223
+ return client;
224
+ }
225
+
226
+ initializeSingleGrpcClient().catch(console.error);
@@ -227,10 +227,11 @@ export class grpcDriftClientAccountSubscriberV2
227
227
  o.source === oracleInfo.source &&
228
228
  o.publicKey.equals(oracleInfo.publicKey)
229
229
  );
230
- if (!exists) {
231
- this.oracleInfos = this.oracleInfos.concat(oracleInfo);
230
+ if (exists) {
231
+ return true; // Already exists, don't add duplicate
232
232
  }
233
233
 
234
+ this.oracleInfos = this.oracleInfos.concat(oracleInfo);
234
235
  this.oracleMultiSubscriber?.addAccounts([oracleInfo.publicKey]);
235
236
 
236
237
  return true;
@@ -708,11 +709,37 @@ export class grpcDriftClientAccountSubscriberV2
708
709
  await this.perpMarketsSubscriber.removeAccounts(
709
710
  perpMarketPubkeysToRemove
710
711
  );
712
+ // Clean up the mapping for removed perp markets
713
+ for (const pubkey of perpMarketPubkeysToRemove) {
714
+ const pubkeyString = pubkey.toBase58();
715
+ for (const [
716
+ marketIndex,
717
+ accountPubkey,
718
+ ] of this.perpMarketIndexToAccountPubkeyMap.entries()) {
719
+ if (accountPubkey === pubkeyString) {
720
+ this.perpMarketIndexToAccountPubkeyMap.delete(marketIndex);
721
+ this.perpOracleMap.delete(marketIndex);
722
+ this.perpOracleStringMap.delete(marketIndex);
723
+ break;
724
+ }
725
+ }
726
+ }
711
727
  }
712
728
 
713
729
  // Remove accounts in batches - oracles
714
730
  if (oraclePubkeysToRemove.length > 0) {
715
731
  await this.oracleMultiSubscriber.removeAccounts(oraclePubkeysToRemove);
732
+ // Clean up oracle data for removed oracles by finding their sources
733
+ for (const pubkey of oraclePubkeysToRemove) {
734
+ // Find the oracle source by checking oracleInfos
735
+ const oracleInfo = this.oracleInfos.find((info) =>
736
+ info.publicKey.equals(pubkey)
737
+ );
738
+ if (oracleInfo) {
739
+ const oracleId = getOracleId(pubkey, oracleInfo.source);
740
+ this.oracleIdToOracleDataMap.delete(oracleId);
741
+ }
742
+ }
716
743
  }
717
744
  }
718
745
 
@@ -731,13 +758,25 @@ export class grpcDriftClientAccountSubscriberV2
731
758
  }
732
759
 
733
760
  async unsubscribe(): Promise<void> {
734
- if (this.isSubscribed) {
761
+ if (!this.isSubscribed) {
735
762
  return;
736
763
  }
737
764
 
738
- await this.stateAccountSubscriber.unsubscribe();
765
+ this.isSubscribed = false;
766
+ this.isSubscribing = false;
767
+
768
+ await this.stateAccountSubscriber?.unsubscribe();
739
769
  await this.unsubscribeFromOracles();
740
770
  await this.perpMarketsSubscriber?.unsubscribe();
741
771
  await this.spotMarketsSubscriber?.unsubscribe();
772
+
773
+ // Clean up all maps to prevent memory leaks
774
+ this.perpMarketIndexToAccountPubkeyMap.clear();
775
+ this.spotMarketIndexToAccountPubkeyMap.clear();
776
+ this.oracleIdToOracleDataMap.clear();
777
+ this.perpOracleMap.clear();
778
+ this.perpOracleStringMap.clear();
779
+ this.spotOracleMap.clear();
780
+ this.spotOracleStringMap.clear();
742
781
  }
743
782
  }