@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
JavaScript
"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;