@kamino-finance/kliquidity-sdk
Version:
Typescript SDK for interacting with the Kamino Liquidity (kliquidity) protocol
254 lines • 12.2 kB
JavaScript
"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