@drift-labs/sdk
Version:
SDK for Drift Protocol
307 lines (306 loc) • 12.2 kB
JavaScript
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;
;