@drift-labs/sdk 2.48.0-beta.2 → 2.48.0-beta.20

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 (50) hide show
  1. package/VERSION +1 -1
  2. package/lib/accounts/bulkAccountLoader.js +1 -1
  3. package/lib/accounts/pollingUserAccountSubscriber.js +14 -7
  4. package/lib/accounts/pollingUserStatsAccountSubscriber.js +14 -7
  5. package/lib/accounts/webSocketAccountSubscriber.js +1 -1
  6. package/lib/accounts/webSocketDriftClientAccountSubscriber.d.ts +3 -2
  7. package/lib/accounts/webSocketDriftClientAccountSubscriber.js +6 -5
  8. package/lib/accounts/webSocketProgramAccountSubscriber.js +1 -1
  9. package/lib/accounts/webSocketUserAccountSubscriber.js +1 -1
  10. package/lib/constants/spotMarkets.js +10 -0
  11. package/lib/dlob/orderBookLevels.js +1 -1
  12. package/lib/driftClient.js +4 -4
  13. package/lib/events/eventSubscriber.js +1 -1
  14. package/lib/events/types.d.ts +1 -0
  15. package/lib/events/webSocketLogProvider.d.ts +7 -1
  16. package/lib/events/webSocketLogProvider.js +45 -4
  17. package/lib/orderSubscriber/OrderSubscriber.d.ts +5 -1
  18. package/lib/orderSubscriber/OrderSubscriber.js +24 -7
  19. package/lib/orderSubscriber/WebsocketSubscription.d.ts +4 -1
  20. package/lib/orderSubscriber/WebsocketSubscription.js +4 -3
  21. package/lib/orderSubscriber/types.d.ts +3 -1
  22. package/lib/slot/SlotSubscriber.js +4 -2
  23. package/lib/user.js +3 -3
  24. package/lib/userMap/userMap.d.ts +16 -4
  25. package/lib/userMap/userMap.js +83 -41
  26. package/lib/userMap/userStatsMap.d.ts +29 -8
  27. package/lib/userMap/userStatsMap.js +46 -41
  28. package/package.json +1 -1
  29. package/src/accounts/bulkAccountLoader.ts +1 -1
  30. package/src/accounts/pollingUserAccountSubscriber.ts +19 -11
  31. package/src/accounts/pollingUserStatsAccountSubscriber.ts +20 -12
  32. package/src/accounts/webSocketAccountSubscriber.ts +1 -1
  33. package/src/accounts/webSocketDriftClientAccountSubscriber.ts +13 -6
  34. package/src/accounts/webSocketProgramAccountSubscriber.ts +1 -1
  35. package/src/accounts/webSocketUserAccountSubscriber.ts +1 -1
  36. package/src/constants/spotMarkets.ts +10 -0
  37. package/src/dlob/orderBookLevels.ts +1 -1
  38. package/src/driftClient.ts +3 -2
  39. package/src/events/eventSubscriber.ts +2 -1
  40. package/src/events/types.ts +1 -0
  41. package/src/events/webSocketLogProvider.ts +51 -4
  42. package/src/orderSubscriber/OrderSubscriber.ts +39 -15
  43. package/src/orderSubscriber/WebsocketSubscription.ts +7 -2
  44. package/src/orderSubscriber/types.ts +3 -1
  45. package/src/slot/SlotSubscriber.ts +4 -2
  46. package/src/user.ts +3 -3
  47. package/src/userMap/userMap.ts +139 -66
  48. package/src/userMap/userStatsMap.ts +64 -69
  49. package/tests/amm/test.ts +3 -2
  50. package/tests/dlob/test.ts +8 -5
@@ -3,10 +3,15 @@ import { Commitment, Connection, PublicKey } from '@solana/web3.js';
3
3
 
