@drift-labs/sdk 2.49.0-beta.15 → 2.49.0-beta.17

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 (43) hide show
  1. package/VERSION +1 -1
  2. package/lib/adminClient.d.ts +1 -0
  3. package/lib/adminClient.js +8 -0
  4. package/lib/decode/user.d.ts +3 -0
  5. package/lib/decode/user.js +328 -0
  6. package/lib/idl/drift.json +31 -1
  7. package/lib/index.d.ts +1 -0
  8. package/lib/index.js +1 -0
  9. package/lib/math/state.d.ts +5 -0
  10. package/lib/math/state.js +30 -0
  11. package/lib/orderSubscriber/OrderSubscriber.d.ts +3 -0
  12. package/lib/orderSubscriber/OrderSubscriber.js +13 -3
  13. package/lib/orderSubscriber/WebsocketSubscription.js +1 -1
  14. package/lib/orderSubscriber/types.d.ts +4 -1
  15. package/lib/types.d.ts +1 -1
  16. package/lib/user.d.ts +1 -1
  17. package/lib/user.js +7 -2
  18. package/lib/userMap/PollingSubscription.js +3 -1
  19. package/lib/userMap/WebsocketSubscription.d.ts +5 -1
  20. package/lib/userMap/WebsocketSubscription.js +3 -2
  21. package/lib/userMap/userMap.d.ts +1 -0
  22. package/lib/userMap/userMap.js +14 -6
  23. package/lib/userMap/userMapConfig.d.ts +1 -0
  24. package/package.json +2 -1
  25. package/src/adminClient.ts +14 -0
  26. package/src/decode/user.ts +357 -0
  27. package/src/idl/drift.json +32 -2
  28. package/src/index.ts +1 -0
  29. package/src/math/state.ts +29 -0
  30. package/src/orderSubscriber/OrderSubscriber.ts +27 -6
  31. package/src/orderSubscriber/WebsocketSubscription.ts +1 -3
  32. package/src/orderSubscriber/types.ts +13 -1
  33. package/src/types.ts +1 -1
  34. package/src/user.ts +12 -2
  35. package/src/userMap/PollingSubscription.ts +6 -4
  36. package/src/userMap/WebsocketSubscription.ts +5 -3
  37. package/src/userMap/userMap.ts +17 -12
  38. package/src/userMap/userMapConfig.ts +3 -0
  39. package/tests/decode/test.ts +266 -0
  40. package/tests/decode/userAccountBufferStrings.ts +102 -0
  41. package/tests/dlob/helpers.ts +1 -0
  42. package/tests/dlob/test.ts +4 -6
  43. package/tests/user/helpers.ts +0 -1
@@ -16,14 +16,26 @@ export type OrderSubscriberConfig = {
16
16
  resubTimeoutMs?: number;
17
17
  commitment?: Commitment;
18
18
  };
19
+ fastDecode?: boolean;
19
20
  };
20
21
 
