@drift-labs/sdk 2.40.0-beta.8 → 2.40.0-beta.9
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/spotMarkets.js +1 -1
- package/lib/math/superStake.d.ts +51 -4
- package/lib/math/superStake.js +173 -21
- package/lib/math/utils.d.ts +1 -0
- package/lib/math/utils.js +10 -1
- package/package.json +1 -1
- package/src/constants/spotMarkets.ts +1 -1
- package/src/math/superStake.ts +248 -24
- package/src/math/utils.ts +12 -0
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
2.40.0-beta.
|
|
1
|
+
2.40.0-beta.9
|
|
@@ -100,7 +100,7 @@ exports.MainnetSpotMarkets = [
|
|
|
100
100
|
serumMarket: new web3_js_1.PublicKey('B2na8Awyd7cpC59iEU43FagJAPLigr3AP3s38KM982bu'),
|
|
101
101
|
},
|
|
102
102
|
{
|
|
103
|
-
symbol: '
|
|
103
|
+
symbol: 'JitoSOL',
|
|
104
104
|
marketIndex: 6,
|
|
105
105
|
oracle: new web3_js_1.PublicKey('7yyaeuJ1GGtVBLT2z2xub5ZWYKaNhF28mj1RdV4VDFVk'),
|
|
106
106
|
oracleSource: __1.OracleSource.PYTH,
|
package/lib/math/superStake.d.ts
CHANGED
|
@@ -4,11 +4,12 @@ import { DriftClient } from '../driftClient';
|
|
|
4
4
|
import { BN } from '@coral-xyz/anchor';
|
|
5
5
|
import { User } from '../user';
|
|
6
6
|
import { DepositRecord } from '../types';
|
|
7
|
-
export declare function findBestSuperStakeIxs({ amount, jupiterClient, driftClient, userAccountPublicKey,
|
|
7
|
+
export declare function findBestSuperStakeIxs({ marketIndex, amount, jupiterClient, driftClient, userAccountPublicKey, price, forceMarinade, onlyDirectRoutes, }: {
|
|
8
|
+
marketIndex: number;
|
|
8
9
|
amount: BN;
|
|
9
10
|
jupiterClient: JupiterClient;
|
|
10
11
|
driftClient: DriftClient;
|
|
11
|
-
|
|
12
|
+
price?: number;
|
|
12
13
|
userAccountPublicKey?: PublicKey;
|
|
13
14
|
forceMarinade?: boolean;
|
|
14
15
|
onlyDirectRoutes?: boolean;
|
|
@@ -18,8 +19,54 @@ export declare function findBestSuperStakeIxs({ amount, jupiterClient, driftClie
|
|
|
18
19
|
method: 'jupiter' | 'marinade';
|
|
19
20
|
price: number;
|
|
20
21
|
}>;
|
|
21
|
-
export declare function
|
|
22
|
+
export declare function findBestMSolSuperStakeIxs({ amount, jupiterClient, driftClient, userAccountPublicKey, price, forceMarinade, onlyDirectRoutes, }: {
|
|
23
|
+
amount: BN;
|
|
24
|
+
jupiterClient: JupiterClient;
|
|
25
|
+
driftClient: DriftClient;
|
|
26
|
+
price?: number;
|
|
27
|
+
userAccountPublicKey?: PublicKey;
|
|
28
|
+
forceMarinade?: boolean;
|
|
29
|
+
onlyDirectRoutes?: boolean;
|
|
30
|
+
}): Promise<{
|
|
31
|
+
ixs: TransactionInstruction[];
|
|
32
|
+
lookupTables: AddressLookupTableAccount[];
|
|
33
|
+
method: 'jupiter' | 'marinade';
|
|
34
|
+
price: number;
|
|
35
|
+
}>;
|
|
36
|
+
export declare function findBestJitoSolSuperStakeIxs({ amount, jupiterClient, driftClient, userAccountPublicKey, onlyDirectRoutes, }: {
|
|
37
|
+
amount: BN;
|
|
38
|
+
jupiterClient: JupiterClient;
|
|
39
|
+
driftClient: DriftClient;
|
|
40
|
+
userAccountPublicKey?: PublicKey;
|
|
41
|
+
onlyDirectRoutes?: boolean;
|
|
42
|
+
}): Promise<{
|
|
43
|
+
ixs: TransactionInstruction[];
|
|
44
|
+
lookupTables: AddressLookupTableAccount[];
|
|
45
|
+
method: 'jupiter' | 'marinade';
|
|
46
|
+
price: number;
|
|
47
|
+
}>;
|
|
48
|
+
export type JITO_SOL_METRICS_ENDPOINT_RESPONSE = {
|
|
49
|
+
data: {
|
|
50
|
+
getStakePoolStats: {
|
|
51
|
+
tvl: {
|
|
52
|
+
data: number;
|
|
53
|
+
date: string;
|
|
54
|
+
}[];
|
|
55
|
+
supply: {
|
|
56
|
+
data: number;
|
|
57
|
+
date: string;
|
|
58
|
+
}[];
|
|
59
|
+
apy: {
|
|
60
|
+
data: number;
|
|
61
|
+
date: string;
|
|
62
|
+
}[];
|
|
63
|
+
};
|
|
64
|
+
};
|
|
65
|
+
};
|
|
66
|
+
export declare function fetchJitoSolMetrics(): Promise<JITO_SOL_METRICS_ENDPOINT_RESPONSE>;
|
|
67
|
+
export declare function calculateSolEarned({ marketIndex, user, depositRecords, }: {
|
|
68
|
+
marketIndex: number;
|
|
22
69
|
user: User;
|
|
23
70
|
depositRecords: DepositRecord[];
|
|
24
71
|
}): Promise<BN>;
|
|
25
|
-
export declare function calculateEstimatedSuperStakeLiquidationPrice(
|
|
72
|
+
export declare function calculateEstimatedSuperStakeLiquidationPrice(lstDepositAmount: number, lstMaintenanceAssetWeight: number, solBorrowAmount: number, solMaintenanceLiabilityWeight: number, lstPriceRatio: number): number;
|
package/lib/math/superStake.js
CHANGED
|
@@ -3,17 +3,44 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.calculateEstimatedSuperStakeLiquidationPrice = exports.calculateSolEarned = exports.findBestSuperStakeIxs = void 0;
|
|
6
|
+
exports.calculateEstimatedSuperStakeLiquidationPrice = exports.calculateSolEarned = exports.fetchJitoSolMetrics = exports.findBestJitoSolSuperStakeIxs = exports.findBestMSolSuperStakeIxs = exports.findBestSuperStakeIxs = void 0;
|
|
7
7
|
const web3_js_1 = require("@solana/web3.js");
|
|
8
8
|
const marinade_1 = require("../marinade");
|
|
9
9
|
const anchor_1 = require("@coral-xyz/anchor");
|
|
10
10
|
const types_1 = require("../types");
|
|
11
11
|
const numericConstants_1 = require("../constants/numericConstants");
|
|
12
12
|
const node_fetch_1 = __importDefault(require("node-fetch"));
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
const utils_1 = require("./utils");
|
|
14
|
+
async function findBestSuperStakeIxs({ marketIndex, amount, jupiterClient, driftClient, userAccountPublicKey, price, forceMarinade, onlyDirectRoutes, }) {
|
|
15
|
+
if (marketIndex === 2) {
|
|
16
|
+
return findBestMSolSuperStakeIxs({
|
|
17
|
+
amount,
|
|
18
|
+
jupiterClient,
|
|
19
|
+
driftClient,
|
|
20
|
+
userAccountPublicKey,
|
|
21
|
+
price,
|
|
22
|
+
forceMarinade,
|
|
23
|
+
onlyDirectRoutes,
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
else if (marketIndex === 6) {
|
|
27
|
+
return findBestJitoSolSuperStakeIxs({
|
|
28
|
+
amount,
|
|
29
|
+
jupiterClient,
|
|
30
|
+
driftClient,
|
|
31
|
+
userAccountPublicKey,
|
|
32
|
+
onlyDirectRoutes,
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
throw new Error(`Unsupported superstake market index: ${marketIndex}`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
exports.findBestSuperStakeIxs = findBestSuperStakeIxs;
|
|
40
|
+
async function findBestMSolSuperStakeIxs({ amount, jupiterClient, driftClient, userAccountPublicKey, price, forceMarinade, onlyDirectRoutes, }) {
|
|
41
|
+
if (!price) {
|
|
15
42
|
const marinadeProgram = (0, marinade_1.getMarinadeFinanceProgram)(driftClient.provider);
|
|
16
|
-
|
|
43
|
+
price = await (0, marinade_1.getMarinadeMSolPrice)(marinadeProgram);
|
|
17
44
|
}
|
|
18
45
|
const solMint = driftClient.getSpotMarketAccount(1).mint;
|
|
19
46
|
const mSOLMint = driftClient.getSpotMarketAccount(2).mint;
|
|
@@ -32,7 +59,7 @@ async function findBestSuperStakeIxs({ amount, jupiterClient, driftClient, userA
|
|
|
32
59
|
catch (e) {
|
|
33
60
|
console.error('Error getting jupiter price', e);
|
|
34
61
|
}
|
|
35
|
-
if (!jupiterPrice ||
|
|
62
|
+
if (!jupiterPrice || price <= jupiterPrice || forceMarinade) {
|
|
36
63
|
const ixs = await driftClient.getStakeForMSOLIx({
|
|
37
64
|
amount,
|
|
38
65
|
userAccountPublicKey,
|
|
@@ -41,7 +68,7 @@ async function findBestSuperStakeIxs({ amount, jupiterClient, driftClient, userA
|
|
|
41
68
|
method: 'marinade',
|
|
42
69
|
ixs,
|
|
43
70
|
lookupTables: [],
|
|
44
|
-
price:
|
|
71
|
+
price: price,
|
|
45
72
|
};
|
|
46
73
|
}
|
|
47
74
|
else {
|
|
@@ -61,25 +88,137 @@ async function findBestSuperStakeIxs({ amount, jupiterClient, driftClient, userA
|
|
|
61
88
|
};
|
|
62
89
|
}
|
|
63
90
|
}
|
|
64
|
-
exports.
|
|
65
|
-
async function
|
|
91
|
+
exports.findBestMSolSuperStakeIxs = findBestMSolSuperStakeIxs;
|
|
92
|
+
async function findBestJitoSolSuperStakeIxs({ amount, jupiterClient, driftClient, userAccountPublicKey, onlyDirectRoutes, }) {
|
|
93
|
+
const solMint = driftClient.getSpotMarketAccount(1).mint;
|
|
94
|
+
const JitoSolMint = driftClient.getSpotMarketAccount(6).mint;
|
|
95
|
+
let jupiterPrice;
|
|
96
|
+
let bestRoute;
|
|
97
|
+
try {
|
|
98
|
+
const jupiterRoutes = await jupiterClient.getRoutes({
|
|
99
|
+
inputMint: solMint,
|
|
100
|
+
outputMint: JitoSolMint,
|
|
101
|
+
amount,
|
|
102
|
+
onlyDirectRoutes,
|
|
103
|
+
});
|
|
104
|
+
bestRoute = jupiterRoutes[0];
|
|
105
|
+
jupiterPrice = bestRoute.inAmount / bestRoute.outAmount;
|
|
106
|
+
}
|
|
107
|
+
catch (e) {
|
|
108
|
+
console.error('Error getting jupiter price', e);
|
|
109
|
+
throw e;
|
|
110
|
+
}
|
|
111
|
+
const { ixs, lookupTables } = await driftClient.getJupiterSwapIx({
|
|
112
|
+
inMarketIndex: 1,
|
|
113
|
+
outMarketIndex: 6,
|
|
114
|
+
route: bestRoute,
|
|
115
|
+
jupiterClient,
|
|
116
|
+
amount,
|
|
117
|
+
userAccountPublicKey,
|
|
118
|
+
});
|
|
119
|
+
return {
|
|
120
|
+
method: 'jupiter',
|
|
121
|
+
ixs,
|
|
122
|
+
lookupTables,
|
|
123
|
+
price: jupiterPrice,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
exports.findBestJitoSolSuperStakeIxs = findBestJitoSolSuperStakeIxs;
|
|
127
|
+
const JITO_SOL_START_DATE = '2022-10-31T00:00:00Z';
|
|
128
|
+
async function fetchJitoSolMetrics() {
|
|
129
|
+
const res = await (0, node_fetch_1.default)('https://kobe.mainnet.jito.network/', {
|
|
130
|
+
body: JSON.stringify({
|
|
131
|
+
operationName: 'QueryRoot',
|
|
132
|
+
variables: {
|
|
133
|
+
request: {
|
|
134
|
+
bucketType: 'DAILY',
|
|
135
|
+
rangeFilter: {
|
|
136
|
+
start: JITO_SOL_START_DATE,
|
|
137
|
+
end: new Date().toISOString(),
|
|
138
|
+
},
|
|
139
|
+
sortBy: {
|
|
140
|
+
order: 'ASC',
|
|
141
|
+
field: 'BLOCK_TIME',
|
|
142
|
+
},
|
|
143
|
+
},
|
|
144
|
+
},
|
|
145
|
+
query: `
|
|
146
|
+
query QueryRoot($request: GetStakePoolStatsRequest!) {
|
|
147
|
+
getStakePoolStats(req: $request) {
|
|
148
|
+
tvl {
|
|
149
|
+
data
|
|
150
|
+
date
|
|
151
|
+
}
|
|
152
|
+
apy {
|
|
153
|
+
data
|
|
154
|
+
date
|
|
155
|
+
}
|
|
156
|
+
supply {
|
|
157
|
+
data
|
|
158
|
+
date
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
`,
|
|
163
|
+
}),
|
|
164
|
+
method: 'POST',
|
|
165
|
+
});
|
|
166
|
+
const data = await res.json();
|
|
167
|
+
return data;
|
|
168
|
+
}
|
|
169
|
+
exports.fetchJitoSolMetrics = fetchJitoSolMetrics;
|
|
170
|
+
const getJitoSolHistoricalPriceMap = async (timestamps) => {
|
|
171
|
+
try {
|
|
172
|
+
const data = await fetchJitoSolMetrics();
|
|
173
|
+
const jitoSolHistoricalPriceMap = new Map();
|
|
174
|
+
const jitoSolHistoricalPriceInSol = [];
|
|
175
|
+
for (let i = 0; i < data.data.getStakePoolStats.supply.length; i++) {
|
|
176
|
+
const priceInSol = data.data.getStakePoolStats.tvl[i].data /
|
|
177
|
+
new anchor_1.BN(10).pow(numericConstants_1.NINE) /
|
|
178
|
+
data.data.getStakePoolStats.supply[i].data;
|
|
179
|
+
jitoSolHistoricalPriceInSol.push({
|
|
180
|
+
price: priceInSol,
|
|
181
|
+
ts: data.data.getStakePoolStats.tvl[i].date,
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
for (const timestamp of timestamps) {
|
|
185
|
+
const date = new Date(timestamp * 1000);
|
|
186
|
+
const dateString = date.toISOString();
|
|
187
|
+
const price = jitoSolHistoricalPriceInSol.find((p) => (0, utils_1.checkSameDate)(p.ts, dateString));
|
|
188
|
+
if (price) {
|
|
189
|
+
jitoSolHistoricalPriceMap.set(timestamp, price.price);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
return jitoSolHistoricalPriceMap;
|
|
193
|
+
}
|
|
194
|
+
catch (err) {
|
|
195
|
+
console.error(err);
|
|
196
|
+
return undefined;
|
|
197
|
+
}
|
|
198
|
+
};
|
|
199
|
+
async function calculateSolEarned({ marketIndex, user, depositRecords, }) {
|
|
66
200
|
const now = Date.now() / 1000;
|
|
67
201
|
const timestamps = [
|
|
68
202
|
now,
|
|
69
203
|
...depositRecords.map((r) => r.ts.toNumber()),
|
|
70
204
|
];
|
|
71
|
-
|
|
72
|
-
const
|
|
205
|
+
let lstRatios = new Map();
|
|
206
|
+
const getMsolPrice = async (timestamp) => {
|
|
73
207
|
const date = new Date(timestamp * 1000); // Convert Unix timestamp to milliseconds
|
|
74
208
|
const swaggerApiDateTime = date.toISOString(); // Format date as swagger API date-time
|
|
75
209
|
const url = `https://api.marinade.finance/msol/price_sol?time=${swaggerApiDateTime}`;
|
|
76
210
|
const response = await (0, node_fetch_1.default)(url);
|
|
77
211
|
if (response.status === 200) {
|
|
78
212
|
const data = await response.json();
|
|
79
|
-
|
|
213
|
+
lstRatios.set(timestamp, data);
|
|
80
214
|
}
|
|
81
215
|
};
|
|
82
|
-
|
|
216
|
+
if (marketIndex === 2) {
|
|
217
|
+
await Promise.all(timestamps.map(getMsolPrice));
|
|
218
|
+
}
|
|
219
|
+
else if (marketIndex === 6) {
|
|
220
|
+
lstRatios = await getJitoSolHistoricalPriceMap(timestamps);
|
|
221
|
+
}
|
|
83
222
|
let solEarned = numericConstants_1.ZERO;
|
|
84
223
|
for (const record of depositRecords) {
|
|
85
224
|
if (record.marketIndex === 1) {
|
|
@@ -91,7 +230,7 @@ async function calculateSolEarned({ user, depositRecords, }) {
|
|
|
91
230
|
}
|
|
92
231
|
}
|
|
93
232
|
else if (record.marketIndex === 2) {
|
|
94
|
-
const msolRatio =
|
|
233
|
+
const msolRatio = lstRatios.get(record.ts.toNumber());
|
|
95
234
|
const msolRatioBN = new anchor_1.BN(msolRatio * web3_js_1.LAMPORTS_PER_SOL);
|
|
96
235
|
const solAmount = record.amount.mul(msolRatioBN).div(numericConstants_1.LAMPORTS_PRECISION);
|
|
97
236
|
if ((0, types_1.isVariant)(record.direction, 'deposit')) {
|
|
@@ -101,21 +240,34 @@ async function calculateSolEarned({ user, depositRecords, }) {
|
|
|
101
240
|
solEarned = solEarned.add(solAmount);
|
|
102
241
|
}
|
|
103
242
|
}
|
|
243
|
+
else if (record.marketIndex === 6) {
|
|
244
|
+
const jitoSolRatio = lstRatios.get(record.ts.toNumber());
|
|
245
|
+
const jitoSolRatioBN = new anchor_1.BN(jitoSolRatio * web3_js_1.LAMPORTS_PER_SOL);
|
|
246
|
+
const solAmount = record.amount
|
|
247
|
+
.mul(jitoSolRatioBN)
|
|
248
|
+
.div(numericConstants_1.LAMPORTS_PRECISION);
|
|
249
|
+
if ((0, types_1.isVariant)(record.direction, 'deposit')) {
|
|
250
|
+
solEarned = solEarned.sub(solAmount);
|
|
251
|
+
}
|
|
252
|
+
else {
|
|
253
|
+
solEarned = solEarned.add(solAmount);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
104
256
|
}
|
|
105
|
-
const
|
|
257
|
+
const currentLstTokenAmount = await user.getTokenAmount(marketIndex);
|
|
258
|
+
const currentLstRatio = lstRatios.get(now);
|
|
259
|
+
const currentLstRatioBN = new anchor_1.BN(currentLstRatio * web3_js_1.LAMPORTS_PER_SOL);
|
|
260
|
+
solEarned = solEarned.add(currentLstTokenAmount.mul(currentLstRatioBN).div(numericConstants_1.LAMPORTS_PRECISION));
|
|
106
261
|
const currentSOLTokenAmount = await user.getTokenAmount(1);
|
|
107
|
-
const currentMSOLRatio = msolRatios.get(now);
|
|
108
|
-
const currentMSOLRatioBN = new anchor_1.BN(currentMSOLRatio * web3_js_1.LAMPORTS_PER_SOL);
|
|
109
|
-
solEarned = solEarned.add(currentMSOLTokenAmount.mul(currentMSOLRatioBN).div(numericConstants_1.LAMPORTS_PRECISION));
|
|
110
262
|
solEarned = solEarned.add(currentSOLTokenAmount);
|
|
111
263
|
return solEarned;
|
|
112
264
|
}
|
|
113
265
|
exports.calculateSolEarned = calculateSolEarned;
|
|
114
|
-
// calculate estimated liquidation price (in
|
|
115
|
-
function calculateEstimatedSuperStakeLiquidationPrice(
|
|
266
|
+
// calculate estimated liquidation price (in LST/SOL) based on target amounts
|
|
267
|
+
function calculateEstimatedSuperStakeLiquidationPrice(lstDepositAmount, lstMaintenanceAssetWeight, solBorrowAmount, solMaintenanceLiabilityWeight, lstPriceRatio) {
|
|
116
268
|
const liquidationDivergence = (solMaintenanceLiabilityWeight * solBorrowAmount) /
|
|
117
|
-
(
|
|
118
|
-
const liquidationPrice =
|
|
269
|
+
(lstMaintenanceAssetWeight * lstDepositAmount * lstPriceRatio);
|
|
270
|
+
const liquidationPrice = lstPriceRatio * liquidationDivergence;
|
|
119
271
|
return liquidationPrice;
|
|
120
272
|
}
|
|
121
273
|
exports.calculateEstimatedSuperStakeLiquidationPrice = calculateEstimatedSuperStakeLiquidationPrice;
|
package/lib/math/utils.d.ts
CHANGED
|
@@ -12,3 +12,4 @@ export declare const sigNum: (x: BN) => BN;
|
|
|
12
12
|
* @returns: timeRemainingUntilUpdate (in seconds)
|
|
13
13
|
*/
|
|
14
14
|
export declare function timeRemainingUntilUpdate(now: BN, lastUpdateTs: BN, updatePeriod: BN): BN;
|
|
15
|
+
export declare const checkSameDate: (dateString1: string, dateString2: string) => boolean;
|
package/lib/math/utils.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.timeRemainingUntilUpdate = exports.sigNum = exports.divCeil = exports.squareRootBN = exports.clampBN = void 0;
|
|
3
|
+
exports.checkSameDate = exports.timeRemainingUntilUpdate = exports.sigNum = exports.divCeil = exports.squareRootBN = exports.clampBN = void 0;
|
|
4
4
|
const __1 = require("../");
|
|
5
5
|
function clampBN(x, min, max) {
|
|
6
6
|
return __1.BN.max(min, __1.BN.min(x, max));
|
|
@@ -76,3 +76,12 @@ function timeRemainingUntilUpdate(now, lastUpdateTs, updatePeriod) {
|
|
|
76
76
|
return timeRemainingUntilUpdate;
|
|
77
77
|
}
|
|
78
78
|
exports.timeRemainingUntilUpdate = timeRemainingUntilUpdate;
|
|
79
|
+
const checkSameDate = (dateString1, dateString2) => {
|
|
80
|
+
const date1 = new Date(dateString1);
|
|
81
|
+
const date2 = new Date(dateString2);
|
|
82
|
+
const isSameDate = date1.getDate() === date2.getDate() &&
|
|
83
|
+
date1.getMonth() === date2.getMonth() &&
|
|
84
|
+
date1.getFullYear() === date2.getFullYear();
|
|
85
|
+
return isSameDate;
|
|
86
|
+
};
|
|
87
|
+
exports.checkSameDate = checkSameDate;
|
package/package.json
CHANGED
|
@@ -128,7 +128,7 @@ export const MainnetSpotMarkets: SpotMarketConfig[] = [
|
|
|
128
128
|
serumMarket: new PublicKey('B2na8Awyd7cpC59iEU43FagJAPLigr3AP3s38KM982bu'),
|
|
129
129
|
},
|
|
130
130
|
{
|
|
131
|
-
symbol: '
|
|
131
|
+
symbol: 'JitoSOL',
|
|
132
132
|
marketIndex: 6,
|
|
133
133
|
oracle: new PublicKey('7yyaeuJ1GGtVBLT2z2xub5ZWYKaNhF28mj1RdV4VDFVk'),
|
|
134
134
|
oracleSource: OracleSource.PYTH,
|
package/src/math/superStake.ts
CHANGED
|
@@ -10,22 +10,25 @@ import { getMarinadeFinanceProgram, getMarinadeMSolPrice } from '../marinade';
|
|
|
10
10
|
import { BN } from '@coral-xyz/anchor';
|
|
11
11
|
import { User } from '../user';
|
|
12
12
|
import { DepositRecord, isVariant } from '../types';
|
|
13
|
-
import { LAMPORTS_PRECISION, ZERO } from '../constants/numericConstants';
|
|
13
|
+
import { LAMPORTS_PRECISION, ZERO, NINE } from '../constants/numericConstants';
|
|
14
14
|
import fetch from 'node-fetch';
|
|
15
|
+
import { checkSameDate } from './utils';
|
|
15
16
|
|
|
16
17
|
export async function findBestSuperStakeIxs({
|
|
18
|
+
marketIndex,
|
|
17
19
|
amount,
|
|
18
20
|
jupiterClient,
|
|
19
21
|
driftClient,
|
|
20
22
|
userAccountPublicKey,
|
|
21
|
-
|
|
23
|
+
price,
|
|
22
24
|
forceMarinade,
|
|
23
25
|
onlyDirectRoutes,
|
|
24
26
|
}: {
|
|
27
|
+
marketIndex: number;
|
|
25
28
|
amount: BN;
|
|
26
29
|
jupiterClient: JupiterClient;
|
|
27
30
|
driftClient: DriftClient;
|
|
28
|
-
|
|
31
|
+
price?: number;
|
|
29
32
|
userAccountPublicKey?: PublicKey;
|
|
30
33
|
forceMarinade?: boolean;
|
|
31
34
|
onlyDirectRoutes?: boolean;
|
|
@@ -35,9 +38,54 @@ export async function findBestSuperStakeIxs({
|
|
|
35
38
|
method: 'jupiter' | 'marinade';
|
|
36
39
|
price: number;
|
|
37
40
|
}> {
|
|
38
|
-
if (
|
|
41
|
+
if (marketIndex === 2) {
|
|
42
|
+
return findBestMSolSuperStakeIxs({
|
|
43
|
+
amount,
|
|
44
|
+
jupiterClient,
|
|
45
|
+
driftClient,
|
|
46
|
+
userAccountPublicKey,
|
|
47
|
+
price,
|
|
48
|
+
forceMarinade,
|
|
49
|
+
onlyDirectRoutes,
|
|
50
|
+
});
|
|
51
|
+
} else if (marketIndex === 6) {
|
|
52
|
+
return findBestJitoSolSuperStakeIxs({
|
|
53
|
+
amount,
|
|
54
|
+
jupiterClient,
|
|
55
|
+
driftClient,
|
|
56
|
+
userAccountPublicKey,
|
|
57
|
+
onlyDirectRoutes,
|
|
58
|
+
});
|
|
59
|
+
} else {
|
|
60
|
+
throw new Error(`Unsupported superstake market index: ${marketIndex}`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export async function findBestMSolSuperStakeIxs({
|
|
65
|
+
amount,
|
|
66
|
+
jupiterClient,
|
|
67
|
+
driftClient,
|
|
68
|
+
userAccountPublicKey,
|
|
69
|
+
price,
|
|
70
|
+
forceMarinade,
|
|
71
|
+
onlyDirectRoutes,
|
|
72
|
+
}: {
|
|
73
|
+
amount: BN;
|
|
74
|
+
jupiterClient: JupiterClient;
|
|
75
|
+
driftClient: DriftClient;
|
|
76
|
+
price?: number;
|
|
77
|
+
userAccountPublicKey?: PublicKey;
|
|
78
|
+
forceMarinade?: boolean;
|
|
79
|
+
onlyDirectRoutes?: boolean;
|
|
80
|
+
}): Promise<{
|
|
81
|
+
ixs: TransactionInstruction[];
|
|
82
|
+
lookupTables: AddressLookupTableAccount[];
|
|
83
|
+
method: 'jupiter' | 'marinade';
|
|
84
|
+
price: number;
|
|
85
|
+
}> {
|
|
86
|
+
if (!price) {
|
|
39
87
|
const marinadeProgram = getMarinadeFinanceProgram(driftClient.provider);
|
|
40
|
-
|
|
88
|
+
price = await getMarinadeMSolPrice(marinadeProgram);
|
|
41
89
|
}
|
|
42
90
|
|
|
43
91
|
const solMint = driftClient.getSpotMarketAccount(1).mint;
|
|
@@ -59,7 +107,7 @@ export async function findBestSuperStakeIxs({
|
|
|
59
107
|
console.error('Error getting jupiter price', e);
|
|
60
108
|
}
|
|
61
109
|
|
|
62
|
-
if (!jupiterPrice ||
|
|
110
|
+
if (!jupiterPrice || price <= jupiterPrice || forceMarinade) {
|
|
63
111
|
const ixs = await driftClient.getStakeForMSOLIx({
|
|
64
112
|
amount,
|
|
65
113
|
userAccountPublicKey,
|
|
@@ -68,7 +116,7 @@ export async function findBestSuperStakeIxs({
|
|
|
68
116
|
method: 'marinade',
|
|
69
117
|
ixs,
|
|
70
118
|
lookupTables: [],
|
|
71
|
-
price:
|
|
119
|
+
price: price,
|
|
72
120
|
};
|
|
73
121
|
} else {
|
|
74
122
|
const { ixs, lookupTables } = await driftClient.getJupiterSwapIx({
|
|
@@ -88,10 +136,170 @@ export async function findBestSuperStakeIxs({
|
|
|
88
136
|
}
|
|
89
137
|
}
|
|
90
138
|
|
|
139
|
+
export async function findBestJitoSolSuperStakeIxs({
|
|
140
|
+
amount,
|
|
141
|
+
jupiterClient,
|
|
142
|
+
driftClient,
|
|
143
|
+
userAccountPublicKey,
|
|
144
|
+
onlyDirectRoutes,
|
|
145
|
+
}: {
|
|
146
|
+
amount: BN;
|
|
147
|
+
jupiterClient: JupiterClient;
|
|
148
|
+
driftClient: DriftClient;
|
|
149
|
+
userAccountPublicKey?: PublicKey;
|
|
150
|
+
onlyDirectRoutes?: boolean;
|
|
151
|
+
}): Promise<{
|
|
152
|
+
ixs: TransactionInstruction[];
|
|
153
|
+
lookupTables: AddressLookupTableAccount[];
|
|
154
|
+
method: 'jupiter' | 'marinade';
|
|
155
|
+
price: number;
|
|
156
|
+
}> {
|
|
157
|
+
const solMint = driftClient.getSpotMarketAccount(1).mint;
|
|
158
|
+
const JitoSolMint = driftClient.getSpotMarketAccount(6).mint;
|
|
159
|
+
|
|
160
|
+
let jupiterPrice;
|
|
161
|
+
let bestRoute;
|
|
162
|
+
try {
|
|
163
|
+
const jupiterRoutes = await jupiterClient.getRoutes({
|
|
164
|
+
inputMint: solMint,
|
|
165
|
+
outputMint: JitoSolMint,
|
|
166
|
+
amount,
|
|
167
|
+
onlyDirectRoutes,
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
bestRoute = jupiterRoutes[0];
|
|
171
|
+
jupiterPrice = bestRoute.inAmount / bestRoute.outAmount;
|
|
172
|
+
} catch (e) {
|
|
173
|
+
console.error('Error getting jupiter price', e);
|
|
174
|
+
throw e;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const { ixs, lookupTables } = await driftClient.getJupiterSwapIx({
|
|
178
|
+
inMarketIndex: 1,
|
|
179
|
+
outMarketIndex: 6,
|
|
180
|
+
route: bestRoute,
|
|
181
|
+
jupiterClient,
|
|
182
|
+
amount,
|
|
183
|
+
userAccountPublicKey,
|
|
184
|
+
});
|
|
185
|
+
return {
|
|
186
|
+
method: 'jupiter',
|
|
187
|
+
ixs,
|
|
188
|
+
lookupTables,
|
|
189
|
+
price: jupiterPrice,
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export type JITO_SOL_METRICS_ENDPOINT_RESPONSE = {
|
|
194
|
+
data: {
|
|
195
|
+
getStakePoolStats: {
|
|
196
|
+
tvl: {
|
|
197
|
+
// TVL in SOL, BN
|
|
198
|
+
data: number;
|
|
199
|
+
date: string;
|
|
200
|
+
}[];
|
|
201
|
+
supply: {
|
|
202
|
+
// jitoSOL supply
|
|
203
|
+
data: number;
|
|
204
|
+
date: string;
|
|
205
|
+
}[];
|
|
206
|
+
apy: {
|
|
207
|
+
data: number;
|
|
208
|
+
date: string;
|
|
209
|
+
}[];
|
|
210
|
+
};
|
|
211
|
+
};
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
const JITO_SOL_START_DATE = '2022-10-31T00:00:00Z';
|
|
215
|
+
|
|
216
|
+
export async function fetchJitoSolMetrics() {
|
|
217
|
+
const res = await fetch('https://kobe.mainnet.jito.network/', {
|
|
218
|
+
body: JSON.stringify({
|
|
219
|
+
operationName: 'QueryRoot',
|
|
220
|
+
variables: {
|
|
221
|
+
request: {
|
|
222
|
+
bucketType: 'DAILY',
|
|
223
|
+
rangeFilter: {
|
|
224
|
+
start: JITO_SOL_START_DATE,
|
|
225
|
+
end: new Date().toISOString(),
|
|
226
|
+
},
|
|
227
|
+
sortBy: {
|
|
228
|
+
order: 'ASC',
|
|
229
|
+
field: 'BLOCK_TIME',
|
|
230
|
+
},
|
|
231
|
+
},
|
|
232
|
+
},
|
|
233
|
+
query: `
|
|
234
|
+
query QueryRoot($request: GetStakePoolStatsRequest!) {
|
|
235
|
+
getStakePoolStats(req: $request) {
|
|
236
|
+
tvl {
|
|
237
|
+
data
|
|
238
|
+
date
|
|
239
|
+
}
|
|
240
|
+
apy {
|
|
241
|
+
data
|
|
242
|
+
date
|
|
243
|
+
}
|
|
244
|
+
supply {
|
|
245
|
+
data
|
|
246
|
+
date
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
`,
|
|
251
|
+
}),
|
|
252
|
+
method: 'POST',
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
const data: JITO_SOL_METRICS_ENDPOINT_RESPONSE = await res.json();
|
|
256
|
+
|
|
257
|
+
return data;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const getJitoSolHistoricalPriceMap = async (timestamps: number[]) => {
|
|
261
|
+
try {
|
|
262
|
+
const data = await fetchJitoSolMetrics();
|
|
263
|
+
const jitoSolHistoricalPriceMap = new Map<number, number>();
|
|
264
|
+
const jitoSolHistoricalPriceInSol = [];
|
|
265
|
+
|
|
266
|
+
for (let i = 0; i < data.data.getStakePoolStats.supply.length; i++) {
|
|
267
|
+
const priceInSol =
|
|
268
|
+
data.data.getStakePoolStats.tvl[i].data /
|
|
269
|
+
new BN(10).pow(NINE) /
|
|
270
|
+
data.data.getStakePoolStats.supply[i].data;
|
|
271
|
+
jitoSolHistoricalPriceInSol.push({
|
|
272
|
+
price: priceInSol,
|
|
273
|
+
ts: data.data.getStakePoolStats.tvl[i].date,
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
for (const timestamp of timestamps) {
|
|
278
|
+
const date = new Date(timestamp * 1000);
|
|
279
|
+
const dateString = date.toISOString();
|
|
280
|
+
|
|
281
|
+
const price = jitoSolHistoricalPriceInSol.find((p) =>
|
|
282
|
+
checkSameDate(p.ts, dateString)
|
|
283
|
+
);
|
|
284
|
+
|
|
285
|
+
if (price) {
|
|
286
|
+
jitoSolHistoricalPriceMap.set(timestamp, price.price);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
return jitoSolHistoricalPriceMap;
|
|
291
|
+
} catch (err) {
|
|
292
|
+
console.error(err);
|
|
293
|
+
return undefined;
|
|
294
|
+
}
|
|
295
|
+
};
|
|
296
|
+
|
|
91
297
|
export async function calculateSolEarned({
|
|
298
|
+
marketIndex,
|
|
92
299
|
user,
|
|
93
300
|
depositRecords,
|
|
94
301
|
}: {
|
|
302
|
+
marketIndex: number;
|
|
95
303
|
user: User;
|
|
96
304
|
depositRecords: DepositRecord[];
|
|
97
305
|
}): Promise<BN> {
|
|
@@ -101,20 +309,24 @@ export async function calculateSolEarned({
|
|
|
101
309
|
...depositRecords.map((r) => r.ts.toNumber()),
|
|
102
310
|
];
|
|
103
311
|
|
|
104
|
-
|
|
312
|
+
let lstRatios = new Map<number, number>();
|
|
105
313
|
|
|
106
|
-
const
|
|
314
|
+
const getMsolPrice = async (timestamp) => {
|
|
107
315
|
const date = new Date(timestamp * 1000); // Convert Unix timestamp to milliseconds
|
|
108
316
|
const swaggerApiDateTime = date.toISOString(); // Format date as swagger API date-time
|
|
109
317
|
const url = `https://api.marinade.finance/msol/price_sol?time=${swaggerApiDateTime}`;
|
|
110
318
|
const response = await fetch(url);
|
|
111
319
|
if (response.status === 200) {
|
|
112
320
|
const data = await response.json();
|
|
113
|
-
|
|
321
|
+
lstRatios.set(timestamp, data);
|
|
114
322
|
}
|
|
115
323
|
};
|
|
116
324
|
|
|
117
|
-
|
|
325
|
+
if (marketIndex === 2) {
|
|
326
|
+
await Promise.all(timestamps.map(getMsolPrice));
|
|
327
|
+
} else if (marketIndex === 6) {
|
|
328
|
+
lstRatios = await getJitoSolHistoricalPriceMap(timestamps);
|
|
329
|
+
}
|
|
118
330
|
|
|
119
331
|
let solEarned = ZERO;
|
|
120
332
|
for (const record of depositRecords) {
|
|
@@ -125,7 +337,7 @@ export async function calculateSolEarned({
|
|
|
125
337
|
solEarned = solEarned.add(record.amount);
|
|
126
338
|
}
|
|
127
339
|
} else if (record.marketIndex === 2) {
|
|
128
|
-
const msolRatio =
|
|
340
|
+
const msolRatio = lstRatios.get(record.ts.toNumber());
|
|
129
341
|
const msolRatioBN = new BN(msolRatio * LAMPORTS_PER_SOL);
|
|
130
342
|
|
|
131
343
|
const solAmount = record.amount.mul(msolRatioBN).div(LAMPORTS_PRECISION);
|
|
@@ -134,34 +346,46 @@ export async function calculateSolEarned({
|
|
|
134
346
|
} else {
|
|
135
347
|
solEarned = solEarned.add(solAmount);
|
|
136
348
|
}
|
|
349
|
+
} else if (record.marketIndex === 6) {
|
|
350
|
+
const jitoSolRatio = lstRatios.get(record.ts.toNumber());
|
|
351
|
+
const jitoSolRatioBN = new BN(jitoSolRatio * LAMPORTS_PER_SOL);
|
|
352
|
+
|
|
353
|
+
const solAmount = record.amount
|
|
354
|
+
.mul(jitoSolRatioBN)
|
|
355
|
+
.div(LAMPORTS_PRECISION);
|
|
356
|
+
if (isVariant(record.direction, 'deposit')) {
|
|
357
|
+
solEarned = solEarned.sub(solAmount);
|
|
358
|
+
} else {
|
|
359
|
+
solEarned = solEarned.add(solAmount);
|
|
360
|
+
}
|
|
137
361
|
}
|
|
138
362
|
}
|
|
139
363
|
|
|
140
|
-
const
|
|
141
|
-
const
|
|
142
|
-
|
|
143
|
-
const currentMSOLRatio = msolRatios.get(now);
|
|
144
|
-
const currentMSOLRatioBN = new BN(currentMSOLRatio * LAMPORTS_PER_SOL);
|
|
364
|
+
const currentLstTokenAmount = await user.getTokenAmount(marketIndex);
|
|
365
|
+
const currentLstRatio = lstRatios.get(now);
|
|
366
|
+
const currentLstRatioBN = new BN(currentLstRatio * LAMPORTS_PER_SOL);
|
|
145
367
|
|
|
146
368
|
solEarned = solEarned.add(
|
|
147
|
-
|
|
369
|
+
currentLstTokenAmount.mul(currentLstRatioBN).div(LAMPORTS_PRECISION)
|
|
148
370
|
);
|
|
371
|
+
|
|
372
|
+
const currentSOLTokenAmount = await user.getTokenAmount(1);
|
|
149
373
|
solEarned = solEarned.add(currentSOLTokenAmount);
|
|
150
374
|
|
|
151
375
|
return solEarned;
|
|
152
376
|
}
|
|
153
377
|
|
|
154
|
-
// calculate estimated liquidation price (in
|
|
378
|
+
// calculate estimated liquidation price (in LST/SOL) based on target amounts
|
|
155
379
|
export function calculateEstimatedSuperStakeLiquidationPrice(
|
|
156
|
-
|
|
157
|
-
|
|
380
|
+
lstDepositAmount: number,
|
|
381
|
+
lstMaintenanceAssetWeight: number,
|
|
158
382
|
solBorrowAmount: number,
|
|
159
383
|
solMaintenanceLiabilityWeight: number,
|
|
160
|
-
|
|
384
|
+
lstPriceRatio: number
|
|
161
385
|
): number {
|
|
162
386
|
const liquidationDivergence =
|
|
163
387
|
(solMaintenanceLiabilityWeight * solBorrowAmount) /
|
|
164
|
-
(
|
|
165
|
-
const liquidationPrice =
|
|
388
|
+
(lstMaintenanceAssetWeight * lstDepositAmount * lstPriceRatio);
|
|
389
|
+
const liquidationPrice = lstPriceRatio * liquidationDivergence;
|
|
166
390
|
return liquidationPrice;
|
|
167
391
|
}
|
package/src/math/utils.ts
CHANGED
|
@@ -83,3 +83,15 @@ export function timeRemainingUntilUpdate(
|
|
|
83
83
|
|
|
84
84
|
return timeRemainingUntilUpdate;
|
|
85
85
|
}
|
|
86
|
+
|
|
87
|
+
export const checkSameDate = (dateString1: string, dateString2: string) => {
|
|
88
|
+
const date1 = new Date(dateString1);
|
|
89
|
+
const date2 = new Date(dateString2);
|
|
90
|
+
|
|
91
|
+
const isSameDate =
|
|
92
|
+
date1.getDate() === date2.getDate() &&
|
|
93
|
+
date1.getMonth() === date2.getMonth() &&
|
|
94
|
+
date1.getFullYear() === date2.getFullYear();
|
|
95
|
+
|
|
96
|
+
return isSameDate;
|
|
97
|
+
};
|