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

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.7
@@ -888,8 +888,8 @@ export declare class DriftClient {
888
888
  disableUserHighLeverageMode(user: PublicKey, userAccount?: UserAccount, txParams?: TxParams): Promise<TransactionSignature>;
889
889
  getDisableHighLeverageModeIx(user: PublicKey, userAccount?: UserAccount): Promise<TransactionInstruction>;
890
890
  fetchHighLeverageModeConfig(): Promise<HighLeverageModeConfig>;
891
- updateUserProtectedMakerOrders(subAccountId: number, protectedOrders: boolean, txParams?: TxParams): Promise<TransactionSignature>;
892
- getUpdateUserProtectedMakerOrdersIx(subAccountId: number, protectedOrders: boolean): Promise<TransactionInstruction>;
891
+ updateUserProtectedMakerOrders(subAccountId: number, protectedOrders: boolean, authority?: PublicKey, txParams?: TxParams): Promise<TransactionSignature>;
892
+ getUpdateUserProtectedMakerOrdersIx(subAccountId: number, protectedOrders: boolean, authority?: PublicKey): Promise<TransactionInstruction>;
893
893
  getPauseSpotMarketDepositWithdrawIx(spotMarketIndex: number): Promise<TransactionInstruction>;
894
894
  pauseSpotMarketDepositWithdraw(spotMarketIndex: number, txParams?: TxParams): Promise<TransactionSignature>;
895
895
  private handleSignedTransaction;
@@ -4847,15 +4847,15 @@ class DriftClient {
4847
4847
  const config = await this.program.account.highLeverageModeConfig.fetch((0, pda_1.getHighLeverageModeConfigPublicKey)(this.program.programId));
4848
4848
  return config;
4849
4849
  }
4850
- async updateUserProtectedMakerOrders(subAccountId, protectedOrders, txParams) {
4851
- const { txSig } = await this.sendTransaction(await this.buildTransaction(await this.getUpdateUserProtectedMakerOrdersIx(subAccountId, protectedOrders), txParams), [], this.opts);
4850
+ async updateUserProtectedMakerOrders(subAccountId, protectedOrders, authority, txParams) {
4851
+ const { txSig } = await this.sendTransaction(await this.buildTransaction(await this.getUpdateUserProtectedMakerOrdersIx(subAccountId, protectedOrders, authority), txParams), [], this.opts);
4852
4852
  return txSig;
4853
4853
  }
4854
- async getUpdateUserProtectedMakerOrdersIx(subAccountId, protectedOrders) {
4854
+ async getUpdateUserProtectedMakerOrdersIx(subAccountId, protectedOrders, authority) {
4855
4855
  const ix = await this.program.instruction.updateUserProtectedMakerOrders(subAccountId, protectedOrders, {
4856
4856
  accounts: {
4857
4857
  state: await this.getStatePublicKey(),
4858
- user: (0, pda_1.getUserAccountPublicKeySync)(this.program.programId, this.wallet.publicKey, subAccountId),
4858
+ user: (0, pda_1.getUserAccountPublicKeySync)(this.program.programId, authority !== null && authority !== void 0 ? authority : this.authority, subAccountId),
4859
4859
  authority: this.wallet.publicKey,
4860
4860
  protectedMakerModeConfig: (0, pda_1.getProtectedMakerModeConfigPublicKey)(this.program.programId),
4861
4861
  },
@@ -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();
@@ -888,8 +888,8 @@ export declare class DriftClient {
888
888
  disableUserHighLeverageMode(user: PublicKey, userAccount?: UserAccount, txParams?: TxParams): Promise<TransactionSignature>;
889
889
  getDisableHighLeverageModeIx(user: PublicKey, userAccount?: UserAccount): Promise<TransactionInstruction>;
890
890
  fetchHighLeverageModeConfig(): Promise<HighLeverageModeConfig>;
891
- updateUserProtectedMakerOrders(subAccountId: number, protectedOrders: boolean, txParams?: TxParams): Promise<TransactionSignature>;
892
- getUpdateUserProtectedMakerOrdersIx(subAccountId: number, protectedOrders: boolean): Promise<TransactionInstruction>;
891
+ updateUserProtectedMakerOrders(subAccountId: number, protectedOrders: boolean, authority?: PublicKey, txParams?: TxParams): Promise<TransactionSignature>;
892
+ getUpdateUserProtectedMakerOrdersIx(subAccountId: number, protectedOrders: boolean, authority?: PublicKey): Promise<TransactionInstruction>;
893
893
  getPauseSpotMarketDepositWithdrawIx(spotMarketIndex: number): Promise<TransactionInstruction>;
894
894
  pauseSpotMarketDepositWithdraw(spotMarketIndex: number, txParams?: TxParams): Promise<TransactionSignature>;
895
895
  private handleSignedTransaction;
@@ -4847,15 +4847,15 @@ class DriftClient {
4847
4847
  const config = await this.program.account.highLeverageModeConfig.fetch((0, pda_1.getHighLeverageModeConfigPublicKey)(this.program.programId));
4848
4848
  return config;
4849
4849
  }
4850
- async updateUserProtectedMakerOrders(subAccountId, protectedOrders, txParams) {
4851
- const { txSig } = await this.sendTransaction(await this.buildTransaction(await this.getUpdateUserProtectedMakerOrdersIx(subAccountId, protectedOrders), txParams), [], this.opts);
4850
+ async updateUserProtectedMakerOrders(subAccountId, protectedOrders, authority, txParams) {
4851
+ const { txSig } = await this.sendTransaction(await this.buildTransaction(await this.getUpdateUserProtectedMakerOrdersIx(subAccountId, protectedOrders, authority), txParams), [], this.opts);
4852
4852
  return txSig;
4853
4853
  }
4854
- async getUpdateUserProtectedMakerOrdersIx(subAccountId, protectedOrders) {
4854
+ async getUpdateUserProtectedMakerOrdersIx(subAccountId, protectedOrders, authority) {
4855
4855
  const ix = await this.program.instruction.updateUserProtectedMakerOrders(subAccountId, protectedOrders, {
4856
4856
  accounts: {
4857
4857
  state: await this.getStatePublicKey(),
4858
- user: (0, pda_1.getUserAccountPublicKeySync)(this.program.programId, this.wallet.publicKey, subAccountId),
4858
+ user: (0, pda_1.getUserAccountPublicKeySync)(this.program.programId, authority !== null && authority !== void 0 ? authority : this.authority, subAccountId),
4859
4859
  authority: this.wallet.publicKey,
4860
4860
  protectedMakerModeConfig: (0, pda_1.getProtectedMakerModeConfigPublicKey)(this.program.programId),
4861
4861
  },
@@ -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.7",
4
4
  "main": "lib/node/index.js",
5
5
  "types": "lib/node/index.d.ts",
6
6
  "browser": "./lib/browser/index.js",
@@ -9209,13 +9209,15 @@ export class DriftClient {
9209
9209
  public async updateUserProtectedMakerOrders(
9210
9210
  subAccountId: number,
9211
9211
  protectedOrders: boolean,
9212
+ authority?: PublicKey,
9212
9213
  txParams?: TxParams
9213
9214
  ): Promise<TransactionSignature> {
9214
9215
  const { txSig } = await this.sendTransaction(
9215
9216
  await this.buildTransaction(
9216
9217
  await this.getUpdateUserProtectedMakerOrdersIx(
9217
9218
  subAccountId,
9218
- protectedOrders
9219
+ protectedOrders,
9220
+ authority
9219
9221
  ),
9220
9222
  txParams
9221
9223
  ),
@@ -9227,7 +9229,8 @@ export class DriftClient {
9227
9229
 
9228
9230
  public async getUpdateUserProtectedMakerOrdersIx(
9229
9231
  subAccountId: number,
9230
- protectedOrders: boolean
9232
+ protectedOrders: boolean,
9233
+ authority?: PublicKey
9231
9234
  ): Promise<TransactionInstruction> {
9232
9235
  const ix = await this.program.instruction.updateUserProtectedMakerOrders(
9233
9236
  subAccountId,
@@ -9237,7 +9240,7 @@ export class DriftClient {
9237
9240
  state: await this.getStatePublicKey(),
9238
9241
  user: getUserAccountPublicKeySync(
9239
9242
  this.program.programId,
9240
- this.wallet.publicKey,
9243
+ authority ?? this.authority,
9241
9244
  subAccountId
9242
9245
  ),
9243
9246
  authority: this.wallet.publicKey,
@@ -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();