21
22
  export interface OrderSubscriberEvents {
22
- onUpdate: (
23
+ orderCreated: (
23
24
  account: UserAccount,
24
25
  updatedOrders: Order[],
25
26
  pubkey: PublicKey,
26
27
  slot: number,
27
28
  dataType: 'raw' | 'decoded'
28
29
  ) => void;
30
+ userUpdated: (
31
+ account: UserAccount,
32
+ pubkey: PublicKey,
33
+ slot: number,
34
+ dataType: 'raw' | 'decoded'
35
+ ) => void;
36
+ updateReceived: (
37
+ pubkey: PublicKey,
38
+ slot: number,
39
+ dataType: 'raw' | 'decoded'
40
+ ) => void;
29
41
  }
package/src/types.ts CHANGED
@@ -552,6 +552,7 @@ export type StateAccount = {
552
552
  lpCooldownTime: BN;
553
553
  initialPctToLiquidate: number;
554
554
  liquidationDuration: number;
555
+ maxInitializeUserFee: number;
555
556
  };
556
557
 
557
558
  export type PerpMarketAccount = {
@@ -870,7 +871,6 @@ export type Order = {
870
871
  price: BN;
871
872
  baseAssetAmount: BN;
872
873
  baseAssetAmountFilled: BN;
873
- quoteAssetAmount: BN;
874
874
  quoteAssetAmountFilled: BN;
875
875
  direction: PositionDirection;
876
876
  reduceOnly: boolean;
package/src/user.ts CHANGED
@@ -422,7 +422,8 @@ export class User {
422
422
  public getPerpPositionWithLPSettle(
423
423
  marketIndex: number,
424
424
  originalPosition?: PerpPosition,
425
- burnLpShares = false
425
+ burnLpShares = false,
426
+ includeRemainderInBaseAmount = false
426
427
  ): [PerpPosition, BN, BN] {
427
428
  originalPosition =
428
429
  originalPosition ??
@@ -600,7 +601,16 @@ export class User {
600
601
  position.lastCumulativeFundingRate = ZERO;
601
602
  }
602
603
 
603
- return [position, remainderBaa, pnl];
604
+ const remainderBeforeRemoval = new BN(position.remainderBaseAssetAmount);
605
+
606
+ if (includeRemainderInBaseAmount) {
607
+ position.baseAssetAmount = position.baseAssetAmount.add(
608
+ remainderBeforeRemoval
609
+ );
610
+ position.remainderBaseAssetAmount = 0;
611
+ }
612
+
613
+ return [position, remainderBeforeRemoval, pnl];
604
614
  }
605
615
 
606
616
  /**
@@ -27,10 +27,12 @@ export class PollingSubscription {
27
27
  return;
28
28
  }
29
29
 
30
- this.intervalId = setInterval(
31
- this.userMap.sync.bind(this.userMap),
32
- this.frequency
33
- );
30
+ if (this.frequency > 0) {
31
+ this.intervalId = setInterval(
32
+ this.userMap.sync.bind(this.userMap),
33
+ this.frequency
34
+ );
35
+ }
34
36
 
35
37
  if (!this.skipInitialLoad) {
36
38
  await this.userMap.sync();
@@ -10,6 +10,7 @@ export class WebsocketSubscription {
10
10
  private skipInitialLoad: boolean;
11
11
  private resubTimeoutMs?: number;
12
12
  private includeIdle?: boolean;
13
+ private decodeFn: (name: string, data: Buffer) => UserAccount;
13
14
 
14
15
  private subscriber: WebSocketProgramAccountSubscriber<UserAccount>;
15
16
 
@@ -19,18 +20,21 @@ export class WebsocketSubscription {
19
20
  skipInitialLoad = false,
20
21
  resubTimeoutMs,
21
22
  includeIdle = false,
23
+ decodeFn,
22
24
  }: {
23
25
  userMap: UserMap;
24
26
  commitment: Commitment;
25
27
  skipInitialLoad?: boolean;
26
28
  resubTimeoutMs?: number;
27
29
  includeIdle?: boolean;
30
+ decodeFn: (name: string, data: Buffer) => UserAccount;
28
31
  }) {
29
32
  this.userMap = userMap;
30
33
  this.commitment = commitment;
31
34
  this.skipInitialLoad = skipInitialLoad;
32
35
  this.resubTimeoutMs = resubTimeoutMs;
33
36
  this.includeIdle = includeIdle || false;
37
+ this.decodeFn = decodeFn;
34
38
  }
35
39
 
36
40
  public async subscribe(): Promise<void> {
@@ -43,9 +47,7 @@ export class WebsocketSubscription {
43
47
  'UserMap',
44
48
  'User',
45
49
  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
- ),
50
+ this.decodeFn,
49
51
  {
50
52
  filters,
51
53
  commitment: this.commitment,
@@ -31,6 +31,7 @@ import {
31
31
  } from './userMapConfig';
32
32
  import { WebsocketSubscription } from './WebsocketSubscription';
33
33
  import { PollingSubscription } from './PollingSubscription';
34
+ import { decodeUser } from '../decode/user';
34
35
 
35
36
  export interface UserMapInterface {
36
37
  subscribe(): Promise<void>;
@@ -58,6 +59,7 @@ export class UserMap implements UserMapInterface {
58
59
  this.lastNumberOfSubAccounts = state.numberOfSubAccounts;
59
60
  }
60
61
  };
62
+ private decode;
61
63
 
62
64
  private syncPromise?: Promise<void>;
63
65
  private syncPromiseResolver: () => void;
@@ -75,6 +77,18 @@ export class UserMap implements UserMapInterface {
75
77
  this.commitment =
76
78
  config.subscriptionConfig.commitment ?? this.driftClient.opts.commitment;
77
79
  this.includeIdle = config.includeIdle ?? false;
80
+
81
+ let decodeFn;
82
+ if (config.fastDecode ?? true) {
83
+ decodeFn = (name, buffer) => decodeUser(buffer);
84
+ } else {
85
+ decodeFn =
86
+ this.driftClient.program.account.user.coder.accounts.decodeUnchecked.bind(
87
+ this.driftClient.program.account.user.coder.accounts
88
+ );
89
+ }
90
+ this.decode = decodeFn;
91
+
78
92
  if (config.subscriptionConfig.type === 'polling') {
79
93
  this.subscription = new PollingSubscription({
80
94
  userMap: this,
@@ -87,6 +101,7 @@ export class UserMap implements UserMapInterface {
87
101
  commitment: this.commitment,
88
102
  resubTimeoutMs: config.subscriptionConfig.resubTimeoutMs,
89
103
  skipInitialLoad: config.skipInitialLoad,
104
+ decodeFn,
90
105
  });
91
106
  }
92
107
  }
@@ -309,12 +324,9 @@ export class UserMap implements UserMapInterface {
309
324
 
310
325
  for (const [key, buffer] of programAccountBufferMap.entries()) {
311
326
  if (!this.has(key)) {
312
- const userAccount =
313
- this.driftClient.program.account.user.coder.accounts.decodeUnchecked(
314
- 'User',
315
- buffer
316
- );
327
+ const userAccount = this.decode('User', buffer);
317
328
  await this.addPubkey(new PublicKey(key), userAccount);
329
+ this.userMap.get(key).accountSubscriber.updateData(userAccount, slot);
318
330
  }
319
331
  // give event loop a chance to breathe
320
332
  await new Promise((resolve) => setTimeout(resolve, 0));
@@ -324,13 +336,6 @@ export class UserMap implements UserMapInterface {
324
336
  if (!programAccountBufferMap.has(key)) {
325
337
  await user.unsubscribe();
326
338
  this.userMap.delete(key);
327
- } else {
328
- const userAccount =
329
- this.driftClient.program.account.user.coder.accounts.decodeUnchecked(
330
- 'User',
331
- programAccountBufferMap.get(key)
332
- );
333
- user.accountSubscriber.updateData(userAccount, slot);
334
339
  }
335
340
  // give event loop a chance to breathe
336
341
  await new Promise((resolve) => setTimeout(resolve, 0));
@@ -28,4 +28,7 @@ export type UserMapConfig = {
28
28
 
29
29
  // True to include idle users when loading. Defaults to false to decrease # of accounts subscribed to.
30
30
  includeIdle?: boolean;
31
+
32
+ // Whether to skip loading available perp/spot positions and open orders
33
+ fastDecode?: boolean;
31
34
  };
@@ -0,0 +1,266 @@
1
+ import { AnchorProvider, Idl, Program } from '@coral-xyz/anchor';
2
+ import driftIDL from '../../src/idl/drift.json';
3
+ import { Connection, Keypair } from '@solana/web3.js';
4
+ import { Wallet } from '../../src';
5
+ import {
6
+ DRIFT_PROGRAM_ID,
7
+ isSpotPositionAvailable,
8
+ isVariant,
9
+ Order,
10
+ PerpPosition,
11
+ positionIsAvailable,
12
+ SpotPosition,
13
+ } from '../../lib';
14
+ import { decodeUser } from '../../lib/decode/user';
15
+ import { assert } from 'chai';
16
+ import { userAccountBufferStrings } from './userAccountBufferStrings';
17
+ const sizeof = require('object-sizeof');
18
+
19
+ describe('Custom user decode', () => {
20
+ it('test', async () => {
21
+ const connection = new Connection('http://localhost:8899');
22
+ const wallet = new Wallet(new Keypair());
23
+ // @ts-ignore
24
+ const provider = new AnchorProvider(connection, wallet);
25
+ const program = new Program(driftIDL as Idl, DRIFT_PROGRAM_ID, provider);
26
+
27
+ let totalAnchorSize = 0;
28
+ let totalCustomSize = 0;
29
+ let totalAnchorTime = 0;
30
+ let totalCustomTime = 0;
31
+ for (const [
32
+ i,
33
+ userAccountBufferString,
34
+ ] of userAccountBufferStrings.entries()) {
35
+ const userAccountBuffer = Buffer.from(userAccountBufferString, 'base64');
36
+ const [anchorSize, customSize, anchorTime, customTime] =
37
+ testUserAccountDecode(program, userAccountBuffer, i);
38
+ totalAnchorSize += anchorSize;
39
+ totalCustomSize += customSize;
40
+ totalAnchorTime += anchorTime;
41
+ totalCustomTime += customTime;
42
+ }
43
+
44
+ console.log(`Total anchor size: ${totalAnchorSize}`);
45
+ console.log(`Total custom size: ${totalCustomSize}`);
46
+ console.log(`Total anchor time: ${totalAnchorTime}`);
47
+ console.log(`Total custom size: ${totalCustomTime}`);
48
+ });
49
+ });
50
+
51
+ function testUserAccountDecode(program: Program, buffer: Buffer, i: number) {
52
+ console.log(`Testing user account decode ${i}`);
53
+
54
+ const anchorStartTimestamp = Date.now();
55
+ const anchorUserAccount = program.coder.accounts.decode('User', buffer);
56
+ const anchorEndTimestamp = Date.now();
57
+ const anchorTime = anchorEndTimestamp - anchorStartTimestamp;
58
+
59
+ const customStartTimestamp = Date.now();
60
+ const customUserAccount = decodeUser(buffer);
61
+ const customEndTimestamp = Date.now();
62
+ const customTime = customEndTimestamp - customStartTimestamp;
63
+
64
+ const anchorSize = sizeof(anchorUserAccount);
65
+ const customSize = sizeof(customUserAccount);
66
+
67
+ assert(anchorUserAccount.authority.equals(customUserAccount.authority));
68
+ assert(anchorUserAccount.delegate.equals(customUserAccount.delegate));
69
+ assert(arraysAreEqual(anchorUserAccount.name, customUserAccount.name));
70
+
71
+ const anchorSpotPositionGenerator = getSpotPositions(
72
+ anchorUserAccount.spotPositions
73
+ );
74
+ const customSpotPositionGenerator = getSpotPositions(
75
+ customUserAccount.spotPositions
76
+ );
77
+ for (const [anchorSpotPosition, customSpotPosition] of zipGenerator(
78
+ anchorSpotPositionGenerator,
79
+ customSpotPositionGenerator
80
+ )) {
81
+ testSpotPosition(anchorSpotPosition, customSpotPosition);
82
+ }
83
+
84
+ const anchorPerpPositionGenerator = getPerpPositions(
85
+ anchorUserAccount.perpPositions
86
+ );
87
+ const customPerpPositionGenerator = getPerpPositions(
88
+ customUserAccount.perpPositions
89
+ );
90
+ for (const [anchorPerpPosition, customPerpPosition] of zipGenerator(
91
+ anchorPerpPositionGenerator,
92
+ customPerpPositionGenerator
93
+ )) {
94
+ testPerpPosition(anchorPerpPosition, customPerpPosition);
95
+ }
96
+
97
+ const anchorOrderGenerator = getOrders(anchorUserAccount.orders);
98
+ const customOrderGenerator = getOrders(customUserAccount.orders);
99
+ for (const [anchorOrder, customOrder] of zipGenerator(
100
+ anchorOrderGenerator,
101
+ customOrderGenerator
102
+ )) {
103
+ testOrder(anchorOrder, customOrder);
104
+ }
105
+
106
+ assert(
107
+ anchorUserAccount.lastAddPerpLpSharesTs.eq(
108
+ customUserAccount.lastAddPerpLpSharesTs
109
+ )
110
+ );
111
+ assert(anchorUserAccount.totalDeposits.eq(customUserAccount.totalDeposits));
112
+ assert(anchorUserAccount.totalWithdraws.eq(customUserAccount.totalWithdraws));
113
+ assert(
114
+ anchorUserAccount.totalSocialLoss.eq(customUserAccount.totalSocialLoss)
115
+ );
116
+ assert(anchorUserAccount.settledPerpPnl.eq(customUserAccount.settledPerpPnl));
117
+ assert(
118
+ anchorUserAccount.cumulativeSpotFees.eq(
119
+ customUserAccount.cumulativeSpotFees
120
+ )
121
+ );
122
+ assert(
123
+ anchorUserAccount.cumulativePerpFunding.eq(
124
+ customUserAccount.cumulativePerpFunding
125
+ )
126
+ );
127
+ assert(
128
+ anchorUserAccount.liquidationMarginFreed.eq(
129
+ customUserAccount.liquidationMarginFreed
130
+ )
131
+ );
132
+ assert(anchorUserAccount.lastActiveSlot.eq(customUserAccount.lastActiveSlot));
133
+ assert(anchorUserAccount.subAccountId === customUserAccount.subAccountId);
134
+ assert(anchorUserAccount.status === customUserAccount.status);
135
+ assert(
136
+ anchorUserAccount.nextLiquidationId === customUserAccount.nextLiquidationId
137
+ );
138
+ assert(anchorUserAccount.nextOrderId === customUserAccount.nextOrderId);
139
+ assert(anchorUserAccount.maxMarginRatio === customUserAccount.maxMarginRatio);
140
+ assert(
141
+ anchorUserAccount.isMarginTradingEnabled ===
142
+ customUserAccount.isMarginTradingEnabled
143
+ );
144
+ assert(anchorUserAccount.idle === customUserAccount.idle);
145
+ assert(anchorUserAccount.openOrders === customUserAccount.openOrders);
146
+ assert(anchorUserAccount.hasOpenOrder === customUserAccount.hasOpenOrder);
147
+ assert(anchorUserAccount.openAuctions === customUserAccount.openAuctions);
148
+ assert(anchorUserAccount.hasOpenAuction === customUserAccount.hasOpenAuction);
149
+
150
+ return [anchorSize, customSize, anchorTime, customTime];
151
+ }
152
+
153
+ function* getSpotPositions(spotPositions: SpotPosition[]) {
154
+ for (const spotPosition of spotPositions) {
155
+ if (!isSpotPositionAvailable(spotPosition)) {
156
+ yield spotPosition;
157
+ }
158
+ }
159
+ }
160
+
161
+ function testSpotPosition(anchor: SpotPosition, custom: SpotPosition) {
162
+ assert(anchor.marketIndex === custom.marketIndex);
163
+ assert(enumsAreEqual(anchor.balanceType, custom.balanceType));
164
+ assert(anchor.openOrders === custom.openOrders);
165
+ assert(anchor.scaledBalance.eq(custom.scaledBalance));
166
+ assert(anchor.openBids.eq(custom.openBids));
167
+ assert(anchor.openAsks.eq(custom.openAsks));
168
+ assert(anchor.cumulativeDeposits.eq(custom.cumulativeDeposits));
169
+ }
170
+
171
+ function* getPerpPositions(perpPositions: PerpPosition[]) {
172
+ for (const perpPosition of perpPositions) {
173
+ if (!positionIsAvailable(perpPosition)) {
174
+ yield perpPosition;
175
+ }
176
+ }
177
+ }
178
+
179
+ function testPerpPosition(anchor: PerpPosition, custom: PerpPosition) {
180
+ assert(anchor.baseAssetAmount.eq(custom.baseAssetAmount));
181
+ assert(anchor.lastCumulativeFundingRate.eq(custom.lastCumulativeFundingRate));
182
+ assert(anchor.marketIndex === custom.marketIndex);
183
+ assert(anchor.quoteAssetAmount.eq(custom.quoteAssetAmount));
184
+ assert(anchor.quoteEntryAmount.eq(custom.quoteEntryAmount));
185
+ assert(anchor.quoteBreakEvenAmount.eq(custom.quoteBreakEvenAmount));
186
+ assert(anchor.openBids.eq(custom.openBids));
187
+ assert(anchor.openAsks.eq(custom.openAsks));
188
+ assert(anchor.settledPnl.eq(custom.settledPnl));
189
+ assert(anchor.lpShares.eq(custom.lpShares));
190
+ assert(anchor.lastBaseAssetAmountPerLp.eq(custom.lastBaseAssetAmountPerLp));
191
+ assert(anchor.lastQuoteAssetAmountPerLp.eq(custom.lastQuoteAssetAmountPerLp));
192
+ assert(anchor.openOrders === custom.openOrders);
193
+ assert(anchor.perLpBase === custom.perLpBase);
194
+ }
195
+
196
+ function* getOrders(orders: Order[]) {
197
+ for (const order of orders) {
198
+ if (isVariant(order.status, 'open')) {
199
+ yield order;
200
+ }
201
+ }
202
+ }
203
+
204
+ function testOrder(anchor: Order, custom: Order) {
205
+ assert(enumsAreEqual(anchor.status, custom.status));
206
+ assert(enumsAreEqual(anchor.orderType, custom.orderType));
207
+ assert(enumsAreEqual(anchor.marketType, custom.marketType));
208
+ assert(anchor.slot.eq(custom.slot));
209
+ assert(anchor.orderId === custom.orderId);
210
+ assert(anchor.userOrderId === custom.userOrderId);
211
+ assert(anchor.marketIndex === custom.marketIndex);
212
+ assert(anchor.price.eq(custom.price));
213
+ assert(anchor.baseAssetAmount.eq(custom.baseAssetAmount));
214
+ assert(anchor.baseAssetAmountFilled.eq(custom.baseAssetAmountFilled));
215
+ assert(anchor.quoteAssetAmountFilled.eq(custom.quoteAssetAmountFilled));
216
+ assert(enumsAreEqual(anchor.direction, custom.direction));
217
+ assert(anchor.reduceOnly === custom.reduceOnly);
218
+ assert(anchor.triggerPrice.eq(custom.triggerPrice));
219
+ assert(enumsAreEqual(anchor.triggerCondition, custom.triggerCondition));
220
+ assert(
221
+ enumsAreEqual(
222
+ anchor.existingPositionDirection,
223
+ custom.existingPositionDirection
224
+ )
225
+ );
226
+ assert(anchor.postOnly === custom.postOnly);
227
+ assert(anchor.immediateOrCancel === custom.immediateOrCancel);
228
+ assert(anchor.oraclePriceOffset === custom.oraclePriceOffset);
229
+ assert(anchor.auctionDuration === custom.auctionDuration);
230
+ assert(anchor.auctionStartPrice.eq(custom.auctionStartPrice));
231
+ assert(anchor.auctionEndPrice.eq(custom.auctionEndPrice));
232
+ assert(anchor.maxTs.eq(custom.maxTs));
233
+ }
234
+
235
+ function enumsAreEqual(e1: any, e2: any) {
236
+ return JSON.stringify(e1) === JSON.stringify(e2);
237
+ }
238
+
239
+ function arraysAreEqual(arr1, arr2) {
240
+ if (arr1.length !== arr2.length) {
241
+ return false;
242
+ }
243
+
244
+ for (let i = 0; i < arr1.length; i++) {
245
+ if (arr1[i] !== arr2[i]) {
246
+ return false;
247
+ }
248
+ }
249
+
250
+ return true;
251
+ }
252
+
253
+ function* zipGenerator(gen1, gen2) {
254
+ let iter1 = gen1.next();
255
+ let iter2 = gen2.next();
256
+
257
+ while (!iter1.done && !iter2.done) {
258
+ yield [iter1.value, iter2.value];
259
+ iter1 = gen1.next();
260
+ iter2 = gen2.next();
261
+ }
262
+
263
+ if (iter1.done !== iter2.done) {
264
+ throw new Error('Generators have different lengths');
265
+ }
266
+ }