Tick Math Last modified: June 13, 2026
Why ticks?
v3 does not store prices directly. Instead, prices are encoded as ticks — integer indices where each tick represents a 0.01 % 0.01\% 0.01% (1 basis point) price change. This encoding lets the contract efficiently track which liquidity positions are active at any given price.
Tick-to-price conversion
The relationship between tick i i i and the price it represents:
price ( i ) = 1.0001 i \text{price}(i) = 1.0001^i price ( i ) = 1.000 1 i
The inverse:
tick ( price ) = ⌊ log ( price ) log ( 1.0001 ) ⌋ \text{tick}(\text{price}) = \left\lfloor\frac{\log(\text{price})}{\log(1.0001)}\right\rfloor tick ( price ) = ⌊ log ( 1.0001 ) log ( price ) ⌋
Worked examples
Tick Price (token1 per token0 ) Context 0 1.0001⁰ = 1.0000 Tokens at parity 100 1.0001¹⁰⁰ = 1.01005 ~1 % above parity 1,000 1.0001¹⁰⁰⁰ = 1.10517 ~10.5 % above parity 10,000 1.0001¹⁰⁰⁰⁰ = 2.71828 ≈ e (Euler's number) 100,000 1.0001¹⁰⁰⁰⁰⁰ = 22,015 Typical CTN/USDC tick −100,000 1.0001⁻¹⁰⁰⁰⁰⁰ = 0.0000454 Inverse of above 887,272 MAX_TICK Upper bound ≈ 3.4 × 10³⁸ −887,272 MIN_TICK Lower bound ≈ 2.9 × 10⁻³⁹
Tick 10,000 yields exactly e ≈ 2.71828 because (1 + 1/10000)^10000 is the classic limit definition of e .
sqrtPriceX96 : the on-chain representation
Solidity lacks floating-point arithmetic, so v3 stores the square root of the price scaled by 2 96 2^{96} 2 96 as a uint160 :
sqrtPriceX96 = price ⋅ 2 96 \text{sqrtPriceX96} = \sqrt{\text{price}} \cdot 2^{96} sqrtPriceX96 = price ⋅ 2 96
The square root form is chosen because the core swap math operates on price \sqrt{\text{price}} price directly:
Δ y = L ( P upper − P lower ) \Delta y = L\left(\sqrt{P_{\text{upper}}} - \sqrt{P_{\text{lower}}}\right) Δ y = L ( P upper − P lower )
Δ x = L ( 1 P lower − 1 P upper ) \Delta x = L\left(\frac{1}{\sqrt{P_{\text{lower}}}} - \frac{1}{\sqrt{P_{\text{upper}}}}\right) Δ x = L ( P lower 1 − P upper 1 )
Storing the value pre-rooted avoids an on-chain square-root computation on every swap — a meaningful gas saving.
Converting between representations
sqrtPriceX96 → price:
price = ( sqrtPriceX96 2 96 ) 2 = sqrtPriceX96 2 2 192 \text{price} = \left(\frac{\text{sqrtPriceX96}}{2^{96}}\right)^2
= \frac{\text{sqrtPriceX96}^2}{2^{192}} price = ( 2 96 sqrtPriceX96 ) 2 = 2 192 sqrtPriceX96 2
price → sqrtPriceX96 :
sqrtPriceX96 = price ⋅ 2 96 \text{sqrtPriceX96} = \sqrt{\text{price}} \cdot 2^{96} sqrtPriceX96 = price ⋅ 2 96
tick → sqrtPriceX96 :
sqrtPriceX96 = 1.0001 tick ⋅ 2 96 = 1.0001 tick / 2 ⋅ 2 96 \text{sqrtPriceX96} = \sqrt{1.0001^{\text{tick}}}\cdot 2^{96}
= 1.0001^{\text{tick}/2}\cdot 2^{96} sqrtPriceX96 = 1.000 1 tick ⋅ 2 96 = 1.000 1 tick /2 ⋅ 2 96
Worked example: CTN/USDC
Assume CTN is token0 and USDC is token1 , with CTN priced at $2,000 USDC.
human_price = 2000 \text{human\_price} = 2000 human_price = 2000
Because tick /sqrtPriceX96 use smallest units, convert to raw price first (CTN: 18 decimals, USDC: 6 decimals):
raw_price = 2000 ⋅ 10 6 10 18 = 2 × 10 − 9 \text{raw\_price}
= 2000 \cdot \frac{10^6}{10^{18}}
= 2 \times 10^{-9} raw_price = 2000 ⋅ 1 0 18 1 0 6 = 2 × 1 0 − 9
tick = ⌊ log ( 2 × 10 − 9 ) log ( 1.0001 ) ⌋ = − 200312 \text{tick}
= \left\lfloor\frac{\log(2 \times 10^{-9})}{\log(1.0001)}\right\rfloor
= -200312 tick = ⌊ log ( 1.0001 ) log ( 2 × 1 0 − 9 ) ⌋ = − 200312
sqrtPriceX96 = 2 × 10 − 9 ⋅ 2 96 ≈ 3.5432 × 10 24 \text{sqrtPriceX96}
= \sqrt{2 \times 10^{-9}}\cdot 2^{96}
\approx 3.5432 \times 10^{24} sqrtPriceX96 = 2 × 1 0 − 9 ⋅ 2 96 ≈ 3.5432 × 1 0 24
Verification:
raw_price = ( 3.5432 × 10 24 2 96 ) 2 ≈ 2 × 10 − 9 \text{raw\_price}
= \left(\frac{3.5432 \times 10^{24}}{2^{96}}\right)^2
\approx 2 \times 10^{-9} raw_price = ( 2 96 3.5432 × 1 0 24 ) 2 ≈ 2 × 1 0 − 9
Convert back to human-readable units:
human_price = raw_price ⋅ 10 ( 18 − 6 ) ≈ 2000 USDC per CTN ✓ \text{human\_price}
= \text{raw\_price} \cdot 10^{(18-6)}
\approx 2000\ \text{USDC per CTN} \;\checkmark human_price = raw_price ⋅ 1 0 ( 18 − 6 ) ≈ 2000 USDC per CTN ✓
Tick spacing
Not every tick is usable. Each fee tier defines a tick spacing — only multiples of the spacing can serve as position boundaries:
Fee tier Tick spacing Min price granularity 0.01 % (100) 1 0.01 % per tick 0.05 % (500) 10 0.10 % per usable tick 0.30 % (3000) 60 0.60 % per usable tick 1.00 % (10000) 200 2.00 % per usable tick
The contract snaps requested bounds to the nearest valid tick:
lower_tick = ⌊ desired_lower_tick tick_spacing ⌋ ⋅ tick_spacing \text{lower\_tick} =
\left\lfloor\frac{\text{desired\_lower\_tick}}{\text{tick\_spacing}}\right\rfloor
\cdot \text{tick\_spacing} lower_tick = ⌊ tick_spacing desired_lower_tick ⌋ ⋅ tick_spacing
upper_tick = ⌈ desired_upper_tick tick_spacing ⌉ ⋅ tick_spacing \text{upper\_tick} =
\left\lceil\frac{\text{desired\_upper\_tick}}{\text{tick\_spacing}}\right\rceil
\cdot \text{tick\_spacing} upper_tick = ⌈ tick_spacing desired_upper_tick ⌉ ⋅ tick_spacing
Example: In a 0.30 % 0.30\% 0.30% pool (tick spacing 60), targeting a range of $1,800–$2,200:
tick ( 1800 ) = ⌊ log ( 1800 ) log ( 1.0001 ) ⌋ = 74959 \text{tick}(1800) = \left\lfloor\frac{\log(1800)}{\log(1.0001)}\right\rfloor = 74959 tick ( 1800 ) = ⌊ log ( 1.0001 ) log ( 1800 ) ⌋ = 74959
Snapped lower = ⌊ 74959 60 ⌋ ⋅ 60 = 74940 \text{Snapped lower} = \left\lfloor\frac{74959}{60}\right\rfloor \cdot 60 = 74940 Snapped lower = ⌊ 60 74959 ⌋ ⋅ 60 = 74940
tick ( 2200 ) = ⌊ log ( 2200 ) log ( 1.0001 ) ⌋ = 76961 \text{tick}(2200) = \left\lfloor\frac{\log(2200)}{\log(1.0001)}\right\rfloor = 76961 tick ( 2200 ) = ⌊ log ( 1.0001 ) log ( 2200 ) ⌋ = 76961
Snapped upper = ⌈ 76961 60 ⌉ ⋅ 60 = 76980 \text{Snapped upper} = \left\lceil\frac{76961}{60}\right\rceil \cdot 60 = 76980 Snapped upper = ⌈ 60 76961 ⌉ ⋅ 60 = 76980
Actual price range of the position:
lower = 1.0001 74940 = $ 1,797.42 \text{lower} = 1.0001^{74940} = \$1{,}797.42 lower = 1.000 1 74940 = $1 , 797.42
upper = 1.0001 76980 = $ 2,203.81 \text{upper} = 1.0001^{76980} = \$2{,}203.81 upper = 1.000 1 76980 = $2 , 203.81
Decimal adjustment for different token pairs
The raw tick-derived price gives token1 -per-token0 in the tokens' smallest units (wei, etc.). For human-readable prices, adjust for decimals:
human_price = raw_price ⋅ 10 ( decimals_token0 − decimals_token1 ) \text{human\_price} = \text{raw\_price}\cdot 10^{(\text{decimals\_token0}-\text{decimals\_token1})} human_price = raw_price ⋅ 1 0 ( decimals_token0 − decimals_token1 )
Example: USDC (6 decimals) as token0 , WCTN (18 decimals) as token1 , raw price = 0.0005 = 0.0005 = 0.0005 :
human_price = 0.0005 ⋅ 10 ( 6 − 18 ) = 5 × 10 − 16 WCTN per USDC-unit \text{human\_price} = 0.0005 \cdot 10^{(6-18)} = 5 \times 10^{-16}\ \text{WCTN per USDC-unit} human_price = 0.0005 ⋅ 1 0 ( 6 − 18 ) = 5 × 1 0 − 16 WCTN per USDC-unit
Most integrations compute the inverse — 1 / human_price — to display the familiar "USDC per CTN" format.
Q notation
v3 uses fixed-point arithmetic throughout. The two key formats:
Format Meaning Used for Q64.96 Value × 2⁹⁶, stored as uint160 sqrtPriceX96 Q128.128 Value × 2¹²⁸, stored as uint256 feeGrowthGlobal0X128 , feeGrowthGlobal1X128
To convert from Q notation:
decimal_value = stored_value 2 ( fractional_bits ) \text{decimal\_value} = \frac{\text{stored\_value}}{2^{(\text{fractional\_bits})}} decimal_value = 2 ( fractional_bits ) stored_value
Example: A feeGrowthGlobal0X128 of ≈ 10 39 \approx 10^{39} ≈ 1 0 39 :
fee_growth = 10 39 2 128 = 10 39 3.4 × 10 38 ≈ 2.94 \text{fee\_growth}
= \frac{10^{39}}{2^{128}}
= \frac{10^{39}}{3.4 \times 10^{38}}
\approx 2.94 fee_growth = 2 128 1 0 39 = 3.4 × 1 0 38 1 0 39 ≈ 2.94
This indicates 2.94 units of token0 in fees earned per unit of liquidity since pool creation.
Liquidity math
Liquidity L L L represents the virtual reserves available at the current price:
L = x y ( v2 equivalent ) L = \sqrt{xy} \qquad (\text{v2 equivalent}) L = x y ( v2 equivalent )
L = Δ y P b − P a ( from token1 deposits ) L = \frac{\Delta y}{\sqrt{P_b} - \sqrt{P_a}} \qquad (\text{from token1 deposits}) L = P b − P a Δ y ( from token1 deposits )
L = Δ x 1 P a − 1 P b ( from token0 deposits ) L = \frac{\Delta x}{\frac{1}{\sqrt{P_a}} - \frac{1}{\sqrt{P_b}}}
\qquad (\text{from token0 deposits}) L = P a 1 − P b 1 Δ x ( from token0 deposits )
Where P a P_a P a and P b P_b P b are the lower and upper price bounds.
Token composition of a position
Given liquidity L L L in range [ P a , P b ] [P_a, P_b] [ P a , P b ] at current price P P P :
If P < P a P < P_a P < P a (below range — 100 % 100\% 100% token0 ):
x = L ( 1 P a − 1 P b ) x = L\left(\frac{1}{\sqrt{P_a}} - \frac{1}{\sqrt{P_b}}\right) x = L ( P a 1 − P b 1 )
y = 0 y = 0 y = 0
If P > P b P > P_b P > P b (above range — 100 % 100\% 100% token1 ):
x = 0 x = 0 x = 0
y = L ( P b − P a ) y = L\left(\sqrt{P_b} - \sqrt{P_a}\right) y = L ( P b − P a )
If P a ≤ P ≤ P b P_a \le P \le P_b P a ≤ P ≤ P b (in range — both tokens):
x = L ( 1 P − 1 P b ) x = L\left(\frac{1}{\sqrt{P}} - \frac{1}{\sqrt{P_b}}\right) x = L ( P 1 − P b 1 )
y = L ( P − P a ) y = L\left(\sqrt{P} - \sqrt{P_a}\right) y = L ( P − P a )
Worked example
Position: L = 100,000 L = 100{,}000 L = 100 , 000 , CTN/USDC, range $1,800–$2,200, current price $2,000.
1800 = 42.426 , 2000 = 44.721 , 2200 = 46.904 \sqrt{1800}=42.426,\quad \sqrt{2000}=44.721,\quad \sqrt{2200}=46.904 1800 = 42.426 , 2000 = 44.721 , 2200 = 46.904
x ( CTN ) = 100,000 ( 1 44.721 − 1 46.904 ) = 100,000 ⋅ 0.001039 = 103.9 CTN-units x\ (\text{CTN})
= 100{,}000\left(\frac{1}{44.721}-\frac{1}{46.904}\right)
= 100{,}000\cdot 0.001039
= 103.9\ \text{CTN-units} x ( CTN ) = 100 , 000 ( 44.721 1 − 46.904 1 ) = 100 , 000 ⋅ 0.001039 = 103.9 CTN-units
y ( USDC ) = 100,000 ( 44.721 − 42.426 ) = 100,000 ⋅ 2.295 = 229,500 USDC-units y\ (\text{USDC})
= 100{,}000(44.721-42.426)
= 100{,}000\cdot 2.295
= 229{,}500\ \text{USDC-units} y ( USDC ) = 100 , 000 ( 44.721 − 42.426 ) = 100 , 000 ⋅ 2.295 = 229 , 500 USDC-units
(Values are in smallest units — divide by 10^decimals for human-readable amounts.)
Further reading
Swaps Resources