Advanced level
An automated market maker prices trades with an invariant, a formula its reserves must satisfy before and after every swap. For the constant product design, x times y stays equal to k, which fixes the output of any trade, the price impact and the loss an LP takes when the price moves. Concentrated liquidity keeps the same curve but lets each LP choose the price range where their capital works.
The invariant does the pricing
A constant product pool holds a reserve x of one token and y of another, and it
enforces one rule: after any trade, the product of the reserves must be at least what it was before.
Nothing else sets the price. There is no oracle and no order book. The spot price of x in terms
of y is simply the ratio of the reserves, which is also the slope of the curve at the current
point:
A pool with 100 ETH and 300,000 USDC therefore quotes ETH at 3,000 USDC. That is the only information the pool has about the outside world, which is why arbitrage matters so much, as you will see below.
The swap formula, with the fee
Pay in Δy of one token and the pool pays out Δx of the other, sized so
that the invariant holds on the part of your input that is not taken as a fee. With a fee rate f
(0.003 for a 0.3% pool):
Run it on the pool above with a 0.3% fee:
| You pay | ETH out | Average price | Spot price afterward | Spot moved |
|---|---|---|---|---|
| 3,000 USDC | 0.9872 | 3,039.03 | 3,060.21 | +2.0% |
| 30,000 USDC | 9.0661 | 3,309.03 | 3,629.01 | +21.0% |
Two things are worth noticing. Your average price moved by about the size of your trade relative to the USDC reserve, 1% and 10% plus the fee. The spot price you leave behind moved roughly twice as far, because the average is taken across a curve and you finished at its far end. The next trader starts from your ending price, not your average.
Arbitrage is how the pool learns the price
After the 30,000 USDC buy, the pool quotes ETH at 3,629 while every other venue still says 3,000. That gap is free money, and an arbitrageur takes it within a block: sell ETH into the pool until its spot price is back in line, buy the same ETH cheaper elsewhere, pocket the difference minus gas and fees.
This is healthy and necessary, because it is the only mechanism that keeps an AMM's price honest. It also has a cost, and it falls on the liquidity providers. Every time the outside price moves, the pool sells the asset that went up and buys the one that went down, at a price that is stale by exactly the amount the arbitrageur captures. That cost has a name in the research literature, loss versus rebalancing, and it is the subject of the guide on LP as a strategy.
Impermanent loss, derived
Because the pool always holds reserves in proportion to the price, you can solve for what an LP owns at any
price. If the price of x moves by a factor r, the reserves become
x′ = x / √r and y′ = y · √r. Compare the value of that
position with simply holding the original tokens and you get the familiar result:
| Price moves by | Pool vs holding |
|---|---|
| 1.25x or 0.8x | −0.62% |
| 1.5x | −2.02% |
| 2x or 0.5x | −5.72% |
| 3x | −13.40% |
| 4x or 0.25x | −20.00% |
| 5x | −25.46% |
"Impermanent" only means the loss disappears if the price returns to where it started. Fees earned over the period are the other side of the ledger. The impermanent loss calculator runs this for any pair of prices.
Concentrated liquidity: the same curve, cropped
In a full range pool most of the capital sits at prices the pair will never trade at. A stablecoin pool
holds reserves prepared for USDC to trade at 50 dollars. Uniswap v3 changed that by letting each LP choose a
price range [Pa, Pb] and act as a constant product pool only inside it.
The cleanest way to describe a position is by its liquidity L, and prices by
their square roots. Inside the range the position behaves as if it held these virtual reserves:
But it only has to hold enough real tokens to cover moves to the edges of its range:
Swaps inside a range become two short formulas, which is exactly how the contract computes them:
A worked example, side by side
Take the same ETH price of 3,000 and a position covering 2,700 to 3,300. Size it to hold 150,000 USDC of real
reserves. From the formulas above that implies L ≈ 53,367 and about 45.34 ETH, a total of
roughly 286,000 dollars.
| Full range pool | Concentrated position, 2,700 to 3,300 | |
|---|---|---|
| Capital deposited | About 600,000 dollars (100 ETH + 300,000 USDC) | About 286,000 dollars |
| Buy with 30,000 USDC, no fee: ETH out | 9.09 | 9.90 |
| Average price paid | 3,300 | 3,030.79 |
| Capital needed for the same depth at 3,000 | About 5.85 million dollars | About 286,000 dollars |
With less than half the capital, the concentrated position fills the same trade at about 1% impact instead
of 10%, because all of its liquidity is working near the current price. Matching that depth with a full range
pool would take about 20 times as much money. That ratio is the capital efficiency, and for a range that is
symmetric in percentage terms, from P/r to P·r, it has a closed form:
| Range around the price | Efficiency vs full range |
|---|---|
| ÷2 to ×2 | about 3.4x |
| ÷1.1 to ×1.1 (about ±10%) | about 21x |
| ÷1.01 to ×1.01 (about ±1%) | about 200x |
| ÷1.001 to ×1.001 (about ±0.1%) | about 2,000x |
Ticks, spacing and sqrtPriceX96
Ranges cannot start and end anywhere. Prices are quantized into ticks, each one basis point apart:
Each fee tier also sets a tick spacing, the step between ticks a position is allowed to use. Tighter spacing suits pairs that trade in a tight band.
| Fee tier | Tick spacing | Price step | Typical use |
|---|---|---|---|
| 0.01% | 1 | 0.01% | Stablecoin pairs |
| 0.05% | 10 | about 0.10% | Major, highly correlated or very liquid pairs |
| 0.30% | 60 | about 0.60% | Most volatile pairs |
| 1.00% | 200 | about 2.0% | Long tail and exotic pairs |
The contract stores the current price as a square root in Q64.96 fixed point, which is what you will see if you query a pool yourself:
# Uniswap v3 and v4 store the price as a Q64.96 fixed point square root.
# slot0() on the pool returns sqrtPriceX96. For a pool of token0/token1:
raw = (sqrtPriceX96 / 2**96) ** 2 # token1 per token0, in base units
price = raw * 10 ** (decimals0 - decimals1) # token1 per token0, human units
# The tick is the same information, rounded to one basis point:
price_at_tick = 1.0001 ** tickThe decimals term matters. USDC has 6 decimals and WETH has 18, so forgetting it produces a price off by a factor of 1012, and that mistake has shipped in real dashboards.
Other invariants you will meet
StableSwap (Curve)
Blends a constant sum curve, which has no price impact, with constant product, which never runs dry. An amplification parameter A sets how flat the middle is. Superb for assets meant to trade at par; dangerous when one of them stops being worth par, because the pool keeps buying it.
Weighted pools (Balancer)
Keeps the product of each reserve raised to its weight constant, so an 80/20 pool is possible. The price of token j in units of token i is (Bi/Wi) divided by (Bj/Wj). Less impermanent loss than a 50/50 pool for the same price move, least when the heavier token is the one that moves.
Hooks and dynamic fees
Uniswap v4, live since early 2025, keeps the concentrated liquidity math and lets a pool attach hook contracts that run around swaps and liquidity changes. Dynamic fees that rise with volatility are the most common use.
The Curve invariant is worth seeing once, if only to appreciate what the amplification does. For
n tokens with balances xi and total D at equal prices:
Checking a quote from first principles
Read the reserves or the price
For a v2 style pool,
getReserves()on the pair contract. For v3,slot0()for the price andliquidity()for the activeL. Any block explorer can call these read functions without a wallet.Apply the formula
For a small trade, impact is about your input divided by the input side reserve, or Δy ÷ (L√P) in a concentrated pool while you stay inside one tick range.
Compare with the interface
If your estimate and the quote agree, the router is doing what it says. If the quote is much worse, something is thin, stale or wrong, and it is better to find out before you sign than after.
Common questions
Why does x times y equal k set the price?
Because the pool will only release tokens along that curve. The ratio of the reserves is the marginal rate at which it will swap one for the other, so that ratio is the price. Arbitrageurs trade against it until it matches every other market.
Is impermanent loss real if I never withdraw?
It is real in the sense that your position is worth less than holding the two tokens would be, at today's price. It only disappears if the price returns to your entry ratio. Whether fees cover it depends on volume, fee tier and volatility.
Why does Uniswap v3 use the square root of the price?
Because liquidity and swap sizes become linear in the square root. Within a tick range a swap changes a token1 input changes the square root price by exactly the input divided by L, and a token0 input changes its reciprocal the same way, which is cheap and exact to compute onchain. Storing it in fixed point avoids floating point, which the EVM does not have.
What happens to my concentrated position if the price leaves my range?
The position has been converted completely into one token: the one that became cheaper. It stops earning fees and it no longer takes part in swaps until the price re-enters the range or you close and reopen it somewhere else.
Are StableSwap pools safe for stablecoins?
They are extremely efficient while every asset in the pool is worth par. If one depegs, the curve keeps offering the healthy assets against it close to one for one until the pool is mostly the failing asset. Pool imbalance is often the earliest public sign that a peg is under pressure.
Where to go next
Next: MEV explained: sandwiches, arbitrage, liquidations and protection
Guide 2 of 16 in the Advanced level. Your place is kept on this device, so you can pick up exactly here next time.