UNPKG

@drift-labs/sdk

Version:
307 lines (306 loc) • 12.2 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.calculateEstimatedSuperStakeLiquidationPrice = exports.calculateSolEarned = exports.fetchMSolMetrics = exports.fetchJitoSolMetrics = exports.findBestLstSuperStakeIxs = exports.findBestJitoSolSuperStakeIxs = exports.findBestMSolSuperStakeIxs = exports.findBestSuperStakeIxs = exports.fetchBSolDriftEmissions = exports.fetchBSolMetrics = void 0; const web3_js_1 = require("@solana/web3.js"); const marinade_1 = require("../marinade"); const anchor_1 = require("@coral-xyz/anchor"); const types_1 = require("../types"); const numericConstants_1 = require("../constants/numericConstants"); const node_fetch_1 = __importDefault(require("node-fetch")); const utils_1 = require("./utils"); async function fetchBSolMetrics() { return await (0, node_fetch_1.default)('https://stake.solblaze.org/api/v1/stats'); } exports.fetchBSolMetrics = fetchBSolMetrics; async function fetchBSolDriftEmissions() { return await (0, node_fetch_1.default)('https://stake.solblaze.org/api/v1/drift_emissions'); } exports.fetchBSolDriftEmissions = fetchBSolDriftEmissions; async function findBestSuperStakeIxs({ marketIndex, amount, jupiterClient, driftClient, userAccountPublicKey, price, forceMarinade, onlyDirectRoutes, jupiterQuote, }) { if (marketIndex === 2) { return findBestMSolSuperStakeIxs({ amount, jupiterClient, driftClient, userAccountPublicKey, price, forceMarinade, onlyDirectRoutes, jupiterQuote, }); } else if (marketIndex === 6) { return findBestJitoSolSuperStakeIxs({ amount, jupiterClient, driftClient, userAccountPublicKey, onlyDirectRoutes, jupiterQuote, }); } else if (marketIndex === 8) { return findBestLstSuperStakeIxs({ amount, lstMint: driftClient.getSpotMarketAccount(8).mint, lstMarketIndex: 8, jupiterClient, driftClient, userAccountPublicKey, onlyDirectRoutes, jupiterQuote, }); } else { throw new Error(`Unsupported superstake market index: ${marketIndex}`); } } exports.findBestSuperStakeIxs = findBestSuperStakeIxs; async function findBestMSolSuperStakeIxs({ amount, jupiterClient, driftClient, userAccountPublicKey, price, forceMarinade, onlyDirectRoutes, jupiterQuote, }) { if (!price) { const marinadeProgram = (0, marinade_1.getMarinadeFinanceProgram)(driftClient.provider); price = await (0, marinade_1.getMarinadeMSolPrice)(marinadeProgram); } const solSpotMarketAccount = driftClient.getSpotMarketAccount(1); const mSolSpotMarketAccount = driftClient.getSpotMarketAccount(2); let jupiterPrice; let quote = jupiterQuote; if (!jupiterQuote) { try { const fetchedQuote = await jupiterClient.getQuote({ inputMint: solSpotMarketAccount.mint, outputMint: mSolSpotMarketAccount.mint, amount, slippageBps: 1000, onlyDirectRoutes, }); jupiterPrice = +quote.outAmount / +quote.inAmount; quote = fetchedQuote; } catch (e) { console.error('Error getting jupiter price', e); } } if (!jupiterPrice || price <= jupiterPrice || forceMarinade) { const ixs = await driftClient.getStakeForMSOLIx({ amount, userAccountPublicKey, }); return { method: 'marinade', ixs, lookupTables: [], price: price, }; } else { const { ixs, lookupTables } = await driftClient.getJupiterSwapIxV6({ inMarketIndex: 1, outMarketIndex: 2, jupiterClient, amount, userAccountPublicKey, onlyDirectRoutes, quote, }); return { method: 'jupiter', ixs, lookupTables, price: jupiterPrice, }; } } exports.findBestMSolSuperStakeIxs = findBestMSolSuperStakeIxs; async function findBestJitoSolSuperStakeIxs({ amount, jupiterClient, driftClient, userAccountPublicKey, onlyDirectRoutes, jupiterQuote, }) { return await findBestLstSuperStakeIxs({ amount, jupiterClient, driftClient, userAccountPublicKey, onlyDirectRoutes, lstMint: driftClient.getSpotMarketAccount(6).mint, lstMarketIndex: 6, jupiterQuote, }); } exports.findBestJitoSolSuperStakeIxs = findBestJitoSolSuperStakeIxs; /** * Finds best Jupiter Swap instructions for a generic lstMint * * Without doing any extra steps like checking if you can get a better rate by staking directly with that LST platform */ async function findBestLstSuperStakeIxs({ amount, jupiterClient, driftClient, userAccountPublicKey, onlyDirectRoutes, lstMarketIndex, jupiterQuote, }) { const { ixs, lookupTables } = await driftClient.getJupiterSwapIxV6({ inMarketIndex: 1, outMarketIndex: lstMarketIndex, jupiterClient, amount, userAccountPublicKey, onlyDirectRoutes, quote: jupiterQuote, }); return { method: 'jupiter', ixs, lookupTables, // price: jupiterPrice, }; } exports.findBestLstSuperStakeIxs = findBestLstSuperStakeIxs; /** * Removes hours, minutes, seconds from a date, and returns the ISO string value (with milliseconds trimmed from the output (required by Jito API)) * @param inDate * @returns */ const getNormalizedDateString = (inDate) => { const date = new Date(inDate.getTime()); date.setUTCHours(0, 0, 0, 0); return date.toISOString().slice(0, 19) + 'Z'; }; const get30DAgo = () => { const date = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000); return date; }; async function fetchJitoSolMetrics() { const res = await (0, node_fetch_1.default)('https://kobe.mainnet.jito.network/api/v1/stake_pool_stats', { headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ bucket_type: 'Daily', range_filter: { start: getNormalizedDateString(get30DAgo()), end: getNormalizedDateString(new Date()), }, sort_by: { order: 'Asc', field: 'BlockTime', }, }), method: 'POST', }); const data = await res.json(); return data; } exports.fetchJitoSolMetrics = fetchJitoSolMetrics; const fetchMSolMetrics = async () => { const res = await (0, node_fetch_1.default)('https://api2.marinade.finance/metrics_json'); const data = await res.json(); return data; }; exports.fetchMSolMetrics = fetchMSolMetrics; const getJitoSolHistoricalPriceMap = async (timestamps) => { try { const data = await fetchJitoSolMetrics(); const jitoSolHistoricalPriceMap = new Map(); const jitoSolHistoricalPriceInSol = []; for (let i = 0; i < data.supply.length; i++) { const priceInSol = data.tvl[i].data / 10 ** 9 / data.supply[i].data; jitoSolHistoricalPriceInSol.push({ price: priceInSol, ts: data.tvl[i].date, }); } for (const timestamp of timestamps) { const date = new Date(timestamp * 1000); const dateString = date.toISOString(); const price = jitoSolHistoricalPriceInSol.find((p) => (0, utils_1.checkSameDate)(p.ts, dateString)); if (price) { jitoSolHistoricalPriceMap.set(timestamp, price.price); } } return jitoSolHistoricalPriceMap; } catch (err) { console.error(err); return undefined; } }; async function calculateSolEarned({ marketIndex, user, depositRecords, }) { const now = Date.now() / 1000; const timestamps = [ now, ...depositRecords .filter((r) => r.marketIndex === marketIndex) .map((r) => r.ts.toNumber()), ]; let lstRatios = new Map(); const getMsolPrice = async (timestamp) => { const date = new Date(timestamp * 1000); // Convert Unix timestamp to milliseconds const swaggerApiDateTime = date.toISOString(); // Format date as swagger API date-time const url = `https://api.marinade.finance/msol/price_sol?time=${swaggerApiDateTime}`; const response = await (0, node_fetch_1.default)(url); if (response.status === 200) { const data = await response.json(); lstRatios.set(timestamp, data); } }; const getBSolPrice = async (timestamps) => { var _a, _b; // Currently there's only one bSOL price, no timestamped data // So just use the same price for every timestamp for now const response = await fetchBSolMetrics(); if (response.status === 200) { const data = (await response.json()); const bSolRatio = (_b = (_a = data === null || data === void 0 ? void 0 : data.stats) === null || _a === void 0 ? void 0 : _a.conversion) === null || _b === void 0 ? void 0 : _b.bsol_to_sol; if (bSolRatio) { timestamps.forEach((timestamp) => lstRatios.set(timestamp, bSolRatio)); } } }; // This block kind of assumes the record are all from the same market // Otherwise the following code that checks the record.marketIndex would break if (marketIndex === 2) { await Promise.all(timestamps.map(getMsolPrice)); } else if (marketIndex === 6) { lstRatios = await getJitoSolHistoricalPriceMap(timestamps); } else if (marketIndex === 8) { await getBSolPrice(timestamps); } let solEarned = numericConstants_1.ZERO; for (const record of depositRecords) { if (record.marketIndex === 1) { if ((0, types_1.isVariant)(record.direction, 'deposit')) { solEarned = solEarned.sub(record.amount); } else { solEarned = solEarned.add(record.amount); } } else if (record.marketIndex === 2 || record.marketIndex === 6 || record.marketIndex === 8) { const lstRatio = lstRatios.get(record.ts.toNumber()); const lstRatioBN = new anchor_1.BN(lstRatio * web3_js_1.LAMPORTS_PER_SOL); const solAmount = record.amount.mul(lstRatioBN).div(numericConstants_1.LAMPORTS_PRECISION); if ((0, types_1.isVariant)(record.direction, 'deposit')) { solEarned = solEarned.sub(solAmount); } else { solEarned = solEarned.add(solAmount); } } } const currentLstTokenAmount = await user.getTokenAmount(marketIndex); const currentLstRatio = lstRatios.get(now); const currentLstRatioBN = new anchor_1.BN(currentLstRatio * web3_js_1.LAMPORTS_PER_SOL); solEarned = solEarned.add(currentLstTokenAmount.mul(currentLstRatioBN).div(numericConstants_1.LAMPORTS_PRECISION)); const currentSOLTokenAmount = await user.getTokenAmount(1); solEarned = solEarned.add(currentSOLTokenAmount); return solEarned; } exports.calculateSolEarned = calculateSolEarned; // calculate estimated liquidation price (in LST/SOL) based on target amounts function calculateEstimatedSuperStakeLiquidationPrice(lstDepositAmount, lstMaintenanceAssetWeight, solBorrowAmount, solMaintenanceLiabilityWeight, lstPriceRatio) { const liquidationDivergence = (solMaintenanceLiabilityWeight * solBorrowAmount) / (lstMaintenanceAssetWeight * lstDepositAmount * lstPriceRatio); const liquidationPrice = lstPriceRatio * liquidationDivergence; return liquidationPrice; } exports.calculateEstimatedSuperStakeLiquidationPrice = calculateEstimatedSuperStakeLiquidationPrice;