@drift-labs/sdk 2.31.1-beta.0 → 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.
- package/VERSION +1 -1
- package/lib/accounts/mockUserAccountSubscriber.d.ts +23 -0
- package/lib/accounts/mockUserAccountSubscriber.js +31 -0
- package/lib/constants/perpMarkets.js +20 -0
- package/lib/driftClient.d.ts +10 -1
- package/lib/driftClient.js +68 -27
- package/lib/driftClientConfig.d.ts +4 -9
- package/lib/idl/drift.json +1 -1
- package/lib/math/tiers.d.ts +4 -0
- package/lib/math/tiers.js +52 -0
- package/lib/tx/retryTxSender.d.ts +14 -3
- package/lib/tx/retryTxSender.js +27 -22
- package/lib/tx/types.d.ts +3 -2
- package/lib/user.d.ts +10 -1
- package/lib/user.js +39 -8
- package/lib/userConfig.d.ts +4 -0
- package/lib/userStats.js +4 -1
- package/lib/userStatsConfig.d.ts +2 -0
- package/package.json +1 -1
- package/src/accounts/mockUserAccountSubscriber.ts +53 -0
- package/src/config.ts +2 -2
- package/src/constants/perpMarkets.ts +20 -0
- package/src/driftClient.ts +97 -31
- package/src/driftClientConfig.ts +4 -9
- package/src/idl/drift.json +1 -1
- package/src/math/tiers.ts +44 -0
- package/src/tx/retryTxSender.ts +46 -36
- package/src/tx/types.ts +4 -2
- package/src/user.ts +63 -12
- package/src/userConfig.ts +5 -0
- package/src/userStats.ts +4 -0
- package/src/userStatsConfig.ts +3 -0
package/src/tx/retryTxSender.ts
CHANGED
|
@@ -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
|
-
|
|
30
|
+
connection: Connection;
|
|
31
|
+
wallet: IWallet;
|
|
32
|
+
opts: ConfirmOptions;
|
|
30
33
|
timeout: number;
|
|
31
34
|
retrySleep: number;
|
|
32
35
|
additionalConnections: Connection[];
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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.
|
|
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.
|
|
86
|
+
tx.feePayer = this.wallet.publicKey;
|
|
72
87
|
tx.recentBlockhash = (
|
|
73
|
-
await this.
|
|
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.
|
|
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.
|
|
112
|
+
opts = this.opts;
|
|
100
113
|
}
|
|
101
114
|
|
|
102
115
|
const message = new TransactionMessage({
|
|
103
|
-
payerKey: this.
|
|
116
|
+
payerKey: this.wallet.publicKey,
|
|
104
117
|
recentBlockhash: (
|
|
105
|
-
await this.
|
|
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.
|
|
138
|
+
} else if (this.wallet.payer) {
|
|
128
139
|
// @ts-ignore
|
|
129
|
-
tx.sign((additionalSigners ?? []).concat(this.
|
|
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.
|
|
149
|
+
signedTx = await this.wallet.signTransaction(tx);
|
|
139
150
|
}
|
|
140
151
|
|
|
141
152
|
if (opts === undefined) {
|
|
142
|
-
opts = this.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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.
|
|
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():
|
|
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
|
-
|
|
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
|
|
1465
|
+
const marginRequirement =
|
|
1454
1466
|
this.getMaintenanceMarginRequirement(liquidationBuffer);
|
|
1455
|
-
|
|
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,
|