import { PublicKey } from "@solana/web3.js"; import BN from "bn.js"; import Decimal from "decimal.js"; import { ONE, ZERO } from "../../entity"; import { BIT_PRECISION, Fee, FEE_RATE_DENOMINATOR, LOG_B_2_X32, LOG_B_P_ERR_MARGIN_LOWER_X64, LOG_B_P_ERR_MARGIN_UPPER_X64, MAX_SQRT_PRICE_X64, MAX_TICK, MaxU64, MaxUint128, MIN_SQRT_PRICE_X64, MIN_TICK, NEGATIVE_ONE, Q128, Q64, U64Resolution, } from "./constants"; import { TickArray } from "./tick"; import { TickQuery } from "./tickQuery"; export class MathUtil { public static mulDivRoundingUp(a: BN, b: BN, denominator: BN): BN { const numerator = a.mul(b); let result = numerator.div(denominator); if (!numerator.mod(denominator).eq(ZERO)) { result = result.add(ONE); } return result; } public static mulDivFloor(a: BN, b: BN, denominator: BN): BN { if (denominator.eq(ZERO)) { throw new Error("division by 0"); } return a.mul(b).div(denominator); } public static mulDivCeil(a: BN, b: BN, denominator: BN): BN { if (denominator.eq(ZERO)) { throw new Error("division by 0"); } const numerator = a.mul(b).add(denominator.sub(ONE)); return numerator.div(denominator); } public static x64ToDecimal(num: BN, decimalPlaces?: number): Decimal { return new Decimal(num.toString()) .div(Decimal.pow(2, 64)) .toDecimalPlaces(decimalPlaces); } public static decimalToX64(num: Decimal): BN { return new BN(num.mul(Decimal.pow(2, 64)).floor().toFixed()); } public static wrappingSubU128(n0: BN, n1: BN): BN { return n0.add(Q128).sub(n1).mod(Q128); } } // sqrt price math function mulRightShift(val: BN, mulBy: BN): BN { return signedRightShift(val.mul(mulBy), 64, 256); } function signedLeftShift(n0: BN, shiftBy: number, bitWidth: number) { const twosN0 = n0.toTwos(bitWidth).shln(shiftBy); twosN0.imaskn(bitWidth + 1); return twosN0.fromTwos(bitWidth); } function signedRightShift(n0: BN, shiftBy: number, bitWidth: number) { const twoN0 = n0.toTwos(bitWidth).shrn(shiftBy); twoN0.imaskn(bitWidth - shiftBy + 1); return twoN0.fromTwos(bitWidth - shiftBy); } export class SqrtPriceMath { public static sqrtPriceX64ToPrice( sqrtPriceX64: BN, decimalsA: number, decimalsB: number ): Decimal { return MathUtil.x64ToDecimal(sqrtPriceX64) .pow(2) .mul(Decimal.pow(10, decimalsA - decimalsB)); } public static priceToSqrtPriceX64( price: Decimal, decimalsA: number, decimalsB: number ): BN { return MathUtil.decimalToX64( price.mul(Decimal.pow(10, decimalsB - decimalsA)).sqrt() ); } public static getNextSqrtPriceX64FromInput( sqrtPriceX64: BN, liquidity: BN, amountIn: BN, zeroForOne: boolean ): BN { if (!sqrtPriceX64.gt(ZERO)) { throw new Error("sqrtPriceX64 must greater than 0"); } if (!liquidity.gt(ZERO)) { throw new Error("liquidity must greater than 0"); } return zeroForOne ? this.getNextSqrtPriceFromTokenAmountARoundingUp( sqrtPriceX64, liquidity, amountIn, true ) : this.getNextSqrtPriceFromTokenAmountBRoundingDown( sqrtPriceX64, liquidity, amountIn, true ); } public static getNextSqrtPriceX64FromOutput( sqrtPriceX64: BN, liquidity: BN, amountOut: BN, zeroForOne: boolean ): BN { if (!sqrtPriceX64.gt(ZERO)) { throw new Error("sqrtPriceX64 must greater than 0"); } if (!liquidity.gt(ZERO)) { throw new Error("liquidity must greater than 0"); } return zeroForOne ? this.getNextSqrtPriceFromTokenAmountBRoundingDown( sqrtPriceX64, liquidity, amountOut, false ) : this.getNextSqrtPriceFromTokenAmountARoundingUp( sqrtPriceX64, liquidity, amountOut, false ); } private static getNextSqrtPriceFromTokenAmountARoundingUp( sqrtPriceX64: BN, liquidity: BN, amount: BN, add: boolean ): BN { if (amount.eq(ZERO)) return sqrtPriceX64; const liquidityLeftShift = liquidity.shln(U64Resolution); if (add) { const numerator1 = liquidityLeftShift; const denominator = liquidityLeftShift.add(amount.mul(sqrtPriceX64)); if (denominator.gte(numerator1)) { return MathUtil.mulDivCeil(numerator1, sqrtPriceX64, denominator); } return MathUtil.mulDivRoundingUp( numerator1, ONE, numerator1.div(sqrtPriceX64).add(amount) ); } else { const amountMulSqrtPrice = amount.mul(sqrtPriceX64); if (!liquidityLeftShift.gt(amountMulSqrtPrice)) { throw new Error( "getNextSqrtPriceFromTokenAmountARoundingUp,liquidityLeftShift must gt amountMulSqrtPrice" ); } const denominator = liquidityLeftShift.sub(amountMulSqrtPrice); return MathUtil.mulDivCeil(liquidityLeftShift, sqrtPriceX64, denominator); } } private static getNextSqrtPriceFromTokenAmountBRoundingDown( sqrtPriceX64: BN, liquidity: BN, amount: BN, add: boolean ): BN { const deltaY = amount.shln(U64Resolution); if (add) { return sqrtPriceX64.add(deltaY.div(liquidity)); } else { const amountDivLiquidity = MathUtil.mulDivRoundingUp( deltaY, ONE, liquidity ); if (!sqrtPriceX64.gt(amountDivLiquidity)) { throw new Error( "getNextSqrtPriceFromTokenAmountBRoundingDown sqrtPriceX64 must gt amountDivLiquidity" ); } return sqrtPriceX64.sub(amountDivLiquidity); } } public static getSqrtPriceX64FromTick(tick: number): BN { if (!Number.isInteger(tick)) { throw new Error("tick must be integer"); } if (tick < MIN_TICK || tick > MAX_TICK) { throw new Error("tick must be in MIN_TICK and MAX_TICK"); } const tickAbs: number = tick < 0 ? tick * -1 : tick; let ratio: BN = (tickAbs & 0x1) != 0 ? new BN("18445821805675395072") : new BN("18446744073709551616"); if ((tickAbs & 0x2) != 0) ratio = mulRightShift(ratio, new BN("18444899583751176192")); if ((tickAbs & 0x4) != 0) ratio = mulRightShift(ratio, new BN("18443055278223355904")); if ((tickAbs & 0x8) != 0) ratio = mulRightShift(ratio, new BN("18439367220385607680")); if ((tickAbs & 0x10) != 0) ratio = mulRightShift(ratio, new BN("18431993317065453568")); if ((tickAbs & 0x20) != 0) ratio = mulRightShift(ratio, new BN("18417254355718170624")); if ((tickAbs & 0x40) != 0) ratio = mulRightShift(ratio, new BN("18387811781193609216")); if ((tickAbs & 0x80) != 0) ratio = mulRightShift(ratio, new BN("18329067761203558400")); if ((tickAbs & 0x100) != 0) ratio = mulRightShift(ratio, new BN("18212142134806163456")); if ((tickAbs & 0x200) != 0) ratio = mulRightShift(ratio, new BN("17980523815641700352")); if ((tickAbs & 0x400) != 0) ratio = mulRightShift(ratio, new BN("17526086738831433728")); if ((tickAbs & 0x800) != 0) ratio = mulRightShift(ratio, new BN("16651378430235570176")); if ((tickAbs & 0x1000) != 0) ratio = mulRightShift(ratio, new BN("15030750278694412288")); if ((tickAbs & 0x2000) != 0) ratio = mulRightShift(ratio, new BN("12247334978884435968")); if ((tickAbs & 0x4000) != 0) ratio = mulRightShift(ratio, new BN("8131365268886854656")); if ((tickAbs & 0x8000) != 0) ratio = mulRightShift(ratio, new BN("3584323654725218816")); if ((tickAbs & 0x10000) != 0) ratio = mulRightShift(ratio, new BN("696457651848324352")); if ((tickAbs & 0x20000) != 0) ratio = mulRightShift(ratio, new BN("26294789957507116")); if ((tickAbs & 0x40000) != 0) ratio = mulRightShift(ratio, new BN("37481735321082")); if (tick > 0) ratio = MaxUint128.div(ratio); return ratio; } public static getTickFromPrice( price: Decimal, decimalsA: number, decimalsB: number ): number { return SqrtPriceMath.getTickFromSqrtPriceX64( SqrtPriceMath.priceToSqrtPriceX64(price, decimalsA, decimalsB) ); } public static getTickFromSqrtPriceX64(sqrtPriceX64: BN): number { if ( sqrtPriceX64.gt(MAX_SQRT_PRICE_X64) || sqrtPriceX64.lt(MIN_SQRT_PRICE_X64) ) { throw new Error( "Provided sqrtPrice is not within the supported sqrtPrice range." ); } const msb = sqrtPriceX64.bitLength() - 1; const adjustedMsb = new BN(msb - 64); const log2pIntegerX32 = signedLeftShift(adjustedMsb, 32, 128); let bit = new BN("8000000000000000", "hex"); let precision = 0; let log2pFractionX64 = new BN(0); let r = msb >= 64 ? sqrtPriceX64.shrn(msb - 63) : sqrtPriceX64.shln(63 - msb); while (bit.gt(new BN(0)) && precision < BIT_PRECISION) { r = r.mul(r); const rMoreThanTwo = r.shrn(127); r = r.shrn(63 + rMoreThanTwo.toNumber()); log2pFractionX64 = log2pFractionX64.add(bit.mul(rMoreThanTwo)); bit = bit.shrn(1); precision += 1; } const log2pFractionX32 = log2pFractionX64.shrn(32); const log2pX32 = log2pIntegerX32.add(log2pFractionX32); const logbpX64 = log2pX32.mul(new BN(LOG_B_2_X32)); const tickLow = signedRightShift( logbpX64.sub(new BN(LOG_B_P_ERR_MARGIN_LOWER_X64)), 64, 128 ).toNumber(); const tickHigh = signedRightShift( logbpX64.add(new BN(LOG_B_P_ERR_MARGIN_UPPER_X64)), 64, 128 ).toNumber(); if (tickLow == tickHigh) { return tickLow; } else { const derivedTickHighSqrtPriceX64 = SqrtPriceMath.getSqrtPriceX64FromTick(tickHigh) return derivedTickHighSqrtPriceX64.lte(sqrtPriceX64) ? tickHigh : tickLow } } } // tick math export class TickMath { public static getTickWithPriceAndTickspacing( price: Decimal, tickSpacing: number, mintDecimalsA: number, mintDecimalsB: number ) { const tick = SqrtPriceMath.getTickFromSqrtPriceX64( SqrtPriceMath.priceToSqrtPriceX64( price, mintDecimalsA, mintDecimalsB ) ); let result = tick / tickSpacing; if (result < 0) { result = Math.floor(result); } else { result = Math.ceil(result); } return result * tickSpacing; } public static roundPriceWithTickspacing( price: Decimal, tickSpacing: number, mintDecimalsA: number, mintDecimalsB: number ) { const tick = TickMath.getTickWithPriceAndTickspacing( price, tickSpacing, mintDecimalsA, mintDecimalsB ); const sqrtPriceX64 = SqrtPriceMath.getSqrtPriceX64FromTick(tick); return SqrtPriceMath.sqrtPriceX64ToPrice( sqrtPriceX64, mintDecimalsA, mintDecimalsB ); } } export class LiquidityMath { public static addDelta(x: BN, y: BN): BN { return x.add(y); } public static getTokenAmountAFromLiquidity( sqrtPriceX64A: BN, sqrtPriceX64B: BN, liquidity: BN, roundUp: boolean ): BN { if (sqrtPriceX64A.gt(sqrtPriceX64B)) { [sqrtPriceX64A, sqrtPriceX64B] = [sqrtPriceX64B, sqrtPriceX64A]; } if (!sqrtPriceX64A.gt(ZERO)) { throw new Error("sqrtPriceX64A must greater than 0"); } const numerator1 = liquidity.ushln(U64Resolution); const numerator2 = sqrtPriceX64B.sub(sqrtPriceX64A); return roundUp ? MathUtil.mulDivRoundingUp( MathUtil.mulDivCeil(numerator1, numerator2, sqrtPriceX64B), ONE, sqrtPriceX64A ) : MathUtil.mulDivFloor(numerator1, numerator2, sqrtPriceX64B).div( sqrtPriceX64A ); } public static getTokenAmountBFromLiquidity( sqrtPriceX64A: BN, sqrtPriceX64B: BN, liquidity: BN, roundUp: boolean ): BN { if (sqrtPriceX64A.gt(sqrtPriceX64B)) { [sqrtPriceX64A, sqrtPriceX64B] = [sqrtPriceX64B, sqrtPriceX64A]; } if (!sqrtPriceX64A.gt(ZERO)) { throw new Error("sqrtPriceX64A must greater than 0"); } return roundUp ? MathUtil.mulDivCeil(liquidity, sqrtPriceX64B.sub(sqrtPriceX64A), Q64) : MathUtil.mulDivFloor(liquidity, sqrtPriceX64B.sub(sqrtPriceX64A), Q64); } public static getLiquidityFromTokenAmountA( sqrtPriceX64A: BN, sqrtPriceX64B: BN, amountA: BN, roundUp: boolean ): BN { if (sqrtPriceX64A.gt(sqrtPriceX64B)) { [sqrtPriceX64A, sqrtPriceX64B] = [sqrtPriceX64B, sqrtPriceX64A]; } const numerator = amountA.mul(sqrtPriceX64A).mul(sqrtPriceX64B); const denominator = sqrtPriceX64B.sub(sqrtPriceX64A); const result = numerator.div(denominator); if (roundUp) { return MathUtil.mulDivRoundingUp(result, ONE, MaxU64); } else { return result.shrn(U64Resolution); } } public static getLiquidityFromTokenAmountB( sqrtPriceX64A: BN, sqrtPriceX64B: BN, amountB: BN ): BN { if (sqrtPriceX64A.gt(sqrtPriceX64B)) { [sqrtPriceX64A, sqrtPriceX64B] = [sqrtPriceX64B, sqrtPriceX64A]; } return MathUtil.mulDivFloor( amountB, MaxU64, sqrtPriceX64B.sub(sqrtPriceX64A) ); } public static getLiquidityFromTokenAmounts( sqrtPriceCurrentX64: BN, sqrtPriceX64A: BN, sqrtPriceX64B: BN, amountA: BN, amountB: BN ): BN { if (sqrtPriceX64A.gt(sqrtPriceX64B)) { [sqrtPriceX64A, sqrtPriceX64B] = [sqrtPriceX64B, sqrtPriceX64A]; } if (sqrtPriceCurrentX64.lte(sqrtPriceX64A)) { return LiquidityMath.getLiquidityFromTokenAmountA( sqrtPriceX64A, sqrtPriceX64B, amountA, false ); } else if (sqrtPriceCurrentX64.lt(sqrtPriceX64B)) { const liquidity0 = LiquidityMath.getLiquidityFromTokenAmountA( sqrtPriceCurrentX64, sqrtPriceX64B, amountA, false ); const liquidity1 = LiquidityMath.getLiquidityFromTokenAmountB( sqrtPriceX64A, sqrtPriceCurrentX64, amountB ); return liquidity0.lt(liquidity1) ? liquidity0 : liquidity1; } else { return LiquidityMath.getLiquidityFromTokenAmountB( sqrtPriceX64A, sqrtPriceX64B, amountB ); } } public static getAmountsFromLiquidity( sqrtPriceCurrentX64: BN, sqrtPriceX64A: BN, sqrtPriceX64B: BN, liquidity: BN, roundUp: boolean ): { amountA: BN, amountB: BN } { if (sqrtPriceX64A.gt(sqrtPriceX64B)) { [sqrtPriceX64A, sqrtPriceX64B] = [sqrtPriceX64B, sqrtPriceX64A]; } if (sqrtPriceCurrentX64.lte(sqrtPriceX64A)) { return { amountA: LiquidityMath.getTokenAmountAFromLiquidity( sqrtPriceX64A, sqrtPriceX64B, liquidity, roundUp ), amountB: new BN(0) }; } else if (sqrtPriceCurrentX64.lt(sqrtPriceX64B)) { const amountA = LiquidityMath.getTokenAmountAFromLiquidity( sqrtPriceCurrentX64, sqrtPriceX64B, liquidity, roundUp ); const amountB = LiquidityMath.getTokenAmountBFromLiquidity( sqrtPriceX64A, sqrtPriceCurrentX64, liquidity, roundUp ); return { amountA, amountB }; } else { return { amountA: new BN(0), amountB: LiquidityMath.getTokenAmountBFromLiquidity( sqrtPriceX64A, sqrtPriceX64B, liquidity, roundUp ), }; } } public static getAmountsFromLiquidityWithSlippage( sqrtPriceCurrentX64: BN, sqrtPriceX64A: BN, sqrtPriceX64B: BN, liquidity: BN, amountMax: boolean, roundUp: boolean, amountSlippage: number ) { const { amountA, amountB } = LiquidityMath.getAmountsFromLiquidity( sqrtPriceCurrentX64, sqrtPriceX64A, sqrtPriceX64B, liquidity, roundUp ); const coefficient = amountMax ? 1 + amountSlippage : 1 - amountSlippage; const amount0Slippage = amountA.muln(coefficient) const amount1Slippage = amountB.muln(coefficient) return { amountSlippageA: amount0Slippage, amountSlippageB: amount1Slippage, }; } } // swap math type SwapStep = { sqrtPriceX64Next: BN; amountIn: BN; amountOut: BN; feeAmount: BN; }; export interface StepComputations { sqrtPriceStartX64: BN; tickNext: number; initialized: boolean; sqrtPriceNextX64: BN; amountIn: BN; amountOut: BN; feeAmount: BN; } export abstract class SwapMath { public static swapCompute( programId: PublicKey, poolId: PublicKey, tickArrayCache: { [key: string]: TickArray }, zeroForOne: boolean, fee: number, liquidity: BN, currentTick: number, tickSpacing: number, currentSqrtPriceX64: BN, amountSpecified: BN, lastSavedTickArrayStartIndex: number, sqrtPriceLimitX64?: BN ) { if (amountSpecified.eq(ZERO)) { throw new Error("amountSpecified must not be 0"); } if (!sqrtPriceLimitX64) sqrtPriceLimitX64 = zeroForOne ? MIN_SQRT_PRICE_X64.add(ONE) : MAX_SQRT_PRICE_X64.sub(ONE); if (zeroForOne) { if (sqrtPriceLimitX64.lt(MIN_SQRT_PRICE_X64)) { throw new Error("sqrtPriceX64 must greater than MIN_SQRT_PRICE_X64"); } if (sqrtPriceLimitX64.gte(currentSqrtPriceX64)) { throw new Error("sqrtPriceX64 must smaller than current"); } } else { if (sqrtPriceLimitX64.gt(MAX_SQRT_PRICE_X64)) { throw new Error("sqrtPriceX64 must smaller than MAX_SQRT_PRICE_X64"); } if (sqrtPriceLimitX64.lte(currentSqrtPriceX64)) { throw new Error("sqrtPriceX64 must greater than current"); } } const baseInput = amountSpecified.gt(ZERO); const state = { amountSpecifiedRemaining: amountSpecified, amountCalculated: ZERO, sqrtPriceX64: currentSqrtPriceX64, tick: currentTick, accounts: [] as PublicKey[], liquidity, feeAmount: new BN(0), }; let loopCount = 0; while ( !state.amountSpecifiedRemaining.eq(ZERO) && state.sqrtPriceX64 != sqrtPriceLimitX64 && state.tick < MAX_TICK && state.tick > MIN_TICK ) { if (loopCount > 10) { throw Error("liquidity limit"); } const step: Partial = {}; step.sqrtPriceStartX64 = state.sqrtPriceX64; const { nextTick: nextInitTick, tickArrayAddress, tickArrayStartTickIndex: tickAarrayStartIndex } = TickQuery.nextInitializedTick( programId, poolId, tickArrayCache, state.tick, tickSpacing, zeroForOne ); step.tickNext = nextInitTick.tick; step.initialized = nextInitTick.liquidityGross.gtn(0); if ( lastSavedTickArrayStartIndex !== tickAarrayStartIndex && tickArrayAddress ) { state.accounts.push(tickArrayAddress); lastSavedTickArrayStartIndex = tickAarrayStartIndex; } if (step.tickNext < MIN_TICK) { step.tickNext = MIN_TICK; } else if (step.tickNext > MAX_TICK) { step.tickNext = MAX_TICK; } step.sqrtPriceNextX64 = SqrtPriceMath.getSqrtPriceX64FromTick( step.tickNext ); let targetPrice: BN; if ( (zeroForOne && step.sqrtPriceNextX64.lt(sqrtPriceLimitX64)) || (!zeroForOne && step.sqrtPriceNextX64.gt(sqrtPriceLimitX64)) ) { targetPrice = sqrtPriceLimitX64; } else { targetPrice = step.sqrtPriceNextX64; } [state.sqrtPriceX64, step.amountIn, step.amountOut, step.feeAmount] = SwapMath.swapStepCompute( state.sqrtPriceX64, targetPrice, state.liquidity, state.amountSpecifiedRemaining, fee ); state.feeAmount = state.feeAmount.add(step.feeAmount) if (baseInput) { state.amountSpecifiedRemaining = state.amountSpecifiedRemaining.sub( step.amountIn.add(step.feeAmount) ); state.amountCalculated = state.amountCalculated.sub(step.amountOut); } else { state.amountSpecifiedRemaining = state.amountSpecifiedRemaining.add( step.amountOut ); state.amountCalculated = state.amountCalculated.add( step.amountIn.add(step.feeAmount) ); } if (state.sqrtPriceX64.eq(step.sqrtPriceNextX64)) { if (step.initialized) { let liquidityNet = nextInitTick.liquidityNet; if (zeroForOne) liquidityNet = liquidityNet.mul(NEGATIVE_ONE); state.liquidity = LiquidityMath.addDelta( state.liquidity, liquidityNet ); } state.tick = zeroForOne ? step.tickNext - 1 : step.tickNext; } else if (state.sqrtPriceX64 != step.sqrtPriceStartX64) { state.tick = SqrtPriceMath.getTickFromSqrtPriceX64(state.sqrtPriceX64); } ++loopCount; } return { amountCalculated: state.amountCalculated, feeAmount: state.feeAmount, sqrtPriceX64: state.sqrtPriceX64, liquidity: state.liquidity, tickCurrent: state.tick, accounts: state.accounts, }; } private static swapStepCompute( sqrtPriceX64Current: BN, sqrtPriceX64Target: BN, liquidity: BN, amountRemaining: BN, feeRate: Fee ): [BN, BN, BN, BN] { const swapStep: SwapStep = { sqrtPriceX64Next: new BN(0), amountIn: new BN(0), amountOut: new BN(0), feeAmount: new BN(0), }; const zeroForOne = sqrtPriceX64Current.gte(sqrtPriceX64Target); const baseInput = amountRemaining.gte(ZERO); if (baseInput) { const amountRemainingSubtractFee = MathUtil.mulDivFloor( amountRemaining, FEE_RATE_DENOMINATOR.sub(new BN(feeRate.toString())), FEE_RATE_DENOMINATOR ); swapStep.amountIn = zeroForOne ? LiquidityMath.getTokenAmountAFromLiquidity( sqrtPriceX64Target, sqrtPriceX64Current, liquidity, true ) : LiquidityMath.getTokenAmountBFromLiquidity( sqrtPriceX64Current, sqrtPriceX64Target, liquidity, true ); if (amountRemainingSubtractFee.gte(swapStep.amountIn)) { swapStep.sqrtPriceX64Next = sqrtPriceX64Target; } else { swapStep.sqrtPriceX64Next = SqrtPriceMath.getNextSqrtPriceX64FromInput( sqrtPriceX64Current, liquidity, amountRemainingSubtractFee, zeroForOne ); } } else { swapStep.amountOut = zeroForOne ? LiquidityMath.getTokenAmountBFromLiquidity( sqrtPriceX64Target, sqrtPriceX64Current, liquidity, false ) : LiquidityMath.getTokenAmountAFromLiquidity( sqrtPriceX64Current, sqrtPriceX64Target, liquidity, false ); if (amountRemaining.mul(NEGATIVE_ONE).gte(swapStep.amountOut)) { swapStep.sqrtPriceX64Next = sqrtPriceX64Target; } else { swapStep.sqrtPriceX64Next = SqrtPriceMath.getNextSqrtPriceX64FromOutput( sqrtPriceX64Current, liquidity, amountRemaining.mul(NEGATIVE_ONE), zeroForOne ); } } const reachTargetPrice = sqrtPriceX64Target.eq(swapStep.sqrtPriceX64Next); if (zeroForOne) { if (!(reachTargetPrice && baseInput)) { swapStep.amountIn = LiquidityMath.getTokenAmountAFromLiquidity( swapStep.sqrtPriceX64Next, sqrtPriceX64Current, liquidity, true ); } if (!(reachTargetPrice && !baseInput)) { swapStep.amountOut = LiquidityMath.getTokenAmountBFromLiquidity( swapStep.sqrtPriceX64Next, sqrtPriceX64Current, liquidity, false ); } } else { swapStep.amountIn = reachTargetPrice && baseInput ? swapStep.amountIn : LiquidityMath.getTokenAmountBFromLiquidity( sqrtPriceX64Current, swapStep.sqrtPriceX64Next, liquidity, true ); swapStep.amountOut = reachTargetPrice && !baseInput ? swapStep.amountOut : LiquidityMath.getTokenAmountAFromLiquidity( sqrtPriceX64Current, swapStep.sqrtPriceX64Next, liquidity, false ); } if ( !baseInput && swapStep.amountOut.gt(amountRemaining.mul(NEGATIVE_ONE)) ) { swapStep.amountOut = amountRemaining.mul(NEGATIVE_ONE); } if (baseInput && !swapStep.sqrtPriceX64Next.eq(sqrtPriceX64Target)) { swapStep.feeAmount = amountRemaining.sub(swapStep.amountIn); } else { swapStep.feeAmount = MathUtil.mulDivCeil( swapStep.amountIn, new BN(feeRate), FEE_RATE_DENOMINATOR.sub(new BN(feeRate)) ); } return [ swapStep.sqrtPriceX64Next, swapStep.amountIn, swapStep.amountOut, swapStep.feeAmount, ]; } }