@drift-labs/sdk 2.31.1-beta.9 → 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.
Files changed (42) hide show
  1. package/VERSION +1 -1
  2. package/lib/constants/perpMarkets.js +20 -0
  3. package/lib/dlob/orderBookLevels.js +2 -2
  4. package/lib/driftClient.d.ts +51 -4
  5. package/lib/driftClient.js +195 -194
  6. package/lib/idl/drift.json +31 -1
  7. package/lib/index.d.ts +3 -0
  8. package/lib/index.js +3 -0
  9. package/lib/marinade/index.d.ts +11 -0
  10. package/lib/marinade/index.js +36 -0
  11. package/lib/marinade/types.d.ts +1963 -0
  12. package/lib/marinade/types.js +1965 -0
  13. package/lib/math/spotBalance.d.ts +9 -2
  14. package/lib/math/spotBalance.js +54 -6
  15. package/lib/math/superStake.d.ts +22 -0
  16. package/lib/math/superStake.js +108 -0
  17. package/lib/math/utils.d.ts +1 -0
  18. package/lib/math/utils.js +5 -1
  19. package/lib/orderParams.d.ts +18 -5
  20. package/lib/orderParams.js +17 -1
  21. package/lib/user.d.ts +45 -1
  22. package/lib/user.js +227 -9
  23. package/package.json +1 -1
  24. package/src/assert/assert.js +9 -0
  25. package/src/constants/perpMarkets.ts +20 -0
  26. package/src/dlob/orderBookLevels.ts +3 -2
  27. package/src/driftClient.ts +373 -223
  28. package/src/idl/drift.json +31 -1
  29. package/src/index.ts +3 -0
  30. package/src/marinade/idl/idl.json +1962 -0
  31. package/src/marinade/index.ts +64 -0
  32. package/src/marinade/types.ts +3925 -0
  33. package/src/math/spotBalance.ts +83 -5
  34. package/src/math/superStake.ts +148 -0
  35. package/src/math/utils.ts +4 -0
  36. package/src/orderParams.ts +35 -5
  37. package/src/token/index.js +38 -0
  38. package/src/user.ts +453 -15
  39. package/src/util/computeUnits.js +27 -0
  40. package/src/util/promiseTimeout.js +14 -0
  41. package/src/util/tps.js +27 -0
  42. package/tests/spot/test.ts +156 -0
@@ -236,18 +236,27 @@ export function calculateLiabilityWeight(
236
236
  return liabilityWeight;
237
237
  }
238
238
 
239
- export function calculateUtilization(bank: SpotMarketAccount): BN {
240
- const tokenDepositAmount = getTokenAmount(
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
- const tokenBorrowAmount = getTokenAmount(
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
- export function calculateInterestRate(bank: SpotMarketAccount): BN {
266
- const utilization = calculateUtilization(bank);
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
@@ -1,8 +1,14 @@
1
- import { OptionalOrderParams, OrderTriggerCondition, OrderType } from './types';
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' | 'marketType'> & { price: BN }
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' | 'marketType'> & {
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' | 'marketType'> & {
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' | 'marketType'>
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;