@drift-labs/sdk 2.31.1-beta.1 → 2.31.1-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.
@@ -17,6 +17,7 @@ import {
17
17
  import { AnchorProvider } from '@coral-xyz/anchor';
18
18
  import assert from 'assert';
19
19
  import bs58 from 'bs58';
20
+ import { IWallet } from '../types';
20
21
 
21
22
  const DEFAULT_TIMEOUT = 35000;
22
23
  const DEFAULT_RETRY = 8000;
@@ -26,20 +27,34 @@ type ResolveReference = {
26
27
  };
27
28
 
28
29
  export class RetryTxSender implements TxSender {
29
- provider: AnchorProvider;
30
+ connection: Connection;
31
+ wallet: IWallet;
32
+ opts: ConfirmOptions;
30
33
  timeout: number;
31
34
  retrySleep: number;
32
35
  additionalConnections: Connection[];
33
-
34
- public constructor(
35
- provider: AnchorProvider,
36
- timeout?: number,
37
- retrySleep?: number,
38
- additionalConnections = new Array<Connection>()
39
- ) {
40
- this.provider = provider;
41
- this.timeout = timeout ?? DEFAULT_TIMEOUT;
42
- this.retrySleep = retrySleep ?? DEFAULT_RETRY;
36
+ timoutCount = 0;
37
+
38
+ public constructor({
39
+ connection,
40
+ wallet,
41
+ opts = AnchorProvider.defaultOptions(),
42
+ timeout = DEFAULT_TIMEOUT,
43
+ retrySleep = DEFAULT_RETRY,
44
+ additionalConnections = new Array<Connection>(),
45
+ }: {
46
+ connection: Connection;
47
+ wallet: IWallet;
48
+ opts?: ConfirmOptions;
49
+ timeout?: number;
50
+ retrySleep?: number;
51
+ additionalConnections?;
52
+ }) {
53
+ this.connection = connection;
54
+ this.wallet = wallet;
55
+ this.opts = opts;
56
+ this.timeout = timeout;
57
+ this.retrySleep = retrySleep;
43
58
  this.additionalConnections = additionalConnections;
44
59
  }
45
60
 
@@ -53,7 +68,7 @@ export class RetryTxSender implements TxSender {
53
68
  additionalSigners = [];
54
69
  }
55
70
  if (opts === undefined) {
56
- opts = this.provider.opts;
71
+ opts = this.opts;
57
72
  }
58
73
 
59
74
  const signedTx = preSigned
@@ -68,11 +83,9 @@ export class RetryTxSender implements TxSender {
68
83
  additionalSigners: Array<Signer>,
69
84
  opts: ConfirmOptions
70
85
  ): Promise<Transaction> {
71
- tx.feePayer = this.provider.wallet.publicKey;
86
+ tx.feePayer = this.wallet.publicKey;
72
87
  tx.recentBlockhash = (
73
- await this.provider.connection.getRecentBlockhash(
74
- opts.preflightCommitment
75
- )
88
+ await this.connection.getRecentBlockhash(opts.preflightCommitment)
76
89
  ).blockhash;
77
90
 
78
91
  additionalSigners
@@ -81,7 +94,7 @@ export class RetryTxSender implements TxSender {
81
94
  tx.partialSign(kp);
82
95
  });
83
96
 
84
- const signedTx = await this.provider.wallet.signTransaction(tx);
97
+ const signedTx = await this.wallet.signTransaction(tx);
85
98
 
86
99
  return signedTx;
87
100
  }
@@ -96,15 +109,13 @@ export class RetryTxSender implements TxSender {
96
109
  additionalSigners = [];
97
110
  }
98
111
  if (opts === undefined) {
99
- opts = this.provider.opts;
112
+ opts = this.opts;
100
113
  }
101
114
 
102
115
  const message = new TransactionMessage({
103
- payerKey: this.provider.wallet.publicKey,
116
+ payerKey: this.wallet.publicKey,
104
117
  recentBlockhash: (
105
- await this.provider.connection.getRecentBlockhash(
106
- opts.preflightCommitment
107
- )
118
+ await this.connection.getRecentBlockhash(opts.preflightCommitment)
108
119
  ).blockhash,
109
120
  instructions: ixs,
110
121
  }).compileToV0Message(lookupTableAccounts);
@@ -124,9 +135,9 @@ export class RetryTxSender implements TxSender {
124
135
  if (preSigned) {
125
136
  signedTx = tx;
126
137
  // @ts-ignore
127
- } else if (this.provider.wallet.payer) {
138
+ } else if (this.wallet.payer) {
128
139
  // @ts-ignore
129
- tx.sign((additionalSigners ?? []).concat(this.provider.wallet.payer));
140
+ tx.sign((additionalSigners ?? []).concat(this.wallet.payer));
130
141
  signedTx = tx;
131
142
  } else {
132
143
  additionalSigners
@@ -135,11 +146,11 @@ export class RetryTxSender implements TxSender {
135
146
  tx.sign([kp]);
136
147
  });
137
148
  // @ts-ignore
138
- signedTx = await this.provider.wallet.signTransaction(tx);
149
+ signedTx = await this.wallet.signTransaction(tx);
139
150
  }
140
151
 
141
152
  if (opts === undefined) {
142
- opts = this.provider.opts;
153
+ opts = this.opts;
143
154
  }
144
155
 
145
156
  return this.sendRawTransaction(signedTx.serialize(), opts);
@@ -153,10 +164,7 @@ export class RetryTxSender implements TxSender {
153
164
 
154
165
  let txid: TransactionSignature;
155
166
  try {
156
- txid = await this.provider.connection.sendRawTransaction(
157
- rawTransaction,
158
- opts
159
- );
167
+ txid = await this.connection.sendRawTransaction(rawTransaction, opts);
160
168
  this.sendToAdditionalConnections(rawTransaction, opts);
161
169
  } catch (e) {
162
170
  console.error(e);
@@ -178,7 +186,7 @@ export class RetryTxSender implements TxSender {
178
186
  while (!done && this.getTimestamp() - startTime < this.timeout) {
179
187
  await this.sleep(resolveReference);
180
188
  if (!done) {
181
- this.provider.connection
189
+ this.connection
182
190
  .sendRawTransaction(rawTransaction, opts)
183
191
  .catch((e) => {
184
192
  console.error(e);
@@ -217,13 +225,10 @@ export class RetryTxSender implements TxSender {
217
225
  assert(decodedSignature.length === 64, 'signature has invalid length');
218
226
 
219
227
  const start = Date.now();
220
- const subscriptionCommitment = commitment || this.provider.opts.commitment;
228
+ const subscriptionCommitment = commitment || this.opts.commitment;
221
229
 
222
230
  const subscriptionIds = new Array<number>();
223
- const connections = [
224
- this.provider.connection,
225
- ...this.additionalConnections,
226
- ];
231
+ const connections = [this.connection, ...this.additionalConnections];
227
232
  let response: RpcResponseAndContext<SignatureResult> | null = null;
228
233
  const promises = connections.map((connection, i) => {
229
234
  let subscriptionId;
@@ -260,6 +265,7 @@ export class RetryTxSender implements TxSender {
260
265
  }
261
266
 
262
267
  if (response === null) {
268
+ this.timoutCount += 1;
263
269
  const duration = (Date.now() - start) / 1000;
264
270
  throw new Error(
265
271
  `Transaction was not confirmed in ${duration.toFixed(
@@ -325,4 +331,8 @@ export class RetryTxSender implements TxSender {
325
331
  this.additionalConnections.push(newConnection);
326
332
  }
327
333
  }
334
+
335
+ public getTimeoutCount(): number {
336
+ return this.timoutCount;
337
+ }
328
338
  }
package/src/tx/types.ts CHANGED
@@ -1,4 +1,3 @@
1
- import { Provider } from '@coral-xyz/anchor';
2
1
  import {
3
2
  AddressLookupTableAccount,
4
3
  ConfirmOptions,
@@ -8,6 +7,7 @@ import {
8
7
  TransactionSignature,
9
8
  VersionedTransaction,
10
9
  } from '@solana/web3.js';
10
+ import { IWallet } from '../types';
11
11
 
12
12
  export type TxSigAndSlot = {
13
13
  txSig: TransactionSignature;
@@ -15,7 +15,7 @@ export type TxSigAndSlot = {
15
15
  };
16
16
 
17
17
  export interface TxSender {
18
- provider: Provider;
18
+ wallet: IWallet;
19
19
 
20
20
  send(
21
21
  tx: Transaction,
@@ -42,4 +42,6 @@ export interface TxSender {
42
42
  rawTransaction: Buffer | Uint8Array,
43
43
  opts: ConfirmOptions
44
44
  ): Promise<TxSigAndSlot>;
45
+
46
+ getTimeoutCount(): number;
45
47
  }
package/src/user.ts CHANGED
@@ -72,6 +72,7 @@ import {
72
72
  } from './math/spotPosition';
73
73
 
74
74
  import { calculateLiveOracleTwap } from './math/oracles';
75
+ import { getPerpMarketTierNumber, getSpotMarketTierNumber } from './math/tiers';
75
76
 
76
77
  export class User {
77
78
  driftClient: DriftClient;
@@ -97,6 +98,8 @@ export class User {
97
98
  config.userAccountPublicKey,
98
99
  config.accountSubscription.accountLoader
99
100
  );
101
+ } else if (config.accountSubscription?.type === 'custom') {
102
+ this.accountSubscriber = config.accountSubscription.userAccountSubscriber;
100
103
  } else {
101
104
  this.accountSubscriber = new WebSocketUserAccountSubscriber(
102
105
  config.driftClient.program,
@@ -301,7 +304,10 @@ export class User {
301
304
  marketIndex: number,
302
305
  originalPosition?: PerpPosition
303
306
  ): [PerpPosition, BN, BN] {
304
- originalPosition = originalPosition ?? this.getPerpPosition(marketIndex);
307
+ originalPosition =
308
+ originalPosition ??
309
+ this.getPerpPosition(marketIndex) ??
310
+ this.getEmptyPosition(marketIndex);
305
311
 
306
312
  if (originalPosition.lpShares.eq(ZERO)) {
307
313
  return [originalPosition, ZERO, ZERO];
@@ -368,7 +374,7 @@ export class User {
368
374
  let pnl;
369
375
  if (updateType == 'open' || updateType == 'increase') {
370
376
  newQuoteEntry = position.quoteEntryAmount.add(deltaQaa);
371
- pnl = 0;
377
+ pnl = ZERO;
372
378
  } else if (updateType == 'reduce' || updateType == 'close') {
373
379
  newQuoteEntry = position.quoteEntryAmount.sub(
374
380
  position.quoteEntryAmount
@@ -490,6 +496,12 @@ export class User {
490
496
  );
491
497
  }
492
498
 
499
+ public getActiveSpotPositions(): SpotPosition[] {
500
+ return this.getUserAccount().spotPositions.filter(
501
+ (pos) => !isSpotPositionAvailable(pos)
502
+ );
503
+ }
504
+
493
505
  /**
494
506
  * calculates unrealized position price pnl
495
507
  * @returns : Precision QUOTE_PRECISION
@@ -1127,7 +1139,8 @@ export class User {
1127
1139
  includeOpenOrders = false
1128
1140
  ): BN {
1129
1141
  const userPosition =
1130
- this.getPerpPosition(marketIndex) || this.getEmptyPosition(marketIndex);
1142
+ this.getPerpPositionWithLPSettle(marketIndex)[0] ||
1143
+ this.getEmptyPosition(marketIndex);
1131
1144
  const market = this.driftClient.getPerpMarketAccount(
1132
1145
  userPosition.marketIndex
1133
1146
  );
@@ -1435,24 +1448,29 @@ export class User {
1435
1448
  return netAssetValue.mul(TEN_THOUSAND).div(totalLiabilityValue);
1436
1449
  }
1437
1450
 
1438
- public canBeLiquidated(): boolean {
1451
+ public canBeLiquidated(): {
1452
+ canBeLiquidated: boolean;
1453
+ marginRequirement: BN;
1454
+ totalCollateral: BN;
1455
+ } {
1439
1456
  const totalCollateral = this.getTotalCollateral('Maintenance');
1440
1457
 
1441
1458
  // if user being liq'd, can continue to be liq'd until total collateral above the margin requirement plus buffer
1442
1459
  let liquidationBuffer = undefined;
1443
- const isBeingLiquidated = isVariant(
1444
- this.getUserAccount().status,
1445
- 'beingLiquidated'
1446
- );
1447
-
1448
- if (isBeingLiquidated) {
1460
+ if (this.isBeingLiquidated()) {
1449
1461
  liquidationBuffer = new BN(
1450
1462
  this.driftClient.getStateAccount().liquidationMarginBufferRatio
1451
1463
  );
1452
1464
  }
1453
- const maintenanceRequirement =
1465
+ const marginRequirement =
1454
1466
  this.getMaintenanceMarginRequirement(liquidationBuffer);
1455
- return totalCollateral.lt(maintenanceRequirement);
1467
+ const canBeLiquidated = totalCollateral.lt(marginRequirement);
1468
+
1469
+ return {
1470
+ canBeLiquidated,
1471
+ marginRequirement,
1472
+ totalCollateral,
1473
+ };
1456
1474
  }
1457
1475
 
1458
1476
  public isBeingLiquidated(): boolean {
@@ -2304,6 +2322,38 @@ export class User {
2304
2322
  return true;
2305
2323
  }
2306
2324
 
2325
+ public getSafestTiers(): { perpTier: number; spotTier: number } {
2326
+ let safestPerpTier = 4;
2327
+ let safestSpotTier = 4;
2328
+
2329
+ for (const perpPosition of this.getActivePerpPositions()) {
2330
+ safestPerpTier = Math.min(
2331
+ safestPerpTier,
2332
+ getPerpMarketTierNumber(
2333
+ this.driftClient.getPerpMarketAccount(perpPosition.marketIndex)
2334
+ )
2335
+ );
2336
+ }
2337
+
2338
+ for (const spotPosition of this.getActiveSpotPositions()) {
2339
+ if (isVariant(spotPosition.balanceType, 'deposit')) {
2340
+ continue;
2341
+ }
2342
+
2343
+ safestSpotTier = Math.min(
2344
+ safestSpotTier,
2345
+ getSpotMarketTierNumber(
2346
+ this.driftClient.getSpotMarketAccount(spotPosition.marketIndex)
2347
+ )
2348
+ );
2349
+ }
2350
+
2351
+ return {
2352
+ perpTier: safestPerpTier,
2353
+ spotTier: safestSpotTier,
2354
+ };
2355
+ }
2356
+
2307
2357
  /**
2308
2358
  * Get the total position value, excluding any position coming from the given target market
2309
2359
  * @param marketToIgnore
@@ -2345,6 +2395,7 @@ export class User {
2345
2395
 
2346
2396
  return oracleData;
2347
2397
  }
2398
+
2348
2399
  private getOracleDataForSpotMarket(marketIndex: number): OraclePriceData {
2349
2400
  const oracleKey = this.driftClient.getSpotMarketAccount(marketIndex).oracle;
2350
2401
 
package/src/userConfig.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { DriftClient } from './driftClient';
2
2
  import { PublicKey } from '@solana/web3.js';
3
3
  import { BulkAccountLoader } from './accounts/bulkAccountLoader';
4
+ import { UserAccountSubscriber } from './accounts/types';
4
5
 
5
6
  export type UserConfig = {
6
7
  accountSubscription?: UserSubscriptionConfig;
@@ -15,4 +16,8 @@ export type UserSubscriptionConfig =
15
16
  | {
16
17
  type: 'polling';
17
18
  accountLoader: BulkAccountLoader;
19
+ }
20
+ | {
21
+ type: 'custom';
22
+ userAccountSubscriber: UserAccountSubscriber;
18
23
  };
package/src/userStats.ts CHANGED
@@ -25,6 +25,10 @@ export class UserStats {
25
25
  config.userStatsAccountPublicKey,
26
26
  config.accountSubscription.accountLoader
27
27
  );
28
+ } else if (config.accountSubscription?.type === 'custom') {
29
+ throw new Error(
30
+ 'Custom account subscription not yet implemented for user stats'
31
+ );
28
32
  } else {
29
33
  this.accountSubscriber = new WebSocketUserStatsAccountSubscriber(
30
34
  config.driftClient.program,
@@ -15,4 +15,7 @@ export type UserStatsSubscriptionConfig =
15
15
  | {
16
16
  type: 'polling';
17
17
  accountLoader: BulkAccountLoader;
18
+ }
19
+ | {
20
+ type: 'custom';
18
21
  };