4
4
  export class WebSocketLogProvider implements LogProvider {
5
5
  private subscriptionId: number;
6
+ private isUnsubscribing = false;
7
+ private receivingData = false;
8
+ private timeoutId?: NodeJS.Timeout;
9
+ private callback?: logProviderCallback;
6
10
  public constructor(
7
11
  private connection: Connection,
8
12
  private address: PublicKey,
9
- private commitment: Commitment
13
+ private commitment: Commitment,
14
+ private resubTimeoutMs?: number
10
15
  ) {}
11
16
 
12
17
  public subscribe(callback: logProviderCallback): boolean {
@@ -14,13 +19,24 @@ export class WebSocketLogProvider implements LogProvider {
14
19
  return true;
15
20
  }
16
21
 
22
+ this.callback = callback;
17
23
  this.subscriptionId = this.connection.onLogs(
18
24
  this.address,
19
25
  (logs, ctx) => {
26
+ if (this.resubTimeoutMs && !this.isUnsubscribing) {
27
+ this.receivingData = true;
28
+ clearTimeout(this.timeoutId);
29
+ this.setTimeout();
30
+ }
20
31
  callback(logs.signature, ctx.slot, logs.logs, undefined);
21
32
  },
22
33
  this.commitment
23
34
  );
35
+
36
+ if (this.resubTimeoutMs) {
37
+ this.setTimeout();
38
+ }
39
+
24
40
  return true;
25
41
  }
26
42
 
@@ -29,10 +45,41 @@ export class WebSocketLogProvider implements LogProvider {
29
45
  }
30
46
 
31
47
  public async unsubscribe(): Promise<boolean> {
48
+ this.isUnsubscribing = true;
49
+ clearTimeout(this.timeoutId);
50
+
32
51
  if (this.subscriptionId !== undefined) {
33
- await this.connection.removeOnLogsListener(this.subscriptionId);
34
- this.subscriptionId = undefined;
52
+ this.connection
53
+ .removeOnLogsListener(this.subscriptionId)
54
+ .then(() => {
55
+ this.subscriptionId = undefined;
56
+ this.isUnsubscribing = false;
57
+ return true;
58
+ })
59
+ .catch((err) => {
60
+ console.log('Error unsubscribing from logs: ', err);
61
+ this.isUnsubscribing = false;
62
+ return false;
63
+ });
64
+ } else {
65
+ this.isUnsubscribing = false;
66
+ return true;
35
67
  }
36
- return true;
68
+ }
69
+
70
+ private setTimeout(): void {
71
+ this.timeoutId = setTimeout(async () => {
72
+ if (this.isUnsubscribing) {
73
+ // If we are in the process of unsubscribing, do not attempt to resubscribe
74
+ return;
75
+ }
76
+
77
+ if (this.receivingData) {
78
+ console.log(`No log data in ${this.resubTimeoutMs}ms, resubscribing`);
79
+ await this.unsubscribe();
80
+ this.receivingData = false;
81
+ this.subscribe(this.callback);
82
+ }
83
+ }, this.resubTimeoutMs);
37
84
  }
38
85
  }
@@ -1,7 +1,7 @@
1
1
  import { DriftClient } from '../driftClient';
2
2
  import { UserAccount } from '../types';
3
3
  import { getUserFilter, getUserWithOrderFilter } from '../memcmp';
4
- import { PublicKey, RpcResponseAndContext } from '@solana/web3.js';
4
+ import { Commitment, PublicKey, RpcResponseAndContext } from '@solana/web3.js';
5
5
  import { Buffer } from 'buffer';
6
6
  import { DLOB } from '../dlob/DLOB';
7
7
  import { OrderSubscriberConfig, OrderSubscriberEvents } from './types';
