Sign in

Uniswap V3

What's wrong with V2

A V2 pool prices trades along the hyperbola x * y = k. The curve covers every possible price from zero to infinity. As price moves up or down, reserves shift along the curve. The math is simple and the system works.

The trouble is that LPs end up providing depth at prices that will never trade. If the current ETH price is $3,500 and you deposit $10,000 into a V2 ETH/USDC pool, a tiny fraction of your $10,000 is providing liquidity at prices near $3,500 (where trades actually happen). The rest of your capital is reserved for hypothetical trades at $1, at $100,000, at $1,000,000. Trades there will never happen, but your dollars are pinned to those price points anyway. They sit idle, earning no fees.

Empirically, in a V2 ETH/USDC pool, an LP's capital that's "active" within any reasonable trading band is often less than 5% of their deposit. The other 95% is dead weight. They earn fees proportional to their share of total liquidity, but most of that liquidity isn't doing anything useful.

V3's idea: concentrated liquidity

V3 lets an LP say: "I think ETH will trade between $3,000 and $4,000. Concentrate all my capital in that range." The LP picks a lower price Pl and an upper price Pu. Their capital provides depth only in [Pl, Pu]. If trades happen inside that range, they earn fees. If price moves outside their range, their position earns nothing until either the price comes back or the LP repositions.

V2 spreads your capital across every price. V3 lets you concentrate it. Uniswap V2: same $10,000 deposit spread across all prices price current $0 $3,500 $∞ Most of your money provides liquidity at prices that will never trade. It's "deployed" but earning no fees. Uniswap V3: same $10,000 concentrated in [$3,000, $4,000] price $3,000 $4,000 current $0 $∞ All your money provides deep liquidity exactly where trades actually happen. Fees per dollar deposited are dramatically higher. The tradeoff: If price leaves your range, your position earns zero fees until it re-enters or you reposition.

The same $10,000 deposit in V3 with a range of [$3,000, $4,000] provides depth comparable to roughly $100,000+ of V2 liquidity, since none of the capital is wasted at unreachable prices. The exact multiplier depends on how narrow the range is. Narrower range = more concentration = more fees per dollar when active, but a higher risk that price leaves the range and the position earns nothing.

V3 isn't strictly better than V2. It's a tradeoff: capital efficiency in exchange for active management. An LP who picks a tight range gets great returns when price stays inside, but loses fee income whenever price exits. V2's passive LPs earn less per dollar but never go "out of range." Which is better depends on the LP's willingness to monitor and adjust.

Ticks: discrete price levels

Now the implementation question. V3 lets you pick a range [Pl, Pu]. But V3 doesn't allow arbitrary prices for the boundaries. Ranges must start and end at specific discrete price levels called ticks.

A tick is just a price level on a predefined grid. The formula is:

price(tick) = 1.0001 ^ tick

Each tick is 0.0001 of a price multiplier from the next, which is 0.01% (one basis point). Tick 0 corresponds to price 1.0. Tick 1 corresponds to price 1.0001. Tick -1 to price 0.9999. And so on.

For an ETH/USDC pool with ETH around $3,500, the corresponding tick is around 81,800. The integer is large because the price ratio is far from 1, but the math handles it cleanly. Frontends and SDKs convert between tick numbers and human-readable prices for you. You almost never compute ticks by hand.

A tick is just a discrete price level. Each step is 0.01% from the next. Possible price levels: tick 81797 81798 81799 81800 81801 81802 81803 81804 81805 81806 3565.07 3565.42 3565.78 3566.14 3566.49 3566.85 3567.21 3567.56 3567.92 3568.28 price in USDC per ETH +0.01% +0.01% The formula: price(tick) = 1.0001 ^ tick Each tick is one 0.01% step away from the next. The math doesn't need to be memorized. Not every tick is usable: fee tier sets the spacing 0.01% fee → tick spacing 1 (stablecoin pairs, tight ranges) 0.05% fee → tick spacing 10 (correlated assets like ETH/staked-ETH) 0.30% fee → tick spacing 60 (typical pairs like ETH/USDC) 1.00% fee → tick spacing 200 (exotic pairs, wide ranges)

The number 1.0001 was chosen because it makes the per-tick step equal to a clean basis point. The whole tick range V3 supports runs from roughly -887,272 to +887,272, which corresponds to a price range from about 1e-38 to 1e38. More than enough to cover every conceivable asset pair.

A few practical things to internalize about ticks:

Tick boundaries are integer-valued. You can't say "my range starts at price $3,517.23." You say "my range starts at tick 81,802," which the SDK will round to from your input price.

