@drift-labs/sdk 2.49.0-beta.1 → 2.49.0-beta.10

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 (51) hide show
  1. package/VERSION +1 -1
  2. package/lib/accounts/{mockUserAccountSubscriber.d.ts → basicUserAccountSubscriber.d.ts} +2 -2
  3. package/lib/accounts/{mockUserAccountSubscriber.js → basicUserAccountSubscriber.js} +9 -6
  4. package/lib/accounts/pollingInsuranceFundStakeAccountSubscriber.d.ts +29 -0
  5. package/lib/accounts/pollingInsuranceFundStakeAccountSubscriber.js +110 -0
  6. package/lib/accounts/types.d.ts +14 -1
  7. package/lib/accounts/webSocketInsuranceFundStakeAccountSubscriber.d.ts +23 -0
  8. package/lib/accounts/webSocketInsuranceFundStakeAccountSubscriber.js +65 -0
  9. package/lib/dlob/DLOB.d.ts +6 -2
  10. package/lib/dlob/DLOB.js +37 -12
  11. package/lib/driftClient.d.ts +66 -66
  12. package/lib/driftClient.js +208 -194
  13. package/lib/events/eventSubscriber.js +2 -1
  14. package/lib/events/sort.d.ts +2 -2
  15. package/lib/events/sort.js +6 -23
  16. package/lib/examples/loadDlob.js +10 -5
  17. package/lib/index.d.ts +3 -1
  18. package/lib/index.js +3 -1
  19. package/lib/orderSubscriber/OrderSubscriber.js +4 -0
  20. package/lib/orderSubscriber/WebsocketSubscription.d.ts +1 -1
  21. package/lib/orderSubscriber/WebsocketSubscription.js +8 -6
  22. package/lib/types.d.ts +0 -2
  23. package/lib/userMap/PollingSubscription.d.ts +15 -0
  24. package/lib/userMap/PollingSubscription.js +26 -0
  25. package/lib/userMap/WebsocketSubscription.d.ts +19 -0
  26. package/lib/userMap/WebsocketSubscription.js +40 -0
  27. package/lib/userMap/userMap.d.ts +15 -18
  28. package/lib/userMap/userMap.js +62 -31
  29. package/lib/userMap/userMapConfig.d.ts +20 -0
  30. package/lib/userMap/userMapConfig.js +2 -0
  31. package/package.json +1 -1
  32. package/src/accounts/{mockUserAccountSubscriber.ts → basicUserAccountSubscriber.ts} +8 -6
  33. package/src/accounts/pollingInsuranceFundStakeAccountSubscriber.ts +185 -0
  34. package/src/accounts/types.ts +21 -0
  35. package/src/accounts/webSocketInsuranceFundStakeAccountSubscriber.ts +127 -0
  36. package/src/dlob/DLOB.ts +55 -15
  37. package/src/driftClient.ts +429 -272
  38. package/src/events/eventSubscriber.ts +2 -1
  39. package/src/events/sort.ts +7 -29
  40. package/src/examples/loadDlob.ts +11 -6
  41. package/src/index.ts +3 -1
  42. package/src/orderSubscriber/OrderSubscriber.ts +4 -0
  43. package/src/orderSubscriber/WebsocketSubscription.ts +19 -16
  44. package/src/types.ts +0 -2
  45. package/src/userMap/PollingSubscription.ts +46 -0
  46. package/src/userMap/WebsocketSubscription.ts +74 -0
  47. package/src/userMap/userMap.ts +88 -60
  48. package/src/userMap/userMapConfig.ts +31 -0
  49. package/tests/amm/test.ts +6 -3
  50. package/tests/dlob/helpers.ts +2 -6
  51. package/tests/dlob/test.ts +194 -0
