@drift-labs/sdk 2.49.0-beta.9 → 2.52.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/VERSION +1 -1
  2. package/lib/accounts/{mockUserAccountSubscriber.d.ts → basicUserAccountSubscriber.d.ts} +6 -2
  3. package/lib/accounts/{mockUserAccountSubscriber.js → basicUserAccountSubscriber.js} +13 -6
  4. package/lib/accounts/bulkAccountLoader.js +7 -1
  5. package/lib/accounts/oneShotUserAccountSubscriber.d.ts +17 -0
  6. package/lib/accounts/oneShotUserAccountSubscriber.js +48 -0
  7. package/lib/accounts/pollingInsuranceFundStakeAccountSubscriber.js +1 -0
  8. package/lib/adminClient.d.ts +2 -0
  9. package/lib/adminClient.js +17 -0
  10. package/lib/constants/numericConstants.d.ts +3 -0
  11. package/lib/constants/numericConstants.js +4 -1
  12. package/lib/constants/perpMarkets.js +40 -0
  13. package/lib/constants/spotMarkets.js +10 -0
  14. package/lib/decode/user.d.ts +3 -0
  15. package/lib/decode/user.js +329 -0
  16. package/lib/driftClient.d.ts +6 -1
  17. package/lib/driftClient.js +35 -10
  18. package/lib/examples/loadDlob.js +10 -5
  19. package/lib/idl/drift.json +32 -2
  20. package/lib/index.d.ts +3 -1
  21. package/lib/index.js +3 -1
  22. package/lib/math/state.d.ts +5 -0
  23. package/lib/math/state.js +27 -0
  24. package/lib/math/superStake.d.ts +43 -0
  25. package/lib/math/superStake.js +64 -22
  26. package/lib/orderSubscriber/OrderSubscriber.d.ts +3 -0
  27. package/lib/orderSubscriber/OrderSubscriber.js +17 -3
  28. package/lib/orderSubscriber/WebsocketSubscription.d.ts +1 -1
  29. package/lib/orderSubscriber/WebsocketSubscription.js +8 -6
  30. package/lib/orderSubscriber/types.d.ts +4 -1
  31. package/lib/types.d.ts +2 -1
  32. package/lib/user.d.ts +2 -1
  33. package/lib/user.js +13 -5
  34. package/lib/userMap/PollingSubscription.d.ts +15 -0
  35. package/lib/userMap/PollingSubscription.js +28 -0
  36. package/lib/userMap/WebsocketSubscription.d.ts +23 -0
  37. package/lib/userMap/WebsocketSubscription.js +41 -0
  38. package/lib/userMap/userMap.d.ts +16 -18
  39. package/lib/userMap/userMap.js +73 -34
  40. package/lib/userMap/userMapConfig.d.ts +21 -0
  41. package/lib/userMap/userMapConfig.js +2 -0
  42. package/lib/userStats.d.ts +1 -0
  43. package/lib/userStats.js +3 -0
  44. package/package.json +2 -1
  45. package/src/accounts/{mockUserAccountSubscriber.ts → basicUserAccountSubscriber.ts} +12 -6
  46. package/src/accounts/bulkAccountLoader.ts +10 -3
  47. package/src/accounts/oneShotUserAccountSubscriber.ts +64 -0
  48. package/src/accounts/pollingInsuranceFundStakeAccountSubscriber.ts +1 -0
  49. package/src/adminClient.ts +27 -0
  50. package/src/constants/numericConstants.ts +4 -0
  51. package/src/constants/perpMarkets.ts +40 -0
  52. package/src/constants/spotMarkets.ts +10 -0
  53. package/src/decode/user.ts +358 -0
  54. package/src/driftClient.ts +62 -15
  55. package/src/examples/loadDlob.ts +11 -6
  56. package/src/idl/drift.json +33 -3
  57. package/src/index.ts +3 -1
  58. package/src/math/state.ts +26 -0
  59. package/src/math/superStake.ts +108 -20
  60. package/src/orderSubscriber/OrderSubscriber.ts +33 -7
  61. package/src/orderSubscriber/WebsocketSubscription.ts +17 -16
  62. package/src/orderSubscriber/types.ts +15 -2
  63. package/src/types.ts +2 -1
  64. package/src/user.ts +19 -6
  65. package/src/userMap/PollingSubscription.ts +48 -0
  66. package/src/userMap/WebsocketSubscription.ts +76 -0
  67. package/src/userMap/userMap.ts +105 -70
  68. package/src/userMap/userMapConfig.ts +34 -0
  69. package/src/userStats.ts +8 -0
  70. package/tests/amm/test.ts +6 -3
  71. package/tests/decode/test.ts +266 -0
  72. package/tests/decode/userAccountBufferStrings.ts +102 -0
  73. package/tests/dlob/helpers.ts +1 -0
  74. package/tests/dlob/test.ts +10 -8
  75. package/tests/user/helpers.ts +0 -1