The same range can be expressed by different tick pairs depending on token order. V3 pools have a notion of token0 and token1. Price is conventionally token1 per token0. If you swap the order, ticks flip sign. ETH/USDC at $3,500 has positive ticks, USDC/ETH at 1/3,500 has the same magnitude with negative ticks.

Tick spacing controls granularity. A pool with tick spacing 60 only allows positions whose endpoints are multiples of 60. So instead of using every possible tick from 81,797 to 81,806, you'd be picking from a coarser ladder.

Fee tiers

V2 had one fee: 0.30%. V3 has four, each with its own pool and tick spacing:

  • 0.01% — for pairs that should trade at almost identical prices, like two stablecoins. Tick spacing 1 (most granular).
  • 0.05% — for correlated assets that move closely together, like ETH and a liquid staked ETH derivative. Tick spacing 10.
  • 0.30% — the V2 standard, used for typical volatile pairs like ETH/USDC. Tick spacing 60.
  • 1.00% — for exotic pairs with high volatility. Tick spacing 200.

Each fee tier is a separate pool. So when you talk about "the ETH/USDC pool" on V3, you actually have to specify which one of three or four pools you mean. Liquidity is split across these tiers, which is a real downside: thin markets fragment further. The Uniswap router handles this by routing trades through whichever pool gives the best execution.

LPs choose a fee tier based on the volatility they expect. Higher volatility means higher fee income but also more risk of price moving out of range. The coarser tick spacing for higher fee tiers reflects that fine-grained ranges are less useful for assets that move a lot.

Three position types: where single-sided liquidity comes from

Now to the part where V3 starts to feel different from V2.

In V2, you always deposit both tokens, in the ratio set by the current pool price. There's no other option. In V3, the ratio depends on where your chosen range sits relative to the current price. Sometimes you deposit both tokens. Sometimes only one. And it's not up to you which case applies. The range you pick forces the composition.

Three position types. The range you pick decides which token(s) you deposit. Range BELOW current price -> deposit only USDC $2,000 $3,000 current $3,500 price You deposit: 100% USDC Why: for price to enter this range, it must FALL. As price falls, the pool buys ETH from sellers, paying out USDC. Your USDC is the inventory being spent. If price falls to $2,000, all your USDC is now ETH. Functionally a "buy ETH at $3,000 or lower" order. Range STRADDLING current price -> deposit BOTH tokens $3,000 $4,500 current $3,500 price You deposit: both USDC and ETH Why: the range covers prices above AND below current. If price rises, the pool needs ETH to sell. If price falls, USDC to buy. Position needs both, in a ratio determined by where current sits in the range. Closer to lower bound → more ETH. Closer to upper bound → more USDC. Range ABOVE current price -> deposit only ETH $4,500 $5,500 current $3,500 price You deposit: 100% ETH Why: for price to enter this range, it must RISE. As price rises, the pool sells ETH to buyers, collecting USDC. Your ETH is the inventory being sold. If price rises to $5,500, all your ETH is now USDC. Functionally a "sell ETH at $4,500 or higher" order. The deposit ratio is forced by the range, not by you.

Why this works: think in trade flows

The single-sided deposit rule seems strange at first. To make sense of it, watch what the pool actually does when price moves.

When price rises, someone is buying ETH. They send USDC into the pool, the pool sends ETH out. The pool gains USDC and loses ETH. From the pool's perspective, it just "sold ETH for USDC."

When price falls, someone is selling ETH. They send ETH into the pool, the pool sends USDC out. The pool gains ETH and loses USDC. From the pool's perspective, it just "bought ETH with USDC."

So a pool acts as a market maker that's always taking the other side of trades. To sell ETH (when price rises) it needs ETH inventory. To buy ETH (when price falls) it needs USDC inventory.

Now apply this to a V3 position with range [Pl, Pu]:

Range entirely above current price (current price < Pl). For price to ever reach this range, it has to rise from where it is to Pl, then potentially keep rising up to Pu. The entire trip through the range is a rising-price trip. The pool is selling ETH the whole way. Your position needs ETH on hand to participate, so you deposit only ETH. As price rises through your range, your ETH gradually gets sold off in exchange for USDC. By the time price reaches Pu, all your ETH is gone, replaced by USDC at the highest prices in your range.

Range entirely below current price (current price > Pu). For price to enter this range, it has to fall from where it is to Pu, then potentially keep falling to Pl. The entire trip is a falling-price trip. The pool is buying ETH the whole way. Your position needs USDC ready to spend, so you deposit only USDC. As price falls through your range, your USDC gradually gets converted to ETH at the falling prices. By the time price reaches Pl, all your USDC is gone, replaced by ETH at the lowest prices in your range.

