@drift-labs/sdk 2.31.1-beta.8 → 2.32.0
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/constants/perpMarkets.js +20 -0
- package/lib/dlob/orderBookLevels.js +2 -2
- package/lib/driftClient.d.ts +53 -5
- package/lib/driftClient.js +203 -196
- package/lib/idl/drift.json +31 -1
- package/lib/index.d.ts +3 -0
- package/lib/index.js +3 -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/utils.d.ts +1 -0
- package/lib/math/utils.js +5 -1
- package/lib/orderParams.d.ts +18 -5
- package/lib/orderParams.js +17 -1
- package/lib/user.d.ts +45 -1
- package/lib/user.js +227 -9
- package/package.json +1 -1
- package/src/assert/assert.js +9 -0
- package/src/constants/perpMarkets.ts +20 -0
- package/src/dlob/orderBookLevels.ts +3 -2
- package/src/driftClient.ts +383 -225
- package/src/idl/drift.json +31 -1
- package/src/index.ts +3 -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/utils.ts +4 -0
- package/src/orderParams.ts +35 -5
- package/src/token/index.js +38 -0
- package/src/user.ts +453 -15
- package/src/util/computeUnits.js +27 -0
- package/src/util/promiseTimeout.js +14 -0
- package/src/util/tps.js +27 -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
|
+
}
|
package/src/math/utils.ts
CHANGED
|
@@ -34,6 +34,10 @@ export const divCeil = (a: BN, b: BN): BN => {
|
|
|
34
34
|
}
|
|
35
35
|
};
|
|
36
36
|
|
|
37
|
+
export const sigNum = (x: BN): BN => {
|
|
38
|
+
return x.isNeg() ? new BN(-1) : new BN(1);
|
|
39
|
+
};
|
|
40
|
+
|
|
37
41
|
/**
|
|
38
42
|
* calculates the time remaining until the next update based on a rounded, "on-the-hour" update schedule
|
|
39
43
|
* this schedule is used for Perpetual Funding Rate and Revenue -> Insurance Updates
|
package/src/orderParams.ts
CHANGED
|
@@ -1,8 +1,14 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
DefaultOrderParams,
|
|
3
|
+
OptionalOrderParams,
|
|
4
|
+
OrderParams,
|
|
5
|
+
OrderTriggerCondition,
|
|
6
|
+
OrderType,
|
|
7
|
+
} from './types';
|
|
2
8
|
import { BN } from '@coral-xyz/anchor';
|
|
3
9
|
|
|
4
10
|
export function getLimitOrderParams(
|
|
5
|
-
params: Omit<OptionalOrderParams, 'orderType'
|
|
11
|
+
params: Omit<OptionalOrderParams, 'orderType'> & { price: BN }
|
|
6
12
|
): OptionalOrderParams {
|
|
7
13
|
return Object.assign({}, params, {
|
|
8
14
|
orderType: OrderType.LIMIT,
|
|
@@ -10,7 +16,7 @@ export function getLimitOrderParams(
|
|
|
10
16
|
}
|
|
11
17
|
|
|
12
18
|
export function getTriggerMarketOrderParams(
|
|
13
|
-
params: Omit<OptionalOrderParams, 'orderType'
|
|
19
|
+
params: Omit<OptionalOrderParams, 'orderType'> & {
|
|
14
20
|
triggerCondition: OrderTriggerCondition;
|
|
15
21
|
triggerPrice: BN;
|
|
16
22
|
}
|
|
@@ -21,7 +27,7 @@ export function getTriggerMarketOrderParams(
|
|
|
21
27
|
}
|
|
22
28
|
|
|
23
29
|
export function getTriggerLimitOrderParams(
|
|
24
|
-
params: Omit<OptionalOrderParams, 'orderType'
|
|
30
|
+
params: Omit<OptionalOrderParams, 'orderType'> & {
|
|
25
31
|
triggerCondition: OrderTriggerCondition;
|
|
26
32
|
triggerPrice: BN;
|
|
27
33
|
price: BN;
|
|
@@ -33,9 +39,33 @@ export function getTriggerLimitOrderParams(
|
|
|
33
39
|
}
|
|
34
40
|
|
|
35
41
|
export function getMarketOrderParams(
|
|
36
|
-
params: Omit<OptionalOrderParams, 'orderType'
|
|
42
|
+
params: Omit<OptionalOrderParams, 'orderType'>
|
|
37
43
|
): OptionalOrderParams {
|
|
38
44
|
return Object.assign({}, params, {
|
|
39
45
|
orderType: OrderType.MARKET,
|
|
40
46
|
});
|
|
41
47
|
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Creates an OrderParams object with the given OptionalOrderParams and any params to override.
|
|
51
|
+
*
|
|
52
|
+
* example:
|
|
53
|
+
* ```
|
|
54
|
+
* const orderParams = getOrderParams(optionalOrderParams, { marketType: MarketType.PERP });
|
|
55
|
+
* ```
|
|
56
|
+
*
|
|
57
|
+
* @param optionalOrderParams
|
|
58
|
+
* @param overridingParams
|
|
59
|
+
* @returns
|
|
60
|
+
*/
|
|
61
|
+
export function getOrderParams(
|
|
62
|
+
optionalOrderParams: OptionalOrderParams,
|
|
63
|
+
overridingParams: Record<string, any> = {}
|
|
64
|
+
): OrderParams {
|
|
65
|
+
return Object.assign(
|
|
66
|
+
{},
|
|
67
|
+
DefaultOrderParams,
|
|
68
|
+
optionalOrderParams,
|
|
69
|
+
overridingParams
|
|
70
|
+
);
|
|
71
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parseTokenAccount = void 0;
|
|
4
|
+
const spl_token_1 = require("@solana/spl-token");
|
|
5
|
+
const web3_js_1 = require("@solana/web3.js");
|
|
6
|
+
function parseTokenAccount(data) {
|
|
7
|
+
const accountInfo = spl_token_1.AccountLayout.decode(data);
|
|
8
|
+
accountInfo.mint = new web3_js_1.PublicKey(accountInfo.mint);
|
|
9
|
+
accountInfo.owner = new web3_js_1.PublicKey(accountInfo.owner);
|
|
10
|
+
accountInfo.amount = spl_token_1.u64.fromBuffer(accountInfo.amount);
|
|
11
|
+
if (accountInfo.delegateOption === 0) {
|
|
12
|
+
accountInfo.delegate = null;
|
|
13
|
+
// eslint-disable-next-line new-cap
|
|
14
|
+
accountInfo.delegatedAmount = new spl_token_1.u64(0);
|
|
15
|
+
}
|
|
16
|
+
else {
|
|
17
|
+
accountInfo.delegate = new web3_js_1.PublicKey(accountInfo.delegate);
|
|
18
|
+
accountInfo.delegatedAmount = spl_token_1.u64.fromBuffer(accountInfo.delegatedAmount);
|
|
19
|
+
}
|
|
20
|
+
accountInfo.isInitialized = accountInfo.state !== 0;
|
|
21
|
+
accountInfo.isFrozen = accountInfo.state === 2;
|
|
22
|
+
if (accountInfo.isNativeOption === 1) {
|
|
23
|
+
accountInfo.rentExemptReserve = spl_token_1.u64.fromBuffer(accountInfo.isNative);
|
|
24
|
+
accountInfo.isNative = true;
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
accountInfo.rentExemptReserve = null;
|
|
28
|
+
accountInfo.isNative = false;
|
|
29
|
+
}
|
|
30
|
+
if (accountInfo.closeAuthorityOption === 0) {
|
|
31
|
+
accountInfo.closeAuthority = null;
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
accountInfo.closeAuthority = new web3_js_1.PublicKey(accountInfo.closeAuthority);
|
|
35
|
+
}
|
|
36
|
+
return accountInfo;
|
|
37
|
+
}
|
|
38
|
+
exports.parseTokenAccount = parseTokenAccount;
|