@drift-labs/sdk-browser 2.109.0-beta.5 → 2.109.0-beta.6

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.109.0-beta.5
1
+ 2.109.0-beta.6
@@ -81,7 +81,17 @@ export declare class UserMap implements UserMapInterface {
81
81
  */
82
82
  getUniqueAuthorities(filterCriteria?: UserFilterCriteria): PublicKey[];
83
83
  sync(): Promise<void>;
84
+ /**
85
+ * Syncs the UserMap using the default sync method (single getProgramAccounts call with filters).
86
+ * This method may fail when drift has too many users. (nodejs response size limits)
87
+ * @returns
88
+ */
84
89
  private defaultSync;
90
+ /**
91
+ * Syncs the UserMap using the paginated sync method (multiple getMultipleAccounts calls with filters).
92
+ * This method is more reliable when drift has many users.
93
+ * @returns
94
+ */
85
95
  private paginatedSync;
86
96
  unsubscribe(): Promise<void>;
87
97
  updateUserAccount(key: string, userAccount: UserAccount, slot: number): Promise<void>;
@@ -259,6 +259,11 @@ class UserMap {
259
259
  return this.paginatedSync();
260
260
  }
261
261
  }
262
+ /**
263
+ * Syncs the UserMap using the default sync method (single getProgramAccounts call with filters).
264
+ * This method may fail when drift has too many users. (nodejs response size limits)
265
+ * @returns
266
+ */
262
267
  async defaultSync() {
263
268
  var _a;
264
269
  if (this.syncPromise) {
@@ -334,6 +339,11 @@ class UserMap {
334
339
  this.syncPromise = undefined;
335
340
  }
336
341
  }
342
+ /**
343
+ * Syncs the UserMap using the paginated sync method (multiple getMultipleAccounts calls with filters).
344
+ * This method is more reliable when drift has many users.
345
+ * @returns
346
+ */
337
347
  async paginatedSync() {
338
348
  var _a, _b;
339
349
  if (this.syncPromise) {
@@ -1,4 +1,4 @@
1
- import { DriftClient, OrderRecord, UserStatsAccount, UserStats, WrappedEvent, BulkAccountLoader } from '..';
1
+ import { DriftClient, OrderRecord, UserStatsAccount, UserStats, WrappedEvent, BulkAccountLoader, SyncConfig } from '..';
2
2
  import { PublicKey } from '@solana/web3.js';
3
3
  import { UserMap } from './userMap';
4
4
  export declare class UserStatsMap {
@@ -8,13 +8,17 @@ export declare class UserStatsMap {
8
8
  private userStatsMap;
9
9
  private driftClient;
10
10
  private bulkAccountLoader;
11
+ private decode;
12
+ private syncConfig;
13
+ private syncPromise?;
14
+ private syncPromiseResolver;
11
15
  /**
12
16
  * Creates a new UserStatsMap instance.
13
17
  *
14
18
  * @param {DriftClient} driftClient - The DriftClient instance.
15
19
  * @param {BulkAccountLoader} [bulkAccountLoader] - If not provided, a new BulkAccountLoader with polling disabled will be created.
16
20
  */
17
- constructor(driftClient: DriftClient, bulkAccountLoader?: BulkAccountLoader);
21
+ constructor(driftClient: DriftClient, bulkAccountLoader?: BulkAccountLoader, syncConfig?: SyncConfig);
18
22
  subscribe(authorities: PublicKey[]): Promise<void>;
19
23
  /**
20
24
  *
@@ -42,5 +46,16 @@ export declare class UserStatsMap {
42
46
  * You may want to get this list from UserMap in order to filter out idle users
43
47
  */
44
48
  sync(authorities: PublicKey[]): Promise<void>;
49
+ /**
50
+ * Sync the UserStatsMap using the default sync method, which loads individual users into the bulkAccountLoader and
51
+ * loads them. (bulkAccountLoader uses batch getMultipleAccounts)
52
+ * @param authorities
53
+ */
54
+ private defaultSync;
55
+ /**
56
+ * Sync the UserStatsMap using the paginated sync method, which uses multiple getMultipleAccounts calls (without RPC batching), and limits concurrency.
57
+ * @param authorities
58
+ */
59
+ private paginatedSync;
45
60
  unsubscribe(): Promise<void>;
46
61
  }
@@ -10,7 +10,7 @@ class UserStatsMap {
10
10
  * @param {DriftClient} driftClient - The DriftClient instance.
11
11
  * @param {BulkAccountLoader} [bulkAccountLoader] - If not provided, a new BulkAccountLoader with polling disabled will be created.
12
12
  */
13
- constructor(driftClient, bulkAccountLoader) {
13
+ constructor(driftClient, bulkAccountLoader, syncConfig) {
14
14
  /**
15
15
  * map from authority pubkey to UserStats
16
16
  */
@@ -20,6 +20,11 @@ class UserStatsMap {
20
20
  bulkAccountLoader = new __1.BulkAccountLoader(driftClient.connection, driftClient.opts.commitment, 0);
21
21
  }
22
22
  this.bulkAccountLoader = bulkAccountLoader;
23
+ this.syncConfig = syncConfig !== null && syncConfig !== void 0 ? syncConfig : {
24
+ type: 'default',
25
+ };
26
+ this.decode =
27
+ this.driftClient.program.account.userStats.coder.accounts.decodeUnchecked.bind(this.driftClient.program.account.userStats.coder.accounts);
23
28
  }
24
29
  async subscribe(authorities) {
25
30
  if (this.size() > 0) {
@@ -152,9 +157,111 @@ class UserStatsMap {
152
157
  * You may want to get this list from UserMap in order to filter out idle users
153
158
  */
154
159
  async sync(authorities) {
160
+ if (this.syncConfig.type === 'default') {
161
+ return this.defaultSync(authorities);
162
+ }
163
+ else {
164
+ return this.paginatedSync(authorities);
165
+ }
166
+ }
167
+ /**
168
+ * Sync the UserStatsMap using the default sync method, which loads individual users into the bulkAccountLoader and
169
+ * loads them. (bulkAccountLoader uses batch getMultipleAccounts)
170
+ * @param authorities
171
+ */
172
+ async defaultSync(authorities) {
155
173
  await Promise.all(authorities.map((authority) => this.addUserStat(authority, undefined, true)));
156
174
  await this.bulkAccountLoader.load();
157
175
  }
176
+ /**
177
+ * Sync the UserStatsMap using the paginated sync method, which uses multiple getMultipleAccounts calls (without RPC batching), and limits concurrency.
178
+ * @param authorities
179
+ */
180
+ async paginatedSync(authorities) {
181
+ var _a, _b;
182
+ if (this.syncPromise) {
183
+ return this.syncPromise;
184
+ }
185
+ this.syncPromise = new Promise((resolve) => {
186
+ this.syncPromiseResolver = resolve;
187
+ });
188
+ try {
189
+ let accountsToLoad = authorities;
190
+ if (authorities.length === 0) {
191
+ const accountsPrefetch = await this.driftClient.connection.getProgramAccounts(this.driftClient.program.programId, {
192
+ dataSlice: { offset: 0, length: 0 },
193
+ filters: [(0, __1.getUserStatsFilter)()],
194
+ });
195
+ accountsToLoad = accountsPrefetch.map((account) => account.pubkey);
196
+ }
197
+ const limitConcurrency = async (tasks, limit) => {
198
+ const executing = [];
199
+ const results = [];
200
+ for (let i = 0; i < tasks.length; i++) {
201
+ const executor = Promise.resolve().then(tasks[i]);
202
+ results.push(executor);
203
+ if (executing.length < limit) {
204
+ executing.push(executor);
205
+ executor.finally(() => {
206
+ const index = executing.indexOf(executor);
207
+ if (index > -1) {
208
+ executing.splice(index, 1);
209
+ }
210
+ });
211
+ }
212
+ else {
213
+ await Promise.race(executing);
214
+ }
215
+ }
216
+ return Promise.all(results);
217
+ };
218
+ const programAccountBufferMap = new Set();
219
+ // @ts-ignore
220
+ const chunkSize = (_a = this.syncConfig.chunkSize) !== null && _a !== void 0 ? _a : 100;
221
+ const tasks = [];
222
+ for (let i = 0; i < accountsToLoad.length; i += chunkSize) {
223
+ const chunk = accountsToLoad.slice(i, i + chunkSize);
224
+ tasks.push(async () => {
225
+ const accountInfos = await this.driftClient.connection.getMultipleAccountsInfoAndContext(chunk, {
226
+ commitment: this.driftClient.opts.commitment,
227
+ });
228
+ for (let j = 0; j < accountInfos.value.length; j += 1) {
229
+ const accountInfo = accountInfos.value[j];
230
+ if (accountInfo === null)
231
+ continue;
232
+ const publicKeyString = chunk[j].toString();
233
+ if (!this.has(publicKeyString)) {
234
+ const buffer = Buffer.from(accountInfo.data);
235
+ const decodedUserStats = this.decode('UserStats', buffer);
236
+ programAccountBufferMap.add(decodedUserStats.authority.toBase58());
237
+ this.addUserStat(decodedUserStats.authority, decodedUserStats, false);
238
+ }
239
+ }
240
+ });
241
+ }
242
+ // @ts-ignore
243
+ const concurrencyLimit = (_b = this.syncConfig.concurrencyLimit) !== null && _b !== void 0 ? _b : 10;
244
+ await limitConcurrency(tasks, concurrencyLimit);
245
+ for (const [key] of this.userStatsMap.entries()) {
246
+ if (!programAccountBufferMap.has(key)) {
247
+ const user = this.get(key);
248
+ if (user) {
249
+ await user.unsubscribe();
250
+ this.userStatsMap.delete(key);
251
+ }
252
+ }
253
+ }
254
+ }
255
+ catch (err) {
256
+ console.error(`Error in UserStatsMap.paginatedSync():`, err);
257
+ }
258
+ finally {
259
+ if (this.syncPromiseResolver) {
260
+ this.syncPromiseResolver();
261
+ }
262
+ this.syncPromise = undefined;
263
+ }
264
+ }
158
265
  async unsubscribe() {
159
266
  for (const [key, userStats] of this.userStatsMap.entries()) {
160
267
  await userStats.unsubscribe();
@@ -81,7 +81,17 @@ export declare class UserMap implements UserMapInterface {
81
81
  */
82
82
  getUniqueAuthorities(filterCriteria?: UserFilterCriteria): PublicKey[];
83
83
  sync(): Promise<void>;
84
+ /**
85
+ * Syncs the UserMap using the default sync method (single getProgramAccounts call with filters).
86
+ * This method may fail when drift has too many users. (nodejs response size limits)
87
+ * @returns
88
+ */
84
89
  private defaultSync;
90
+ /**
91
+ * Syncs the UserMap using the paginated sync method (multiple getMultipleAccounts calls with filters).
92
+ * This method is more reliable when drift has many users.
93
+ * @returns
94
+ */
85
95
  private paginatedSync;
86
96
  unsubscribe(): Promise<void>;
87
97
  updateUserAccount(key: string, userAccount: UserAccount, slot: number): Promise<void>;
@@ -259,6 +259,11 @@ class UserMap {
259
259
  return this.paginatedSync();
260
260
  }
261
261
  }
262
+ /**
263
+ * Syncs the UserMap using the default sync method (single getProgramAccounts call with filters).
264
+ * This method may fail when drift has too many users. (nodejs response size limits)
265
+ * @returns
266
+ */
262
267
  async defaultSync() {
263
268
  var _a;
264
269
  if (this.syncPromise) {
@@ -334,6 +339,11 @@ class UserMap {
334
339
  this.syncPromise = undefined;
335
340
  }
336
341
  }
342
+ /**
343
+ * Syncs the UserMap using the paginated sync method (multiple getMultipleAccounts calls with filters).
344
+ * This method is more reliable when drift has many users.
345
+ * @returns
346
+ */
337
347
  async paginatedSync() {
338
348
  var _a, _b;
339
349
  if (this.syncPromise) {
@@ -1,4 +1,4 @@
1
- import { DriftClient, OrderRecord, UserStatsAccount, UserStats, WrappedEvent, BulkAccountLoader } from '..';
1
+ import { DriftClient, OrderRecord, UserStatsAccount, UserStats, WrappedEvent, BulkAccountLoader, SyncConfig } from '..';
2
2
  import { PublicKey } from '@solana/web3.js';
3
3
  import { UserMap } from './userMap';
4
4
  export declare class UserStatsMap {
@@ -8,13 +8,17 @@ export declare class UserStatsMap {
8
8
  private userStatsMap;
9
9
  private driftClient;
10
10
  private bulkAccountLoader;
11
+ private decode;
12
+ private syncConfig;
13
+ private syncPromise?;
14
+ private syncPromiseResolver;
11
15
  /**
12
16
  * Creates a new UserStatsMap instance.
13
17
  *
14
18
  * @param {DriftClient} driftClient - The DriftClient instance.
15
19
  * @param {BulkAccountLoader} [bulkAccountLoader] - If not provided, a new BulkAccountLoader with polling disabled will be created.
16
20
  */
17
- constructor(driftClient: DriftClient, bulkAccountLoader?: BulkAccountLoader);
21
+ constructor(driftClient: DriftClient, bulkAccountLoader?: BulkAccountLoader, syncConfig?: SyncConfig);
18
22
  subscribe(authorities: PublicKey[]): Promise<void>;
19
23
  /**
20
24
  *
@@ -42,5 +46,16 @@ export declare class UserStatsMap {
42
46
  * You may want to get this list from UserMap in order to filter out idle users
43
47
  */
44
48
  sync(authorities: PublicKey[]): Promise<void>;
49
+ /**
50
+ * Sync the UserStatsMap using the default sync method, which loads individual users into the bulkAccountLoader and
51
+ * loads them. (bulkAccountLoader uses batch getMultipleAccounts)
52
+ * @param authorities
53
+ */
54
+ private defaultSync;
55
+ /**
56
+ * Sync the UserStatsMap using the paginated sync method, which uses multiple getMultipleAccounts calls (without RPC batching), and limits concurrency.
57
+ * @param authorities
58
+ */
59
+ private paginatedSync;
45
60
  unsubscribe(): Promise<void>;
46
61
  }
@@ -10,7 +10,7 @@ class UserStatsMap {
10
10
  * @param {DriftClient} driftClient - The DriftClient instance.
11
11
  * @param {BulkAccountLoader} [bulkAccountLoader] - If not provided, a new BulkAccountLoader with polling disabled will be created.
12
12
  */
13
- constructor(driftClient, bulkAccountLoader) {
13
+ constructor(driftClient, bulkAccountLoader, syncConfig) {
14
14
  /**
15
15
  * map from authority pubkey to UserStats
16
16
  */
@@ -20,6 +20,11 @@ class UserStatsMap {
20
20
  bulkAccountLoader = new __1.BulkAccountLoader(driftClient.connection, driftClient.opts.commitment, 0);
21
21
  }
22
22
  this.bulkAccountLoader = bulkAccountLoader;
23
+ this.syncConfig = syncConfig !== null && syncConfig !== void 0 ? syncConfig : {
24
+ type: 'default',
25
+ };
26
+ this.decode =
27
+ this.driftClient.program.account.userStats.coder.accounts.decodeUnchecked.bind(this.driftClient.program.account.userStats.coder.accounts);
23
28
  }
24
29
  async subscribe(authorities) {
25
30
  if (this.size() > 0) {
@@ -152,9 +157,111 @@ class UserStatsMap {
152
157
  * You may want to get this list from UserMap in order to filter out idle users
153
158
  */
154
159
  async sync(authorities) {
160
+ if (this.syncConfig.type === 'default') {
161
+ return this.defaultSync(authorities);
162
+ }
163
+ else {
164
+ return this.paginatedSync(authorities);
165
+ }
166
+ }
167
+ /**
168
+ * Sync the UserStatsMap using the default sync method, which loads individual users into the bulkAccountLoader and
169
+ * loads them. (bulkAccountLoader uses batch getMultipleAccounts)
170
+ * @param authorities
171
+ */
172
+ async defaultSync(authorities) {
155
173
  await Promise.all(authorities.map((authority) => this.addUserStat(authority, undefined, true)));
156
174
  await this.bulkAccountLoader.load();
157
175
  }
176
+ /**
177
+ * Sync the UserStatsMap using the paginated sync method, which uses multiple getMultipleAccounts calls (without RPC batching), and limits concurrency.
178
+ * @param authorities
179
+ */
180
+ async paginatedSync(authorities) {
181
+ var _a, _b;
182
+ if (this.syncPromise) {
183
+ return this.syncPromise;
184
+ }
185
+ this.syncPromise = new Promise((resolve) => {
186
+ this.syncPromiseResolver = resolve;
187
+ });
188
+ try {
189
+ let accountsToLoad = authorities;
190
+ if (authorities.length === 0) {
191
+ const accountsPrefetch = await this.driftClient.connection.getProgramAccounts(this.driftClient.program.programId, {
192
+ dataSlice: { offset: 0, length: 0 },
193
+ filters: [(0, __1.getUserStatsFilter)()],
194
+ });
195
+ accountsToLoad = accountsPrefetch.map((account) => account.pubkey);
196
+ }
197
+ const limitConcurrency = async (tasks, limit) => {
198
+ const executing = [];
199
+ const results = [];
200
+ for (let i = 0; i < tasks.length; i++) {
201
+ const executor = Promise.resolve().then(tasks[i]);
202
+ results.push(executor);
203
+ if (executing.length < limit) {
204
+ executing.push(executor);
205
+ executor.finally(() => {
206
+ const index = executing.indexOf(executor);
207
+ if (index > -1) {
208
+ executing.splice(index, 1);
209
+ }
210
+ });
211
+ }
212
+ else {
213
+ await Promise.race(executing);
214
+ }
215
+ }
216
+ return Promise.all(results);
217
+ };
218
+ const programAccountBufferMap = new Set();
219
+ // @ts-ignore
220
+ const chunkSize = (_a = this.syncConfig.chunkSize) !== null && _a !== void 0 ? _a : 100;
221
+ const tasks = [];
222
+ for (let i = 0; i < accountsToLoad.length; i += chunkSize) {
223
+ const chunk = accountsToLoad.slice(i, i + chunkSize);
224
+ tasks.push(async () => {
225
+ const accountInfos = await this.driftClient.connection.getMultipleAccountsInfoAndContext(chunk, {
226
+ commitment: this.driftClient.opts.commitment,
227
+ });
228
+ for (let j = 0; j < accountInfos.value.length; j += 1) {
229
+ const accountInfo = accountInfos.value[j];
230
+ if (accountInfo === null)
231
+ continue;
232
+ const publicKeyString = chunk[j].toString();
233
+ if (!this.has(publicKeyString)) {
234
+ const buffer = Buffer.from(accountInfo.data);
235
+ const decodedUserStats = this.decode('UserStats', buffer);
236
+ programAccountBufferMap.add(decodedUserStats.authority.toBase58());
237
+ this.addUserStat(decodedUserStats.authority, decodedUserStats, false);
238
+ }
239
+ }
240
+ });
241
+ }
242
+ // @ts-ignore
243
+ const concurrencyLimit = (_b = this.syncConfig.concurrencyLimit) !== null && _b !== void 0 ? _b : 10;
244
+ await limitConcurrency(tasks, concurrencyLimit);
245
+ for (const [key] of this.userStatsMap.entries()) {
246
+ if (!programAccountBufferMap.has(key)) {
247
+ const user = this.get(key);
248
+ if (user) {
249
+ await user.unsubscribe();
250
+ this.userStatsMap.delete(key);
251
+ }
252
+ }
253
+ }
254
+ }
255
+ catch (err) {
256
+ console.error(`Error in UserStatsMap.paginatedSync():`, err);
257
+ }
258
+ finally {
259
+ if (this.syncPromiseResolver) {
260
+ this.syncPromiseResolver();
261
+ }
262
+ this.syncPromise = undefined;
263
+ }
264
+ }
158
265
  async unsubscribe() {
159
266
  for (const [key, userStats] of this.userStatsMap.entries()) {
160
267
  await userStats.unsubscribe();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@drift-labs/sdk-browser",
3
- "version": "2.109.0-beta.5",
3
+ "version": "2.109.0-beta.6",
4
4
  "main": "lib/node/index.js",
5
5
  "types": "lib/node/index.d.ts",
6
6
  "browser": "./lib/browser/index.js",
@@ -388,6 +388,11 @@ export class UserMap implements UserMapInterface {
388
388
  }
389
389
  }
390
390
 
391
+ /**
392
+ * Syncs the UserMap using the default sync method (single getProgramAccounts call with filters).
393
+ * This method may fail when drift has too many users. (nodejs response size limits)
394
+ * @returns
395
+ */
391
396
  private async defaultSync() {
392
397
  if (this.syncPromise) {
393
398
  return this.syncPromise;
@@ -487,6 +492,11 @@ export class UserMap implements UserMapInterface {
487
492
  }
488
493
  }
489
494
 
495
+ /**
496
+ * Syncs the UserMap using the paginated sync method (multiple getMultipleAccounts calls with filters).
497
+ * This method is more reliable when drift has many users.
498
+ * @returns
499
+ */
490
500
  private async paginatedSync() {
491
501
  if (this.syncPromise) {
492
502
  return this.syncPromise;
@@ -15,6 +15,8 @@ import {
15
15
  InsuranceFundStakeRecord,
16
16
  BulkAccountLoader,
17
17
  PollingUserStatsAccountSubscriber,
18
+ SyncConfig,
19
+ getUserStatsFilter,
18
20
  } from '..';
19
21
  import { PublicKey } from '@solana/web3.js';
20
22
 
@@ -27,6 +29,11 @@ export class UserStatsMap {
27
29
  private userStatsMap = new Map<string, UserStats>();
28
30
  private driftClient: DriftClient;
29
31
  private bulkAccountLoader: BulkAccountLoader;
32
+ private decode;
33
+ private syncConfig: SyncConfig;
34
+
35
+ private syncPromise?: Promise<void>;
36
+ private syncPromiseResolver: () => void;
30
37
 
31
38
  /**
32
39
  * Creates a new UserStatsMap instance.
@@ -34,7 +41,11 @@ export class UserStatsMap {
34
41
  * @param {DriftClient} driftClient - The DriftClient instance.
35
42
  * @param {BulkAccountLoader} [bulkAccountLoader] - If not provided, a new BulkAccountLoader with polling disabled will be created.
36
43
  */
37
- constructor(driftClient: DriftClient, bulkAccountLoader?: BulkAccountLoader) {
44
+ constructor(
45
+ driftClient: DriftClient,
46
+ bulkAccountLoader?: BulkAccountLoader,
47
+ syncConfig?: SyncConfig
48
+ ) {
38
49
  this.driftClient = driftClient;
39
50
  if (!bulkAccountLoader) {
40
51
  bulkAccountLoader = new BulkAccountLoader(
@@ -44,6 +55,15 @@ export class UserStatsMap {
44
55
  );
45
56
  }
46
57
  this.bulkAccountLoader = bulkAccountLoader;
58
+
59
+ this.syncConfig = syncConfig ?? {
60
+ type: 'default',
61
+ };
62
+
63
+ this.decode =
64
+ this.driftClient.program.account.userStats.coder.accounts.decodeUnchecked.bind(
65
+ this.driftClient.program.account.userStats.coder.accounts
66
+ );
47
67
  }
48
68
 
49
69
  public async subscribe(authorities: PublicKey[]) {
@@ -201,6 +221,19 @@ export class UserStatsMap {
201
221
  * You may want to get this list from UserMap in order to filter out idle users
202
222
  */
203
223
  public async sync(authorities: PublicKey[]) {
224
+ if (this.syncConfig.type === 'default') {
225
+ return this.defaultSync(authorities);
226
+ } else {
227
+ return this.paginatedSync(authorities);
228
+ }
229
+ }
230
+
231
+ /**
232
+ * Sync the UserStatsMap using the default sync method, which loads individual users into the bulkAccountLoader and
233
+ * loads them. (bulkAccountLoader uses batch getMultipleAccounts)
234
+ * @param authorities
235
+ */
236
+ private async defaultSync(authorities: PublicKey[]) {
204
237
  await Promise.all(
205
238
  authorities.map((authority) =>
206
239
  this.addUserStat(authority, undefined, true)
@@ -209,6 +242,120 @@ export class UserStatsMap {
209
242
  await this.bulkAccountLoader.load();
210
243
  }
211
244
 
245
+ /**
246
+ * Sync the UserStatsMap using the paginated sync method, which uses multiple getMultipleAccounts calls (without RPC batching), and limits concurrency.
247
+ * @param authorities
248
+ */
249
+ private async paginatedSync(authorities: PublicKey[]) {
250
+ if (this.syncPromise) {
251
+ return this.syncPromise;
252
+ }
253
+
254
+ this.syncPromise = new Promise<void>((resolve) => {
255
+ this.syncPromiseResolver = resolve;
256
+ });
257
+
258
+ try {
259
+ let accountsToLoad = authorities;
260
+ if (authorities.length === 0) {
261
+ const accountsPrefetch =
262
+ await this.driftClient.connection.getProgramAccounts(
263
+ this.driftClient.program.programId,
264
+ {
265
+ dataSlice: { offset: 0, length: 0 },
266
+ filters: [getUserStatsFilter()],
267
+ }
268
+ );
269
+ accountsToLoad = accountsPrefetch.map((account) => account.pubkey);
270
+ }
271
+
272
+ const limitConcurrency = async (tasks, limit) => {
273
+ const executing = [];
274
+ const results = [];
275
+
276
+ for (let i = 0; i < tasks.length; i++) {
277
+ const executor = Promise.resolve().then(tasks[i]);
278
+ results.push(executor);
279
+
280
+ if (executing.length < limit) {
281
+ executing.push(executor);
282
+ executor.finally(() => {
283
+ const index = executing.indexOf(executor);
284
+ if (index > -1) {
285
+ executing.splice(index, 1);
286
+ }
287
+ });
288
+ } else {
289
+ await Promise.race(executing);
290
+ }
291
+ }
292
+
293
+ return Promise.all(results);
294
+ };
295
+
296
+ const programAccountBufferMap = new Set<string>();
297
+
298
+ // @ts-ignore
299
+ const chunkSize = this.syncConfig.chunkSize ?? 100;
300
+ const tasks = [];
301
+ for (let i = 0; i < accountsToLoad.length; i += chunkSize) {
302
+ const chunk = accountsToLoad.slice(i, i + chunkSize);
303
+ tasks.push(async () => {
304
+ const accountInfos =
305
+ await this.driftClient.connection.getMultipleAccountsInfoAndContext(
306
+ chunk,
307
+ {
308
+ commitment: this.driftClient.opts.commitment,
309
+ }
310
+ );
311
+
312
+ for (let j = 0; j < accountInfos.value.length; j += 1) {
313
+ const accountInfo = accountInfos.value[j];
314
+ if (accountInfo === null) continue;
315
+
316
+ const publicKeyString = chunk[j].toString();
317
+ if (!this.has(publicKeyString)) {
318
+ const buffer = Buffer.from(accountInfo.data);
319
+ const decodedUserStats = this.decode(
320
+ 'UserStats',
321
+ buffer
322
+ ) as UserStatsAccount;
323
+ programAccountBufferMap.add(
324
+ decodedUserStats.authority.toBase58()
325
+ );
326
+ this.addUserStat(
327
+ decodedUserStats.authority,
328
+ decodedUserStats,
329
+ false
330
+ );
331
+ }
332
+ }
333
+ });
334
+ }
335
+
336
+ // @ts-ignore
337
+ const concurrencyLimit = this.syncConfig.concurrencyLimit ?? 10;
338
+ await limitConcurrency(tasks, concurrencyLimit);
339
+
340
+ for (const [key] of this.userStatsMap.entries()) {
341
+ if (!programAccountBufferMap.has(key)) {
342
+ const user = this.get(key);
343
+ if (user) {
344
+ await user.unsubscribe();
345
+ this.userStatsMap.delete(key);
346
+ }
347
+ }
348
+ }
349
+ } catch (err) {
350
+ console.error(`Error in UserStatsMap.paginatedSync():`, err);
351
+ } finally {
352
+ if (this.syncPromiseResolver) {
353
+ this.syncPromiseResolver();
354
+ }
355
+ this.syncPromise = undefined;
356
+ }
357
+ }
358
+
212
359
  public async unsubscribe() {
213
360
  for (const [key, userStats] of this.userStatsMap.entries()) {
214
361
  await userStats.unsubscribe();