UNPKG

@firefly-exchange/library-sui

Version:

Sui library housing helper methods, classes to interact with Bluefin protocol(s) deployed on Sui

445 lines (444 loc) 20.5 kB
"use strict"; /** * This file incorporates code from cetus-clmm-sui-sdk by CetusProtocol, * licensed under the Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0) * which can be found at https://github.com/CetusProtocol/cetus-clmm-sui-sdk/blob/main/LICENSE */ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.ClmmPoolUtil = exports.estimateLiquidityForCoinB = exports.estimateLiquidityForCoinA = exports.computeSwapStep = exports.getDeltaDownFromOutput = exports.getDeltaUpFromInput = exports.getNextSqrtPriceFromOutput = exports.getNextSqrtPriceFromInput = exports.getNextSqrtPriceBDown = exports.getNextSqrtPriceAUp = exports.getDeltaB = exports.getDeltaA = exports.toCoinAmount = void 0; const bn_js_1 = __importDefault(require("bn.js")); const tick_1 = require("./tick"); const utils_1 = require("./utils"); const errors_1 = require("./errors"); const constants_1 = require("./constants"); const decimal_1 = __importDefault(require("./decimal")); const number_1 = require("./number"); function toCoinAmount(a, b) { return { coinA: new bn_js_1.default(a.toString()), coinB: new bn_js_1.default(b.toString()) }; } exports.toCoinAmount = toCoinAmount; /** * Calculates the change in amount A between two prices based on a given amount of liquidity. * The formula is `delta_a = (liquidity * delta_sqrt_price) / (sqrt_price_upper * sqrt_price_lower)` * * @param sqrtPrice0 - The first sqrt price * @param sqrtPrice1 - The second sqrt price * @param liquidity - The available liquidity to use * @param roundUp - Flag to indicate whether to round the result up or down * @returns */ function getDeltaA(sqrtPrice0, sqrtPrice1, liquidity, roundUp) { const sqrtPriceDiff = sqrtPrice0.gt(sqrtPrice1) ? sqrtPrice0.sub(sqrtPrice1) : sqrtPrice1.sub(sqrtPrice0); const numerator = liquidity.mul(sqrtPriceDiff).shln(64); const denominator = sqrtPrice0.mul(sqrtPrice1); const quotient = numerator.div(denominator); const remainder = numerator.mod(denominator); const result = roundUp && !remainder.eq(utils_1.ZERO) ? quotient.add(new bn_js_1.default(1)) : quotient; return result; } exports.getDeltaA = getDeltaA; /** * Computes the change in amount B between two prices for a given liquidity amount. * The formula used is `delta_a = (liquidity * delta_sqrt_price) / (sqrt_price_upper * sqrt_price_lower)` * * @param sqrtPrice0 - The first sqrt price * @param sqrtPrice1 - The second sqrt price * @param liquidity - The amount of available liquidity * @param roundUp - Determines if the result should be rounded up or down * @returns */ function getDeltaB(sqrtPrice0, sqrtPrice1, liquidity, roundUp) { const sqrtPriceDiff = sqrtPrice0.gt(sqrtPrice1) ? sqrtPrice0.sub(sqrtPrice1) : sqrtPrice1.sub(sqrtPrice0); if (liquidity.eq(utils_1.ZERO) || sqrtPriceDiff.eq(utils_1.ZERO)) { return utils_1.ZERO; } const p = liquidity.mul(sqrtPriceDiff); const shoudRoundUp = roundUp && p.and(utils_1.U64_MAX).gt(utils_1.ZERO); const result = shoudRoundUp ? p.shrn(64).add(utils_1.ONE) : p.shrn(64); if (utils_1.MathUtil.isOverflow(result, 64)) { throw new errors_1.ClmmpoolsError("Result exceeds the maximum value allowed by u64", errors_1.MathErrorCode.IntegerDowncastOverflow); } return result; } exports.getDeltaB = getDeltaB; /** * Calculates the next sqrt price based on a delta of token_a. * The formula is `new_sqrt_price = (sqrt_price * liquidity) / (liquidity +/- amount * sqrt_price)` * * @param sqrtPrice - The initial sqrt price * @param liquidity - The available liquidity * @param amount - The amount of token_a involved * @param byAmountIn - Determines whether the input is fixed */ function getNextSqrtPriceAUp(sqrtPrice, liquidity, amount, byAmountIn) { if (amount.eq(utils_1.ZERO)) { return sqrtPrice; } const numerator = utils_1.MathUtil.checkMulShiftLeft(sqrtPrice, liquidity, 64, 256); const liquidityShl64 = liquidity.shln(64); const product = utils_1.MathUtil.checkMul(sqrtPrice, amount, 256); if (!byAmountIn && liquidityShl64.lte(product)) { throw new errors_1.ClmmpoolsError("getNextSqrtPriceAUp - Division of liquidityShl64 by product failed", errors_1.MathErrorCode.DivideByZero); } const nextSqrtPrice = byAmountIn ? utils_1.MathUtil.checkDivRoundUpIf(numerator, liquidityShl64.add(product), true) : utils_1.MathUtil.checkDivRoundUpIf(numerator, liquidityShl64.sub(product), true); if (nextSqrtPrice.lt(new bn_js_1.default(constants_1.MIN_SQRT_PRICE))) { throw new errors_1.ClmmpoolsError("getNextSqrtPriceAUp - The calculated next sqrt price is lower than the minimum allowed sqrt price", errors_1.CoinErrorCode.CoinAmountMinSubceeded); } if (nextSqrtPrice.gt(new bn_js_1.default(constants_1.MAX_SQRT_PRICE))) { throw new errors_1.ClmmpoolsError("getNextSqrtPriceAUp - The calculated next sqrt price exceeds the maximum allowed sqrt price", errors_1.CoinErrorCode.CoinAmountMaxExceeded); } return nextSqrtPrice; } exports.getNextSqrtPriceAUp = getNextSqrtPriceAUp; /** * Calculates the next sqrt price based on a delta of token_b. * The formula is `new_sqrt_price = (sqrt_price + (delta_b / liquidity))` * * @param sqrtPrice - The initial sqrt price * @param liquidity - The available liquidity * @param amount - The amount of token_b involved * @param byAmountIn - Indicates whether the input is fixed */ function getNextSqrtPriceBDown(sqrtPrice, liquidity, amount, byAmountIn) { const deltaSqrtPrice = utils_1.MathUtil.checkDivRoundUpIf(amount.shln(64), liquidity, !byAmountIn); const nextSqrtPrice = byAmountIn ? sqrtPrice.add(deltaSqrtPrice) : sqrtPrice.sub(deltaSqrtPrice); if (nextSqrtPrice.lt(new bn_js_1.default(constants_1.MIN_SQRT_PRICE)) || nextSqrtPrice.gt(new bn_js_1.default(constants_1.MAX_SQRT_PRICE))) { throw new errors_1.ClmmpoolsError("getNextSqrtPriceAUp - The calculated next sqrt price is out of bounds", errors_1.CoinErrorCode.SqrtPriceOutOfBounds); } return nextSqrtPrice; } exports.getNextSqrtPriceBDown = getNextSqrtPriceBDown; /** * Calculates the next sqrt price based on the provided parameters. * * @param sqrtPrice - The current sqrt price * @param liquidity - The available liquidity * @param amount - The token amount involved * @param aToB - A flag indicating if the calculation is from token_a to token_b * @returns */ function getNextSqrtPriceFromInput(sqrtPrice, liquidity, amount, aToB) { return aToB ? getNextSqrtPriceAUp(sqrtPrice, liquidity, amount, true) : getNextSqrtPriceBDown(sqrtPrice, liquidity, amount, true); } exports.getNextSqrtPriceFromInput = getNextSqrtPriceFromInput; /** * Calculates the next sqrt price based on the output parameters. * * @param sqrtPrice - The current sqrt price * @param liquidity - The available liquidity * @param amount - The token amount involved * @param a2b - A flag indicating if the operation is from token_a to token_b * @returns */ function getNextSqrtPriceFromOutput(sqrtPrice, liquidity, amount, a2b) { return a2b ? getNextSqrtPriceBDown(sqrtPrice, liquidity, amount, false) : getNextSqrtPriceAUp(sqrtPrice, liquidity, amount, false); } exports.getNextSqrtPriceFromOutput = getNextSqrtPriceFromOutput; /** * Calculates the amount of delta_a or delta_b based on the input parameters, rounding the result up. * * @param currentSqrtPrice - The current sqrt price * @param targetSqrtPrice - The target sqrt price * @param liquidity - The available liquidity * @param a2b - A flag indicating if the calculation is from token_a to token_b * @returns */ function getDeltaUpFromInput(currentSqrtPrice, targetSqrtPrice, liquidity, a2b) { const sqrtPriceDiff = currentSqrtPrice.gt(targetSqrtPrice) ? currentSqrtPrice.sub(targetSqrtPrice) : targetSqrtPrice.sub(currentSqrtPrice); if (liquidity.lte(utils_1.ZERO) || sqrtPriceDiff.eq(utils_1.ZERO)) { return utils_1.ZERO; } let result; if (a2b) { const numerator = new bn_js_1.default(liquidity).mul(new bn_js_1.default(sqrtPriceDiff)).shln(64); const denominator = targetSqrtPrice.mul(currentSqrtPrice); const quotient = numerator.div(denominator); const remainder = numerator.mod(denominator); result = !remainder.eq(utils_1.ZERO) ? quotient.add(utils_1.ONE) : quotient; } else { const product = new bn_js_1.default(liquidity).mul(new bn_js_1.default(sqrtPriceDiff)); const shoudRoundUp = product.and(utils_1.U64_MAX).gt(utils_1.ZERO); result = shoudRoundUp ? product.shrn(64).add(utils_1.ONE) : product.shrn(64); } return result; } exports.getDeltaUpFromInput = getDeltaUpFromInput; /** * Calculates the amount of delta_a or delta_b based on the output parameters, rounding the result down. * * @param currentSqrtPrice - The current sqrt price * @param targetSqrtPrice - The target sqrt price * @param liquidity - The available liquidity * @param a2b - A flag indicating if the operation is from token_a to token_b * @returns */ function getDeltaDownFromOutput(currentSqrtPrice, targetSqrtPrice, liquidity, a2b) { const sqrtPriceDiff = currentSqrtPrice.gt(targetSqrtPrice) ? currentSqrtPrice.sub(targetSqrtPrice) : targetSqrtPrice.sub(currentSqrtPrice); if (liquidity.lte(utils_1.ZERO) || sqrtPriceDiff.eq(utils_1.ZERO)) { return utils_1.ZERO; } let result; if (a2b) { const product = liquidity.mul(sqrtPriceDiff); result = product.shrn(64); } else { const numerator = liquidity.mul(sqrtPriceDiff).shln(64); const denominator = targetSqrtPrice.mul(currentSqrtPrice); result = numerator.div(denominator); } return result; } exports.getDeltaDownFromOutput = getDeltaDownFromOutput; /** * Simulates each step of a swap for every tick. * * @param currentSqrtPrice - The current sqrt price * @param targetSqrtPrice - The target sqrt price * @param liquidity - The available liquidity * @param amount - The token amount involved * @param feeRate - The applied fee rate for the swap * @param byAmountIn - Indicates whether the input amount is fixed * @returns */ function computeSwapStep(currentSqrtPrice, targetSqrtPrice, liquidity, amount, feeRate, byAmountIn) { if (liquidity === utils_1.ZERO) { return { amountIn: utils_1.ZERO, amountOut: utils_1.ZERO, nextSqrtPrice: targetSqrtPrice, feeAmount: utils_1.ZERO }; } const a2b = currentSqrtPrice.gte(targetSqrtPrice); let amountIn; let amountOut; let nextSqrtPrice; let feeAmount; if (byAmountIn) { const amountRemain = utils_1.MathUtil.checkMulDivFloor(amount, utils_1.MathUtil.checkUnsignedSub(constants_1.FEE_RATE_DENOMINATOR, feeRate), constants_1.FEE_RATE_DENOMINATOR, 64); const maxAmountIn = getDeltaUpFromInput(currentSqrtPrice, targetSqrtPrice, liquidity, a2b); if (maxAmountIn.gt(amountRemain)) { amountIn = amountRemain; feeAmount = utils_1.MathUtil.checkUnsignedSub(amount, amountRemain); nextSqrtPrice = getNextSqrtPriceFromInput(currentSqrtPrice, liquidity, amountRemain, a2b); } else { amountIn = maxAmountIn; feeAmount = utils_1.MathUtil.checkMulDivCeil(amountIn, feeRate, constants_1.FEE_RATE_DENOMINATOR.sub(feeRate), 64); nextSqrtPrice = targetSqrtPrice; } amountOut = getDeltaDownFromOutput(currentSqrtPrice, nextSqrtPrice, liquidity, a2b); } else { const maxAmountOut = getDeltaDownFromOutput(currentSqrtPrice, targetSqrtPrice, liquidity, a2b); if (maxAmountOut.gt(amount)) { amountOut = amount; nextSqrtPrice = getNextSqrtPriceFromOutput(currentSqrtPrice, liquidity, amount, a2b); } else { amountOut = maxAmountOut; nextSqrtPrice = targetSqrtPrice; } amountIn = getDeltaUpFromInput(currentSqrtPrice, nextSqrtPrice, liquidity, a2b); feeAmount = utils_1.MathUtil.checkMulDivCeil(amountIn, feeRate, constants_1.FEE_RATE_DENOMINATOR.sub(feeRate), 64); } return { amountIn, amountOut, nextSqrtPrice, feeAmount }; } exports.computeSwapStep = computeSwapStep; /** * Estimates the liquidity for coin A. * * @param sqrtPriceX - The sqrt price of coin A * @param sqrtPriceY - The sqrt price of coin B * @param coinAmount - The amount of tokens involved * @returns */ function estimateLiquidityForCoinA(sqrtPriceX, sqrtPriceY, coinAmount) { const lowerSqrtPriceX64 = bn_js_1.default.min(sqrtPriceX, sqrtPriceY); const upperSqrtPriceX64 = bn_js_1.default.max(sqrtPriceX, sqrtPriceY); const num = utils_1.MathUtil.fromX64_BN(coinAmount.mul(upperSqrtPriceX64).mul(lowerSqrtPriceX64)); const dem = upperSqrtPriceX64.sub(lowerSqrtPriceX64); return num.div(dem); } exports.estimateLiquidityForCoinA = estimateLiquidityForCoinA; /** * Estimates the liquidity for coin B. * * @param sqrtPriceX - The sqrt price of coin A * @param sqrtPriceY - The sqrt price of coin B * @param coinAmount - The amount of tokens involved * @returns */ function estimateLiquidityForCoinB(sqrtPriceX, sqrtPriceY, coinAmount) { const lowerSqrtPriceX64 = bn_js_1.default.min(sqrtPriceX, sqrtPriceY); const upperSqrtPriceX64 = bn_js_1.default.max(sqrtPriceX, sqrtPriceY); const delta = upperSqrtPriceX64.sub(lowerSqrtPriceX64); return coinAmount.shln(64).div(delta); } exports.estimateLiquidityForCoinB = estimateLiquidityForCoinB; class ClmmPoolUtil { /** * Calculates the token amount from liquidity. * * @param liquidity - The available liquidity * @param curSqrtPrice - The current sqrt price of the pool * @param lowerSqrtPrice - The lower sqrt price of the position * @param upperSqrtPrice - The upper sqrt price of the position * @param roundUp - Specifies whether to round the result up * @returns */ static getCoinAmountFromLiquidity(liquidity, curSqrtPrice, lowerSqrtPrice, upperSqrtPrice, roundUp) { const liq = new decimal_1.default(liquidity.toString()); const curSqrtPriceStr = new decimal_1.default(curSqrtPrice.toString()); const lowerPriceStr = new decimal_1.default(lowerSqrtPrice.toString()); const upperPriceStr = new decimal_1.default(upperSqrtPrice.toString()); let coinA; let coinB; if (curSqrtPrice.lt(lowerSqrtPrice)) { coinA = utils_1.MathUtil.toX64_Decimal(liq) .mul(upperPriceStr.sub(lowerPriceStr)) .div(lowerPriceStr.mul(upperPriceStr)); coinB = new decimal_1.default(0); } else if (curSqrtPrice.lt(upperSqrtPrice)) { coinA = utils_1.MathUtil.toX64_Decimal(liq) .mul(upperPriceStr.sub(curSqrtPriceStr)) .div(curSqrtPriceStr.mul(upperPriceStr)); coinB = utils_1.MathUtil.fromX64_Decimal(liq.mul(curSqrtPriceStr.sub(lowerPriceStr))); } else { coinA = new decimal_1.default(0); coinB = utils_1.MathUtil.fromX64_Decimal(liq.mul(upperPriceStr.sub(lowerPriceStr))); } if (roundUp) { return { coinA: new bn_js_1.default(coinA.ceil().toString()), coinB: new bn_js_1.default(coinB.ceil().toString()) }; } return { coinA: new bn_js_1.default(coinA.floor().toString()), coinB: new bn_js_1.default(coinB.floor().toString()) }; } /** * Estimates liquidity based on token amounts. * * @param curSqrtPrice - The current sqrt price * @param lowerTick - The lower tick * @param upperTick - The upper tick * @param tokenAmount - The amount of tokens * @returns */ static estimateLiquidityFromCoinAmounts(curSqrtPrice, lowerTick, upperTick, tokenAmount) { if (lowerTick > upperTick) { throw new errors_1.ClmmpoolsError("Lower tick value cannot be greater than the upper tick value", errors_1.MathErrorCode.InvalidTwoTickIndex); } const currTick = tick_1.TickMath.sqrtPriceX64ToTickIndex(curSqrtPrice); const lowerSqrtPrice = tick_1.TickMath.tickIndexToSqrtPriceX64(lowerTick); const upperSqrtPrice = tick_1.TickMath.tickIndexToSqrtPriceX64(upperTick); if (currTick < lowerTick) { return estimateLiquidityForCoinA(lowerSqrtPrice, upperSqrtPrice, tokenAmount.coinA); } if (currTick >= upperTick) { return estimateLiquidityForCoinB(upperSqrtPrice, lowerSqrtPrice, tokenAmount.coinB); } const estimateLiquidityAmountA = estimateLiquidityForCoinA(curSqrtPrice, upperSqrtPrice, tokenAmount.coinA); const estimateLiquidityAmountB = estimateLiquidityForCoinB(curSqrtPrice, lowerSqrtPrice, tokenAmount.coinB); return bn_js_1.default.min(estimateLiquidityAmountA, estimateLiquidityAmountB); } /** * Estimate liquidity and token amount from one amounts * @param lowerTick - lower tick * @param upperTick - upper tick * @param coinAmount - token amount * @param isCoinA - is token A * @param roundUp - is round up * @param slippage - slippage percentage * @param curSqrtPrice - current sqrt price. * @return IncreaseLiquidityInput */ static estLiquidityAndCoinAmountFromOneAmounts(lowerTick, upperTick, coinAmount, isCoinA, roundUp, slippage, curSqrtPrice) { const currentTick = tick_1.TickMath.sqrtPriceX64ToTickIndex(curSqrtPrice); const lowerSqrtPrice = tick_1.TickMath.tickIndexToSqrtPriceX64(lowerTick); const upperSqrtPrice = tick_1.TickMath.tickIndexToSqrtPriceX64(upperTick); let liquidity; if (currentTick < lowerTick) { if (!isCoinA) { throw new errors_1.ClmmpoolsError("lower tick cannot calculate liquidity by coinB", errors_1.MathErrorCode.NotSupportedThisCoin); } liquidity = estimateLiquidityForCoinA(lowerSqrtPrice, upperSqrtPrice, coinAmount); } else if (currentTick > upperTick) { if (isCoinA) { throw new errors_1.ClmmpoolsError("upper tick cannot calculate liquidity by coinA", errors_1.MathErrorCode.NotSupportedThisCoin); } liquidity = estimateLiquidityForCoinB(upperSqrtPrice, lowerSqrtPrice, coinAmount); } else if (isCoinA) { liquidity = estimateLiquidityForCoinA(curSqrtPrice, upperSqrtPrice, coinAmount); } else { liquidity = estimateLiquidityForCoinB(curSqrtPrice, lowerSqrtPrice, coinAmount); } const coinAmounts = ClmmPoolUtil.getCoinAmountFromLiquidity(liquidity, curSqrtPrice, lowerSqrtPrice, upperSqrtPrice, roundUp); const tokenLimitA = roundUp ? (0, number_1.d)(coinAmounts.coinA.toString()) .mul(1 + slippage) .toString() : (0, number_1.d)(coinAmounts.coinA.toString()) .mul(1 - slippage) .toString(); const tokenLimitB = roundUp ? (0, number_1.d)(coinAmounts.coinB.toString()) .mul(1 + slippage) .toString() : (0, number_1.d)(coinAmounts.coinB.toString()) .mul(1 - slippage) .toString(); return { coinAmount, coinAmountA: coinAmounts.coinA, coinAmountB: coinAmounts.coinB, tokenMaxA: roundUp ? new bn_js_1.default(decimal_1.default.ceil(tokenLimitA).toString()) : new bn_js_1.default(decimal_1.default.floor(tokenLimitA).toString()), tokenMaxB: roundUp ? new bn_js_1.default(decimal_1.default.ceil(tokenLimitB).toString()) : new bn_js_1.default(decimal_1.default.floor(tokenLimitB).toString()), liquidityAmount: liquidity, fix_amount_a: isCoinA }; } } exports.ClmmPoolUtil = ClmmPoolUtil;