harb/onchain/UNISWAP_V3_MATH.md
2025-08-18 00:16:09 +02:00

6.4 KiB

Uniswap V3 Math - Critical Learnings

Critical Implementation Details

Price Representation in KRAIKEN

  • _priceAtTick(tick) returns price² in X96 format, NOT regular price or sqrtPrice
  • This is intentional: price² = (sqrtPrice)² / 2^96
  • Used for ETH requirement calculations in floor positioning
  • To get regular price from price²: take sqrt and shift left by 48 bits
  • VWAP stores price² for volume-weighted averaging

ETH Scarcity vs Abundance

  • Scarcity: Floor ETH < Outstanding KRAIKEN * VWAP price → Floor moves to extreme ticks (140k+)
  • Abundance: Sufficient ETH → Floor placed near VWAP tick
  • Extreme floor positions (tick 141,200+) are CORRECT behavior in scarcity scenarios
  • Scarcity protects protocol solvency by making KRAIKEN very cheap (ETH expensive)

Token Ordering and Price Representation

Price Direction Reference Table

Scenario token0 token1 Price Represents Lower Tick → Higher Tick
token0isETH = true ETH KRAIKEN ETH per KRAIKEN KRAIKEN cheap → expensive
token0isETH = false KRAIKEN ETH KRAIKEN per ETH ETH cheap → expensive

Understanding "Above" and "Below"

Critical distinction: "Price" in Uniswap V3 always refers to token1's price in units of token0.

When token0isETH = true (ETH is token0, KRAIKEN is token1):

  • Price = KRAIKEN price in ETH (how much ETH to buy 1 KRAIKEN)
  • Higher tick = Higher KRAIKEN price in ETH
  • Lower tick = Lower KRAIKEN price in ETH
  • "Price moved up" = KRAIKEN became more expensive = ETH became cheaper
  • "Price moved down" = KRAIKEN became cheaper = ETH became more expensive

When token0isETH = false (KRAIKEN is token0, ETH is token1):

  • Price = ETH price in KRAIKEN (how much KRAIKEN to buy 1 ETH)
  • Higher tick = Higher ETH price in KRAIKEN
  • Lower tick = Lower ETH price in KRAIKEN
  • "Price moved up" = ETH became more expensive = KRAIKEN became cheaper
  • "Price moved down" = ETH became cheaper = KRAIKEN became more expensive

This determines token composition:

Current Price vs Position Position Contains Why
Below range (tick < tickLower) 100% token1 Token0 is too expensive to hold
Within range (tickLower ≤ tick ≤ tickUpper) Both tokens Active liquidity range
Above range (tick > tickUpper) 100% token0 Token1 is too expensive to hold

Liquidity vs Token Amounts

Key Insight: Liquidity (L) is a mathematical constant representing capital efficiency, NOT token count.

  1. Liquidity is invariant: The liquidity value L doesn't change when price moves
  2. Token amounts are variable: Depend on liquidity L, price range, and current price location
  3. Same L, different ranges: Results in different token amounts due to price differences

Why Positions at Different Ranges Have Different Token Ratios

For the same liquidity value L:

  • Position at lower ticks: Higher token1 price → fewer token1, more token0 potential
  • Position at higher ticks: Lower token1 price → more token1, less token0 potential

This explains why a position with fewer tokens can have more liquidity (and thus more price impact resistance).

Liquidity Per Tick - The Critical Metric

When comparing positions of different widths, always normalize to liquidity per tick:

liquidityPerTick = totalLiquidity / (tickUpper - tickLower)

The discovery position maintains its target liquidity density through width adjustment:

// Ensure discovery has X times more liquidity per tick than anchor
discoveryLiquidity = anchorLiquidity * multiplier * discoveryWidth / anchorWidth

Key Takeaways

  1. Liquidity ≠ Token Count: Higher liquidity can mean fewer tokens at different price ranges
  2. Price Range Matters: Token composition depends on where positions sit relative to current price
  3. Normalize for Width: Always compare liquidity per tick when positions have different widths
  4. Token0 Ordering is Critical: Determines which direction is "up" or "down" in price

Critical Formulas for Out-of-Range Positions

When Position is Below Current Price (currentTick < tickLower)

The position holds only token0:

amount0 = L * (sqrt(priceUpper) - sqrt(priceLower)) / (sqrt(priceUpper) * sqrt(priceLower))

In Solidity/JavaScript with Q96 notation:

amount0 = liquidity * (sqrtRatioBX96 - sqrtRatioAX96) / sqrtRatioAX96 * Q96 / sqrtRatioBX96

When Position is Above Current Price (currentTick > tickUpper)

The position holds only token1:

amount1 = L * (sqrt(priceUpper) - sqrt(priceLower))

In Solidity/JavaScript with Q96 notation:

amount1 = liquidity * (sqrtRatioBX96 - sqrtRatioAX96) / Q96

Critical Insight: ETH Position Creation

When creating a position above current price with ETH in a token0isWeth pool:

  1. ETH is used as token1 (via getLiquidityForAmount1)
  2. The position will hold KRAIKEN when in range
  3. But if price drops below the range, it converts to holding ETH as token0

Example: Floor position created with 38 ETH at ticks [127400, 127600] when current is 123890:

  • Creation: Uses getLiquidityForAmount1 with 38 ETH
  • Result: Liquidity = 6.48e18
  • Current holdings: 38 ETH (as token0, since price is below range)

Common Confusion Points

1. Price Direction vs ETH Value

When token0isWeth = true:

  • Higher tick = Higher price = More KRAIKEN per ETH = ETH is MORE valuable
  • Lower tick = Lower price = Less KRAIKEN per ETH = ETH is LESS valuable

This is counterintuitive because "price goes up" means the denominated asset (ETH) becomes more valuable.

2. Position Token Holdings

A position's token composition depends ONLY on:

  • Current price location relative to the position range
  • NOT on how the position was created
  • NOT on which token was used to mint

3. The Floor Position "Below Current Price" Confusion

In KRAIKEN's three-position strategy:

  • Floor position is placed where ETH is MORE valuable (higher ticks when token0isWeth)
  • This is "below" current price from an ETH value perspective
  • But "above" current tick numerically
  • Selling KRAIKEN for ETH moves price TOWARD the floor position

4. Liquidity Calculation Direction

When minting a position out of range:

  • Use getLiquidityForAmount0 if providing token0
  • Use getLiquidityForAmount1 if providing token1
  • The same liquidity value will require different token amounts depending on which token you provide