import { TickMath } from '@uniswap/v3-sdk'; const Q96 = 2n ** 96n; export function _getV3AmountOut( amountIn: bigint, v3Data: { tickDatas: { tick: number; liquidityNet: bigint; }[]; slot0: { sqrtPriceX96: bigint; tick: number; }; liquidity: bigint; fee: bigint; }, zeroForOne: boolean ): bigint { const MIN_SQRT_RATIO = 4295128739n + 1n; const MAX_SQRT_RATIO = 1461446703485210103287273052203988822378723970342n - 1n; let currentTick = v3Data.slot0.tick; let sqrtRatioX96 = v3Data.slot0.sqrtPriceX96; let liquidity = v3Data.liquidity; let amountRemaining = amountIn - (amountIn * v3Data.fee) / 1000000n; let amountOut = 0n; while (amountRemaining > 0n && sqrtRatioX96 > 0n) { let nextTick = -1; if (zeroForOne) { for (let i = 0; i < v3Data.tickDatas.length; i++) { if (v3Data.tickDatas[i].tick >= currentTick) { nextTick = v3Data.tickDatas[i - 1].tick; break; } } if (nextTick === -1) { nextTick = -887272; } } else { for (let i = v3Data.tickDatas.length - 1; i >= 0; i--) { if (v3Data.tickDatas[i].tick <= currentTick) { nextTick = v3Data.tickDatas[i + 1].tick; break; } } if (nextTick === -1) { nextTick = 887272; } } const nextSqrtPriceX96 = getSqrtRatioAtTick(nextTick); const targetSqrtPriceX96 = zeroForOne ? nextSqrtPriceX96 > MIN_SQRT_RATIO ? nextSqrtPriceX96 : MIN_SQRT_RATIO : nextSqrtPriceX96 < MAX_SQRT_RATIO ? nextSqrtPriceX96 : MAX_SQRT_RATIO; const swapResult = computeSwapStep( sqrtRatioX96, targetSqrtPriceX96, liquidity, amountRemaining, zeroForOne ); sqrtRatioX96 = swapResult.sqrtRatioNext; amountOut += swapResult.amountOut; amountRemaining -= swapResult.amountIn; if (sqrtRatioX96 === nextSqrtPriceX96) { const tickData = v3Data.tickDatas.find((tickData) => tickData.tick === nextTick); if (tickData) { liquidity = zeroForOne ? liquidity - tickData.liquidityNet : liquidity + tickData.liquidityNet; } currentTick = zeroForOne ? nextTick - 1 : nextTick + 1; } else { break; } } return amountOut; } export function getPriceFromSqrtPriceX96( sqrtPriceX96: bigint, token0Decimals: number, token1Decimals: number ): number { const Q96 = BigInt(2) ** BigInt(96); const sqrtPrice = Number(sqrtPriceX96) / Number(Q96); const price = sqrtPrice ** 2; const decimalAdjustment = 10 ** (token0Decimals - token1Decimals); return price * decimalAdjustment; } export function getAmountsForTick( tick: number, liquidityNet: bigint, sqrtPriceX96: bigint, tickSpacing: number ): { amount0: bigint; amount1: bigint } { const liquidity = BigInt(liquidityNet > 0 ? liquidityNet : -liquidityNet); const tickLower = tick; const tickUpper = tick + tickSpacing; const sqrtRatioA = getSqrtRatioAtTick(tickLower); const sqrtRatioB = getSqrtRatioAtTick(tickUpper); const sqrtPrice = sqrtPriceX96; const [sqrtLower, sqrtUpper] = sqrtRatioA < sqrtRatioB ? [sqrtRatioA, sqrtRatioB] : [sqrtRatioB, sqrtRatioA]; let amount0: bigint = 0n; let amount1: bigint = 0n; if (sqrtPrice <= sqrtLower) { // 全部是 token0 amount0 = (liquidity * (sqrtUpper - sqrtLower)) / ((sqrtUpper * sqrtLower) >> 96n); } else if (sqrtPrice < sqrtUpper) { // token0 + token1 混合区间 amount0 = (liquidity * (sqrtUpper - sqrtPrice)) / ((sqrtUpper * sqrtPrice) >> 96n); amount1 = (liquidity * (sqrtPrice - sqrtLower)) >> 96n; } else { // 全部是 token1 amount1 = (liquidity * (sqrtUpper - sqrtLower)) >> 96n; } return { amount0, amount1 }; } export function getSqrtRatioAtTick(tick: number): bigint { return BigInt(TickMath.getSqrtRatioAtTick(tick).toString()); } /** * 计算单步交换 */ function computeSwapStep( sqrtRatioX96: bigint, targetSqrtRatioX96: bigint, liquidity: bigint, amountRemaining: bigint, zeroForOne: boolean ): { amountIn: bigint; amountOut: bigint; sqrtRatioNext: bigint } { // 确保方向正确 if ( (zeroForOne && sqrtRatioX96 <= targetSqrtRatioX96) || (!zeroForOne && sqrtRatioX96 >= targetSqrtRatioX96) ) { throw new Error('价格方向错误'); } let sqrtRatioNext: bigint; let amountIn: bigint; let amountOut: bigint; if (zeroForOne) { // token0 -> token1 // 计算此步骤的最大输入量 const maxAmountIn = computeAmount0Delta(sqrtRatioX96, targetSqrtRatioX96, liquidity, true); // 如果剩余输入量大于等于最大输入量,则价格会到达目标价格 if (amountRemaining >= maxAmountIn) { sqrtRatioNext = targetSqrtRatioX96; amountIn = maxAmountIn; } else { // 否则,计算价格变化 sqrtRatioNext = getNextSqrtPriceFromInput(sqrtRatioX96, liquidity, amountRemaining, true); amountIn = computeAmount0Delta(sqrtRatioX96, sqrtRatioNext, liquidity, true); } // 计算输出量 amountOut = computeAmount1Delta(sqrtRatioX96, sqrtRatioNext, liquidity, false); } else { // token1 -> token0 // 计算此步骤的最大输入量 const maxAmountIn = computeAmount1Delta(targetSqrtRatioX96, sqrtRatioX96, liquidity, true); // 如果剩余输入量大于等于最大输入量,则价格会到达目标价格 if (amountRemaining >= maxAmountIn) { sqrtRatioNext = targetSqrtRatioX96; amountIn = maxAmountIn; } else { // 否则,计算价格变化 sqrtRatioNext = getNextSqrtPriceFromInput(sqrtRatioX96, liquidity, amountRemaining, false); amountIn = computeAmount1Delta(sqrtRatioNext, sqrtRatioX96, liquidity, true); } // 计算输出量 amountOut = computeAmount0Delta(sqrtRatioNext, sqrtRatioX96, liquidity, false); } return { amountIn, amountOut, sqrtRatioNext }; } /** * 计算token0数量变化 */ function computeAmount0Delta( sqrtRatioA: bigint, sqrtRatioB: bigint, liquidity: bigint, roundUp: boolean ): bigint { // 确保 sqrtRatioA 和 sqrtRatioB 的顺序 if (sqrtRatioA > sqrtRatioB) { [sqrtRatioA, sqrtRatioB] = [sqrtRatioB, sqrtRatioA]; } const numerator = liquidity * Q96 * (sqrtRatioB - sqrtRatioA); const denominator = sqrtRatioB * sqrtRatioA; // 根据需要向上或向下取整 if (roundUp) { return numerator / denominator + (numerator % denominator > 0n ? 1n : 0n); } else { return numerator / denominator; } } // public static getAmount0Delta(sqrtRatioAX96: JSBI, sqrtRatioBX96: JSBI, liquidity: JSBI, roundUp: boolean): JSBI { // if (JSBI.greaterThan(sqrtRatioAX96, sqrtRatioBX96)) { // ;[sqrtRatioAX96, sqrtRatioBX96] = [sqrtRatioBX96, sqrtRatioAX96] // } // const numerator1 = JSBI.leftShift(liquidity, JSBI.BigInt(96)) // const numerator2 = JSBI.subtract(sqrtRatioBX96, sqrtRatioAX96) // return roundUp // ? FullMath.mulDivRoundingUp(FullMath.mulDivRoundingUp(numerator1, numerator2, sqrtRatioBX96), ONE, sqrtRatioAX96) // : JSBI.divide(JSBI.divide(JSBI.multiply(numerator1, numerator2), sqrtRatioBX96), sqrtRatioAX96) // } /** * 计算token1数量变化 */ function computeAmount1Delta( sqrtRatioA: bigint, sqrtRatioB: bigint, liquidity: bigint, roundUp: boolean ): bigint { // 确保 sqrtRatioA 和 sqrtRatioB 的顺序 if (sqrtRatioA > sqrtRatioB) { [sqrtRatioA, sqrtRatioB] = [sqrtRatioB, sqrtRatioA]; } // 计算token1的变化量 const amount = (liquidity * (sqrtRatioB - sqrtRatioA)) / 2n ** 96n; // 根据需要向上或向下取整 if (roundUp && (liquidity * (sqrtRatioB - sqrtRatioA)) % 2n ** 96n > 0n) { return amount + 1n; } return amount; } /** * 根据输入金额计算下一个价格 */ function getNextSqrtPriceFromInput( sqrtPriceX96: bigint, liquidity: bigint, amountIn: bigint, zeroForOne: boolean ): bigint { if (zeroForOne) { // token0 -> token1 const numerator = liquidity * sqrtPriceX96; const product = amountIn * sqrtPriceX96; const denominator = liquidity * Q96 + product; return (numerator * Q96) / denominator; } else { // token1 -> token0 return sqrtPriceX96 + (amountIn * Q96) / liquidity; } }