UNPKG

@renec-foundation/redex-sdk

Version:

Typescript SDK to interact with Orca's Whirlpool program.

204 lines (203 loc) 9.13 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.toTokenAmount = exports.PoolUtil = void 0; const common_sdk_1 = require("@orca-so/common-sdk"); const anchor_1 = require("@project-serum/anchor"); const spl_token_1 = require("@solana/spl-token"); const web3_js_1 = require("@solana/web3.js"); const decimal_js_1 = __importDefault(require("decimal.js")); const price_math_1 = require("./price-math"); const types_1 = require("./types"); /** * @category Whirlpool Utils */ class PoolUtil { constructor() { } static isRewardInitialized(rewardInfo) { return (!web3_js_1.PublicKey.default.equals(rewardInfo.mint) && !web3_js_1.PublicKey.default.equals(rewardInfo.vault)); } /** * Return the corresponding token type (TokenA/B) for this mint key for a Whirlpool. * * @param pool The Whirlpool to evaluate the mint against * @param mint The token mint PublicKey * @returns The match result in the form of TokenType enum. undefined if the token mint is not part of the trade pair of the pool. */ static getTokenType(pool, mint) { if (pool.tokenMintA.equals(mint)) { return types_1.TokenType.TokenA; } else if (pool.tokenMintB.equals(mint)) { return types_1.TokenType.TokenB; } return undefined; } static getFeeRate(feeRate) { /** * Smart Contract comment: https://github.com/orca-so/whirlpool/blob/main/programs/whirlpool/src/state/whirlpool.rs#L9-L11 * // Stored as hundredths of a basis point * // u16::MAX corresponds to ~6.5% * pub fee_rate: u16, */ return common_sdk_1.Percentage.fromFraction(feeRate, 1e6); // TODO } static getProtocolFeeRate(protocolFeeRate) { /** * Smart Contract comment: https://github.com/orca-so/whirlpool/blob/main/programs/whirlpool/src/state/whirlpool.rs#L13-L14 * // Stored as a basis point * pub protocol_fee_rate: u16, */ return common_sdk_1.Percentage.fromFraction(protocolFeeRate, 1e4); // TODO } static orderMints(mintX, mintY) { return this.compareMints(mintX, mintY) < 0 ? [mintX, mintY] : [mintY, mintX]; } static compareMints(mintX, mintY) { return Buffer.compare(common_sdk_1.AddressUtil.toPubKey(mintX).toBuffer(), common_sdk_1.AddressUtil.toPubKey(mintY).toBuffer()); } /** * @category Whirlpool Utils * @param liquidity * @param currentSqrtPrice * @param lowerSqrtPrice * @param upperSqrtPrice * @param round_up * @returns */ static getTokenAmountsFromLiquidity(liquidity, currentSqrtPrice, lowerSqrtPrice, upperSqrtPrice, round_up) { const _liquidity = new decimal_js_1.default(liquidity.toString()); const _currentPrice = new decimal_js_1.default(currentSqrtPrice.toString()); const _lowerPrice = new decimal_js_1.default(lowerSqrtPrice.toString()); const _upperPrice = new decimal_js_1.default(upperSqrtPrice.toString()); let tokenA, tokenB; if (currentSqrtPrice.lt(lowerSqrtPrice)) { // x = L * (pb - pa) / (pa * pb) tokenA = common_sdk_1.MathUtil.toX64_Decimal(_liquidity) .mul(_upperPrice.sub(_lowerPrice)) .div(_lowerPrice.mul(_upperPrice)); tokenB = new decimal_js_1.default(0); } else if (currentSqrtPrice.lt(upperSqrtPrice)) { // x = L * (pb - p) / (p * pb) // y = L * (p - pa) tokenA = common_sdk_1.MathUtil.toX64_Decimal(_liquidity) .mul(_upperPrice.sub(_currentPrice)) .div(_currentPrice.mul(_upperPrice)); tokenB = common_sdk_1.MathUtil.fromX64_Decimal(_liquidity.mul(_currentPrice.sub(_lowerPrice))); } else { // y = L * (pb - pa) tokenA = new decimal_js_1.default(0); tokenB = common_sdk_1.MathUtil.fromX64_Decimal(_liquidity.mul(_upperPrice.sub(_lowerPrice))); } // TODO: round up if (round_up) { return { tokenA: new spl_token_1.u64(tokenA.ceil().toString()), tokenB: new spl_token_1.u64(tokenB.ceil().toString()), }; } else { return { tokenA: new spl_token_1.u64(tokenA.floor().toString()), tokenB: new spl_token_1.u64(tokenB.floor().toString()), }; } } /** * Estimate the liquidity amount required to increase/decrease liquidity. * * // TODO: At the top end of the price range, tick calcuation is off therefore the results can be off * * @category Whirlpool Utils * @param currTick - Whirlpool's current tick index (aka price) * @param lowerTick - Position lower tick index * @param upperTick - Position upper tick index * @param tokenAmount - The desired amount of tokens to deposit/withdraw * @returns An estimated amount of liquidity needed to deposit/withdraw the desired amount of tokens. */ static estimateLiquidityFromTokenAmounts(currTick, lowerTick, upperTick, tokenAmount) { if (upperTick < lowerTick) { throw new Error("upper tick cannot be lower than the lower tick"); } const currSqrtPrice = price_math_1.PriceMath.tickIndexToSqrtPriceX64(currTick); const lowerSqrtPrice = price_math_1.PriceMath.tickIndexToSqrtPriceX64(lowerTick); const upperSqrtPrice = price_math_1.PriceMath.tickIndexToSqrtPriceX64(upperTick); if (currTick >= upperTick) { return estLiquidityForTokenB(upperSqrtPrice, lowerSqrtPrice, tokenAmount.tokenB); } else if (currTick < lowerTick) { return estLiquidityForTokenA(lowerSqrtPrice, upperSqrtPrice, tokenAmount.tokenA); } else { const estLiquidityAmountA = estLiquidityForTokenA(currSqrtPrice, upperSqrtPrice, tokenAmount.tokenA); const estLiquidityAmountB = estLiquidityForTokenB(currSqrtPrice, lowerSqrtPrice, tokenAmount.tokenB); return anchor_1.BN.min(estLiquidityAmountA, estLiquidityAmountB); } } /** * Given an arbitrary pair of token mints, this function returns an ordering of the token mints * in the format [base, quote]. USD based stable coins are prioritized as the quote currency * followed by variants of SOL. * * @category Whirlpool Utils * @param tokenMintAKey - The mint of token A in the token pair. * @param tokenMintBKey - The mint of token B in the token pair. * @returns A two-element array with the tokens sorted in the order of [baseToken, quoteToken]. */ static toBaseQuoteOrder(tokenMintAKey, tokenMintBKey) { const pair = [tokenMintAKey, tokenMintBKey]; return pair.sort(sortByQuotePriority); } } exports.PoolUtil = PoolUtil; /** * @category Whirlpool Utils */ function toTokenAmount(a, b) { return { tokenA: new spl_token_1.u64(a.toString()), tokenB: new spl_token_1.u64(b.toString()), }; } exports.toTokenAmount = toTokenAmount; // These are the token mints that will be prioritized as the second token in the pair (quote). // The number that the mint maps to determines the priority that it will be used as the quote // currency. const QUOTE_TOKENS = { Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB: 100, EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v: 90, USDH1SM1ojwWUga67PGrgFWUHibbjqMvuMaDkRJTgkX: 80, So11111111111111111111111111111111111111112: 70, mSoLzYCxHdYgdzU16g5QSh3i5K3z3KZK7ytfqcJm7So: 60, "7dHbWXmci3dT8UFYWYZweBLXgycu7Y3iL6trKn1Y7ARj": 50, // stSOL }; const DEFAULT_QUOTE_PRIORITY = 0; function getQuoteTokenPriority(mint) { const value = QUOTE_TOKENS[mint]; if (value) { return value; } return DEFAULT_QUOTE_PRIORITY; } function sortByQuotePriority(mintLeft, mintRight) { return getQuoteTokenPriority(mintLeft.toString()) - getQuoteTokenPriority(mintRight.toString()); } // Convert this function based on Delta A = Delta L * (1/sqrt(lower) - 1/sqrt(upper)) function estLiquidityForTokenA(sqrtPrice1, sqrtPrice2, tokenAmount) { const lowerSqrtPriceX64 = anchor_1.BN.min(sqrtPrice1, sqrtPrice2); const upperSqrtPriceX64 = anchor_1.BN.max(sqrtPrice1, sqrtPrice2); const num = common_sdk_1.MathUtil.fromX64_BN(tokenAmount.mul(upperSqrtPriceX64).mul(lowerSqrtPriceX64)); const dem = upperSqrtPriceX64.sub(lowerSqrtPriceX64); return num.div(dem); } // Convert this function based on Delta B = Delta L * (sqrt_price(upper) - sqrt_price(lower)) function estLiquidityForTokenB(sqrtPrice1, sqrtPrice2, tokenAmount) { const lowerSqrtPriceX64 = anchor_1.BN.min(sqrtPrice1, sqrtPrice2); const upperSqrtPriceX64 = anchor_1.BN.max(sqrtPrice1, sqrtPrice2); const delta = upperSqrtPriceX64.sub(lowerSqrtPriceX64); return tokenAmount.shln(64).div(delta); }