package/lib/user.js CHANGED
@@ -251,7 +251,7 @@ class User {
251
251
  * @returns : the dust base asset amount (ie, < stepsize)
252
252
  * @returns : pnl from settle
253
253
  */
254
- getPerpPositionWithLPSettle(marketIndex, originalPosition, burnLpShares = false) {
254
+ getPerpPositionWithLPSettle(marketIndex, originalPosition, burnLpShares = false, includeRemainderInBaseAmount = false) {
255
255
  var _a;
256
256
  originalPosition =
257
257
  (_a = originalPosition !== null && originalPosition !== void 0 ? originalPosition : this.getPerpPosition(marketIndex)) !== null && _a !== void 0 ? _a : this.getEmptyPosition(marketIndex);
@@ -396,7 +396,12 @@ class User {
396
396
  else {
397
397
  position.lastCumulativeFundingRate = numericConstants_1.ZERO;
398
398
  }
399
- return [position, remainderBaa, pnl];
399
+ const remainderBeforeRemoval = new _1.BN(position.remainderBaseAssetAmount);
400
+ if (includeRemainderInBaseAmount) {
401
+ position.baseAssetAmount = position.baseAssetAmount.add(remainderBeforeRemoval);
402
+ position.remainderBaseAssetAmount = 0;
403
+ }
404
+ return [position, remainderBeforeRemoval, pnl];
400
405
  }
401
406
  /**
402
407
  * calculates Buying Power = free collateral / initial margin ratio
@@ -852,15 +857,18 @@ class User {
852
857
  getTotalAssetValue(marginCategory) {
853
858
  return this.getSpotMarketAssetValue(undefined, marginCategory, true).add(this.getUnrealizedPNL(true, undefined, marginCategory));
854
859
  }
860
+ getNetUsdValue() {
861
+ const netSpotValue = this.getNetSpotMarketValue();
862
+ const unrealizedPnl = this.getUnrealizedPNL(true, undefined, undefined);
863
+ return netSpotValue.add(unrealizedPnl);
864
+ }
855
865
  /**
856
866
  * Calculates the all time P&L of the user.
857
867
  *
858
868
  * Net withdraws + Net spot market value + Net unrealized P&L -
859
869
  */
860
870
  getTotalAllTimePnl() {
861
- const netBankValue = this.getNetSpotMarketValue();
862
- const unrealizedPnl = this.getUnrealizedPNL(true, undefined, undefined);
863
- const netUsdValue = netBankValue.add(unrealizedPnl);
871
+ const netUsdValue = this.getNetUsdValue();
864
872
  const totalDeposits = this.getUserAccount().totalDeposits;
865
873
  const totalWithdraws = this.getUserAccount().totalWithdraws;
866
874
  const totalPnl = netUsdValue.add(totalWithdraws).sub(totalDeposits);
@@ -0,0 +1,15 @@
1
+ import { UserMap } from './userMap';
2
+ export declare class PollingSubscription {
3
+ private userMap;
4
+ private frequency;
5
+ private skipInitialLoad;
6
+ intervalId?: ReturnType<typeof setTimeout>;
7
+ constructor({ userMap, frequency, skipInitialLoad, }: {
8
+ userMap: UserMap;
9
+ frequency: number;
10
+ skipInitialLoad?: boolean;
11
+ includeIdle?: boolean;
12
+ });
13
+ subscribe(): Promise<void>;
14
+ unsubscribe(): Promise<void>;
15
+ }
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PollingSubscription = void 0;
4
+ class PollingSubscription {
5
+ constructor({ userMap, frequency, skipInitialLoad = false, }) {
6
+ this.userMap = userMap;
7
+ this.frequency = frequency;
8
+ this.skipInitialLoad = skipInitialLoad;
9
+ }
10
+ async subscribe() {
11
+ if (this.intervalId) {
12
+ return;
13
+ }
14
+ if (this.frequency > 0) {
15
+ this.intervalId = setInterval(this.userMap.sync.bind(this.userMap), this.frequency);
16
+ }
17
+ if (!this.skipInitialLoad) {
18
+ await this.userMap.sync();
19
+ }
20
+ }
21
+ async unsubscribe() {
22
+ if (this.intervalId) {
23
+ clearInterval(this.intervalId);
24
+ this.intervalId = undefined;
25
+ }
26
+ }
27
+ }
28
+ exports.PollingSubscription = PollingSubscription;
@@ -0,0 +1,23 @@
1
+ /// <reference types="node" />
2
+ import { UserMap } from './userMap';
3
+ import { UserAccount } from '../types';
4
+ import { Commitment } from '@solana/web3.js';
5
+ export declare class WebsocketSubscription {
6
+ private userMap;
7
+ private commitment;
8
+ private skipInitialLoad;
9
+ private resubTimeoutMs?;
10
+ private includeIdle?;
11
+ private decodeFn;
12
+ private subscriber;
13
+ constructor({ userMap, commitment, skipInitialLoad, resubTimeoutMs, includeIdle, decodeFn, }: {
14
+ userMap: UserMap;
15
+ commitment: Commitment;
16
+ skipInitialLoad?: boolean;
17
+ resubTimeoutMs?: number;
18
+ includeIdle?: boolean;
19
+ decodeFn: (name: string, data: Buffer) => UserAccount;
20
+ });
21
+ subscribe(): Promise<void>;
22
+ unsubscribe(): Promise<void>;
23
+ }
@@ -0,0 +1,41 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.WebsocketSubscription = void 0;
4
+ const memcmp_1 = require("../memcmp");
5
+ const webSocketProgramAccountSubscriber_1 = require("../accounts/webSocketProgramAccountSubscriber");
6
+ class WebsocketSubscription {
7
+ constructor({ userMap, commitment, skipInitialLoad = false, resubTimeoutMs, includeIdle = false, decodeFn, }) {
8
+ this.userMap = userMap;
9
+ this.commitment = commitment;
10
+ this.skipInitialLoad = skipInitialLoad;
11
+ this.resubTimeoutMs = resubTimeoutMs;
12
+ this.includeIdle = includeIdle || false;
13
+ this.decodeFn = decodeFn;
14
+ }
15
+ async subscribe() {
16
+ if (!this.subscriber) {
17
+ const filters = [(0, memcmp_1.getUserFilter)()];
18
+ if (!this.includeIdle) {
19
+ filters.push((0, memcmp_1.getNonIdleUserFilter)());
20
+ }
21
+ this.subscriber = new webSocketProgramAccountSubscriber_1.WebSocketProgramAccountSubscriber('UserMap', 'User', this.userMap.driftClient.program, this.decodeFn, {
22
+ filters,
23
+ commitment: this.commitment,
24
+ }, this.resubTimeoutMs);
25
+ }
26
+ await this.subscriber.subscribe((accountId, account, context) => {
27
+ const userKey = accountId.toBase58();
28
+ this.userMap.updateUserAccount(userKey, account, context.slot);
29
+ });
30
+ if (!this.skipInitialLoad) {
31
+ await this.userMap.sync();
32
+ }
33
+ }
34
+ async unsubscribe() {
35
+ if (!this.subscriber)
36
+ return;
37
+ await this.subscriber.unsubscribe();
38
+ this.subscriber = undefined;
39
+ }
40
+ }
41
+ exports.WebsocketSubscription = WebsocketSubscription;
@@ -1,5 +1,6 @@
1
- import { User, DriftClient, UserAccount, OrderRecord, UserSubscriptionConfig, WrappedEvent, DLOB } from '..';
1
+ import { User, DriftClient, UserAccount, OrderRecord, WrappedEvent, DLOB } from '..';
2
2
  import { PublicKey } from '@solana/web3.js';
3
+ import { UserAccountFilterCriteria as UserFilterCriteria, UserMapConfig } from './userMapConfig';
3
4
  export interface UserMapInterface {
4
5
  subscribe(): Promise<void>;
5
6
  unsubscribe(): Promise<void>;
@@ -11,33 +12,24 @@ export interface UserMapInterface {
11
12
  updateWithOrderRecord(record: OrderRecord): Promise<void>;
12
13
  values(): IterableIterator<User>;
13
14
  }
14
- export type SyncCallbackCriteria = {
15
- hasOpenOrders: boolean;
16
- };
17
15
  export declare class UserMap implements UserMapInterface {
18
16
  private userMap;
19
- private driftClient;
20
- private accountSubscription;
17
+ driftClient: DriftClient;
18
+ private connection;
19
+ private commitment;
21
20
  private includeIdle;
22
21
  private lastNumberOfSubAccounts;
22
+ private subscription;
23
23
  private stateAccountUpdateCallback;
24
- private syncCallback;
25
- private syncCallbackCriteria;
24
+ private decode;
26
25
  private syncPromise?;
27
26
  private syncPromiseResolver;
28
27
  /**
29
28
  * Constructs a new UserMap instance.
30
- *
31
- * @param {DriftClient} driftClient - The DriftClient instance.
32
- * @param {UserSubscriptionConfig} accountSubscription - The UserSubscriptionConfig instance.
33
- * @param {boolean} includeIdle - Whether idle users are subscribed to. Defaults to false to decrease # of user subscriptions.
34
- * @param {(authorities: PublicKey[]) => Promise<void>} syncCallback - Called after `sync` completes, will pas in unique list of authorities. Useful for using it to sync UserStatsMap.
35
- * @param {SyncCallbackCriteria} syncCallbackCriteria - The criteria for the sync callback. Defaults to having no filters
36
29
  */
37
- constructor(driftClient: DriftClient, accountSubscription: UserSubscriptionConfig, includeIdle?: boolean, syncCallback?: (authorities: PublicKey[]) => Promise<void>, syncCallbackCriteria?: SyncCallbackCriteria);
38
- addSyncCallback(syncCallback?: (authorities: PublicKey[]) => Promise<void>, syncCallbackCriteria?: SyncCallbackCriteria): void;
30
+ constructor(config: UserMapConfig);
39
31
  subscribe(): Promise<void>;
40
- addPubkey(userAccountPublicKey: PublicKey, userAccount?: UserAccount): Promise<void>;
32
+ addPubkey(userAccountPublicKey: PublicKey, userAccount?: UserAccount, slot?: number): Promise<void>;
41
33
  has(key: string): boolean;
42
34
  /**
43
35
  * gets the User for a particular userAccountPublicKey, if no User exists, undefined is returned
@@ -67,7 +59,13 @@ export declare class UserMap implements UserMapInterface {
67
59
  updateWithEventRecord(record: WrappedEvent<any>): Promise<void>;
68
60
  values(): IterableIterator<User>;
69
61
  size(): number;
70
- getUniqueAuthorities(useSyncCallbackCriteria?: boolean): PublicKey[];
62
+ /**
63
+ * Returns a unique list of authorities for all users in the UserMap that meet the filter criteria
64
+ * @param filterCriteria: Users must meet these criteria to be included
65
+ * @returns
66
+ */
67
+ getUniqueAuthorities(filterCriteria?: UserFilterCriteria): PublicKey[];
71
68
  sync(): Promise<void>;
72
69
  unsubscribe(): Promise<void>;
70
+ updateUserAccount(key: string, userAccount: UserAccount, slot: number): Promise<void>;
73
71
  }
@@ -5,33 +5,57 @@ const __1 = require("..");
5
5
  const web3_js_1 = require("@solana/web3.js");
6
6
  const buffer_1 = require("buffer");
7
7
  const memcmp_1 = require("../memcmp");
8
+ const WebsocketSubscription_1 = require("./WebsocketSubscription");
9
+ const PollingSubscription_1 = require("./PollingSubscription");
10
+ const user_1 = require("../decode/user");
8
11
  class UserMap {
9
12
  /**
10
13
  * Constructs a new UserMap instance.
11
- *
12
- * @param {DriftClient} driftClient - The DriftClient instance.
13
- * @param {UserSubscriptionConfig} accountSubscription - The UserSubscriptionConfig instance.
14
- * @param {boolean} includeIdle - Whether idle users are subscribed to. Defaults to false to decrease # of user subscriptions.
15
- * @param {(authorities: PublicKey[]) => Promise<void>} syncCallback - Called after `sync` completes, will pas in unique list of authorities. Useful for using it to sync UserStatsMap.
16
- * @param {SyncCallbackCriteria} syncCallbackCriteria - The criteria for the sync callback. Defaults to having no filters
17
14
  */
18
- constructor(driftClient, accountSubscription, includeIdle = false, syncCallback, syncCallbackCriteria = { hasOpenOrders: false }) {
15
+ constructor(config) {
16
+ var _a, _b, _c;
19
17
  this.userMap = new Map();
20
18
  this.stateAccountUpdateCallback = async (state) => {
21
- if (state.numberOfSubAccounts !== this.lastNumberOfSubAccounts) {
19
+ if (!state.numberOfSubAccounts.eq(this.lastNumberOfSubAccounts)) {
22
20
  await this.sync();
23
21
  this.lastNumberOfSubAccounts = state.numberOfSubAccounts;
24
22
  }
25
23
  };
26
- this.driftClient = driftClient;
27
- this.accountSubscription = accountSubscription;
28
- this.includeIdle = includeIdle;
29
- this.syncCallback = syncCallback;
30
- this.syncCallbackCriteria = syncCallbackCriteria;
31
- }
32
- addSyncCallback(syncCallback, syncCallbackCriteria = { hasOpenOrders: false }) {
33
- this.syncCallback = syncCallback;
34
- this.syncCallbackCriteria = syncCallbackCriteria;
24
+ this.driftClient = config.driftClient;
25
+ if (config.connection) {
26
+ this.connection = config.connection;
27
+ }
28
+ else {
29
+ this.connection = this.driftClient.connection;
30
+ }
31
+ this.commitment =
32
+ (_a = config.subscriptionConfig.commitment) !== null && _a !== void 0 ? _a : this.driftClient.opts.commitment;
33
+ this.includeIdle = (_b = config.includeIdle) !== null && _b !== void 0 ? _b : false;
34
+ let decodeFn;
35
+ if ((_c = config.fastDecode) !== null && _c !== void 0 ? _c : true) {
36
+ decodeFn = (name, buffer) => (0, user_1.decodeUser)(buffer);
37
+ }
38
+ else {
39
+ decodeFn =
40
+ this.driftClient.program.account.user.coder.accounts.decodeUnchecked.bind(this.driftClient.program.account.user.coder.accounts);
41
+ }
42
+ this.decode = decodeFn;
43
+ if (config.subscriptionConfig.type === 'polling') {
44
+ this.subscription = new PollingSubscription_1.PollingSubscription({
45
+ userMap: this,
46
+ frequency: config.subscriptionConfig.frequency,
47
+ skipInitialLoad: config.skipInitialLoad,
48
+ });
49
+ }
50
+ else {
51
+ this.subscription = new WebsocketSubscription_1.WebsocketSubscription({
52
+ userMap: this,
53
+ commitment: this.commitment,
54
+ resubTimeoutMs: config.subscriptionConfig.resubTimeoutMs,
55
+ skipInitialLoad: config.skipInitialLoad,
56
+ decodeFn,
57
+ });
58
+ }
35
59
  }
36
60
  async subscribe() {
37
61
  if (this.size() > 0) {
@@ -41,13 +65,16 @@ class UserMap {
41
65
  this.lastNumberOfSubAccounts =
42
66
  this.driftClient.getStateAccount().numberOfSubAccounts;
43
67
  this.driftClient.eventEmitter.on('stateAccountUpdate', this.stateAccountUpdateCallback);
44
- await this.sync();
68
+ await this.subscription.subscribe();
45
69
  }
46
- async addPubkey(userAccountPublicKey, userAccount) {
70
+ async addPubkey(userAccountPublicKey, userAccount, slot) {
47
71
  const user = new __1.User({
48
72
  driftClient: this.driftClient,
49
73
  userAccountPublicKey,
50
- accountSubscription: this.accountSubscription,
74
+ accountSubscription: {
75
+ type: 'custom',
76
+ userAccountSubscriber: new __1.OneShotUserAccountSubscriber(this.driftClient.program, userAccountPublicKey, userAccount, slot, this.commitment),
77
+ },
51
78
  });
52
79
  await user.subscribe(userAccount);
53
80
  this.userMap.set(userAccountPublicKey.toString(), user);
@@ -148,11 +175,15 @@ class UserMap {
148
175
  size() {
149
176
  return this.userMap.size;
150
177
  }
151
- getUniqueAuthorities(useSyncCallbackCriteria = true) {
178
+ /**
179
+ * Returns a unique list of authorities for all users in the UserMap that meet the filter criteria
180
+ * @param filterCriteria: Users must meet these criteria to be included
181
+ * @returns
182
+ */
183
+ getUniqueAuthorities(filterCriteria) {
152
184
  const usersMeetingCriteria = Array.from(this.userMap.values()).filter((user) => {
153
185
  let pass = true;
154
- if (useSyncCallbackCriteria &&
155
- this.syncCallbackCriteria.hasOpenOrders) {
186
+ if (filterCriteria && filterCriteria.hasOpenOrders) {
156
187
  pass = pass && user.getUserAccount().hasOpenOrder;
157
188
  }
158
189
  return pass;
@@ -176,7 +207,7 @@ class UserMap {
176
207
  const rpcRequestArgs = [
177
208
  this.driftClient.program.programId.toBase58(),
178
209
  {
179
- commitment: this.driftClient.connection.commitment,
210
+ commitment: this.commitment,
180
211
  filters,
181
212
  encoding: 'base64',
182
213
  withContext: true,
@@ -184,7 +215,7 @@ class UserMap {
184
215
  ];
185
216
  const rpcJSONResponse =
186
217
  // @ts-ignore
187
- await this.driftClient.connection._rpcRequest('getProgramAccounts', rpcRequestArgs);
218
+ await this.connection._rpcRequest('getProgramAccounts', rpcRequestArgs);
188
219
  const rpcResponseAndContext = rpcJSONResponse.result;
189
220
  const slot = rpcResponseAndContext.context.slot;
190
221
  const programAccountBufferMap = new Map();
@@ -195,26 +226,24 @@ class UserMap {
195
226
  }
196
227
  for (const [key, buffer] of programAccountBufferMap.entries()) {
197
228
  if (!this.has(key)) {
198
- const userAccount = this.driftClient.program.account.user.coder.accounts.decode('User', buffer);
229
+ const userAccount = this.decode('User', buffer);
199
230
  await this.addPubkey(new web3_js_1.PublicKey(key), userAccount);
231
+ this.userMap.get(key).accountSubscriber.updateData(userAccount, slot);
200
232
  }
233
+ // give event loop a chance to breathe
234
+ await new Promise((resolve) => setTimeout(resolve, 0));
201
235
  }
202
236
  for (const [key, user] of this.userMap.entries()) {
203
237
  if (!programAccountBufferMap.has(key)) {
204
238
  await user.unsubscribe();
205
239
  this.userMap.delete(key);
206
240
  }
207
- else {
208
- const userAccount = this.driftClient.program.account.user.coder.accounts.decode('User', programAccountBufferMap.get(key));
209
- user.accountSubscriber.updateData(userAccount, slot);
210
- }
211
- }
212
- if (this.syncCallback) {
213
- await this.syncCallback(this.getUniqueAuthorities());
241
+ // give event loop a chance to breathe
242
+ await new Promise((resolve) => setTimeout(resolve, 0));
214
243
  }
215
244
  }
216
245
  catch (e) {
217
- console.error(`Error in UserMap.sync()`);
246
+ console.error(`Error in UserMap.sync():`);
218
247
  console.error(e);
219
248
  }
220
249
  finally {
@@ -223,6 +252,7 @@ class UserMap {
223
252
  }
224
253
  }
225
254
  async unsubscribe() {
255
+ await this.subscription.unsubscribe();
226
256
  for (const [key, user] of this.userMap.entries()) {
227
257
  await user.unsubscribe();
228
258
  this.userMap.delete(key);
@@ -232,5 +262,14 @@ class UserMap {
232
262
  this.lastNumberOfSubAccounts = undefined;
233
263
  }
234
264
  }
265
+ async updateUserAccount(key, userAccount, slot) {
266
+ if (!this.userMap.has(key)) {
267
+ this.addPubkey(new web3_js_1.PublicKey(key), userAccount, slot);
268
+ }
269
+ else {
270
+ const user = this.userMap.get(key);
271
+ user.accountSubscriber.updateData(userAccount, slot);
272
+ }
273
+ }
235
274
  }
236
275
  exports.UserMap = UserMap;
@@ -0,0 +1,21 @@
1
+ import { Commitment, Connection } from '@solana/web3.js';
2
+ import { DriftClient } from '../driftClient';
3
+ export type UserAccountFilterCriteria = {
4
+ hasOpenOrders: boolean;
5
+ };
6
+ export type UserMapConfig = {
7
+ driftClient: DriftClient;
8
+ connection?: Connection;
9
+ subscriptionConfig: {
10
+ type: 'polling';
11
+ frequency: number;
12
+ commitment?: Commitment;
13
+ } | {
14
+ type: 'websocket';
15
+ resubTimeoutMs?: number;
16
+ commitment?: Commitment;
17
+ };
18
+ skipInitialLoad?: boolean;
19
+ includeIdle?: boolean;
20
+ fastDecode?: boolean;
21
+ };
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -15,4 +15,5 @@ export declare class UserStats {
15
15
  getAccountAndSlot(): DataAndSlot<UserStatsAccount>;
16
16
  getAccount(): UserStatsAccount;
17
17
  getReferrerInfo(): ReferrerInfo | undefined;
18
+ static getOldestActionTs(account: UserStatsAccount): number;
18
19
  }
package/lib/userStats.js CHANGED
@@ -48,5 +48,8 @@ class UserStats {
48
48
  };
49
49
  }
50
50
  }
51
+ static getOldestActionTs(account) {
52
+ return Math.min(account.lastFillerVolume30DTs.toNumber(), account.lastMakerVolume30DTs.toNumber(), account.lastTakerVolume30DTs.toNumber());
53
+ }
51
54
  }
52
55
  exports.UserStats = UserStats;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@drift-labs/sdk",
3
- "version": "2.49.0-beta.9",
3
+ "version": "2.52.0-beta.1",
4
4
  "main": "lib/index.js",
5
5
  "types": "lib/index.d.ts",
6
6
  "author": "crispheaney",
@@ -44,6 +44,7 @@
44
44
  "uuid": "^8.3.2"
45
45
  },
46
46
  "devDependencies": {
47
+ "object-sizeof": "^2.6.3",
47
48
  "@types/big.js": "^6.2.0",
48
49
  "@types/bn.js": "^5.1.3",
49
50
  "@types/chai": "^4.3.1",
@@ -4,7 +4,11 @@ import StrictEventEmitter from 'strict-event-emitter-types';
4
4
  import { EventEmitter } from 'events';
5
5
  import { UserAccount } from '../types';
6
6
 
7
- export class MockUserAccountSubscriber implements UserAccountSubscriber {
7
+ /**
8
+ * Basic implementation of UserAccountSubscriber. It will only take in UserAccount
9
+ * data during initialization and will not fetch or subscribe to updates.
10
+ */
11
+ export class BasicUserAccountSubscriber implements UserAccountSubscriber {
8
12
  isSubscribed: boolean;
9
13
  eventEmitter: StrictEventEmitter<EventEmitter, UserAccountEvents>;
10
14
  userAccountPublicKey: PublicKey;
@@ -16,8 +20,8 @@ export class MockUserAccountSubscriber implements UserAccountSubscriber {
16
20
 
17
21
  public constructor(
18
22
  userAccountPublicKey: PublicKey,
19
- data: UserAccount,
20
- slot: number
23
+ data?: UserAccount,
24
+ slot?: number
21
25
  ) {
22
26
  this.isSubscribed = true;
23
27
  this.eventEmitter = new EventEmitter();
@@ -46,8 +50,10 @@ export class MockUserAccountSubscriber implements UserAccountSubscriber {
46
50
  }
47
51
 
48
52
  public updateData(userAccount: UserAccount, slot: number): void {
49
- this.user = { data: userAccount, slot };
50
- this.eventEmitter.emit('userAccountUpdate', userAccount);
51
- this.eventEmitter.emit('update');
53
+ if (!this.user || slot >= (this.user.slot ?? 0)) {
54
+ this.user = { data: userAccount, slot };
55
+ this.eventEmitter.emit('userAccountUpdate', userAccount);
56
+ this.eventEmitter.emit('update');
57
+ }
52
58
  }
53
59
  }
@@ -78,6 +78,7 @@ export class BulkAccountLoader {
78
78
  if (existingAccountToLoad) {
79
79
  existingAccountToLoad.callbacks.delete(callbackId);
80
80
  if (existingAccountToLoad.callbacks.size === 0) {
81
+ this.bufferAndSlotMap.delete(publicKey.toString());
81
82
  this.accountsToLoad.delete(existingAccountToLoad.publicKey.toString());
82
83
  }
83
84
  }
@@ -153,9 +154,11 @@ export class BulkAccountLoader {
153
154
  const requests = new Array<{ methodName: string; args: any }>();
154
155
  for (const accountsToLoadChunk of accountsToLoadChunks) {
155
156
  const args = [
156
- accountsToLoadChunk.map((accountToLoad) => {
157
- return accountToLoad.publicKey.toBase58();
158
- }),
157
+ accountsToLoadChunk
158
+ .filter((accountToLoad) => accountToLoad.callbacks.size > 0)
159
+ .map((accountToLoad) => {
160
+ return accountToLoad.publicKey.toBase58();
161
+ }),
159
162
  { commitment: this.commitment },
160
163
  ];
161
164
 
@@ -190,6 +193,10 @@ export class BulkAccountLoader {
190
193
 
191
194
  const accountsToLoad = accountsToLoadChunks[i];
192
195
  accountsToLoad.forEach((accountToLoad, j) => {
196
+ if (accountToLoad.callbacks.size === 0) {
197
+ return;
198
+ }
199
+
193
200
  const key = accountToLoad.publicKey.toBase58();
194
201
  const oldRPCResponse = this.bufferAndSlotMap.get(key);
195
202
 
@@ -0,0 +1,64 @@
1
+ import { Commitment, PublicKey } from '@solana/web3.js';
2
+ import { UserAccount } from '../types';
3
+ import { BasicUserAccountSubscriber } from './basicUserAccountSubscriber';
4
+ import { Program } from '@coral-xyz/anchor';
5
+
6
+ /**
7
+ * Simple implementation of UserAccountSubscriber. It will fetch the UserAccount
8
+ * date on subscribe (or call to fetch) if no account data is provided on init.
9
+ * Expect to use only 1 RPC call unless you call fetch repeatedly.
10
+ */
11
+ export class OneShotUserAccountSubscriber extends BasicUserAccountSubscriber {
12
+ program: Program;
13
+ commitment: Commitment;
14
+
15
+ public constructor(
16
+ program: Program,
17
+ userAccountPublicKey: PublicKey,
18
+ data?: UserAccount,
19
+ slot?: number,
20
+ commitment?: Commitment
21
+ ) {
22
+ super(userAccountPublicKey, data, slot);
23
+ this.program = program;
24
+ this.commitment = commitment ?? 'confirmed';
25
+ }
26
+
27
+ async subscribe(userAccount?: UserAccount): Promise<boolean> {
28
+ if (userAccount) {
29
+ this.user = { data: userAccount, slot: this.user.slot };
30
+ return true;
31
+ }
32
+
33
+ await this.fetchIfUnloaded();
34
+ if (this.doesAccountExist()) {
35
+ this.eventEmitter.emit('update');
36
+ }
37
+ return true;
38
+ }
39
+
40
+ async fetchIfUnloaded(): Promise<void> {
41
+ if (this.user.data === undefined) {
42
+ await this.fetch();
43
+ }
44
+ }
45
+
46
+ async fetch(): Promise<void> {
47
+ try {
48
+ const dataAndContext = await this.program.account.user.fetchAndContext(
49
+ this.userAccountPublicKey,
50
+ this.commitment
51
+ );
52
+ if (dataAndContext.context.slot > (this.user?.slot ?? 0)) {
53
+ this.user = {
54
+ data: dataAndContext.data as UserAccount,
55
+ slot: dataAndContext.context.slot,
56
+ };
57
+ }
58
+ } catch (e) {
59
+ console.error(
60
+ `OneShotUserAccountSubscriber.fetch() UserAccount does not exist: ${e.message}`
61
+ );
62
+ }
63
+ }
64
+ }
@@ -54,6 +54,7 @@ export class PollingInsuranceFundStakeAccountSubscriber
54
54
 
55
55
  await this.addToAccountLoader();
56
56
 
57
+ await this.fetchIfUnloaded();
57
58
  if (this.doesAccountExist()) {
58
59
  this.eventEmitter.emit('update');
59
60
  }
@@ -846,6 +846,20 @@ export class AdminClient extends DriftClient {
846
846
  );
847
847
  }
848
848
 
849
+ public async updateStateMaxInitializeUserFee(
850
+ maxInitializeUserFee: number
851
+ ): Promise<TransactionSignature> {
852
+ return await this.program.rpc.updateStateMaxInitializeUserFee(
853
+ maxInitializeUserFee,
854
+ {
855
+ accounts: {
856
+ admin: this.wallet.publicKey,
857
+ state: await this.getStatePublicKey(),
858
+ },
859
+ }
860
+ );
861
+ }
862
+
849
863
  public async updateWithdrawGuardThreshold(
850
864
  spotMarketIndex: number,
851
865
  withdrawGuardThreshold: BN
@@ -1167,6 +1181,19 @@ export class AdminClient extends DriftClient {
1167
1181
  });
1168
1182
  }
1169
1183
 
1184
+ public async updatePhoenixFulfillmentConfigStatus(
1185
+ phoenixFulfillmentConfig: PublicKey,
1186
+ status: SpotFulfillmentConfigStatus
1187
+ ): Promise<TransactionSignature> {
1188
+ return await this.program.rpc.phoenixFulfillmentConfigStatus(status, {
1189
+ accounts: {
1190
+ admin: this.wallet.publicKey,
1191
+ state: await this.getStatePublicKey(),
1192
+ phoenixFulfillmentConfig,
1193
+ },
1194
+ });
1195
+ }
1196
+
1170
1197
  public async updateSpotMarketExpiry(
1171
1198
  spotMarketIndex: number,
1172
1199
  expiryTs: BN
@@ -99,3 +99,7 @@ export const OPEN_ORDER_MARGIN_REQUIREMENT = QUOTE_PRECISION.div(new BN(100));
99
99
  export const DEFAULT_REVENUE_SINCE_LAST_FUNDING_SPREAD_RETREAT = new BN(
100
100
  -25
101
101
  ).mul(QUOTE_PRECISION);
102
+
103
+ export const ACCOUNT_AGE_DELETION_CUTOFF_SECONDS = 60 * 60 * 24 * 13; // 13 days
104
+ export const IDLE_TIME_SLOTS = 9000;
105
+ export const SLOT_TIME_ESTIMATE_MS = 400;