@@ -14,13 +14,17 @@ export class OrderSubscriber {
14
14
  driftClient: DriftClient;
15
15
  usersAccounts = new Map<string, { slot: number; userAccount: UserAccount }>();
16
16
  subscription: PollingSubscription | WebsocketSubscription;
17
+ commitment: Commitment;
17
18
  eventEmitter: StrictEventEmitter<EventEmitter, OrderSubscriberEvents>;
18
19
 
19
20
  fetchPromise?: Promise<void>;
20
21
  fetchPromiseResolver: () => void;
21
22
 
23
+ mostRecentSlot: number;
24
+
22
25
  constructor(config: OrderSubscriberConfig) {
23
26
  this.driftClient = config.driftClient;
27
+ this.commitment = config.subscriptionConfig.commitment || 'processed';
24
28
  if (config.subscriptionConfig.type === 'polling') {
25
29
  this.subscription = new PollingSubscription({
26
30
  orderSubscriber: this,
@@ -29,6 +33,7 @@ export class OrderSubscriber {
29
33
  } else {
30
34
  this.subscription = new WebsocketSubscription({
31
35
  orderSubscriber: this,
36
+ commitment: this.commitment,
32
37
  skipInitialLoad: config.subscriptionConfig.skipInitialLoad,
33
38
  resubTimeoutMs: config.subscriptionConfig.resubTimeoutMs,
34
39
  });
@@ -53,7 +58,7 @@ export class OrderSubscriber {
53
58
  const rpcRequestArgs = [
54
59
  this.driftClient.program.programId.toBase58(),
55
60
  {
56
- commitment: this.driftClient.opts.commitment,
61
+ commitment: this.commitment,
57
62
  filters: [getUserFilter(), getUserWithOrderFilter()],
58
63
  encoding: 'base64',
59
64
  withContext: true,
@@ -81,18 +86,13 @@ export class OrderSubscriber {
81
86
  const programAccountSet = new Set<string>();
82
87
  for (const programAccount of rpcResponseAndContext.value) {
83
88
  const key = programAccount.pubkey.toString();
84
- // @ts-ignore
85
- const buffer = Buffer.from(
86
- programAccount.account.data[0],
87
- programAccount.account.data[1]
88
- );
89
89
  programAccountSet.add(key);
90
- const userAccount =
91
- this.driftClient.program.account.user.coder.accounts.decode(
92
- 'User',
93
- buffer
94
- ) as UserAccount;
95
- this.tryUpdateUserAccount(key, userAccount, slot);
90
+ this.tryUpdateUserAccount(
91
+ key,
92
+ 'raw',
93
+ programAccount.account.data,
94
+ slot
95
+ );
96
96
  }
97
97
 
98
98
  for (const key of this.usersAccounts.keys()) {
@@ -110,11 +110,31 @@ export class OrderSubscriber {
110
110
 
111
111
  tryUpdateUserAccount(
112
112
  key: string,
113
- userAccount: UserAccount,
113
+ dataType: 'raw' | 'decoded',
114
+ data: string[] | UserAccount,
114
115
  slot: number
115
116
  ): void {
117
+ if (!this.mostRecentSlot || slot > this.mostRecentSlot) {
118
+ this.mostRecentSlot = slot;
119
+ }
120
+
116
121
  const slotAndUserAccount = this.usersAccounts.get(key);
117
- if (!slotAndUserAccount || slotAndUserAccount.slot < slot) {
122
+ if (!slotAndUserAccount || slotAndUserAccount.slot <= slot) {
123
+ let userAccount: UserAccount;
124
+ // Polling leads to a lot of redundant decoding, so we only decode if data is from a fresh slot
125
+ if (dataType === 'raw') {
126
+ // @ts-ignore
127
+ const buffer = Buffer.from(data[0], data[1]);
128
+
129
+ userAccount =
130
+ this.driftClient.program.account.user.coder.accounts.decodeUnchecked(
131
+ 'User',
132
+ buffer
133
+ ) as UserAccount;
134
+ } else {
135
+ userAccount = data as UserAccount;
136
+ }
137
+
118
138
  const newOrders = userAccount.orders.filter(
119
139
  (order) =>
120
140
  order.slot.toNumber() > (slotAndUserAccount?.slot ?? 0) &&
@@ -148,6 +168,10 @@ export class OrderSubscriber {
148
168
  return dlob;
149
169
  }
150
170
 
171
+ public getSlot(): number {
172
+ return this.mostRecentSlot ?? 0;
173
+ }
174
+
151
175
  public async unsubscribe(): Promise<void> {
152
176
  await this.subscription.unsubscribe();
153
177
  }
@@ -2,10 +2,11 @@ import { OrderSubscriber } from './OrderSubscriber';
2
2
  import { getNonIdleUserFilter, getUserFilter } from '../memcmp';
3
3
  import { WebSocketProgramAccountSubscriber } from '../accounts/webSocketProgramAccountSubscriber';
4
4
  import { UserAccount } from '../types';
5
- import { Context, PublicKey } from '@solana/web3.js';
5
+ import { Commitment, Context, PublicKey } from '@solana/web3.js';
6
6
 
7
7
  export class WebsocketSubscription {
8
8
  private orderSubscriber: OrderSubscriber;
9
+ private commitment: Commitment;
9
10
  private skipInitialLoad: boolean;
10
11
  private resubTimeoutMs?: number;
11
12
 
@@ -13,14 +14,17 @@ export class WebsocketSubscription {
13
14
 
14
15
  constructor({
15
16
  orderSubscriber,
17
+ commitment,
16
18
  skipInitialLoad = false,
17
19
  resubTimeoutMs,
18
20
  }: {
19
21
  orderSubscriber: OrderSubscriber;
22
+ commitment: Commitment;
20
23
  skipInitialLoad?: boolean;
21
24
  resubTimeoutMs?: number;
22
25
  }) {
23
26
  this.orderSubscriber = orderSubscriber;
27
+ this.commitment = commitment;
24
28
  this.skipInitialLoad = skipInitialLoad;
25
29
  this.resubTimeoutMs = resubTimeoutMs;
26
30
  }
@@ -36,7 +40,7 @@ export class WebsocketSubscription {
36
40
  ),
37
41
  {
38
42
  filters: [getUserFilter(), getNonIdleUserFilter()],
39
- commitment: this.orderSubscriber.driftClient.opts.commitment,
43
+ commitment: this.commitment,
40
44
  },
41
45
  this.resubTimeoutMs
42
46
  );
@@ -47,6 +51,7 @@ export class WebsocketSubscription {
47
51
  const userKey = accountId.toBase58();
48
52
  this.orderSubscriber.tryUpdateUserAccount(
49
53
  userKey,
54
+ 'decoded',
50
55
  account,
51
56
  context.slot
52
57
  );
@@ -1,4 +1,4 @@
1
- import { PublicKey } from '@solana/web3.js';
1
+ import { Commitment, PublicKey } from '@solana/web3.js';
2
2
  import { Order, UserAccount } from '../types';
3
3
  import { DriftClient } from '../driftClient';
4
4
 
@@ -8,11 +8,13 @@ export type OrderSubscriberConfig = {
8
8
  | {
9
9
  type: 'polling';
10
10
  frequency: number;
11
+ commitment?: Commitment;
11
12
  }
12
13
  | {
13
14
  type: 'websocket';
14
15
  skipInitialLoad?: boolean;
15
16
  resubTimeoutMs?: number;
17
+ commitment?: Commitment;
16
18
  };
17
19
  };
18
20
 
@@ -29,8 +29,10 @@ export class SlotSubscriber {
29
29
  this.currentSlot = await this.connection.getSlot('confirmed');
30
30
 
31
31
  this.subscriptionId = this.connection.onSlotChange((slotInfo) => {
32
- this.currentSlot = slotInfo.slot;
33
- this.eventEmitter.emit('newSlot', slotInfo.slot);
32
+ if (!this.currentSlot || this.currentSlot < slotInfo.slot) {
33
+ this.currentSlot = slotInfo.slot;
34
+ this.eventEmitter.emit('newSlot', slotInfo.slot);
35
+ }
34
36
  });
35
37
  }
36
38
 
package/src/user.ts CHANGED
@@ -109,12 +109,12 @@ export class User {
109
109
  );
110
110
  } else if (config.accountSubscription?.type === 'custom') {
111
111
  this.accountSubscriber = config.accountSubscription.userAccountSubscriber;
112
- } else if (config.accountSubscription?.type === 'websocket') {
112
+ } else {
113
113
  this.accountSubscriber = new WebSocketUserAccountSubscriber(
114
114
  config.driftClient.program,
115
115
  config.userAccountPublicKey,
116
- config.accountSubscription.resubTimeoutMs,
117
- config.accountSubscription.commitment
116
+ config.accountSubscription?.resubTimeoutMs,
117
+ config.accountSubscription?.commitment
118
118
  );
119
119
  }
120
120
  this.eventEmitter = this.accountSubscriber.eventEmitter;
@@ -32,33 +32,59 @@ export interface UserMapInterface {
32
32
  values(): IterableIterator<User>;
33
33
  }
34
34
 
35
+ // filter users that meet these criteria when passing into syncCallback
36
+ export type SyncCallbackCriteria = {
37
+ // only sync users that have open orders
38
+ hasOpenOrders: boolean;
39
+ };
40
+
35
41
  export class UserMap implements UserMapInterface {
36
42
  private userMap = new Map<string, User>();
37
43
  private driftClient: DriftClient;
38
44
  private accountSubscription: UserSubscriptionConfig;
39
45
  private includeIdle: boolean;
40
46
  private lastNumberOfSubAccounts;
41
- private syncCallback = async (state: StateAccount) => {
47
+ private stateAccountUpdateCallback = async (state: StateAccount) => {
42
48
  if (state.numberOfSubAccounts !== this.lastNumberOfSubAccounts) {
43
49
  await this.sync();
44
50
  this.lastNumberOfSubAccounts = state.numberOfSubAccounts;
45
51
  }
46
52
  };
53
+ private syncCallback: (authorities: PublicKey[]) => Promise<void>;
54
+ private syncCallbackCriteria: SyncCallbackCriteria;
55
+
56
+ private syncPromise?: Promise<void>;
57
+ private syncPromiseResolver: () => void;
47
58
 
48
59
  /**
60
+ * Constructs a new UserMap instance.
49
61
  *
50
- * @param driftClient
51
- * @param accountSubscription
52
- * @param includeIdle whether idle users are subscribed to. defaults to false to decrease # of user subscriptions
62
+ * @param {DriftClient} driftClient - The DriftClient instance.
63
+ * @param {UserSubscriptionConfig} accountSubscription - The UserSubscriptionConfig instance.
64
+ * @param {boolean} includeIdle - Whether idle users are subscribed to. Defaults to false to decrease # of user subscriptions.
65
+ * @param {(authorities: PublicKey[]) => Promise<void>} syncCallback - Called after `sync` completes, will pas in unique list of authorities. Useful for using it to sync UserStatsMap.
66
+ * @param {SyncCallbackCriteria} syncCallbackCriteria - The criteria for the sync callback. Defaults to having no filters
53
67
  */
54
68
  constructor(
55
69
  driftClient: DriftClient,
56
70
  accountSubscription: UserSubscriptionConfig,
57
- includeIdle = false
71
+ includeIdle = false,
72
+ syncCallback?: (authorities: PublicKey[]) => Promise<void>,
73
+ syncCallbackCriteria: SyncCallbackCriteria = { hasOpenOrders: false }
58
74
  ) {
59
75
  this.driftClient = driftClient;
60
76
  this.accountSubscription = accountSubscription;
61
77
  this.includeIdle = includeIdle;
78
+ this.syncCallback = syncCallback;
79
+ this.syncCallbackCriteria = syncCallbackCriteria;
80
+ }
81
+
82
+ public addSyncCallback(
83
+ syncCallback?: (authorities: PublicKey[]) => Promise<void>,
84
+ syncCallbackCriteria: SyncCallbackCriteria = { hasOpenOrders: false }
85
+ ) {
86
+ this.syncCallback = syncCallback;
87
+ this.syncCallbackCriteria = syncCallbackCriteria;
62
88
  }
63
89
 
64
90
  public async subscribe() {
@@ -69,7 +95,10 @@ export class UserMap implements UserMapInterface {
69
95
  await this.driftClient.subscribe();
70
96
  this.lastNumberOfSubAccounts =
71
97
  this.driftClient.getStateAccount().numberOfSubAccounts;
72
- this.driftClient.eventEmitter.on('stateAccountUpdate', this.syncCallback);
98
+ this.driftClient.eventEmitter.on(
99
+ 'stateAccountUpdate',
100
+ this.stateAccountUpdateCallback
101
+ );
73
102
 
74
103
  await this.sync();
75
104
  }
@@ -187,74 +216,118 @@ export class UserMap implements UserMapInterface {
187
216
  return this.userMap.size;
188
217
  }
189
218
 
219
+ public getUniqueAuthorities(useSyncCallbackCriteria = true): PublicKey[] {
220
+ const usersMeetingCriteria = Array.from(this.userMap.values()).filter(
221
+ (user) => {
222
+ let pass = true;
223
+ if (
224
+ useSyncCallbackCriteria &&
225
+ this.syncCallbackCriteria.hasOpenOrders
226
+ ) {
227
+ pass = pass && user.getUserAccount().hasOpenOrder;
228
+ }
229
+ return pass;
230
+ }
231
+ );
232
+ const userAuths = new Set(
233
+ usersMeetingCriteria.map((user) =>
234
+ user.getUserAccount().authority.toBase58()
235
+ )
236
+ );
237
+ const userAuthKeys = Array.from(userAuths).map(
238
+ (userAuth) => new PublicKey(userAuth)
239
+ );
240
+ return userAuthKeys;
241
+ }
242
+
190
243
  public async sync() {
191
- const filters = [getUserFilter()];
192
- if (!this.includeIdle) {
193
- filters.push(getNonIdleUserFilter());
244
+ if (this.syncPromise) {
245
+ return this.syncPromise;
194
246
  }
247
+ this.syncPromise = new Promise((resolver) => {
248
+ this.syncPromiseResolver = resolver;
249
+ });
195
250
 
196
- const rpcRequestArgs = [
197
- this.driftClient.program.programId.toBase58(),
198
- {
199
- commitment: this.driftClient.connection.commitment,
200
- filters,
201
- encoding: 'base64',
202
- withContext: true,
203
- },
204
- ];
205
-
206
- // @ts-ignore
207
- const rpcJSONResponse: any = await this.driftClient.connection._rpcRequest(
208
- 'getProgramAccounts',
209
- rpcRequestArgs
210
- );
251
+ try {
252
+ const filters = [getUserFilter()];
253
+ if (!this.includeIdle) {
254
+ filters.push(getNonIdleUserFilter());
255
+ }
211
256
 
212
- const rpcResponseAndContext: RpcResponseAndContext<
213
- Array<{
214
- pubkey: PublicKey;
215
- account: {
216
- data: [string, string];
217
- };
218
- }>
219
- > = rpcJSONResponse.result;
220
-
221
- const slot = rpcResponseAndContext.context.slot;
222
-
223
- const programAccountBufferMap = new Map<string, Buffer>();
224
- for (const programAccount of rpcResponseAndContext.value) {
225
- programAccountBufferMap.set(
226
- programAccount.pubkey.toString(),
257
+ const rpcRequestArgs = [
258
+ this.driftClient.program.programId.toBase58(),
259
+ {
260
+ commitment: this.driftClient.connection.commitment,
261
+ filters,
262
+ encoding: 'base64',
263
+ withContext: true,
264
+ },
265
+ ];
266
+
267
+ const rpcJSONResponse: any =
227
268
  // @ts-ignore
228
- Buffer.from(
229
- programAccount.account.data[0],
230
- programAccount.account.data[1]
231
- )
232
- );
233
- }
269
+ await this.driftClient.connection._rpcRequest(
270
+ 'getProgramAccounts',
271
+ rpcRequestArgs
272
+ );
273
+
274
+ const rpcResponseAndContext: RpcResponseAndContext<
275
+ Array<{
276
+ pubkey: PublicKey;
277
+ account: {
278
+ data: [string, string];
279
+ };
280
+ }>
281
+ > = rpcJSONResponse.result;
282
+
283
+ const slot = rpcResponseAndContext.context.slot;
234
284
 
235
- for (const [key, buffer] of programAccountBufferMap.entries()) {
236
- if (!this.has(key)) {
237
- const userAccount =
238
- this.driftClient.program.account.user.coder.accounts.decode(
239
- 'User',
240
- buffer
241
- );
242
- await this.addPubkey(new PublicKey(key), userAccount);
285
+ const programAccountBufferMap = new Map<string, Buffer>();
286
+ for (const programAccount of rpcResponseAndContext.value) {
287
+ programAccountBufferMap.set(
288
+ programAccount.pubkey.toString(),
289
+ // @ts-ignore
290
+ Buffer.from(
291
+ programAccount.account.data[0],
292
+ programAccount.account.data[1]
293
+ )
294
+ );
243
295
  }
244
- }
245
296
 
246
- for (const [key, user] of this.userMap.entries()) {
247
- if (!programAccountBufferMap.has(key)) {
248
- await user.unsubscribe();
249
- this.userMap.delete(key);
250
- } else {
251
- const userAccount =
252
- this.driftClient.program.account.user.coder.accounts.decode(
253
- 'User',
254
- programAccountBufferMap.get(key)
255
- );
256
- user.accountSubscriber.updateData(userAccount, slot);
297
+ for (const [key, buffer] of programAccountBufferMap.entries()) {
298
+ if (!this.has(key)) {
299
+ const userAccount =
300
+ this.driftClient.program.account.user.coder.accounts.decode(
301
+ 'User',
302
+ buffer
303
+ );
304
+ await this.addPubkey(new PublicKey(key), userAccount);
305
+ }
306
+ }
307
+
308
+ for (const [key, user] of this.userMap.entries()) {
309
+ if (!programAccountBufferMap.has(key)) {
310
+ await user.unsubscribe();
311
+ this.userMap.delete(key);
312
+ } else {
313
+ const userAccount =
314
+ this.driftClient.program.account.user.coder.accounts.decode(
315
+ 'User',
316
+ programAccountBufferMap.get(key)
317
+ );
318
+ user.accountSubscriber.updateData(userAccount, slot);
319
+ }
320
+ }
321
+
322
+ if (this.syncCallback) {
323
+ await this.syncCallback(this.getUniqueAuthorities());
257
324
  }
325
+ } catch (e) {
326
+ console.error(`Error in UserMap.sync()`);
327
+ console.error(e);
328
+ } finally {
329
+ this.syncPromiseResolver();
330
+ this.syncPromise = undefined;
258
331
  }
259
332
  }
260
333
 
@@ -267,7 +340,7 @@ export class UserMap implements UserMapInterface {
267
340
  if (this.lastNumberOfSubAccounts) {
268
341
  this.driftClient.eventEmitter.removeListener(
269
342
  'stateAccountUpdate',
270
- this.syncCallback
343
+ this.stateAccountUpdateCallback
271
344
  );
272
345
  this.lastNumberOfSubAccounts = undefined;
273
346
  }