@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
|
@@ -49,8 +49,15 @@ export declare function getStrictTokenValue(tokenAmount: BN, spotDecimals: numbe
|
|
|
49
49
|
export declare function getTokenValue(tokenAmount: BN, spotDecimals: number, oraclePriceData: OraclePriceData): BN;
|
|
50
50
|
export declare function calculateAssetWeight(balanceAmount: BN, spotMarket: SpotMarketAccount, marginCategory: MarginCategory): BN;
|
|
51
51
|
export declare function calculateLiabilityWeight(size: BN, spotMarket: SpotMarketAccount, marginCategory: MarginCategory): BN;
|
|
52
|
-
export declare function calculateUtilization(bank: SpotMarketAccount): BN;
|
|
53
|
-
|
|
52
|
+
export declare function calculateUtilization(bank: SpotMarketAccount, delta?: any): BN;
|
|
53
|
+
/**
|
|
54
|
+
* calculates max borrow amount where rate would stay below targetBorrowRate
|
|
55
|
+
* @param spotMarketAccount
|
|
56
|
+
* @param targetBorrowRate
|
|
57
|
+
* @returns : Precision: TOKEN DECIMALS
|
|
58
|
+
*/
|
|
59
|
+
export declare function calculateSpotMarketBorrowCapacity(spotMarketAccount: SpotMarketAccount, targetBorrowRate: BN): BN;
|
|
60
|
+
export declare function calculateInterestRate(bank: SpotMarketAccount, delta?: any): BN;
|
|
54
61
|
export declare function calculateDepositRate(bank: SpotMarketAccount): BN;
|
|
55
62
|
export declare function calculateBorrowRate(bank: SpotMarketAccount): BN;
|
|
56
63
|
export declare function calculateInterestAccumulated(bank: SpotMarketAccount, now: BN): {
|
package/lib/math/spotBalance.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.calculateWithdrawLimit = exports.calculateInterestAccumulated = exports.calculateBorrowRate = exports.calculateDepositRate = exports.calculateInterestRate = exports.calculateUtilization = exports.calculateLiabilityWeight = exports.calculateAssetWeight = exports.getTokenValue = exports.getStrictTokenValue = exports.getSignedTokenAmount = exports.getTokenAmount = exports.getBalance = void 0;
|
|
3
|
+
exports.calculateWithdrawLimit = exports.calculateInterestAccumulated = exports.calculateBorrowRate = exports.calculateDepositRate = exports.calculateInterestRate = exports.calculateSpotMarketBorrowCapacity = exports.calculateUtilization = exports.calculateLiabilityWeight = exports.calculateAssetWeight = exports.getTokenValue = exports.getStrictTokenValue = exports.getSignedTokenAmount = exports.getTokenAmount = exports.getBalance = void 0;
|
|
4
4
|
const types_1 = require("../types");
|
|
5
5
|
const anchor_1 = require("@coral-xyz/anchor");
|
|
6
6
|
const numericConstants_1 = require("../constants/numericConstants");
|
|
@@ -156,9 +156,15 @@ function calculateLiabilityWeight(size, spotMarket, marginCategory) {
|
|
|
156
156
|
return liabilityWeight;
|
|
157
157
|
}
|
|
158
158
|
exports.calculateLiabilityWeight = calculateLiabilityWeight;
|
|
159
|
-
function calculateUtilization(bank) {
|
|
160
|
-
|
|
161
|
-
|
|
159
|
+
function calculateUtilization(bank, delta = numericConstants_1.ZERO) {
|
|
160
|
+
let tokenDepositAmount = getTokenAmount(bank.depositBalance, bank, types_1.SpotBalanceType.DEPOSIT);
|
|
161
|
+
let tokenBorrowAmount = getTokenAmount(bank.borrowBalance, bank, types_1.SpotBalanceType.BORROW);
|
|
162
|
+
if (delta.gt(numericConstants_1.ZERO)) {
|
|
163
|
+
tokenDepositAmount = tokenDepositAmount.add(delta);
|
|
164
|
+
}
|
|
165
|
+
else if (delta.lt(numericConstants_1.ZERO)) {
|
|
166
|
+
tokenBorrowAmount = tokenBorrowAmount.add(delta.abs());
|
|
167
|
+
}
|
|
162
168
|
let utilization;
|
|
163
169
|
if (tokenBorrowAmount.eq(numericConstants_1.ZERO) && tokenDepositAmount.eq(numericConstants_1.ZERO)) {
|
|
164
170
|
utilization = numericConstants_1.ZERO;
|
|
@@ -174,8 +180,50 @@ function calculateUtilization(bank) {
|
|
|
174
180
|
return utilization;
|
|
175
181
|
}
|
|
176
182
|
exports.calculateUtilization = calculateUtilization;
|
|
177
|
-
|
|
178
|
-
|
|
183
|
+
/**
|
|
184
|
+
* calculates max borrow amount where rate would stay below targetBorrowRate
|
|
185
|
+
* @param spotMarketAccount
|
|
186
|
+
* @param targetBorrowRate
|
|
187
|
+
* @returns : Precision: TOKEN DECIMALS
|
|
188
|
+
*/
|
|
189
|
+
function calculateSpotMarketBorrowCapacity(spotMarketAccount, targetBorrowRate) {
|
|
190
|
+
const currentBorrowRate = calculateBorrowRate(spotMarketAccount);
|
|
191
|
+
if (currentBorrowRate.gte(targetBorrowRate)) {
|
|
192
|
+
return numericConstants_1.ZERO;
|
|
193
|
+
}
|
|
194
|
+
else {
|
|
195
|
+
const tokenDepositAmount = getTokenAmount(spotMarketAccount.depositBalance, spotMarketAccount, types_1.SpotBalanceType.DEPOSIT);
|
|
196
|
+
const tokenBorrowAmount = getTokenAmount(spotMarketAccount.borrowBalance, spotMarketAccount, types_1.SpotBalanceType.BORROW);
|
|
197
|
+
let targetUtilization;
|
|
198
|
+
// target utilization past mid point
|
|
199
|
+
if (targetBorrowRate.gte(new anchor_1.BN(spotMarketAccount.optimalBorrowRate))) {
|
|
200
|
+
const borrowRateSlope = new anchor_1.BN(spotMarketAccount.maxBorrowRate - spotMarketAccount.optimalBorrowRate)
|
|
201
|
+
.mul(numericConstants_1.SPOT_MARKET_UTILIZATION_PRECISION)
|
|
202
|
+
.div(numericConstants_1.SPOT_MARKET_UTILIZATION_PRECISION.sub(new anchor_1.BN(spotMarketAccount.optimalUtilization)));
|
|
203
|
+
const surplusTargetUtilization = targetBorrowRate
|
|
204
|
+
.sub(new anchor_1.BN(spotMarketAccount.optimalBorrowRate))
|
|
205
|
+
.mul(numericConstants_1.SPOT_MARKET_UTILIZATION_PRECISION)
|
|
206
|
+
.div(borrowRateSlope);
|
|
207
|
+
targetUtilization = surplusTargetUtilization.add(new anchor_1.BN(spotMarketAccount.optimalUtilization));
|
|
208
|
+
}
|
|
209
|
+
else {
|
|
210
|
+
const borrowRateSlope = new anchor_1.BN(spotMarketAccount.optimalBorrowRate)
|
|
211
|
+
.mul(numericConstants_1.SPOT_MARKET_UTILIZATION_PRECISION)
|
|
212
|
+
.div(new anchor_1.BN(spotMarketAccount.optimalUtilization));
|
|
213
|
+
targetUtilization = targetBorrowRate
|
|
214
|
+
.mul(numericConstants_1.SPOT_MARKET_UTILIZATION_PRECISION)
|
|
215
|
+
.div(borrowRateSlope);
|
|
216
|
+
}
|
|
217
|
+
const targetBorrowAmount = tokenDepositAmount
|
|
218
|
+
.mul(targetUtilization)
|
|
219
|
+
.div(numericConstants_1.SPOT_MARKET_UTILIZATION_PRECISION);
|
|
220
|
+
const capacity = anchor_1.BN.max(numericConstants_1.ZERO, targetBorrowAmount.sub(tokenBorrowAmount));
|
|
221
|
+
return capacity;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
exports.calculateSpotMarketBorrowCapacity = calculateSpotMarketBorrowCapacity;
|
|
225
|
+
function calculateInterestRate(bank, delta = numericConstants_1.ZERO) {
|
|
226
|
+
const utilization = calculateUtilization(bank, delta);
|
|
179
227
|
let interestRate;
|
|
180
228
|
if (utilization.gt(new anchor_1.BN(bank.optimalUtilization))) {
|
|
181
229
|
const surplusUtilization = utilization.sub(new anchor_1.BN(bank.optimalUtilization));
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { AddressLookupTableAccount, PublicKey, TransactionInstruction } from '@solana/web3.js';
|
|
2
|
+
import { JupiterClient } from '../jupiter/jupiterClient';
|
|
3
|
+
import { DriftClient } from '../driftClient';
|
|
4
|
+
import { BN } from '@coral-xyz/anchor';
|
|
5
|
+
import { User } from '../user';
|
|
6
|
+
import { DepositRecord } from '../types';
|
|
7
|
+
export declare function findBestSuperStakeIxs({ amount, jupiterClient, driftClient, userAccountPublicKey, }: {
|
|
8
|
+
amount: BN;
|
|
9
|
+
jupiterClient: JupiterClient;
|
|
10
|
+
driftClient: DriftClient;
|
|
11
|
+
userAccountPublicKey?: PublicKey;
|
|
12
|
+
}): Promise<{
|
|
13
|
+
ixs: TransactionInstruction[];
|
|
14
|
+
lookupTables: AddressLookupTableAccount[];
|
|
15
|
+
method: 'jupiter' | 'marinade';
|
|
16
|
+
price: number;
|
|
17
|
+
}>;
|
|
18
|
+
export declare function calculateSolEarned({ user, depositRecords, }: {
|
|
19
|
+
user: User;
|
|
20
|
+
depositRecords: DepositRecord[];
|
|
21
|
+
}): Promise<BN>;
|
|
22
|
+
export declare function calculateEstimatedSuperStakeLiquidationPrice(msolDepositAmount: number, msolMaintenanceAssetWeight: number, solBorrowAmount: number, solMaintenanceLiabilityWeight: number, msolPriceRatio: number): number;
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.calculateEstimatedSuperStakeLiquidationPrice = exports.calculateSolEarned = exports.findBestSuperStakeIxs = void 0;
|
|
7
|
+
const web3_js_1 = require("@solana/web3.js");
|
|
8
|
+
const marinade_1 = require("../marinade");
|
|
9
|
+
const anchor_1 = require("@coral-xyz/anchor");
|
|
10
|
+
const types_1 = require("../types");
|
|
11
|
+
const numericConstants_1 = require("../constants/numericConstants");
|
|
12
|
+
const node_fetch_1 = __importDefault(require("node-fetch"));
|
|
13
|
+
async function findBestSuperStakeIxs({ amount, jupiterClient, driftClient, userAccountPublicKey, }) {
|
|
14
|
+
const marinadeProgram = (0, marinade_1.getMarinadeFinanceProgram)(driftClient.provider);
|
|
15
|
+
const marinadePrice = await (0, marinade_1.getMarinadeMSolPrice)(marinadeProgram);
|
|
16
|
+
const solMint = driftClient.getSpotMarketAccount(1).mint;
|
|
17
|
+
const mSOLMint = driftClient.getSpotMarketAccount(2).mint;
|
|
18
|
+
const jupiterRoutes = await jupiterClient.getRoutes({
|
|
19
|
+
inputMint: solMint,
|
|
20
|
+
outputMint: mSOLMint,
|
|
21
|
+
amount,
|
|
22
|
+
});
|
|
23
|
+
const bestRoute = jupiterRoutes[0];
|
|
24
|
+
const jupiterPrice = bestRoute.inAmount / bestRoute.outAmount;
|
|
25
|
+
if (marinadePrice <= jupiterPrice) {
|
|
26
|
+
const ixs = await driftClient.getStakeForMSOLIx({ amount });
|
|
27
|
+
return {
|
|
28
|
+
method: 'marinade',
|
|
29
|
+
ixs,
|
|
30
|
+
lookupTables: [],
|
|
31
|
+
price: marinadePrice,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
const { ixs, lookupTables } = await driftClient.getJupiterSwapIx({
|
|
36
|
+
inMarketIndex: 1,
|
|
37
|
+
outMarketIndex: 2,
|
|
38
|
+
route: bestRoute,
|
|
39
|
+
jupiterClient,
|
|
40
|
+
amount,
|
|
41
|
+
userAccountPublicKey,
|
|
42
|
+
});
|
|
43
|
+
return {
|
|
44
|
+
method: 'jupiter',
|
|
45
|
+
ixs,
|
|
46
|
+
lookupTables,
|
|
47
|
+
price: jupiterPrice,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
exports.findBestSuperStakeIxs = findBestSuperStakeIxs;
|
|
52
|
+
async function calculateSolEarned({ user, depositRecords, }) {
|
|
53
|
+
const now = Date.now() / 1000;
|
|
54
|
+
const timestamps = [
|
|
55
|
+
now,
|
|
56
|
+
...depositRecords.map((r) => r.ts.toNumber()),
|
|
57
|
+
];
|
|
58
|
+
const msolRatios = new Map();
|
|
59
|
+
const getPrice = async (timestamp) => {
|
|
60
|
+
const date = new Date(timestamp * 1000); // Convert Unix timestamp to milliseconds
|
|
61
|
+
const swaggerApiDateTime = date.toISOString(); // Format date as swagger API date-time
|
|
62
|
+
const url = `https://api.marinade.finance/msol/price_sol?time=${swaggerApiDateTime}`;
|
|
63
|
+
const response = await (0, node_fetch_1.default)(url);
|
|
64
|
+
if (response.status === 200) {
|
|
65
|
+
const data = await response.json();
|
|
66
|
+
msolRatios.set(timestamp, data);
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
await Promise.all(timestamps.map(getPrice));
|
|
70
|
+
let solEarned = numericConstants_1.ZERO;
|
|
71
|
+
for (const record of depositRecords) {
|
|
72
|
+
if (record.marketIndex === 1) {
|
|
73
|
+
if ((0, types_1.isVariant)(record.direction, 'deposit')) {
|
|
74
|
+
solEarned = solEarned.sub(record.amount);
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
solEarned = solEarned.add(record.amount);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
else if (record.marketIndex === 2) {
|
|
81
|
+
const msolRatio = msolRatios.get(record.ts.toNumber());
|
|
82
|
+
const msolRatioBN = new anchor_1.BN(msolRatio * web3_js_1.LAMPORTS_PER_SOL);
|
|
83
|
+
const solAmount = record.amount.mul(msolRatioBN).div(numericConstants_1.LAMPORTS_PRECISION);
|
|
84
|
+
if ((0, types_1.isVariant)(record.direction, 'deposit')) {
|
|
85
|
+
solEarned = solEarned.sub(solAmount);
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
solEarned = solEarned.add(solAmount);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
const currentMSOLTokenAmount = await user.getTokenAmount(2);
|
|
93
|
+
const currentSOLTokenAmount = await user.getTokenAmount(1);
|
|
94
|
+
const currentMSOLRatio = msolRatios.get(now);
|
|
95
|
+
const currentMSOLRatioBN = new anchor_1.BN(currentMSOLRatio * web3_js_1.LAMPORTS_PER_SOL);
|
|
96
|
+
solEarned = solEarned.add(currentMSOLTokenAmount.mul(currentMSOLRatioBN).div(numericConstants_1.LAMPORTS_PRECISION));
|
|
97
|
+
solEarned = solEarned.add(currentSOLTokenAmount);
|
|
98
|
+
return solEarned;
|
|
99
|
+
}
|
|
100
|
+
exports.calculateSolEarned = calculateSolEarned;
|
|
101
|
+
// calculate estimated liquidation price (in mSOL/SOL) based on target amounts
|
|
102
|
+
function calculateEstimatedSuperStakeLiquidationPrice(msolDepositAmount, msolMaintenanceAssetWeight, solBorrowAmount, solMaintenanceLiabilityWeight, msolPriceRatio) {
|
|
103
|
+
const liquidationDivergence = (solMaintenanceLiabilityWeight * solBorrowAmount) /
|
|
104
|
+
(msolMaintenanceAssetWeight * msolDepositAmount * msolPriceRatio);
|
|
105
|
+
const liquidationPrice = msolPriceRatio * liquidationDivergence;
|
|
106
|
+
return liquidationPrice;
|
|
107
|
+
}
|
|
108
|
+
exports.calculateEstimatedSuperStakeLiquidationPrice = calculateEstimatedSuperStakeLiquidationPrice;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { PerpMarketAccount, SpotMarketAccount } from '../types';
|
|
2
|
+
export declare function getPerpMarketTierNumber(perpMarket: PerpMarketAccount): number;
|
|
3
|
+
export declare function getSpotMarketTierNumber(spotMarket: SpotMarketAccount): number;
|
|
4
|
+
export declare function perpTierIsAsSafeAs(perpTier: number, otherPerpTier: number, otherSpotTier: number): boolean;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.perpTierIsAsSafeAs = exports.getSpotMarketTierNumber = exports.getPerpMarketTierNumber = void 0;
|
|
4
|
+
const types_1 = require("../types");
|
|
5
|
+
function getPerpMarketTierNumber(perpMarket) {
|
|
6
|
+
if ((0, types_1.isVariant)(perpMarket.contractTier, 'a')) {
|
|
7
|
+
return 0;
|
|
8
|
+
}
|
|
9
|
+
else if ((0, types_1.isVariant)(perpMarket.contractTier, 'b')) {
|
|
10
|
+
return 1;
|
|
11
|
+
}
|
|
12
|
+
else if ((0, types_1.isVariant)(perpMarket.contractTier, 'c')) {
|
|
13
|
+
return 2;
|
|
14
|
+
}
|
|
15
|
+
else if ((0, types_1.isVariant)(perpMarket.contractTier, 'speculative')) {
|
|
16
|
+
return 3;
|
|
17
|
+
}
|
|
18
|
+
else if ((0, types_1.isVariant)(perpMarket.contractTier, 'isolated')) {
|
|
19
|
+
return 4;
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
return 5;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
exports.getPerpMarketTierNumber = getPerpMarketTierNumber;
|
|
26
|
+
function getSpotMarketTierNumber(spotMarket) {
|
|
27
|
+
if ((0, types_1.isVariant)(spotMarket.assetTier, 'collateral')) {
|
|
28
|
+
return 0;
|
|
29
|
+
}
|
|
30
|
+
else if ((0, types_1.isVariant)(spotMarket.assetTier, 'protected')) {
|
|
31
|
+
return 1;
|
|
32
|
+
}
|
|
33
|
+
else if ((0, types_1.isVariant)(spotMarket.assetTier, 'cross')) {
|
|
34
|
+
return 2;
|
|
35
|
+
}
|
|
36
|
+
else if ((0, types_1.isVariant)(spotMarket.assetTier, 'isolated')) {
|
|
37
|
+
return 3;
|
|
38
|
+
}
|
|
39
|
+
else if ((0, types_1.isVariant)(spotMarket.assetTier, 'unlisted')) {
|
|
40
|
+
return 4;
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
return 5;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
exports.getSpotMarketTierNumber = getSpotMarketTierNumber;
|
|
47
|
+
function perpTierIsAsSafeAs(perpTier, otherPerpTier, otherSpotTier) {
|
|
48
|
+
const asSafeAsPerp = perpTier <= otherPerpTier;
|
|
49
|
+
const asSafeAsSpot = otherSpotTier === 4 || (otherSpotTier >= 2 && perpTier <= 2);
|
|
50
|
+
return asSafeAsSpot && asSafeAsPerp;
|
|
51
|
+
}
|
|
52
|
+
exports.perpTierIsAsSafeAs = perpTierIsAsSafeAs;
|
|
@@ -1,17 +1,26 @@
|
|
|
1
1
|
/// <reference types="node" />
|
|
2
2
|
import { TxSender, TxSigAndSlot } from './types';
|
|
3
3
|
import { Commitment, ConfirmOptions, RpcResponseAndContext, Signer, SignatureResult, Transaction, TransactionSignature, Connection, VersionedTransaction, TransactionInstruction, AddressLookupTableAccount } from '@solana/web3.js';
|
|
4
|
-
import {
|
|
4
|
+
import { IWallet } from '../types';
|
|
5
5
|
type ResolveReference = {
|
|
6
6
|
resolve?: () => void;
|
|
7
7
|
};
|
|
8
8
|
export declare class RetryTxSender implements TxSender {
|
|
9
|
-
|
|
9
|
+
connection: Connection;
|
|
10
|
+
wallet: IWallet;
|
|
11
|
+
opts: ConfirmOptions;
|
|
10
12
|
timeout: number;
|
|
11
13
|
retrySleep: number;
|
|
12
14
|
additionalConnections: Connection[];
|
|
13
15
|
timoutCount: number;
|
|
14
|
-
constructor(
|
|
16
|
+
constructor({ connection, wallet, opts, timeout, retrySleep, additionalConnections, }: {
|
|
17
|
+
connection: Connection;
|
|
18
|
+
wallet: IWallet;
|
|
19
|
+
opts?: ConfirmOptions;
|
|
20
|
+
timeout?: number;
|
|
21
|
+
retrySleep?: number;
|
|
22
|
+
additionalConnections?: any;
|
|
23
|
+
});
|
|
15
24
|
send(tx: Transaction, additionalSigners?: Array<Signer>, opts?: ConfirmOptions, preSigned?: boolean): Promise<TxSigAndSlot>;
|
|
16
25
|
prepareTx(tx: Transaction, additionalSigners: Array<Signer>, opts: ConfirmOptions): Promise<Transaction>;
|
|
17
26
|
getVersionedTransaction(ixs: TransactionInstruction[], lookupTableAccounts: AddressLookupTableAccount[], additionalSigners?: Array<Signer>, opts?: ConfirmOptions): Promise<VersionedTransaction>;
|
package/lib/tx/retryTxSender.js
CHANGED
|
@@ -5,16 +5,19 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.RetryTxSender = void 0;
|
|
7
7
|
const web3_js_1 = require("@solana/web3.js");
|
|
8
|
+
const anchor_1 = require("@coral-xyz/anchor");
|
|
8
9
|
const assert_1 = __importDefault(require("assert"));
|
|
9
10
|
const bs58_1 = __importDefault(require("bs58"));
|
|
10
11
|
const DEFAULT_TIMEOUT = 35000;
|
|
11
12
|
const DEFAULT_RETRY = 8000;
|
|
12
13
|
class RetryTxSender {
|
|
13
|
-
constructor(
|
|
14
|
+
constructor({ connection, wallet, opts = anchor_1.AnchorProvider.defaultOptions(), timeout = DEFAULT_TIMEOUT, retrySleep = DEFAULT_RETRY, additionalConnections = new Array(), }) {
|
|
14
15
|
this.timoutCount = 0;
|
|
15
|
-
this.
|
|
16
|
-
this.
|
|
17
|
-
this.
|
|
16
|
+
this.connection = connection;
|
|
17
|
+
this.wallet = wallet;
|
|
18
|
+
this.opts = opts;
|
|
19
|
+
this.timeout = timeout;
|
|
20
|
+
this.retrySleep = retrySleep;
|
|
18
21
|
this.additionalConnections = additionalConnections;
|
|
19
22
|
}
|
|
20
23
|
async send(tx, additionalSigners, opts, preSigned) {
|
|
@@ -22,7 +25,7 @@ class RetryTxSender {
|
|
|
22
25
|
additionalSigners = [];
|
|
23
26
|
}
|
|
24
27
|
if (opts === undefined) {
|
|
25
|
-
opts = this.
|
|
28
|
+
opts = this.opts;
|
|
26
29
|
}
|
|
27
30
|
const signedTx = preSigned
|
|
28
31
|
? tx
|
|
@@ -30,14 +33,14 @@ class RetryTxSender {
|
|
|
30
33
|
return this.sendRawTransaction(signedTx.serialize(), opts);
|
|
31
34
|
}
|
|
32
35
|
async prepareTx(tx, additionalSigners, opts) {
|
|
33
|
-
tx.feePayer = this.
|
|
34
|
-
tx.recentBlockhash = (await this.
|
|
36
|
+
tx.feePayer = this.wallet.publicKey;
|
|
37
|
+
tx.recentBlockhash = (await this.connection.getRecentBlockhash(opts.preflightCommitment)).blockhash;
|
|
35
38
|
additionalSigners
|
|
36
39
|
.filter((s) => s !== undefined)
|
|
37
40
|
.forEach((kp) => {
|
|
38
41
|
tx.partialSign(kp);
|
|
39
42
|
});
|
|
40
|
-
const signedTx = await this.
|
|
43
|
+
const signedTx = await this.wallet.signTransaction(tx);
|
|
41
44
|
return signedTx;
|
|
42
45
|
}
|
|
43
46
|
async getVersionedTransaction(ixs, lookupTableAccounts, additionalSigners, opts) {
|
|
@@ -45,11 +48,11 @@ class RetryTxSender {
|
|
|
45
48
|
additionalSigners = [];
|
|
46
49
|
}
|
|
47
50
|
if (opts === undefined) {
|
|
48
|
-
opts = this.
|
|
51
|
+
opts = this.opts;
|
|
49
52
|
}
|
|
50
53
|
const message = new web3_js_1.TransactionMessage({
|
|
51
|
-
payerKey: this.
|
|
52
|
-
recentBlockhash: (await this.
|
|
54
|
+
payerKey: this.wallet.publicKey,
|
|
55
|
+
recentBlockhash: (await this.connection.getRecentBlockhash(opts.preflightCommitment)).blockhash,
|
|
53
56
|
instructions: ixs,
|
|
54
57
|
}).compileToV0Message(lookupTableAccounts);
|
|
55
58
|
const tx = new web3_js_1.VersionedTransaction(message);
|
|
@@ -61,9 +64,9 @@ class RetryTxSender {
|
|
|
61
64
|
signedTx = tx;
|
|
62
65
|
// @ts-ignore
|
|
63
66
|
}
|
|
64
|
-
else if (this.
|
|
67
|
+
else if (this.wallet.payer) {
|
|
65
68
|
// @ts-ignore
|
|
66
|
-
tx.sign((additionalSigners !== null && additionalSigners !== void 0 ? additionalSigners : []).concat(this.
|
|
69
|
+
tx.sign((additionalSigners !== null && additionalSigners !== void 0 ? additionalSigners : []).concat(this.wallet.payer));
|
|
67
70
|
signedTx = tx;
|
|
68
71
|
}
|
|
69
72
|
else {
|
|
@@ -71,10 +74,10 @@ class RetryTxSender {
|
|
|
71
74
|
tx.sign([kp]);
|
|
72
75
|
});
|
|
73
76
|
// @ts-ignore
|
|
74
|
-
signedTx = await this.
|
|
77
|
+
signedTx = await this.wallet.signTransaction(tx);
|
|
75
78
|
}
|
|
76
79
|
if (opts === undefined) {
|
|
77
|
-
opts = this.
|
|
80
|
+
opts = this.opts;
|
|
78
81
|
}
|
|
79
82
|
return this.sendRawTransaction(signedTx.serialize(), opts);
|
|
80
83
|
}
|
|
@@ -82,7 +85,7 @@ class RetryTxSender {
|
|
|
82
85
|
const startTime = this.getTimestamp();
|
|
83
86
|
let txid;
|
|
84
87
|
try {
|
|
85
|
-
txid = await this.
|
|
88
|
+
txid = await this.connection.sendRawTransaction(rawTransaction, opts);
|
|
86
89
|
this.sendToAdditionalConnections(rawTransaction, opts);
|
|
87
90
|
}
|
|
88
91
|
catch (e) {
|
|
@@ -103,7 +106,7 @@ class RetryTxSender {
|
|
|
103
106
|
while (!done && this.getTimestamp() - startTime < this.timeout) {
|
|
104
107
|
await this.sleep(resolveReference);
|
|
105
108
|
if (!done) {
|
|
106
|
-
this.
|
|
109
|
+
this.connection
|
|
107
110
|
.sendRawTransaction(rawTransaction, opts)
|
|
108
111
|
.catch((e) => {
|
|
109
112
|
console.error(e);
|
|
@@ -137,12 +140,9 @@ class RetryTxSender {
|
|
|
137
140
|
}
|
|
138
141
|
(0, assert_1.default)(decodedSignature.length === 64, 'signature has invalid length');
|
|
139
142
|
const start = Date.now();
|
|
140
|
-
const subscriptionCommitment = commitment || this.
|
|
143
|
+
const subscriptionCommitment = commitment || this.opts.commitment;
|
|
141
144
|
const subscriptionIds = new Array();
|
|
142
|
-
const connections = [
|
|
143
|
-
this.provider.connection,
|
|
144
|
-
...this.additionalConnections,
|
|
145
|
-
];
|
|
145
|
+
const connections = [this.connection, ...this.additionalConnections];
|
|
146
146
|
let response = null;
|
|
147
147
|
const promises = connections.map((connection, i) => {
|
|
148
148
|
let subscriptionId;
|
package/lib/tx/types.d.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
/// <reference types="node" />
|
|
2
|
-
import { Provider } from '@coral-xyz/anchor';
|
|
3
2
|
import { AddressLookupTableAccount, ConfirmOptions, Signer, Transaction, TransactionInstruction, TransactionSignature, VersionedTransaction } from '@solana/web3.js';
|
|
3
|
+
import { IWallet } from '../types';
|
|
4
4
|
export type TxSigAndSlot = {
|
|
5
5
|
txSig: TransactionSignature;
|
|
6
6
|
slot: number;
|
|
7
7
|
};
|
|
8
8
|
export interface TxSender {
|
|
9
|
-
|
|
9
|
+
wallet: IWallet;
|
|
10
10
|
send(tx: Transaction, additionalSigners?: Array<Signer>, opts?: ConfirmOptions, preSigned?: boolean): Promise<TxSigAndSlot>;
|
|
11
11
|
sendVersionedTransaction(tx: VersionedTransaction, additionalSigners?: Array<Signer>, opts?: ConfirmOptions, preSigned?: boolean): Promise<TxSigAndSlot>;
|
|
12
12
|
getVersionedTransaction(ixs: TransactionInstruction[], lookupTableAccounts: AddressLookupTableAccount[], additionalSigners?: Array<Signer>, opts?: ConfirmOptions): Promise<VersionedTransaction>;
|
package/lib/user.d.ts
CHANGED
|
@@ -107,6 +107,7 @@ export declare class User {
|
|
|
107
107
|
*/
|
|
108
108
|
getMaintenanceMarginRequirement(liquidationBuffer?: BN): BN;
|
|
109
109
|
getActivePerpPositions(): PerpPosition[];
|
|
110
|
+
getActiveSpotPositions(): SpotPosition[];
|
|
110
111
|
/**
|
|
111
112
|
* calculates unrealized position price pnl
|
|
112
113
|
* @returns : Precision QUOTE_PRECISION
|
|
@@ -185,7 +186,11 @@ export declare class User {
|
|
|
185
186
|
* @returns : Precision TEN_THOUSAND
|
|
186
187
|
*/
|
|
187
188
|
getMarginRatio(): BN;
|
|
188
|
-
canBeLiquidated():
|
|
189
|
+
canBeLiquidated(): {
|
|
190
|
+
canBeLiquidated: boolean;
|
|
191
|
+
marginRequirement: BN;
|
|
192
|
+
totalCollateral: BN;
|
|
193
|
+
};
|
|
189
194
|
isBeingLiquidated(): boolean;
|
|
190
195
|
isBankrupt(): boolean;
|
|
191
196
|
/**
|
|
@@ -277,6 +282,10 @@ export declare class User {
|
|
|
277
282
|
maxDepositAmount: BN;
|
|
278
283
|
};
|
|
279
284
|
canMakeIdle(slot: BN, slotsBeforeIdle: BN): boolean;
|
|
285
|
+
getSafestTiers(): {
|
|
286
|
+
perpTier: number;
|
|
287
|
+
spotTier: number;
|
|
288
|
+
};
|
|
280
289
|
/**
|
|
281
290
|
* Get the total position value, excluding any position coming from the given target market
|
|
282
291
|
* @param marketToIgnore
|
package/lib/user.js
CHANGED
|
@@ -12,6 +12,7 @@ const pollingUserAccountSubscriber_1 = require("./accounts/pollingUserAccountSub
|
|
|
12
12
|
const webSocketUserAccountSubscriber_1 = require("./accounts/webSocketUserAccountSubscriber");
|
|
13
13
|
const spotPosition_1 = require("./math/spotPosition");
|
|
14
14
|
const oracles_1 = require("./math/oracles");
|
|
15
|
+
const tiers_1 = require("./math/tiers");
|
|
15
16
|
class User {
|
|
16
17
|
get isSubscribed() {
|
|
17
18
|
return this._isSubscribed && this.accountSubscriber.isSubscribed;
|
|
@@ -20,13 +21,16 @@ class User {
|
|
|
20
21
|
this._isSubscribed = val;
|
|
21
22
|
}
|
|
22
23
|
constructor(config) {
|
|
23
|
-
var _a;
|
|
24
|
+
var _a, _b;
|
|
24
25
|
this._isSubscribed = false;
|
|
25
26
|
this.driftClient = config.driftClient;
|
|
26
27
|
this.userAccountPublicKey = config.userAccountPublicKey;
|
|
27
28
|
if (((_a = config.accountSubscription) === null || _a === void 0 ? void 0 : _a.type) === 'polling') {
|
|
28
29
|
this.accountSubscriber = new pollingUserAccountSubscriber_1.PollingUserAccountSubscriber(config.driftClient.program, config.userAccountPublicKey, config.accountSubscription.accountLoader);
|
|
29
30
|
}
|
|
31
|
+
else if (((_b = config.accountSubscription) === null || _b === void 0 ? void 0 : _b.type) === 'custom') {
|
|
32
|
+
this.accountSubscriber = config.accountSubscription.userAccountSubscriber;
|
|
33
|
+
}
|
|
30
34
|
else {
|
|
31
35
|
this.accountSubscriber = new webSocketUserAccountSubscriber_1.WebSocketUserAccountSubscriber(config.driftClient.program, config.userAccountPublicKey);
|
|
32
36
|
}
|
|
@@ -176,7 +180,9 @@ class User {
|
|
|
176
180
|
* @returns : pnl from settle
|
|
177
181
|
*/
|
|
178
182
|
getPerpPositionWithLPSettle(marketIndex, originalPosition) {
|
|
179
|
-
|
|
183
|
+
var _a;
|
|
184
|
+
originalPosition =
|
|
185
|
+
(_a = originalPosition !== null && originalPosition !== void 0 ? originalPosition : this.getPerpPosition(marketIndex)) !== null && _a !== void 0 ? _a : this.getEmptyPosition(marketIndex);
|
|
180
186
|
if (originalPosition.lpShares.eq(numericConstants_1.ZERO)) {
|
|
181
187
|
return [originalPosition, numericConstants_1.ZERO, numericConstants_1.ZERO];
|
|
182
188
|
}
|
|
@@ -228,7 +234,7 @@ class User {
|
|
|
228
234
|
let pnl;
|
|
229
235
|
if (updateType == 'open' || updateType == 'increase') {
|
|
230
236
|
newQuoteEntry = position.quoteEntryAmount.add(deltaQaa);
|
|
231
|
-
pnl =
|
|
237
|
+
pnl = numericConstants_1.ZERO;
|
|
232
238
|
}
|
|
233
239
|
else if (updateType == 'reduce' || updateType == 'close') {
|
|
234
240
|
newQuoteEntry = position.quoteEntryAmount.sub(position.quoteEntryAmount
|
|
@@ -305,6 +311,9 @@ class User {
|
|
|
305
311
|
!(pos.openOrders == 0) ||
|
|
306
312
|
!pos.lpShares.eq(numericConstants_1.ZERO));
|
|
307
313
|
}
|
|
314
|
+
getActiveSpotPositions() {
|
|
315
|
+
return this.getUserAccount().spotPositions.filter((pos) => !(0, spotPosition_1.isSpotPositionAvailable)(pos));
|
|
316
|
+
}
|
|
308
317
|
/**
|
|
309
318
|
* calculates unrealized position price pnl
|
|
310
319
|
* @returns : Precision QUOTE_PRECISION
|
|
@@ -601,7 +610,8 @@ class User {
|
|
|
601
610
|
* @returns : Precision QUOTE_PRECISION
|
|
602
611
|
*/
|
|
603
612
|
getPerpPositionValue(marketIndex, oraclePriceData, includeOpenOrders = false) {
|
|
604
|
-
const userPosition = this.
|
|
613
|
+
const userPosition = this.getPerpPositionWithLPSettle(marketIndex)[0] ||
|
|
614
|
+
this.getEmptyPosition(marketIndex);
|
|
605
615
|
const market = this.driftClient.getPerpMarketAccount(userPosition.marketIndex);
|
|
606
616
|
return (0, margin_1.calculateBaseAssetValueWithOracle)(market, userPosition, oraclePriceData, includeOpenOrders);
|
|
607
617
|
}
|
|
@@ -775,12 +785,16 @@ class User {
|
|
|
775
785
|
const totalCollateral = this.getTotalCollateral('Maintenance');
|
|
776
786
|
// if user being liq'd, can continue to be liq'd until total collateral above the margin requirement plus buffer
|
|
777
787
|
let liquidationBuffer = undefined;
|
|
778
|
-
|
|
779
|
-
if (isBeingLiquidated) {
|
|
788
|
+
if (this.isBeingLiquidated()) {
|
|
780
789
|
liquidationBuffer = new _1.BN(this.driftClient.getStateAccount().liquidationMarginBufferRatio);
|
|
781
790
|
}
|
|
782
|
-
const
|
|
783
|
-
|
|
791
|
+
const marginRequirement = this.getMaintenanceMarginRequirement(liquidationBuffer);
|
|
792
|
+
const canBeLiquidated = totalCollateral.lt(marginRequirement);
|
|
793
|
+
return {
|
|
794
|
+
canBeLiquidated,
|
|
795
|
+
marginRequirement,
|
|
796
|
+
totalCollateral,
|
|
797
|
+
};
|
|
784
798
|
}
|
|
785
799
|
isBeingLiquidated() {
|
|
786
800
|
return (0, types_1.isOneOfVariant)(this.getUserAccount().status, [
|
|
@@ -1281,6 +1295,23 @@ class User {
|
|
|
1281
1295
|
}
|
|
1282
1296
|
return true;
|
|
1283
1297
|
}
|
|
1298
|
+
getSafestTiers() {
|
|
1299
|
+
let safestPerpTier = 4;
|
|
1300
|
+
let safestSpotTier = 4;
|
|
1301
|
+
for (const perpPosition of this.getActivePerpPositions()) {
|
|
1302
|
+
safestPerpTier = Math.min(safestPerpTier, (0, tiers_1.getPerpMarketTierNumber)(this.driftClient.getPerpMarketAccount(perpPosition.marketIndex)));
|
|
1303
|
+
}
|
|
1304
|
+
for (const spotPosition of this.getActiveSpotPositions()) {
|
|
1305
|
+
if ((0, types_1.isVariant)(spotPosition.balanceType, 'deposit')) {
|
|
1306
|
+
continue;
|
|
1307
|
+
}
|
|
1308
|
+
safestSpotTier = Math.min(safestSpotTier, (0, tiers_1.getSpotMarketTierNumber)(this.driftClient.getSpotMarketAccount(spotPosition.marketIndex)));
|
|
1309
|
+
}
|
|
1310
|
+
return {
|
|
1311
|
+
perpTier: safestPerpTier,
|
|
1312
|
+
spotTier: safestSpotTier,
|
|
1313
|
+
};
|
|
1314
|
+
}
|
|
1284
1315
|
/**
|
|
1285
1316
|
* Get the total position value, excluding any position coming from the given target market
|
|
1286
1317
|
* @param marketToIgnore
|
package/lib/userConfig.d.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
|
export type UserConfig = {
|
|
5
6
|
accountSubscription?: UserSubscriptionConfig;
|
|
6
7
|
driftClient: DriftClient;
|
|
@@ -11,4 +12,7 @@ export type UserSubscriptionConfig = {
|
|
|
11
12
|
} | {
|
|
12
13
|
type: 'polling';
|
|
13
14
|
accountLoader: BulkAccountLoader;
|
|
15
|
+
} | {
|
|
16
|
+
type: 'custom';
|
|
17
|
+
userAccountSubscriber: UserAccountSubscriber;
|
|
14
18
|
};
|
package/lib/userStats.js
CHANGED
|
@@ -7,12 +7,15 @@ const webSocketUserStatsAccountSubsriber_1 = require("./accounts/webSocketUserSt
|
|
|
7
7
|
const pda_1 = require("./addresses/pda");
|
|
8
8
|
class UserStats {
|
|
9
9
|
constructor(config) {
|
|
10
|
-
var _a;
|
|
10
|
+
var _a, _b;
|
|
11
11
|
this.driftClient = config.driftClient;
|
|
12
12
|
this.userStatsAccountPublicKey = config.userStatsAccountPublicKey;
|
|
13
13
|
if (((_a = config.accountSubscription) === null || _a === void 0 ? void 0 : _a.type) === 'polling') {
|
|
14
14
|
this.accountSubscriber = new pollingUserStatsAccountSubscriber_1.PollingUserStatsAccountSubscriber(config.driftClient.program, config.userStatsAccountPublicKey, config.accountSubscription.accountLoader);
|
|
15
15
|
}
|
|
16
|
+
else if (((_b = config.accountSubscription) === null || _b === void 0 ? void 0 : _b.type) === 'custom') {
|
|
17
|
+
throw new Error('Custom account subscription not yet implemented for user stats');
|
|
18
|
+
}
|
|
16
19
|
else {
|
|
17
20
|
this.accountSubscriber = new webSocketUserStatsAccountSubsriber_1.WebSocketUserStatsAccountSubscriber(config.driftClient.program, config.userStatsAccountPublicKey);
|
|
18
21
|
}
|
package/lib/userStatsConfig.d.ts
CHANGED