UNPKG

@kamino-finance/kliquidity-sdk

Version:

Typescript SDK for interacting with the Kamino Liquidity (kliquidity) protocol

254 lines 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.ZERO_APR = exports.defaultSlippagePercentageBPS = void 0; exports.getTickArray = getTickArray; exports.getTokenAFromLiquidity = getTokenAFromLiquidity; exports.getTokenBFromLiquidity = getTokenBFromLiquidity; exports.getNearestValidTickIndexFromTickIndex = getNearestValidTickIndexFromTickIndex; exports.getIncreaseLiquidityQuote = getIncreaseLiquidityQuote; exports.adjustForSlippage = adjustForSlippage; exports.estimateAprsForPriceRange = estimateAprsForPriceRange; exports.getLowestInitializedTickArrayTickIndex = getLowestInitializedTickArrayTickIndex; exports.getHighestInitializedTickArrayTickIndex = getHighestInitializedTickArrayTickIndex; exports.getLiquidityDistribution = getLiquidityDistribution; exports.getTickArrayPda = getTickArrayPda; const anchor_1 = require("@coral-xyz/anchor"); const kit_1 = require("@solana/kit"); const constants_1 = require("../constants"); const whirlpools_1 = require("@orca-so/whirlpools"); const accounts_1 = require("../@codegen/whirlpools/accounts"); const whirlpools_core_1 = require("@orca-so/whirlpools-core"); const whirlpools_core_2 = require("@orca-so/whirlpools-core"); const decimal_1 = __importDefault(require("decimal.js/decimal")); const whirlpools_2 = require("./whirlpools"); const CreationParameters_1 = require("./CreationParameters"); const whirlpools_client_1 = require("@orca-so/whirlpools-client"); const utils_1 = require("./utils"); const math_1 = require("./math"); exports.defaultSlippagePercentageBPS = 10; const addressEncoder = (0, kit_1.getAddressEncoder)(); const TICK_ARRAY_SIZE = 88; const SECONDS_PER_YEAR = 60 * // SECONDS 60 * // MINUTES 24 * // HOURS 365; // DAYS function estimateRewardApr(reward, rewardTokenDecimals, concentratedValue, tokenPrices) { const { mint } = reward; const rewardTokenPrice = tokenPrices.get((0, kit_1.address)(mint)); const emissionsPerSecond = new decimal_1.default(reward.emissionsPerSecond).div(new decimal_1.default(10).pow(rewardTokenDecimals)); if (emissionsPerSecond.eq(math_1.ZERO) || !rewardTokenPrice) { return 0; } const res = emissionsPerSecond.mul(SECONDS_PER_YEAR).mul(rewardTokenPrice).div(concentratedValue).toNumber(); return res; } async function getTickArray(programId, whirlpoolAddress, startTick) { return await (0, kit_1.getProgramDerivedAddress)({ seeds: [Buffer.from('tick_array'), addressEncoder.encode(whirlpoolAddress), Buffer.from(startTick.toString())], programAddress: programId, }); } function getTokenAFromLiquidity(liquidity, sqrtPrice0X64, sqrtPrice1X64, roundUp) { const [sqrtPriceLowerX64, sqrtPriceUpperX64] = orderSqrtPrice(sqrtPrice0X64, sqrtPrice1X64); const numerator = liquidity.mul(sqrtPriceUpperX64.sub(sqrtPriceLowerX64)).shln(64); const denominator = sqrtPriceUpperX64.mul(sqrtPriceLowerX64); if (roundUp) { return divRoundUp(numerator, denominator); } else { return numerator.div(denominator); } } function getTokenBFromLiquidity(liquidity, sqrtPrice0X64, sqrtPrice1X64, roundUp) { const [sqrtPriceLowerX64, sqrtPriceUpperX64] = orderSqrtPrice(sqrtPrice0X64, sqrtPrice1X64); const result = liquidity.mul(sqrtPriceUpperX64.sub(sqrtPriceLowerX64)); if (roundUp) { return shiftRightRoundUp(result); } else { return result.shrn(64); } } function getNearestValidTickIndexFromTickIndex(tickIndex, tickSpacing) { return tickIndex - (tickIndex % tickSpacing); } function getIncreaseLiquidityQuote(param, pool, tickLowerIndex, tickUpperIndex, slippageToleranceBps, transferFeeA, transferFeeB) { if ('liquidity' in param) { return (0, whirlpools_core_2.increaseLiquidityQuote)(param.liquidity, slippageToleranceBps, BigInt(pool.sqrtPrice.toString()), tickLowerIndex, tickUpperIndex, transferFeeA, transferFeeB); } else if ('tokenA' in param) { return (0, whirlpools_core_2.increaseLiquidityQuoteA)(param.tokenA, slippageToleranceBps, BigInt(pool.sqrtPrice.toString()), tickLowerIndex, tickUpperIndex, transferFeeA, transferFeeB); } else { return (0, whirlpools_core_2.increaseLiquidityQuoteB)(param.tokenB, slippageToleranceBps, BigInt(pool.sqrtPrice.toString()), tickLowerIndex, tickUpperIndex, transferFeeA, transferFeeB); } } function orderSqrtPrice(sqrtPrice0X64, sqrtPrice1X64) { if (sqrtPrice0X64.lt(sqrtPrice1X64)) { return [sqrtPrice0X64, sqrtPrice1X64]; } else { return [sqrtPrice1X64, sqrtPrice0X64]; } } function shiftRightRoundUp(n) { let result = n.shrn(64); if (n.mod(new anchor_1.BN(constants_1.U64_MAX)).gt(constants_1.ZERO_BN)) { result = result.add(constants_1.ONE_BN); } return result; } function divRoundUp(n0, n1) { const hasRemainder = !n0.mod(n1).eq(constants_1.ZERO_BN); if (hasRemainder) { return n0.div(n1).add(constants_1.ONE_BN); } else { return n0.div(n1); } } function adjustForSlippage(n, { numerator, denominator }, adjustUp) { if (adjustUp) { return n.mul(denominator.add(numerator)).div(denominator); } else { return n.mul(denominator).div(denominator.add(numerator)); } } exports.ZERO_APR = { fee: 0, rewards: [0, 0, 0], }; function estimateAprsForPriceRange(pool, tokenPrices, fees24h, tickLowerIndex, tickUpperIndex, rewardsDecimals) { const tokenPriceA = tokenPrices.get((0, kit_1.address)(pool.tokenMintA)); const tokenPriceB = tokenPrices.get((0, kit_1.address)(pool.tokenMintB)); if (!fees24h || !tokenPriceA || !tokenPriceB || tickLowerIndex >= tickUpperIndex) { return exports.ZERO_APR; } // Value of liquidity if the entire liquidity were concentrated between tickLower/Upper // Since this is virtual liquidity, concentratedValue should actually be less than totalValue const { minTokenA, minTokenB } = (0, whirlpools_2.getRemoveLiquidityQuote)({ positionAddress: whirlpools_1.DEFAULT_ADDRESS, tickCurrentIndex: pool.tickCurrentIndex, sqrtPrice: new anchor_1.BN(pool.sqrtPrice.toString()), tickLowerIndex, tickUpperIndex, liquidity: new anchor_1.BN(pool.liquidity.toString()), slippageTolerance: { numerator: constants_1.ZERO_BN, denominator: new anchor_1.BN(CreationParameters_1.FullBPS) }, }); const tokenValueA = getTokenValue(minTokenA, pool.tokenA.decimals, tokenPriceA); const tokenValueB = getTokenValue(minTokenB, pool.tokenB.decimals, tokenPriceB); const concentratedValue = tokenValueA.add(tokenValueB); const feesPerYear = new decimal_1.default(fees24h).mul(365).div(new decimal_1.default(10).pow(6)); // scale from lamports of USDC to tokens const feeApr = feesPerYear.div(concentratedValue).toNumber(); const rewards = pool.rewards.map((reward) => { if (rewardsDecimals.has((0, kit_1.address)(reward.mint))) { return estimateRewardApr(reward, rewardsDecimals.get((0, kit_1.address)(reward.mint)), concentratedValue, tokenPrices); } else { return 0; } }); return { fee: feeApr, rewards }; } function getTokenValue(tokenAmount, decimals, tokenPrice) { return tokenPrice.mul(new decimal_1.default(tokenAmount.toString()).div(new decimal_1.default(10).pow(decimals))); } async function getLowestInitializedTickArrayTickIndex(rpc, whirlpoolAddress, tickSpacing) { const minTick = (0, whirlpools_core_1._MIN_TICK_INDEX)(); let startTickIndex = (0, whirlpools_core_1.getTickArrayStartTickIndex)(minTick, tickSpacing); // eslint-disable-next-line while (true) { const [tickArrayAddress] = await (0, whirlpools_client_1.getTickArrayAddress)(whirlpoolAddress, startTickIndex); const tickArray = await (0, whirlpools_client_1.fetchMaybeTickArray)(rpc, tickArrayAddress); if (tickArray.exists) { return startTickIndex; } startTickIndex += TICK_ARRAY_SIZE * tickSpacing; await (0, utils_1.sleep)(500); } } async function getHighestInitializedTickArrayTickIndex(rpc, whirlpoolAddress, tickSpacing) { const maxTick = (0, whirlpools_core_1._MAX_TICK_INDEX)(); let startTickIndex = (0, whirlpools_core_1.getTickArrayStartTickIndex)(maxTick, tickSpacing); // eslint-disable-next-line while (true) { const [tickArrayAddress] = await (0, whirlpools_client_1.getTickArrayAddress)(whirlpoolAddress, startTickIndex); const tickArray = await (0, whirlpools_client_1.fetchMaybeTickArray)(rpc, tickArrayAddress); if (tickArray.exists) { return startTickIndex; } startTickIndex -= TICK_ARRAY_SIZE * tickSpacing; await (0, utils_1.sleep)(500); } } async function getLiquidityDistribution(rpc, poolAddress, poolData, tickLower, tickUpper, whirlpoolProgramId) { const datapoints = []; const tokenDecimalsA = poolData.tokenA.decimals; const tokenDecimalsB = poolData.tokenB.decimals; const tickArrayAddresses = await getSurroundingTickArrayAddresses(poolAddress, poolData, tickLower, tickUpper, whirlpoolProgramId); const tickArrays = await accounts_1.TickArray.fetchMultiple(rpc, tickArrayAddresses, whirlpoolProgramId); const currentLiquidity = new decimal_1.default(poolData.liquidity.toString()); let relativeLiquidity = currentLiquidity; let minLiquidity = new decimal_1.default(0); let liquidity = new decimal_1.default(0); tickArrays.forEach((tickArray) => { if (!tickArray) { return; } const startIndex = tickArray.startTickIndex; tickArray.ticks.forEach((tick, index) => { const tickIndex = startIndex + index * poolData.tickSpacing; const price = (0, whirlpools_core_1.tickIndexToPrice)(tickIndex, tokenDecimalsA, tokenDecimalsB); const liquidityNet = new decimal_1.default(tick.liquidityNet.toString()); liquidity = liquidity.add(liquidityNet); datapoints.push({ liquidity: new decimal_1.default(liquidity), price: new decimal_1.default(price), tickIndex }); minLiquidity = liquidity.lt(minLiquidity) ? liquidity : minLiquidity; if (tickIndex === poolData.tickCurrentIndex) { relativeLiquidity = relativeLiquidity.sub(liquidityNet); } }); }); if (!relativeLiquidity.eq(currentLiquidity)) { minLiquidity = minLiquidity.add(relativeLiquidity); datapoints.forEach((datapoint) => { datapoint.liquidity = datapoint.liquidity.add(relativeLiquidity); }); } if (minLiquidity.lt(0)) { datapoints.forEach((datapoint) => { datapoint.liquidity = datapoint.liquidity.add(minLiquidity.neg()); }); } return { currentPrice: new decimal_1.default(poolData.price), currentTickIndex: poolData.tickCurrentIndex, datapoints, }; } async function getSurroundingTickArrayAddresses(poolAddress, pool, tickLower, tickUpper, programId) { const tickArrayAddresses = []; let startIndex = (0, whirlpools_core_1.getTickArrayStartTickIndex)(tickLower, pool.tickSpacing); while (startIndex <= tickUpper) { const [address, _bump] = await getTickArrayPda(programId, poolAddress, startIndex); tickArrayAddresses.push(address); try { startIndex = (0, whirlpools_core_1.getTickArrayStartTickIndex)(startIndex, pool.tickSpacing); } catch (_e) { return tickArrayAddresses; } } return tickArrayAddresses; } async function getTickArrayPda(programId, poolAddress, startIndex) { const pdaWithBump = await (0, kit_1.getProgramDerivedAddress)({ seeds: [Buffer.from('tick_array'), addressEncoder.encode(poolAddress), Buffer.from(startIndex.toString())], programAddress: programId, }); return [pdaWithBump[0], pdaWithBump[1]]; } //# sourceMappingURL=orca.js.map