UNPKG

@kamino-finance/kliquidity-sdk

Version:

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

286 lines 13.4 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.OrcaService = void 0; const decimal_js_1 = __importDefault(require("decimal.js")); const whirlpool_sdk_1 = require("@orca-so/whirlpool-sdk"); const axios_1 = __importDefault(require("axios")); const accounts_1 = require("../@codegen/whirlpools/accounts"); const utils_1 = require("../utils"); const programId_1 = require("../@codegen/whirlpools/programId"); const compat_1 = require("@solana/compat"); class OrcaService { _rpc; _legacyConnection; _whirlpoolProgramId; _orcaNetwork; _orcaApiUrl; constructor(rpc, legacyConnection, cluster, whirlpoolProgramId = programId_1.PROGRAM_ID) { this._rpc = rpc; this._legacyConnection = legacyConnection; this._whirlpoolProgramId = whirlpoolProgramId; this._orcaNetwork = cluster === 'mainnet-beta' ? whirlpool_sdk_1.OrcaNetwork.MAINNET : whirlpool_sdk_1.OrcaNetwork.DEVNET; this._orcaApiUrl = `https://api.${cluster === 'mainnet-beta' ? 'mainnet' : 'devnet'}.orca.so`; } getWhirlpoolProgramId() { return this._whirlpoolProgramId; } async getOrcaWhirlpools() { return (await axios_1.default.get(`${this._orcaApiUrl}/v1/whirlpool/list`)).data; } /** * Get token prices for a strategy - for use with orca sdk * @param strategy * @param prices * @param collateralInfos * @returns {Record<string, Decimal>} - token prices by mint string * @private */ getTokenPrices(strategy, prices, collateralInfos) { const tokensPrices = {}; const tokenA = collateralInfos[strategy.tokenACollateralId.toNumber()]; const tokenB = collateralInfos[strategy.tokenBCollateralId.toNumber()]; const rewardToken0 = collateralInfos[strategy.reward0CollateralId.toNumber()]; const rewardToken1 = collateralInfos[strategy.reward1CollateralId.toNumber()]; const rewardToken2 = collateralInfos[strategy.reward2CollateralId.toNumber()]; const aPrice = prices.spot[tokenA.mint.toString()]; const bPrice = prices.spot[tokenB.mint.toString()]; const reward0Price = strategy.reward0Decimals.toNumber() !== 0 ? prices.spot[rewardToken0.mint.toString()] : null; const reward1Price = strategy.reward1Decimals.toNumber() !== 0 ? prices.spot[rewardToken1.mint.toString()] : null; const reward2Price = strategy.reward2Decimals.toNumber() !== 0 ? prices.spot[rewardToken2.mint.toString()] : null; const [mintA, mintB] = [strategy.tokenAMint.toString(), strategy.tokenBMint.toString()]; const reward0 = collateralInfos[strategy.reward0CollateralId.toNumber()]?.mint?.toString(); const reward1 = collateralInfos[strategy.reward1CollateralId.toNumber()]?.mint?.toString(); const reward2 = collateralInfos[strategy.reward2CollateralId.toNumber()]?.mint?.toString(); tokensPrices[mintA] = aPrice.price; tokensPrices[mintB] = bPrice.price; if (reward0Price !== null) { tokensPrices[reward0] = reward0Price.price; } if (reward1Price !== null) { tokensPrices[reward1] = reward1Price.price; } if (reward2Price !== null) { tokensPrices[reward2] = reward2Price.price; } return tokensPrices; } getPoolTokensPrices(pool, prices) { const tokensPrices = {}; const tokens = [ pool.tokenMintA.toString(), pool.tokenMintB.toString(), pool.rewards[0].mint.toString(), pool.rewards[1].mint.toString(), pool.rewards[2].mint.toString(), ]; for (const mint of tokens) { if (mint) { const price = prices.spot[mint]?.price; if (!price) { throw new Error(`Could not get token ${mint} price`); } tokensPrices[mint] = price; } } return tokensPrices; } async getPool(poolAddress) { const orca = new whirlpool_sdk_1.OrcaWhirlpoolClient({ connection: this._legacyConnection, network: this._orcaNetwork, }); return orca.getPool(poolAddress); } async getStrategyWhirlpoolPoolAprApy(strategy, collateralInfos, prices, whirlpools) { const orca = new whirlpool_sdk_1.OrcaWhirlpoolClient({ connection: this._legacyConnection, network: this._orcaNetwork, }); const position = await accounts_1.Position.fetch(this._rpc, strategy.position); if (!position) { throw new Error(`Position ${strategy.position.toString()} does not exist`); } const pool = await orca.getPool(strategy.pool); if (!whirlpools) { ({ whirlpools } = await this.getOrcaWhirlpools()); } const whirlpool = whirlpools?.find((x) => x.address === strategy.pool); if (!pool || !whirlpool) { throw Error(`Could not get orca pool data for ${strategy.pool.toString()}`); } const priceRange = (0, utils_1.getStrategyPriceRangeOrca)(position.tickLowerIndex, position.tickUpperIndex, strategy, new decimal_js_1.default(pool.price.toString())); if (priceRange.strategyOutOfRange) { return { ...priceRange, rewardsApy: [], rewardsApr: [], feeApy: utils_1.ZERO, feeApr: utils_1.ZERO, totalApy: utils_1.ZERO, totalApr: utils_1.ZERO, }; } const lpFeeRate = pool.feePercentage; const volume24hUsd = whirlpool?.volume?.day ?? new decimal_js_1.default(0); const fee24Usd = new decimal_js_1.default(volume24hUsd).mul(lpFeeRate).toNumber(); const tokensPrices = this.getTokenPrices(strategy, prices, collateralInfos); const apr = (0, whirlpool_sdk_1.estimateAprsForPriceRange)(pool, tokensPrices, fee24Usd, position.tickLowerIndex, position.tickUpperIndex); const totalApr = new decimal_js_1.default(apr.fee).add(apr.rewards[0]).add(apr.rewards[1]).add(apr.rewards[2]); const feeApr = new decimal_js_1.default(apr.fee); const rewardsApr = apr.rewards.map((r) => new decimal_js_1.default(r)); return { totalApr, totalApy: (0, utils_1.aprToApy)(totalApr, 365), feeApr, feeApy: (0, utils_1.aprToApy)(feeApr, 365), rewardsApr, rewardsApy: rewardsApr.map((x) => (0, utils_1.aprToApy)(x, 365)), ...priceRange, }; } // strongly recommended to pass lowestTick and highestTick because fetching the lowest and highest existent takes very long async getWhirlpoolLiquidityDistribution(pool, keepOrder = true, lowestTick, highestTick) { const orca = new whirlpool_sdk_1.OrcaWhirlpoolClient({ connection: this._legacyConnection, network: this._orcaNetwork, }); const poolData = await orca.getPool(pool); if (!poolData) { throw new Error(`Could not get pool data for Whirlpool ${pool}`); } let lowestInitializedTick; if (lowestTick) { lowestInitializedTick = lowestTick; } else { lowestInitializedTick = await orca.pool.getLowestInitializedTickArrayTickIndex(pool, poolData.tickSpacing); } let highestInitializedTick; if (highestTick) { highestInitializedTick = highestTick; } else { highestInitializedTick = await orca.pool.getHighestInitializedTickArrayTickIndex(pool, poolData.tickSpacing); } const orcaLiqDistribution = await orca.pool.getLiquidityDistribution(pool, lowestInitializedTick, highestInitializedTick); const liqDistribution = { currentPrice: poolData.price, currentTickIndex: poolData.tickCurrentIndex, distribution: [], }; orcaLiqDistribution.datapoints.forEach((entry) => { let priceWithOrder = new decimal_js_1.default(entry.price); if (!keepOrder) { priceWithOrder = new decimal_js_1.default(1).div(priceWithOrder); } const liq = { price: priceWithOrder, liquidity: entry.liquidity, tickIndex: entry.tickIndex, }; liqDistribution.distribution.push(liq); }); return liqDistribution; } async getWhirlpoolPositionAprApy(poolPubkey, priceLower, priceUpper, prices, whirlpools) { const orca = new whirlpool_sdk_1.OrcaWhirlpoolClient({ connection: this._legacyConnection, network: this._orcaNetwork, }); const pool = await orca.getPool(poolPubkey); if (!whirlpools) { ({ whirlpools } = await this.getOrcaWhirlpools()); } const whirlpool = whirlpools?.find((x) => x.address === poolPubkey.toString()); if (!pool || !whirlpool) { throw Error(`Could not get orca pool data for ${poolPubkey}`); } let strategyOutOfRange = false; if (priceLower.gt(pool.price) || priceUpper.lt(pool.price)) { strategyOutOfRange = true; } if (strategyOutOfRange) { return { priceLower, priceUpper, strategyOutOfRange, poolPrice: pool.price, rewardsApy: [], rewardsApr: [], feeApy: utils_1.ZERO, feeApr: utils_1.ZERO, totalApy: utils_1.ZERO, totalApr: utils_1.ZERO, }; } const lpFeeRate = pool.feePercentage; const volume24hUsd = whirlpool?.volume?.day ?? new decimal_js_1.default(0); const fee24Usd = new decimal_js_1.default(volume24hUsd).mul(lpFeeRate).toNumber(); const tokensPrices = this.getPoolTokensPrices(pool, prices); const tickLowerIndex = (0, whirlpool_sdk_1.getNearestValidTickIndexFromTickIndex)((0, whirlpool_sdk_1.priceToTickIndex)(priceLower, pool.tokenDecimalsA, pool.tokenDecimalsB), whirlpool.tickSpacing); const tickUpperIndex = (0, whirlpool_sdk_1.getNearestValidTickIndexFromTickIndex)((0, whirlpool_sdk_1.priceToTickIndex)(priceUpper, pool.tokenDecimalsA, pool.tokenDecimalsB), whirlpool.tickSpacing); const apr = (0, whirlpool_sdk_1.estimateAprsForPriceRange)(pool, tokensPrices, fee24Usd, tickLowerIndex, tickUpperIndex); const totalApr = new decimal_js_1.default(apr.fee).add(apr.rewards[0]).add(apr.rewards[1]).add(apr.rewards[2]); const feeApr = new decimal_js_1.default(apr.fee); const rewardsApr = apr.rewards.map((r) => new decimal_js_1.default(r)); return { totalApr, totalApy: (0, utils_1.aprToApy)(totalApr, 365), feeApr, feeApy: (0, utils_1.aprToApy)(feeApr, 365), rewardsApr, rewardsApy: rewardsApr.map((x) => (0, utils_1.aprToApy)(x, 365)), priceLower, priceUpper, poolPrice: pool.price, strategyOutOfRange, }; } async getGenericPoolInfo(poolPubkey, whirlpools) { const orca = new whirlpool_sdk_1.OrcaWhirlpoolClient({ connection: this._legacyConnection, network: this._orcaNetwork, }); const poolString = poolPubkey.toString(); const pool = await orca.getPool(poolPubkey); if (!whirlpools) { ({ whirlpools } = await this.getOrcaWhirlpools()); } const whirlpool = whirlpools?.find((x) => x.address === poolString); if (!pool || !whirlpool) { throw Error(`Could not get orca pool data for ${poolString}`); } const poolInfo = { dex: 'ORCA', address: poolPubkey, tokenMintA: (0, compat_1.fromLegacyPublicKey)(pool.tokenMintA), tokenMintB: (0, compat_1.fromLegacyPublicKey)(pool.tokenMintB), price: pool.price, feeRate: pool.feePercentage, volumeOnLast7d: whirlpool.volume ? new decimal_js_1.default(whirlpool.volume?.week) : undefined, tvl: whirlpool.tvl ? new decimal_js_1.default(whirlpool.tvl) : undefined, tickSpacing: new decimal_js_1.default(pool.tickSpacing), // todo(Silviu): get real amount of positions positions: new decimal_js_1.default(0), }; return poolInfo; } async getPositionsCountByPool(pool) { const rawPositions = await this._rpc .getProgramAccounts(programId_1.PROGRAM_ID, { commitment: 'confirmed', filters: [ // account LAYOUT: https://github.com/orca-so/whirlpools/blob/main/programs/whirlpool/src/state/position.rs#L20 { dataSize: 216n }, { memcmp: { bytes: pool, offset: 8n, encoding: 'base58' } }, ], }) .send(); return rawPositions.length; } } exports.OrcaService = OrcaService; //# sourceMappingURL=OrcaService.js.map