@drift-labs/sdk 2.31.1-beta.2 → 2.31.1-beta.21
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/dlob/orderBookLevels.js +2 -2
- package/lib/driftClient.d.ts +57 -4
- package/lib/driftClient.js +244 -205
- package/lib/driftClientConfig.d.ts +2 -1
- package/lib/idl/drift.json +31 -1
- package/lib/index.d.ts +2 -0
- package/lib/index.js +2 -0
- package/lib/marinade/index.d.ts +11 -0
- package/lib/marinade/index.js +36 -0
- package/lib/marinade/types.d.ts +1963 -0
- package/lib/marinade/types.js +1965 -0
- package/lib/math/spotBalance.d.ts +9 -2
- package/lib/math/spotBalance.js +54 -6
- package/lib/math/superStake.d.ts +22 -0
- package/lib/math/superStake.js +108 -0
- package/lib/math/tiers.d.ts +4 -0
- package/lib/math/tiers.js +52 -0
- package/lib/tx/retryTxSender.d.ts +12 -3
- package/lib/tx/retryTxSender.js +22 -22
- package/lib/tx/types.d.ts +2 -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/dlob/orderBookLevels.ts +3 -2
- package/src/driftClient.ts +440 -224
- package/src/driftClientConfig.ts +2 -1
- package/src/idl/drift.json +31 -1
- package/src/index.ts +2 -0
- package/src/marinade/idl/idl.json +1962 -0
- package/src/marinade/index.ts +64 -0
- package/src/marinade/types.ts +3925 -0
- package/src/math/spotBalance.ts +83 -5
- package/src/math/superStake.ts +148 -0
- package/src/math/tiers.ts +44 -0
- package/src/tx/retryTxSender.ts +39 -35
- package/src/tx/types.ts +2 -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/tests/spot/test.ts +156 -0
package/src/math/spotBalance.ts
CHANGED
|
@@ -236,18 +236,27 @@ export function calculateLiabilityWeight(
|
|
|
236
236
|
return liabilityWeight;
|
|
237
237
|
}
|
|
238
238
|
|
|
239
|
-
export function calculateUtilization(
|
|
240
|
-
|
|
239
|
+
export function calculateUtilization(
|
|
240
|
+
bank: SpotMarketAccount,
|
|
241
|
+
delta = ZERO
|
|
242
|
+
): BN {
|
|
243
|
+
let tokenDepositAmount = getTokenAmount(
|
|
241
244
|
bank.depositBalance,
|
|
242
245
|
bank,
|
|
243
246
|
SpotBalanceType.DEPOSIT
|
|
244
247
|
);
|
|
245
|
-
|
|
248
|
+
let tokenBorrowAmount = getTokenAmount(
|
|
246
249
|
bank.borrowBalance,
|
|
247
250
|
bank,
|
|
248
251
|
SpotBalanceType.BORROW
|
|
249
252
|
);
|
|
250
253
|
|
|
254
|
+
if (delta.gt(ZERO)) {
|
|
255
|
+
tokenDepositAmount = tokenDepositAmount.add(delta);
|
|
256
|
+
} else if (delta.lt(ZERO)) {
|
|
257
|
+
tokenBorrowAmount = tokenBorrowAmount.add(delta.abs());
|
|
258
|
+
}
|
|
259
|
+
|
|
251
260
|
let utilization: BN;
|
|
252
261
|
if (tokenBorrowAmount.eq(ZERO) && tokenDepositAmount.eq(ZERO)) {
|
|
253
262
|
utilization = ZERO;
|
|
@@ -262,9 +271,78 @@ export function calculateUtilization(bank: SpotMarketAccount): BN {
|
|
|
262
271
|
return utilization;
|
|
263
272
|
}
|
|
264
273
|
|
|
265
|
-
|
|
266
|
-
|
|
274
|
+
/**
|
|
275
|
+
* calculates max borrow amount where rate would stay below targetBorrowRate
|
|
276
|
+
* @param spotMarketAccount
|
|
277
|
+
* @param targetBorrowRate
|
|
278
|
+
* @returns : Precision: TOKEN DECIMALS
|
|
279
|
+
*/
|
|
280
|
+
export function calculateSpotMarketBorrowCapacity(
|
|
281
|
+
spotMarketAccount: SpotMarketAccount,
|
|
282
|
+
targetBorrowRate: BN
|
|
283
|
+
): BN {
|
|
284
|
+
const currentBorrowRate = calculateBorrowRate(spotMarketAccount);
|
|
267
285
|
|
|
286
|
+
if (currentBorrowRate.gte(targetBorrowRate)) {
|
|
287
|
+
return ZERO;
|
|
288
|
+
} else {
|
|
289
|
+
const tokenDepositAmount = getTokenAmount(
|
|
290
|
+
spotMarketAccount.depositBalance,
|
|
291
|
+
spotMarketAccount,
|
|
292
|
+
SpotBalanceType.DEPOSIT
|
|
293
|
+
);
|
|
294
|
+
const tokenBorrowAmount = getTokenAmount(
|
|
295
|
+
spotMarketAccount.borrowBalance,
|
|
296
|
+
spotMarketAccount,
|
|
297
|
+
SpotBalanceType.BORROW
|
|
298
|
+
);
|
|
299
|
+
|
|
300
|
+
let targetUtilization;
|
|
301
|
+
|
|
302
|
+
// target utilization past mid point
|
|
303
|
+
if (targetBorrowRate.gte(new BN(spotMarketAccount.optimalBorrowRate))) {
|
|
304
|
+
const borrowRateSlope = new BN(
|
|
305
|
+
spotMarketAccount.maxBorrowRate - spotMarketAccount.optimalBorrowRate
|
|
306
|
+
)
|
|
307
|
+
.mul(SPOT_MARKET_UTILIZATION_PRECISION)
|
|
308
|
+
.div(
|
|
309
|
+
SPOT_MARKET_UTILIZATION_PRECISION.sub(
|
|
310
|
+
new BN(spotMarketAccount.optimalUtilization)
|
|
311
|
+
)
|
|
312
|
+
);
|
|
313
|
+
|
|
314
|
+
const surplusTargetUtilization = targetBorrowRate
|
|
315
|
+
.sub(new BN(spotMarketAccount.optimalBorrowRate))
|
|
316
|
+
.mul(SPOT_MARKET_UTILIZATION_PRECISION)
|
|
317
|
+
.div(borrowRateSlope);
|
|
318
|
+
|
|
319
|
+
targetUtilization = surplusTargetUtilization.add(
|
|
320
|
+
new BN(spotMarketAccount.optimalUtilization)
|
|
321
|
+
);
|
|
322
|
+
} else {
|
|
323
|
+
const borrowRateSlope = new BN(spotMarketAccount.optimalBorrowRate)
|
|
324
|
+
.mul(SPOT_MARKET_UTILIZATION_PRECISION)
|
|
325
|
+
.div(new BN(spotMarketAccount.optimalUtilization));
|
|
326
|
+
|
|
327
|
+
targetUtilization = targetBorrowRate
|
|
328
|
+
.mul(SPOT_MARKET_UTILIZATION_PRECISION)
|
|
329
|
+
.div(borrowRateSlope);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
const targetBorrowAmount = tokenDepositAmount
|
|
333
|
+
.mul(targetUtilization)
|
|
334
|
+
.div(SPOT_MARKET_UTILIZATION_PRECISION);
|
|
335
|
+
const capacity = BN.max(ZERO, targetBorrowAmount.sub(tokenBorrowAmount));
|
|
336
|
+
|
|
337
|
+
return capacity;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
export function calculateInterestRate(
|
|
342
|
+
bank: SpotMarketAccount,
|
|
343
|
+
delta = ZERO
|
|
344
|
+
): BN {
|
|
345
|
+
const utilization = calculateUtilization(bank, delta);
|
|
268
346
|
let interestRate: BN;
|
|
269
347
|
if (utilization.gt(new BN(bank.optimalUtilization))) {
|
|
270
348
|
const surplusUtilization = utilization.sub(new BN(bank.optimalUtilization));
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AddressLookupTableAccount,
|
|
3
|
+
LAMPORTS_PER_SOL,
|
|
4
|
+
PublicKey,
|
|
5
|
+
TransactionInstruction,
|
|
6
|
+
} from '@solana/web3.js';
|
|
7
|
+
import { JupiterClient } from '../jupiter/jupiterClient';
|
|
8
|
+
import { DriftClient } from '../driftClient';
|
|
9
|
+
import { getMarinadeFinanceProgram, getMarinadeMSolPrice } from '../marinade';
|
|
10
|
+
import { BN } from '@coral-xyz/anchor';
|
|
11
|
+
import { User } from '../user';
|
|
12
|
+
import { DepositRecord, isVariant } from '../types';
|
|
13
|
+
import { LAMPORTS_PRECISION, ZERO } from '../constants/numericConstants';
|
|
14
|
+
import fetch from 'node-fetch';
|
|
15
|
+
|
|
16
|
+
export async function findBestSuperStakeIxs({
|
|
17
|
+
amount,
|
|
18
|
+
jupiterClient,
|
|
19
|
+
driftClient,
|
|
20
|
+
userAccountPublicKey,
|
|
21
|
+
}: {
|
|
22
|
+
amount: BN;
|
|
23
|
+
jupiterClient: JupiterClient;
|
|
24
|
+
driftClient: DriftClient;
|
|
25
|
+
userAccountPublicKey?: PublicKey;
|
|
26
|
+
}): Promise<{
|
|
27
|
+
ixs: TransactionInstruction[];
|
|
28
|
+
lookupTables: AddressLookupTableAccount[];
|
|
29
|
+
method: 'jupiter' | 'marinade';
|
|
30
|
+
price: number;
|
|
31
|
+
}> {
|
|
32
|
+
const marinadeProgram = getMarinadeFinanceProgram(driftClient.provider);
|
|
33
|
+
const marinadePrice = await getMarinadeMSolPrice(marinadeProgram);
|
|
34
|
+
|
|
35
|
+
const solMint = driftClient.getSpotMarketAccount(1).mint;
|
|
36
|
+
const mSOLMint = driftClient.getSpotMarketAccount(2).mint;
|
|
37
|
+
const jupiterRoutes = await jupiterClient.getRoutes({
|
|
38
|
+
inputMint: solMint,
|
|
39
|
+
outputMint: mSOLMint,
|
|
40
|
+
amount,
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
const bestRoute = jupiterRoutes[0];
|
|
44
|
+
const jupiterPrice = bestRoute.inAmount / bestRoute.outAmount;
|
|
45
|
+
|
|
46
|
+
if (marinadePrice <= jupiterPrice) {
|
|
47
|
+
const ixs = await driftClient.getStakeForMSOLIx({ amount });
|
|
48
|
+
return {
|
|
49
|
+
method: 'marinade',
|
|
50
|
+
ixs,
|
|
51
|
+
lookupTables: [],
|
|
52
|
+
price: marinadePrice,
|
|
53
|
+
};
|
|
54
|
+
} else {
|
|
55
|
+
const { ixs, lookupTables } = await driftClient.getJupiterSwapIx({
|
|
56
|
+
inMarketIndex: 1,
|
|
57
|
+
outMarketIndex: 2,
|
|
58
|
+
route: bestRoute,
|
|
59
|
+
jupiterClient,
|
|
60
|
+
amount,
|
|
61
|
+
userAccountPublicKey,
|
|
62
|
+
});
|
|
63
|
+
return {
|
|
64
|
+
method: 'jupiter',
|
|
65
|
+
ixs,
|
|
66
|
+
lookupTables,
|
|
67
|
+
price: jupiterPrice,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export async function calculateSolEarned({
|
|
73
|
+
user,
|
|
74
|
+
depositRecords,
|
|
75
|
+
}: {
|
|
76
|
+
user: User;
|
|
77
|
+
depositRecords: DepositRecord[];
|
|
78
|
+
}): Promise<BN> {
|
|
79
|
+
const now = Date.now() / 1000;
|
|
80
|
+
const timestamps: number[] = [
|
|
81
|
+
now,
|
|
82
|
+
...depositRecords.map((r) => r.ts.toNumber()),
|
|
83
|
+
];
|
|
84
|
+
|
|
85
|
+
const msolRatios = new Map<number, number>();
|
|
86
|
+
|
|
87
|
+
const getPrice = async (timestamp) => {
|
|
88
|
+
const date = new Date(timestamp * 1000); // Convert Unix timestamp to milliseconds
|
|
89
|
+
const swaggerApiDateTime = date.toISOString(); // Format date as swagger API date-time
|
|
90
|
+
const url = `https://api.marinade.finance/msol/price_sol?time=${swaggerApiDateTime}`;
|
|
91
|
+
const response = await fetch(url);
|
|
92
|
+
if (response.status === 200) {
|
|
93
|
+
const data = await response.json();
|
|
94
|
+
msolRatios.set(timestamp, data);
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
await Promise.all(timestamps.map(getPrice));
|
|
99
|
+
|
|
100
|
+
let solEarned = ZERO;
|
|
101
|
+
for (const record of depositRecords) {
|
|
102
|
+
if (record.marketIndex === 1) {
|
|
103
|
+
if (isVariant(record.direction, 'deposit')) {
|
|
104
|
+
solEarned = solEarned.sub(record.amount);
|
|
105
|
+
} else {
|
|
106
|
+
solEarned = solEarned.add(record.amount);
|
|
107
|
+
}
|
|
108
|
+
} else if (record.marketIndex === 2) {
|
|
109
|
+
const msolRatio = msolRatios.get(record.ts.toNumber());
|
|
110
|
+
const msolRatioBN = new BN(msolRatio * LAMPORTS_PER_SOL);
|
|
111
|
+
|
|
112
|
+
const solAmount = record.amount.mul(msolRatioBN).div(LAMPORTS_PRECISION);
|
|
113
|
+
if (isVariant(record.direction, 'deposit')) {
|
|
114
|
+
solEarned = solEarned.sub(solAmount);
|
|
115
|
+
} else {
|
|
116
|
+
solEarned = solEarned.add(solAmount);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const currentMSOLTokenAmount = await user.getTokenAmount(2);
|
|
122
|
+
const currentSOLTokenAmount = await user.getTokenAmount(1);
|
|
123
|
+
|
|
124
|
+
const currentMSOLRatio = msolRatios.get(now);
|
|
125
|
+
const currentMSOLRatioBN = new BN(currentMSOLRatio * LAMPORTS_PER_SOL);
|
|
126
|
+
|
|
127
|
+
solEarned = solEarned.add(
|
|
128
|
+
currentMSOLTokenAmount.mul(currentMSOLRatioBN).div(LAMPORTS_PRECISION)
|
|
129
|
+
);
|
|
130
|
+
solEarned = solEarned.add(currentSOLTokenAmount);
|
|
131
|
+
|
|
132
|
+
return solEarned;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// calculate estimated liquidation price (in mSOL/SOL) based on target amounts
|
|
136
|
+
export function calculateEstimatedSuperStakeLiquidationPrice(
|
|
137
|
+
msolDepositAmount: number,
|
|
138
|
+
msolMaintenanceAssetWeight: number,
|
|
139
|
+
solBorrowAmount: number,
|
|
140
|
+
solMaintenanceLiabilityWeight: number,
|
|
141
|
+
msolPriceRatio: number
|
|
142
|
+
): number {
|
|
143
|
+
const liquidationDivergence =
|
|
144
|
+
(solMaintenanceLiabilityWeight * solBorrowAmount) /
|
|
145
|
+
(msolMaintenanceAssetWeight * msolDepositAmount * msolPriceRatio);
|
|
146
|
+
const liquidationPrice = msolPriceRatio * liquidationDivergence;
|
|
147
|
+
return liquidationPrice;
|
|
148
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { isVariant, PerpMarketAccount, SpotMarketAccount } from '../types';
|
|
2
|
+
|
|
3
|
+
export function getPerpMarketTierNumber(perpMarket: PerpMarketAccount): number {
|
|
4
|
+
if (isVariant(perpMarket.contractTier, 'a')) {
|
|
5
|
+
return 0;
|
|
6
|
+
} else if (isVariant(perpMarket.contractTier, 'b')) {
|
|
7
|
+
return 1;
|
|
8
|
+
} else if (isVariant(perpMarket.contractTier, 'c')) {
|
|
9
|
+
return 2;
|
|
10
|
+
} else if (isVariant(perpMarket.contractTier, 'speculative')) {
|
|
11
|
+
return 3;
|
|
12
|
+
} else if (isVariant(perpMarket.contractTier, 'isolated')) {
|
|
13
|
+
return 4;
|
|
14
|
+
} else {
|
|
15
|
+
return 5;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function getSpotMarketTierNumber(spotMarket: SpotMarketAccount): number {
|
|
20
|
+
if (isVariant(spotMarket.assetTier, 'collateral')) {
|
|
21
|
+
return 0;
|
|
22
|
+
} else if (isVariant(spotMarket.assetTier, 'protected')) {
|
|
23
|
+
return 1;
|
|
24
|
+
} else if (isVariant(spotMarket.assetTier, 'cross')) {
|
|
25
|
+
return 2;
|
|
26
|
+
} else if (isVariant(spotMarket.assetTier, 'isolated')) {
|
|
27
|
+
return 3;
|
|
28
|
+
} else if (isVariant(spotMarket.assetTier, 'unlisted')) {
|
|
29
|
+
return 4;
|
|
30
|
+
} else {
|
|
31
|
+
return 5;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function perpTierIsAsSafeAs(
|
|
36
|
+
perpTier: number,
|
|
37
|
+
otherPerpTier: number,
|
|
38
|
+
otherSpotTier: number
|
|
39
|
+
): boolean {
|
|
40
|
+
const asSafeAsPerp = perpTier <= otherPerpTier;
|
|
41
|
+
const asSafeAsSpot =
|
|
42
|
+
otherSpotTier === 4 || (otherSpotTier >= 2 && perpTier <= 2);
|
|
43
|
+
return asSafeAsSpot && asSafeAsPerp;
|
|
44
|
+
}
|
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,21 +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
36
|
timoutCount = 0;
|
|
34
37
|
|
|
35
|
-
public constructor(
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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;
|
|
44
58
|
this.additionalConnections = additionalConnections;
|
|
45
59
|
}
|
|
46
60
|
|
|
@@ -54,7 +68,7 @@ export class RetryTxSender implements TxSender {
|
|
|
54
68
|
additionalSigners = [];
|
|
55
69
|
}
|
|
56
70
|
if (opts === undefined) {
|
|
57
|
-
opts = this.
|
|
71
|
+
opts = this.opts;
|
|
58
72
|
}
|
|
59
73
|
|
|
60
74
|
const signedTx = preSigned
|
|
@@ -69,11 +83,9 @@ export class RetryTxSender implements TxSender {
|
|
|
69
83
|
additionalSigners: Array<Signer>,
|
|
70
84
|
opts: ConfirmOptions
|
|
71
85
|
): Promise<Transaction> {
|
|
72
|
-
tx.feePayer = this.
|
|
86
|
+
tx.feePayer = this.wallet.publicKey;
|
|
73
87
|
tx.recentBlockhash = (
|
|
74
|
-
await this.
|
|
75
|
-
opts.preflightCommitment
|
|
76
|
-
)
|
|
88
|
+
await this.connection.getRecentBlockhash(opts.preflightCommitment)
|
|
77
89
|
).blockhash;
|
|
78
90
|
|
|
79
91
|
additionalSigners
|
|
@@ -82,7 +94,7 @@ export class RetryTxSender implements TxSender {
|
|
|
82
94
|
tx.partialSign(kp);
|
|
83
95
|
});
|
|
84
96
|
|
|
85
|
-
const signedTx = await this.
|
|
97
|
+
const signedTx = await this.wallet.signTransaction(tx);
|
|
86
98
|
|
|
87
99
|
return signedTx;
|
|
88
100
|
}
|
|
@@ -97,15 +109,13 @@ export class RetryTxSender implements TxSender {
|
|
|
97
109
|
additionalSigners = [];
|
|
98
110
|
}
|
|
99
111
|
if (opts === undefined) {
|
|
100
|
-
opts = this.
|
|
112
|
+
opts = this.opts;
|
|
101
113
|
}
|
|
102
114
|
|
|
103
115
|
const message = new TransactionMessage({
|
|
104
|
-
payerKey: this.
|
|
116
|
+
payerKey: this.wallet.publicKey,
|
|
105
117
|
recentBlockhash: (
|
|
106
|
-
await this.
|
|
107
|
-
opts.preflightCommitment
|
|
108
|
-
)
|
|
118
|
+
await this.connection.getRecentBlockhash(opts.preflightCommitment)
|
|
109
119
|
).blockhash,
|
|
110
120
|
instructions: ixs,
|
|
111
121
|
}).compileToV0Message(lookupTableAccounts);
|
|
@@ -125,9 +135,9 @@ export class RetryTxSender implements TxSender {
|
|
|
125
135
|
if (preSigned) {
|
|
126
136
|
signedTx = tx;
|
|
127
137
|
// @ts-ignore
|
|
128
|
-
} else if (this.
|
|
138
|
+
} else if (this.wallet.payer) {
|
|
129
139
|
// @ts-ignore
|
|
130
|
-
tx.sign((additionalSigners ?? []).concat(this.
|
|
140
|
+
tx.sign((additionalSigners ?? []).concat(this.wallet.payer));
|
|
131
141
|
signedTx = tx;
|
|
132
142
|
} else {
|
|
133
143
|
additionalSigners
|
|
@@ -136,11 +146,11 @@ export class RetryTxSender implements TxSender {
|
|
|
136
146
|
tx.sign([kp]);
|
|
137
147
|
});
|
|
138
148
|
// @ts-ignore
|
|
139
|
-
signedTx = await this.
|
|
149
|
+
signedTx = await this.wallet.signTransaction(tx);
|
|
140
150
|
}
|
|
141
151
|
|
|
142
152
|
if (opts === undefined) {
|
|
143
|
-
opts = this.
|
|
153
|
+
opts = this.opts;
|
|
144
154
|
}
|
|
145
155
|
|
|
146
156
|
return this.sendRawTransaction(signedTx.serialize(), opts);
|
|
@@ -154,10 +164,7 @@ export class RetryTxSender implements TxSender {
|
|
|
154
164
|
|
|
155
165
|
let txid: TransactionSignature;
|
|
156
166
|
try {
|
|
157
|
-
txid = await this.
|
|
158
|
-
rawTransaction,
|
|
159
|
-
opts
|
|
160
|
-
);
|
|
167
|
+
txid = await this.connection.sendRawTransaction(rawTransaction, opts);
|
|
161
168
|
this.sendToAdditionalConnections(rawTransaction, opts);
|
|
162
169
|
} catch (e) {
|
|
163
170
|
console.error(e);
|
|
@@ -179,7 +186,7 @@ export class RetryTxSender implements TxSender {
|
|
|
179
186
|
while (!done && this.getTimestamp() - startTime < this.timeout) {
|
|
180
187
|
await this.sleep(resolveReference);
|
|
181
188
|
if (!done) {
|
|
182
|
-
this.
|
|
189
|
+
this.connection
|
|
183
190
|
.sendRawTransaction(rawTransaction, opts)
|
|
184
191
|
.catch((e) => {
|
|
185
192
|
console.error(e);
|
|
@@ -218,13 +225,10 @@ export class RetryTxSender implements TxSender {
|
|
|
218
225
|
assert(decodedSignature.length === 64, 'signature has invalid length');
|
|
219
226
|
|
|
220
227
|
const start = Date.now();
|
|
221
|
-
const subscriptionCommitment = commitment || this.
|
|
228
|
+
const subscriptionCommitment = commitment || this.opts.commitment;
|
|
222
229
|
|
|
223
230
|
const subscriptionIds = new Array<number>();
|
|
224
|
-
const connections = [
|
|
225
|
-
this.provider.connection,
|
|
226
|
-
...this.additionalConnections,
|
|
227
|
-
];
|
|
231
|
+
const connections = [this.connection, ...this.additionalConnections];
|
|
228
232
|
let response: RpcResponseAndContext<SignatureResult> | null = null;
|
|
229
233
|
const promises = connections.map((connection, i) => {
|
|
230
234
|
let subscriptionId;
|
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,
|
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,
|