Range straddling current (Pl < current < Pu). The price can move either direction from the current spot. The pool might need ETH inventory (for upward moves) or USDC inventory (for downward moves). Your position needs both, in a ratio that depends on where current sits within the range.

The rule of thumb to remember: at the lower boundary Pl, a position holds 100% of token X (ETH in this example). At the upper boundary Pu, it holds 100% of token Y (USDC). In between, the position is a mix. Where the current price sits relative to these boundaries dictates the deposit composition.

Range orders: limit orders in disguise

The single-sided behavior turns V3 into something the previous AMMs couldn't be: a venue for limit orders.

Suppose ETH is at $3,500 and you want to sell at $4,000. In V2, you'd have to actively monitor the price and execute a swap when it hits your target. In V3, you create a tight position with range [$4,000, $4,005]. The position is entirely above current price, so you deposit only ETH. If price never reaches $4,000, the position sits idle. If price moves through $4,000 to $4,005, your ETH gets swapped for USDC inside the range, and by the time price exits at the top, your position holds 100% USDC at an average price near $4,000.

You've effectively placed a sell order that fills around $4,000. Same logic in reverse for limit buys with a tight range below current.

The fill price isn't exactly the target. There's some spread between Pl and Pu, so the actual execution price is an average within the range. Tighter ranges mean closer-to-exact fills but smaller fee earnings while filling. This is the difference between a true limit order and a V3 range order: the V3 version still earns fees during the fill, in exchange for some slippage.

This was a real user-facing feature when V3 launched. It pushed several limit-order frontends to build on top of V3 positions.

NFT positions

V2 LP positions were ERC-20 tokens. Every LP in a V2 pool had the same kind of claim, just for different amounts, so fungibility worked. You could send your LP tokens to anyone, stake them in another protocol, swap them, anything ERC-20s do.

V3 positions can't be ERC-20s because they're not interchangeable. Two LPs in the same pool with different ranges have completely different exposures. If Alice has range [$3,000, $4,000] and Bob has range [$3,500, $5,000], their positions aren't comparable. You can't have a fungible "ETH/USDC V3 LP token" because there's no single representative position.

V3 uses ERC-721 NFTs instead. Each position is a non-fungible token whose metadata includes the pool, the range (tickLower, tickUpper), the liquidity amount, and accumulated fees. The NonfungiblePositionManager contract is the standard entry point: LPs mint NFTs through it, manage liquidity through it, collect fees through it. Position NFTs are transferable like any ERC-721, so you can sell your position on a secondary market or use it as collateral.

This is one of the things that makes building on V3 more complex than V2. Every LP integration has to handle individual positions rather than treating LP shares as fungible balances.

A better TWAP

V2 oracles use a single cumulative price counter, sampled at whatever times consumers care about. V3 ships a more refined version.

Each V3 pool keeps an observations array, a circular buffer of past (timestamp, tick cumulative) entries. Pools default to 1 slot but can be extended up to 65,535 slots. The slots get filled on every state-changing interaction (swap, mint, burn). A consumer reading a TWAP for the last 30 minutes asks the pool to find observations bracketing the start and end of the window, and computes the average from those two readings.

The advantages over V2's design:

  • Consumers don't need to take their own observation. The pool's observations are already there, indexed by time.
  • Multiple consumers reading the same window share the cost.
  • Pools with extended observation arrays cover hours or days of history.

V3 also uses the geometric mean of tick (the log of price) rather than the arithmetic mean of raw price. This matters when prices can swing in large ratios. The geometric mean of $100 and $400 is $200. The arithmetic mean is $250. The geometric version is symmetric around the true price, which is more useful for risk-bearing applications.

The V3 oracle documentation covers the specifics of how to read observations and compute windows.

When V3 vs V2

V3's strengths:

  • Capital efficiency for LPs willing to manage positions
  • Limit-order-like behavior via range orders
  • Lower slippage for traders, since concentrated liquidity makes pools effectively deeper near current prices
  • Stronger oracle for downstream protocols

V3's weaknesses:

  • LPs have to manage active positions, or accept that price will eventually move out of range
  • Multiple fee tiers fragment liquidity across pools
  • Tick math, NFT positions, and per-position fee accounting make the codebase and integrations more complex
  • Less suitable for pairs where price has no natural "center" the LP can target

V2's design is still a reasonable choice for some applications: assets with extreme volatility where any chosen range becomes irrelevant quickly, passive LPs who can't or won't monitor positions, simple integrations where V2's fungible LP tokens make the rest of the contract design cleaner. V2 isn't deprecated; it coexists with V3 because the tradeoffs go different ways for different use cases.