@@ -47,7 +47,7 @@ export class EventSubscriber {
47
47
  new EventList(
48
48
  eventType,
49
49
  this.options.maxEventsPerType,
50
- getSortFn(this.options.orderBy, this.options.orderDir, eventType),
50
+ getSortFn(this.options.orderBy, this.options.orderDir),
51
51
  this.options.orderDir
52
52
  )
53
53
  );
@@ -159,6 +159,7 @@ export class EventSubscriber {
159
159
 
160
160
  if (!this.lastSeenSlot || slot > this.lastSeenSlot) {
161
161
  this.lastSeenTxSig = txSig;
162
+ this.lastSeenSlot = slot;
162
163
  }
163
164
 
164
165
  if (
@@ -4,10 +4,7 @@ import {
4
4
  EventSubscriptionOrderDirection,
5
5
  EventType,
6
6
  SortFn,
7
- Event,
8
7
  } from './types';
9
- import { OrderActionRecord } from '../types';
10
- import { ZERO } from '../index';
11
8
 
12
9
  function clientSortAscFn(): 'less than' {
13
10
  return 'less than';
@@ -17,45 +14,26 @@ function clientSortDescFn(): 'greater than' {
17
14
  return 'greater than';
18
15
  }
19
16
 
20
- function defaultBlockchainSortFn(
17
+ function blockchainSortFn(
21
18
  currentEvent: EventMap[EventType],
22
19
  newEvent: EventMap[EventType]
23
20
  ): 'less than' | 'greater than' {
24
- return currentEvent.slot <= newEvent.slot ? 'less than' : 'greater than';
25
- }
26
-
27
- function orderActionRecordSortFn(
28
- currentEvent: Event<OrderActionRecord>,
29
- newEvent: Event<OrderActionRecord>
30
- ): 'less than' | 'greater than' {
31
- const currentEventMarketIndex = currentEvent.marketIndex;
32
- const newEventMarketIndex = newEvent.marketIndex;
33
- if (currentEventMarketIndex !== newEventMarketIndex) {
34
- return currentEvent.ts.lte(newEvent.ts) ? 'less than' : 'greater than';
35
- }
36
-
37
- if (currentEvent.fillRecordId?.gt(ZERO) && newEvent.fillRecordId?.gt(ZERO)) {
38
- return currentEvent.fillRecordId.lte(newEvent.fillRecordId)
21
+ if (currentEvent.slot == newEvent.slot) {
22
+ return currentEvent.txSigIndex < newEvent.txSigIndex
39
23
  ? 'less than'
40
24
  : 'greater than';
41
- } else {
42
- return currentEvent.ts.lte(newEvent.ts) ? 'less than' : 'greater than';
43
25
  }
26
+
27
+ return currentEvent.slot < newEvent.slot ? 'less than' : 'greater than';
44
28
  }
45
29
 
46
30
  export function getSortFn(
47
31
  orderBy: EventSubscriptionOrderBy,
48
- orderDir: EventSubscriptionOrderDirection,
49
- eventType: EventType
32
+ orderDir: EventSubscriptionOrderDirection
50
33
  ): SortFn {
51
34
  if (orderBy === 'client') {
52
35
  return orderDir === 'asc' ? clientSortAscFn : clientSortDescFn;
53
36
  }
54
37
 
55
- switch (eventType) {
56
- case 'OrderActionRecord':
57
- return orderActionRecordSortFn;
58
- default:
59
- return defaultBlockchainSortFn;
60
- }
38
+ return blockchainSortFn;
61
39
  }
@@ -1,5 +1,5 @@
1
1
  import { AnchorProvider } from '@coral-xyz/anchor';
2
- import { DLOB, UserMap, Wallet } from '..';
2
+ import { UserMap, Wallet } from '..';
3
3
  import { Connection, Keypair, PublicKey } from '@solana/web3.js';
4
4
  import {
5
5
  DriftClient,
@@ -55,17 +55,22 @@ const main = async () => {
55
55
  await driftClient.subscribe();
56
56
 
57
57
  console.log('Loading user map...');
58
- const userMap = new UserMap(driftClient, {
59
- type: 'polling',
60
- accountLoader: bulkAccountLoader,
58
+ const userMap = new UserMap({
59
+ driftClient,
60
+ subscriptionConfig: {
61
+ type: 'websocket',
62
+ commitment: 'processed',
63
+ },
64
+ skipInitialLoad: false,
65
+ includeIdle: false,
61
66
  });
62
67
 
63
68
  // fetches all users and subscribes for updates
64
69
  await userMap.subscribe();
65
70
 
66
71
  console.log('Loading dlob from user map...');
67
- const dlob = new DLOB();
68
- await dlob.initFromUserMap(userMap, bulkAccountLoader.mostRecentSlot);
72
+ const slot = await driftClient.connection.getSlot();
73
+ const dlob = await userMap.getDLOB(slot);
69
74
 
70
75
  console.log('number of orders', dlob.getDLOBOrders().length);
71
76
 
package/src/index.ts CHANGED
@@ -10,6 +10,7 @@ export * from './types';
10
10
  export * from './constants/perpMarkets';
11
11
  export * from './accounts/fetch';
12
12
  export * from './accounts/webSocketDriftClientAccountSubscriber';
13
+ export * from './accounts/webSocketInsuranceFundStakeAccountSubscriber';
13
14
  export * from './accounts/bulkAccountLoader';
14
15
  export * from './accounts/bulkUserSubscription';
15
16
  export * from './accounts/bulkUserStatsSubscription';
@@ -18,7 +19,8 @@ export * from './accounts/pollingOracleAccountSubscriber';
18
19
  export * from './accounts/pollingTokenAccountSubscriber';
19
20
  export * from './accounts/pollingUserAccountSubscriber';
20
21
  export * from './accounts/pollingUserStatsAccountSubscriber';
21
- export * from './accounts/mockUserAccountSubscriber';
22
+ export * from './accounts/pollingInsuranceFundStakeAccountSubscriber';
23
+ export * from './accounts/basicUserAccountSubscriber';
22
24
  export * from './accounts/types';
23
25
  export * from './addresses/pda';
24
26
  export * from './adminClient';
@@ -94,12 +94,16 @@ export class OrderSubscriber {
94
94
  programAccount.account.data,
95
95
  slot
96
96
  );
97
+ // give event loop a chance to breathe
98
+ await new Promise((resolve) => setTimeout(resolve, 0));
97
99
  }
98
100
 
99
101
  for (const key of this.usersAccounts.keys()) {
100
102
  if (!programAccountSet.has(key)) {
101
103
  this.usersAccounts.delete(key);
102
104
  }
105
+ // give event loop a chance to breathe
106
+ await new Promise((resolve) => setTimeout(resolve, 0));
103
107
  }
104
108
  } catch (e) {
105
109
  console.error(e);
@@ -10,7 +10,7 @@ export class WebsocketSubscription {
10
10
  private skipInitialLoad: boolean;
11
11
  private resubTimeoutMs?: number;
12
12
 
13
- private subscriber: WebSocketProgramAccountSubscriber<UserAccount>;
13
+ private subscriber?: WebSocketProgramAccountSubscriber<UserAccount>;
14
14
 
15
15
  constructor({
16
16
  orderSubscriber,
@@ -30,22 +30,24 @@ export class WebsocketSubscription {
30
30
  }
31
31
 
32
32
  public async subscribe(): Promise<void> {
33
- if (!this.subscriber) {
34
- this.subscriber = new WebSocketProgramAccountSubscriber<UserAccount>(
35
- 'OrderSubscriber',
36
- 'User',
37
- this.orderSubscriber.driftClient.program,
38
- this.orderSubscriber.driftClient.program.account.user.coder.accounts.decode.bind(
39
- this.orderSubscriber.driftClient.program.account.user.coder.accounts
40
- ),
41
- {
42
- filters: [getUserFilter(), getNonIdleUserFilter()],
43
- commitment: this.commitment,
44
- },
45
- this.resubTimeoutMs
46
- );
33
+ if (this.subscriber) {
34
+ return;
47
35
  }
48
36
 
37
+ this.subscriber = new WebSocketProgramAccountSubscriber<UserAccount>(
38
+ 'OrderSubscriber',
39
+ 'User',
40
+ this.orderSubscriber.driftClient.program,
41
+ this.orderSubscriber.driftClient.program.account.user.coder.accounts.decodeUnchecked.bind(
42
+ this.orderSubscriber.driftClient.program.account.user.coder.accounts
43
+ ),
44
+ {
45
+ filters: [getUserFilter(), getNonIdleUserFilter()],
46
+ commitment: this.commitment,
47
+ },
48
+ this.resubTimeoutMs
49
+ );
50
+
49
51
  await this.subscriber.subscribe(
50
52
  (accountId: PublicKey, account: UserAccount, context: Context) => {
51
53
  const userKey = accountId.toBase58();
@@ -65,6 +67,7 @@ export class WebsocketSubscription {
65
67
 
66
68
  public async unsubscribe(): Promise<void> {
67
69
  if (!this.subscriber) return;
68
- this.subscriber.unsubscribe();
70
+ await this.subscriber.unsubscribe();
71
+ this.subscriber = undefined;
69
72
  }
70
73
  }
package/src/types.ts CHANGED
@@ -1000,8 +1000,6 @@ export interface IVersionedWallet {
1000
1000
 
1001
1001
  export type FeeStructure = {
1002
1002
  feeTiers: FeeTier[];
1003
- makerRebateNumerator: BN;
1004
- makerRebateDenominator: BN;
1005
1003
  fillerRewardStructure: OrderFillerRewardStructure;
1006
1004
  flatFillerFee: BN;
1007
1005
  referrerRewardEpochUpperBound: BN;
@@ -0,0 +1,46 @@
1
+ import { UserMap } from './userMap';
2
+
3
+ export class PollingSubscription {
4
+ private userMap: UserMap;
5
+ private frequency: number;
6
+ private skipInitialLoad: boolean;
7
+
8
+ intervalId?: ReturnType<typeof setTimeout>;
9
+
10
+ constructor({
11
+ userMap,
12
+ frequency,
13
+ skipInitialLoad = false,
14
+ }: {
15
+ userMap: UserMap;
16
+ frequency: number;
17
+ skipInitialLoad?: boolean;
18
+ includeIdle?: boolean;
19
+ }) {
20
+ this.userMap = userMap;
21
+ this.frequency = frequency;
22
+ this.skipInitialLoad = skipInitialLoad;
23
+ }
24
+
25
+ public async subscribe(): Promise<void> {
26
+ if (this.intervalId) {
27
+ return;
28
+ }
29
+
30
+ this.intervalId = setInterval(
31
+ this.userMap.sync.bind(this.userMap),
32
+ this.frequency
33
+ );
34
+
35
+ if (!this.skipInitialLoad) {
36
+ await this.userMap.sync();
37
+ }
38
+ }
39
+
40
+ public async unsubscribe(): Promise<void> {
41
+ if (this.intervalId) {
42
+ clearInterval(this.intervalId);
43
+ this.intervalId = undefined;
44
+ }
45
+ }
46
+ }
@@ -0,0 +1,74 @@
1
+ import { UserMap } from './userMap';
2
+ import { getNonIdleUserFilter, getUserFilter } from '../memcmp';
3
+ import { WebSocketProgramAccountSubscriber } from '../accounts/webSocketProgramAccountSubscriber';
4
+ import { UserAccount } from '../types';
5
+ import { Commitment, Context, PublicKey } from '@solana/web3.js';
6
+
7
+ export class WebsocketSubscription {
8
+ private userMap: UserMap;
9
+ private commitment: Commitment;
10
+ private skipInitialLoad: boolean;
11
+ private resubTimeoutMs?: number;
12
+ private includeIdle?: boolean;
13
+
14
+ private subscriber: WebSocketProgramAccountSubscriber<UserAccount>;
15
+
16
+ constructor({
17
+ userMap,
18
+ commitment,
19
+ skipInitialLoad = false,
20
+ resubTimeoutMs,
21
+ includeIdle = false,
22
+ }: {
23
+ userMap: UserMap;
24
+ commitment: Commitment;
25
+ skipInitialLoad?: boolean;
26
+ resubTimeoutMs?: number;
27
+ includeIdle?: boolean;
28
+ }) {
29
+ this.userMap = userMap;
30
+ this.commitment = commitment;
31
+ this.skipInitialLoad = skipInitialLoad;
32
+ this.resubTimeoutMs = resubTimeoutMs;
33
+ this.includeIdle = includeIdle || false;
34
+ }
35
+
36
+ public async subscribe(): Promise<void> {
37
+ if (!this.subscriber) {
38
+ const filters = [getUserFilter()];
39
+ if (!this.includeIdle) {
40
+ filters.push(getNonIdleUserFilter());
41
+ }
42
+ this.subscriber = new WebSocketProgramAccountSubscriber<UserAccount>(
43
+ 'UserMap',
44
+ 'User',
45
+ this.userMap.driftClient.program,
46
+ this.userMap.driftClient.program.account.user.coder.accounts.decodeUnchecked.bind(
47
+ this.userMap.driftClient.program.account.user.coder.accounts
48
+ ),
49
+ {
50
+ filters,
51
+ commitment: this.commitment,
52
+ },
53
+ this.resubTimeoutMs
54
+ );
55
+ }
56
+
57
+ await this.subscriber.subscribe(
58
+ (accountId: PublicKey, account: UserAccount, context: Context) => {
59
+ const userKey = accountId.toBase58();
60
+ this.userMap.updateUserAccount(userKey, account, context.slot);
61
+ }
62
+ );
63
+
64
+ if (!this.skipInitialLoad) {
65
+ await this.userMap.sync();
66
+ }
67
+ }
68
+
69
+ public async unsubscribe(): Promise<void> {
70
+ if (!this.subscriber) return;
71
+ await this.subscriber.unsubscribe();
72
+ this.subscriber = undefined;
73
+ }
74
+ }
@@ -3,7 +3,6 @@ import {
3
3
  DriftClient,
4
4
  UserAccount,
5
5
  OrderRecord,
6
- UserSubscriptionConfig,
7
6
  WrappedEvent,
8
7
  DepositRecord,
9
8
  FundingPaymentRecord,
@@ -14,11 +13,24 @@ import {
14
13
  LPRecord,
15
14
  StateAccount,
16
15
  DLOB,
16
+ BasicUserAccountSubscriber,
17
+ BN,
17
18
  } from '..';
18
19
 
19
- import { PublicKey, RpcResponseAndContext } from '@solana/web3.js';
20
+ import {
21
+ Commitment,
22
+ Connection,
23
+ PublicKey,
24
+ RpcResponseAndContext,
25
+ } from '@solana/web3.js';
20
26
  import { Buffer } from 'buffer';
21
27
  import { getNonIdleUserFilter, getUserFilter } from '../memcmp';
28
+ import {
29
+ UserAccountFilterCriteria as UserFilterCriteria,
30
+ UserMapConfig,
31
+ } from './userMapConfig';
32
+ import { WebsocketSubscription } from './WebsocketSubscription';
33
+ import { PollingSubscription } from './PollingSubscription';
22
34
 
23
35
  export interface UserMapInterface {
24
36
  subscribe(): Promise<void>;
@@ -32,59 +44,51 @@ export interface UserMapInterface {
32
44
  values(): IterableIterator<User>;
33
45
  }
34
46
 
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
-
41
47
  export class UserMap implements UserMapInterface {
42
48
  private userMap = new Map<string, User>();
43
- private driftClient: DriftClient;
44
- private accountSubscription: UserSubscriptionConfig;
49
+ driftClient: DriftClient;
50
+ private connection: Connection;
51
+ private commitment: Commitment;
45
52
  private includeIdle: boolean;
46
- private lastNumberOfSubAccounts;
53
+ private lastNumberOfSubAccounts: BN;
54
+ private subscription: PollingSubscription | WebsocketSubscription;
47
55
  private stateAccountUpdateCallback = async (state: StateAccount) => {
48
- if (state.numberOfSubAccounts !== this.lastNumberOfSubAccounts) {
56
+ if (!state.numberOfSubAccounts.eq(this.lastNumberOfSubAccounts)) {
49
57
  await this.sync();
50
58
  this.lastNumberOfSubAccounts = state.numberOfSubAccounts;
51
59
  }
52
60
  };
53
- private syncCallback: (authorities: PublicKey[]) => Promise<void>;
54
- private syncCallbackCriteria: SyncCallbackCriteria;
55
61
 
56
62
  private syncPromise?: Promise<void>;
57
63
  private syncPromiseResolver: () => void;
58
64
 
59
65
  /**
60
66
  * Constructs a new UserMap instance.
61
- *
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
67
67
  */
68
- constructor(
69
- driftClient: DriftClient,
70
- accountSubscription: UserSubscriptionConfig,
71
- includeIdle = false,
72
- syncCallback?: (authorities: PublicKey[]) => Promise<void>,
73
- syncCallbackCriteria: SyncCallbackCriteria = { hasOpenOrders: false }
74
- ) {
75
- this.driftClient = driftClient;
76
- this.accountSubscription = accountSubscription;
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;
68
+ constructor(config: UserMapConfig) {
69
+ this.driftClient = config.driftClient;
70
+ if (config.connection) {
71
+ this.connection = config.connection;
72
+ } else {
73
+ this.connection = this.driftClient.connection;
74
+ }
75
+ this.commitment =
76
+ config.subscriptionConfig.commitment ?? this.driftClient.opts.commitment;
77
+ this.includeIdle = config.includeIdle ?? false;
78
+ if (config.subscriptionConfig.type === 'polling') {
79
+ this.subscription = new PollingSubscription({
80
+ userMap: this,
81
+ frequency: config.subscriptionConfig.frequency,
82
+ skipInitialLoad: config.skipInitialLoad,
83
+ });
84
+ } else {
85
+ this.subscription = new WebsocketSubscription({
86
+ userMap: this,
87
+ commitment: this.commitment,
88
+ resubTimeoutMs: config.subscriptionConfig.resubTimeoutMs,
89
+ skipInitialLoad: config.skipInitialLoad,
90
+ });
91
+ }
88
92
  }
89
93
 
90
94
  public async subscribe() {
@@ -100,17 +104,25 @@ export class UserMap implements UserMapInterface {
100
104
  this.stateAccountUpdateCallback
101
105
  );
102
106
 
103
- await this.sync();
107
+ await this.subscription.subscribe();
104
108
  }
105
109
 
106
110
  public async addPubkey(
107
111
  userAccountPublicKey: PublicKey,
108
- userAccount?: UserAccount
112
+ userAccount?: UserAccount,
113
+ slot?: number
109
114
  ) {
110
115
  const user = new User({
111
116
  driftClient: this.driftClient,
112
117
  userAccountPublicKey,
113
- accountSubscription: this.accountSubscription,
118
+ accountSubscription: {
119
+ type: 'custom',
120
+ userAccountSubscriber: new BasicUserAccountSubscriber(
121
+ userAccountPublicKey,
122
+ userAccount,
123
+ slot
124
+ ),
125
+ },
114
126
  });
115
127
  await user.subscribe(userAccount);
116
128
  this.userMap.set(userAccountPublicKey.toString(), user);
@@ -216,14 +228,18 @@ export class UserMap implements UserMapInterface {
216
228
  return this.userMap.size;
217
229
  }
218
230
 
219
- public getUniqueAuthorities(useSyncCallbackCriteria = true): PublicKey[] {
231
+ /**
232
+ * Returns a unique list of authorities for all users in the UserMap that meet the filter criteria
233
+ * @param filterCriteria: Users must meet these criteria to be included
234
+ * @returns
235
+ */
236
+ public getUniqueAuthorities(
237
+ filterCriteria?: UserFilterCriteria
238
+ ): PublicKey[] {
220
239
  const usersMeetingCriteria = Array.from(this.userMap.values()).filter(
221
240
  (user) => {
222
241
  let pass = true;
223
- if (
224
- useSyncCallbackCriteria &&
225
- this.syncCallbackCriteria.hasOpenOrders
226
- ) {
242
+ if (filterCriteria && filterCriteria.hasOpenOrders) {
227
243
  pass = pass && user.getUserAccount().hasOpenOrder;
228
244
  }
229
245
  return pass;
@@ -257,7 +273,7 @@ export class UserMap implements UserMapInterface {
257
273
  const rpcRequestArgs = [
258
274
  this.driftClient.program.programId.toBase58(),
259
275
  {
260
- commitment: this.driftClient.connection.commitment,
276
+ commitment: this.commitment,
261
277
  filters,
262
278
  encoding: 'base64',
263
279
  withContext: true,
@@ -266,10 +282,7 @@ export class UserMap implements UserMapInterface {
266
282
 
267
283
  const rpcJSONResponse: any =
268
284
  // @ts-ignore
269
- await this.driftClient.connection._rpcRequest(
270
- 'getProgramAccounts',
271
- rpcRequestArgs
272
- );
285
+ await this.connection._rpcRequest('getProgramAccounts', rpcRequestArgs);
273
286
 
274
287
  const rpcResponseAndContext: RpcResponseAndContext<
275
288
  Array<{
@@ -297,12 +310,14 @@ export class UserMap implements UserMapInterface {
297
310
  for (const [key, buffer] of programAccountBufferMap.entries()) {
298
311
  if (!this.has(key)) {
299
312
  const userAccount =
300
- this.driftClient.program.account.user.coder.accounts.decode(
313
+ this.driftClient.program.account.user.coder.accounts.decodeUnchecked(
301
314
  'User',
302
315
  buffer
303
316
  );
304
317
  await this.addPubkey(new PublicKey(key), userAccount);
305
318
  }
319
+ // give event loop a chance to breathe
320
+ await new Promise((resolve) => setTimeout(resolve, 0));
306
321
  }
307
322
 
308
323
  for (const [key, user] of this.userMap.entries()) {
@@ -311,19 +326,17 @@ export class UserMap implements UserMapInterface {
311
326
  this.userMap.delete(key);
312
327
  } else {
313
328
  const userAccount =
314
- this.driftClient.program.account.user.coder.accounts.decode(
329
+ this.driftClient.program.account.user.coder.accounts.decodeUnchecked(
315
330
  'User',
316
331
  programAccountBufferMap.get(key)
317
332
  );
318
333
  user.accountSubscriber.updateData(userAccount, slot);
319
334
  }
320
- }
321
-
322
- if (this.syncCallback) {
323
- await this.syncCallback(this.getUniqueAuthorities());
335
+ // give event loop a chance to breathe
336
+ await new Promise((resolve) => setTimeout(resolve, 0));
324
337
  }
325
338
  } catch (e) {
326
- console.error(`Error in UserMap.sync()`);
339
+ console.error(`Error in UserMap.sync():`);
327
340
  console.error(e);
328
341
  } finally {
329
342
  this.syncPromiseResolver();
@@ -332,6 +345,8 @@ export class UserMap implements UserMapInterface {
332
345
  }
333
346
 
334
347
  public async unsubscribe() {
348
+ await this.subscription.unsubscribe();
349
+
335
350
  for (const [key, user] of this.userMap.entries()) {
336
351
  await user.unsubscribe();
337
352
  this.userMap.delete(key);
@@ -345,4 +360,17 @@ export class UserMap implements UserMapInterface {
345
360
  this.lastNumberOfSubAccounts = undefined;
346
361
  }
347
362
  }
363
+
364
+ public async updateUserAccount(
365
+ key: string,
366
+ userAccount: UserAccount,
367
+ slot: number
368
+ ) {
369
+ if (!this.userMap.has(key)) {
370
+ this.addPubkey(new PublicKey(key), userAccount, slot);
371
+ } else {
372
+ const user = this.userMap.get(key);
373
+ user.accountSubscriber.updateData(userAccount, slot);
374
+ }
375
+ }
348
376
  }
@@ -0,0 +1,31 @@
1
+ import { Commitment, Connection } from '@solana/web3.js';
2
+ import { DriftClient } from '../driftClient';
3
+
4
+ // passed into UserMap.getUniqueAuthorities to filter users
5
+ export type UserAccountFilterCriteria = {
6
+ // only return users that have open orders
7
+ hasOpenOrders: boolean;
8
+ };
9
+
10
+ export type UserMapConfig = {
11
+ driftClient: DriftClient;
12
+ // connection object to use specifically for the UserMap. If undefined, will use the driftClient's connection
13
+ connection?: Connection;
14
+ subscriptionConfig:
15
+ | {
16
+ type: 'polling';
17
+ frequency: number;
18
+ commitment?: Commitment;
19
+ }
20
+ | {
21
+ type: 'websocket';
22
+ resubTimeoutMs?: number;
23
+ commitment?: Commitment;
24
+ };
25
+
26
+ // True to skip the initial load of userAccounts via getProgramAccounts
27
+ skipInitialLoad?: boolean;
28
+
29
+ // True to include idle users when loading. Defaults to false to decrease # of accounts subscribed to.
30
+ includeIdle?: boolean;
31
+ };
package/tests/amm/test.ts CHANGED
@@ -361,7 +361,10 @@ describe('AMM Tests', () => {
361
361
 
362
362
  console.log(terms2);
363
363
  assert(terms2.effectiveLeverageCapped <= 1.000001);
364
- assert(terms2.inventorySpreadScale == 1.013527);
364
+ assert(
365
+ terms2.inventorySpreadScale == 1.013527,
366
+ `got: ${terms2.inventorySpreadScale}`
367
+ );
365
368
  assert(terms2.longSpread == 1146);
366
369
  assert(terms2.shortSpread == 6686);
367
370
  });
@@ -466,7 +469,7 @@ describe('AMM Tests', () => {
466
469
 
467
470
  assert(markTwapLive.eq(new BN('1949826')));
468
471
  assert(oracleTwapLive.eq(new BN('1942510')));
469
- assert(est1.eq(new BN('15692')));
472
+ assert(est1.eq(new BN('15692')), `got: ${est1}`);
470
473
  assert(est2.eq(new BN('15692')));
471
474
  });
472
475
 
@@ -556,7 +559,7 @@ describe('AMM Tests', () => {
556
559
  assert(markTwapLive.eq(new BN('1222131')));
557
560
  assert(oracleTwapLive.eq(new BN('1222586')));
558
561
  assert(est1.eq(est2));
559
- assert(est2.eq(new BN('-1550')));
562
+ assert(est2.eq(new BN('-1550')), `got: ${est2}`);
560
563
  });
561
564
 
562
565
  it('orderbook L2 gen (no topOfBookQuoteAmounts, 10 numOrders, low liquidity)', async () => {