Translate

Monday, July 28, 2025

Can't estimate amount of tokens in a certain range for a Uniswap v3 pool

Wallpapers | July 28, 2025 | No comments


full image - Repost: Can't estimate amount of tokens in a certain range for a Uniswap v3 pool (from Reddit.com, Can't estimate amount of tokens in a certain range for a Uniswap v3 pool)
I am trying to do something I thought it would be simple: given a price range, I'd like to know how much liquidity would be active if the price were within the rage, in amount of tokens. I am somewhat surprised a tool to do this doesn't exist, other than the official Uniswap interface (liquidity tab), which I've found to be inaccurate.I read this guide about how to calculate liquidity per price that was useful, but I couldn't figure out how to convert that to amount of tokens. const tickRanges = [ { name: "Previous Ticks", ticks: prevSlice, descending: true }, { name: "Next Ticks", ticks: nextSlice, descending: false } ]; for (const range of tickRanges) { console.log(`\n=== Processing ${range.name} descending: (${range.descending}) ===`); const rangeTicks = [...range.ticks] if (range.descending) { rangeTicks.reverse(); // Reverse for ascending order } // Keep track of total calculated amounts let totalToken0 = 0; let totalToken1 = 0; // Track the previous tick for calculating ranges let previousTick = activeTick; let liquidity = pool.liquidity for (const tick of rangeTicks) { // Ensure ticks are in correct order (lower, upper) const lowerTick = parseInt(range.descending? tick.tickIdx: activeTick.tickIdx); const upperTick = parseInt(range.descending? activeTick.tickIdx: tick.tickIdx); console.log(`Lower tick: ${lowerTick}, Upper tick: ${upperTick}`); liquidity = range.descending? JSBI.subtract(liquidity, JSBI.BigInt((tick.liquidityNet))): JSBI.add(liquidity, JSBI.BigInt((tick.liquidityNet))) // Calculate amounts for just this specific price range const { amount0, amount1 } = getAmountsForLiquidity( lowerTick, upperTick, pool.tickCurrent, liquidity, token0, token1 ); totalToken0 += amount0; totalToken1 += amount1; if (amount0 === 0 && amount1 === 0) continue console.log("Analysing tick:", tick.tickIdx, "with price0:", tick.price0, "price1:", tick.price1); console.log(`- ${token0.symbol} in this range: ${amount0}`); console.log(`- ${token1.symbol} in this range: ${amount1}`); console.log(`- Running total ${token0.symbol}: ${totalToken0}`); console.log(`- Running total ${token1.symbol}: ${totalToken1}`); previousTick = tick; } // Display total calculated amounts console.log(`\nTotal calculated ${token0.symbol}: ${totalToken0.toLocaleString()}`); console.log(`Total calculated ${token1.symbol}: ${totalToken1.toLocaleString()}`); }function getLiquidityAmounts( sqrtPriceX96: JSBI, sqrtPriceAX96: JSBI, sqrtPriceBX96: JSBI, liquidity: JSBI): { amount0: JSBI; amount1: JSBI } { const Q96 = JSBI.exponentiate(JSBI.BigInt(2), JSBI.BigInt(96)); let amount0 = JSBI.BigInt(0); let amount1 = JSBI.BigInt(0); if (JSBI.lessThanOrEqual(sqrtPriceX96, sqrtPriceAX96)) { // Current price is below range - all liquidity is in token0 const numerator = JSBI.multiply(liquidity, JSBI.subtract(sqrtPriceBX96, sqrtPriceAX96)); const denominator = JSBI.multiply(sqrtPriceBX96, sqrtPriceAX96); amount0 = JSBI.divide(JSBI.multiply(numerator, Q96), denominator); } else if (JSBI.lessThan(sqrtPriceX96, sqrtPriceBX96)) { // Current price is in range const numerator0 = JSBI.multiply(liquidity, JSBI.subtract(sqrtPriceBX96, sqrtPriceX96)); const denominator0 = JSBI.multiply(sqrtPriceBX96, sqrtPriceX96); amount0 = JSBI.divide(JSBI.multiply(numerator0, Q96), denominator0); amount1 = JSBI.multiply(liquidity, JSBI.subtract(sqrtPriceX96, sqrtPriceAX96)); amount1 = JSBI.divide(amount1, Q96); } else { // Current price is above range - all liquidity is in token1 amount1 = JSBI.multiply(liquidity, JSBI.subtract(sqrtPriceBX96, sqrtPriceAX96)); amount1 = JSBI.divide(amount1, Q96); } return { amount0, amount1 };}export function getAmountsForLiquidity( tickLower: number, tickUpper: number, tickCurrent: number, liquidity: JSBI, token0: Token, token1: Token): { amount0: number; amount1: number } { const sqrtPriceLower = TickMath.getSqrtRatioAtTick(tickLower); const sqrtPriceUpper = TickMath.getSqrtRatioAtTick(tickUpper); const sqrtPriceCurrent = TickMath.getSqrtRatioAtTick(tickCurrent); // Use the proper liquidity amounts calculation const { amount0, amount1 } = getLiquidityAmounts( sqrtPriceCurrent, sqrtPriceLower, sqrtPriceUpper, liquidity ); // Convert to human readable amounts with proper decimal scaling return { amount0: parseFloat(amount0.toString()) / Math.pow(10, token0.decimals), amount1: parseFloat(amount1.toString()) / Math.pow(10, token1.decimals) };}I am suspicious of my getAmountsForLiquidity implementation, which I was also surprised there's no implementation in TS/JS available.I'm getting tick data from The Graph and I've cross-checked it with explorers, I'm confident it's correct. But the script is wildly overstating the amount of tokens:I'm using the pool USDC/USDT on Celo for testing 0x1a810e0b6c2dd5629afa2f0c898b9512c6f78846Lower tick: 3, Upper tick: 4Analysing tick: 3 with price0: 1.000300030001 price1: 0.9997000599900014997900279964004499- USD₮ in this range: 0- USDC in this range: 584037.782408- Running total USD₮: 0- Running total USDC: 584037.782408When this is the total pool TVL:=== Pool TVL (Actual Token Balances) ===Actual USD₮ Balance: 672,755.119Actual USDC Balance: 362,185.384Total Pool TVL: $1,034,940.503So for the first tick, is already estimating to be in range more than the TVL of the whole pool.What is that I'm getting wrong? I'm sure theres a key concept I am missing.Note: in the snippets you'll see comments generated by LLMs. I originally tried to vibe code this and they were completely confused, I've since completely rewritten to try to understand it and this is where I landed.


Mining:
Bitcoin, Cryptotab browser - Pi Network cloud PHONE MINING
Fone, cloud PHONE MINING cod. dhvd1dkx - Mintme, PC PHONE MINING


Exchanges:
Coinbase.com - Stex.com - Probit.com


Donations:
Done crypto






Email Newsletter

Like what you read here in this blog post?
Get more like it delivered to your inbox daily.



No comments:

Post a Comment