Agent skill

Uniswap Math

by ccashwell in ccashwell/evm-cortex

A skill your agent uses when working with Uniswap pricing math, tick calculations, liquidity formulas, or Q64.96 fixed-point arithmetic.

MITAuto-check passedBusiness, Finance & HR

Install Uniswap Math

skills CLI
$ npx skills add ccashwell/evm-cortex --skill uniswap-math -a claude-code

Project install by default; add -g for ~/.claude/skills/.

GitHub CLI
$ gh skill install ccashwell/evm-cortex uniswap-math --agent claude-code

Project scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).

Manual copy
$ git clone --depth 1 https://github.com/ccashwell/evm-cortex.git skills-src && mkdir -p .claude/skills && cp -r skills-src/skills/uniswap-math .claude/skills/uniswap-math && rm -rf skills-src

Use ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.

Claude Code skills documentation · loads skills from .claude/skills/

Facts

Skill name
uniswap-math
GitHub stars
131
Token cost
~7.5k tokens
SKILL.md length
1,381 words
Files
1
Skills in repo
89
Repo updated
First seen
Licence
MIT

At a glance

A skill your agent uses when working with Uniswap pricing math, tick calculations, liquidity formulas, or Q64.96 fixed-point arithmetic.

  • Works in 8 steps: Forgetting Decimal Normalization → Off-by-One in Tick Rounding → Rounding Direction Errors → …
  • Working with Uniswap pricing math
  • SKILL.md covers Q64.96 Fixed-Point Arithmetic, TickMath Library, SqrtPriceMath Library and SwapMath Library, plus 3 more sections
  • Instructions only: no scripts, shell commands, URLs or credentials in SKILL.md

What it does

Uniswap Math is an agent skill from ccashwell/evm-cortex. Use when working with Uniswap pricing math, tick calculations, liquidity formulas, or Q64.96 fixed-point arithmetic. Covers TickMath, SqrtPriceMath, SwapMath, FullMath, TickBitmap, LiquidityAmounts, Position library, and all key formulas for concentrated liquidity AMMs.

Its SKILL.md is about 7.5k tokens, which your agent loads only when the skill is triggered. It is a single SKILL.md file with no bundled scripts.

It sits in Business, Finance & HR. It works with Uniswap. The repository describes itself as: Ethereum protocol engineering squad for AI coding assistants. The licence is MIT.

When your agent uses it

  • Working with Uniswap pricing math
  • Tick calculations
  • Liquidity formulas
  • Q64.96 fixed-point arithmetic

Example prompts

  • “/uniswap-math”

Requirements

  • Python 3

Workflow steps

8 steps, taken from the step headings in SKILL.md.

  1. Forgetting Decimal Normalization
  2. Off-by-One in Tick Rounding
  3. Rounding Direction Errors
  4. Overflow in Intermediate Calculations
  5. Tick Spacing Alignment
  6. Liquidity Overflow
  7. Fee Growth Wrapping
  8. sqrtPrice Bounds

What it can do on your machine

Read from SKILL.md and the folder at commit f8f3301. It shows what the files ask for, not the result of running them.

  • Tool permissions

    Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.

    From allowed-tools in the SKILL.md frontmatter.

  • Runs code

    No scripts in the folder and no shell commands in SKILL.md (its code samples are solidity and python).

    From the folder's file list and the shell code blocks in SKILL.md.

  • Network

    No URLs in SKILL.md.

    From URLs in SKILL.md, links to its own repository left out.

  • Credentials

    Names no API keys, tokens, secrets or passwords.

    From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.

Context cost

Uniswap Math loads about 7.5k tokens when it runs. Until then it costs about 71 tokens; SKILL.md has 1,381 words of instructions outside code blocks.

Always · name and description, kept in context so the agent knows when to use it
~71
When it runs · the whole SKILL.md, loaded when a task matches
~7.5k

Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.

Safety

Auto-check passed

The automated check found no risky patterns in SKILL.md.

Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.

SKILL.md

The full file from ccashwell/evm-cortex at commit f8f3301, republished under its MIT licence (© ccashwell). 1,381 words, ~7,550 tokens.

Download SKILL.mdSave it as .claude/skills/uniswap-math/SKILL.md (or your agent's skills folder).
name
uniswap-math
description
Use when working with Uniswap pricing math, tick calculations, liquidity formulas, or Q64.96 fixed-point arithmetic. Covers TickMath, SqrtPriceMath, SwapMath, FullMath, TickBitmap, LiquidityAmounts, Position library, and all key formulas for concentrated liquidity AMMs.

Uniswap Concentrated Liquidity Math

Q64.96 Fixed-Point Arithmetic

Uniswap V3/V4 stores prices as Q64.96 fixed-point numbers representing the square root of the price ratio. This encoding fits in uint160 and enables efficient swap math without division.

sqrtPriceX96 = √(price) × 2⁹⁶
  • 64 bits for the integer part, 96 bits for the fractional part
  • Stored as uint160 — fits alongside int24 tick and uint128 liquidity in the pool's Slot0
  • Price of token1 in terms of token0: price = (sqrtPriceX96 / 2⁹⁶)²
  • Inverse conversion: sqrtPriceX96 = √(price) × 2⁹⁶
Why Store √P Instead of P
  1. The core swap formulas only need √P, never P directly
  2. amount1 = L × Δ√P is a simple multiplication — no square root at runtime
  3. amount0 = L × Δ(1/√P) avoids computing reciprocals of prices
  4. Avoids the precision loss of squaring and rooting during swaps
Decimal Normalization

Prices are always in raw token units — you must account for decimals manually.

token0 = WETH (18 decimals)
token1 = USDC (6 decimals)

Human-readable price: 1 ETH = 3000 USDC
Raw price (token1/token0) = 3000 × 10⁶ / 10¹⁸ = 3000 × 10⁻¹²

sqrtPrice = √(3000 × 10⁻¹²) = √(3 × 10⁻⁹) ≈ 5.47722558 × 10⁻⁵
sqrtPriceX96 = 5.47722558 × 10⁻⁵ × 2⁹⁶ ≈ 4_339_505_179_874_779_489_431_521

For a pair where both tokens have 18 decimals (e.g., WETH/DAI at price 3000):

Raw price = 3000 (decimals cancel)
sqrtPrice = √3000 ≈ 54.7722558
sqrtPriceX96 = 54.7722558 × 2⁹⁶ ≈ 4_339_505_179_874_779_489_431_521_786_241
Converting sqrtPriceX96 to Human Price
solidity
// In Solidity — use FullMath to avoid overflow
uint256 priceX192 = FullMath.mulDiv(sqrtPriceX96, sqrtPriceX96, 1);
// priceX192 is price * 2^192, divide by 2^192 to get raw price
// For display: rawPrice * 10^(decimals0 - decimals1) = human price
python
# In Python (offchain)
def sqrtPriceX96_to_price(sqrtPriceX96, decimals0, decimals1):
    price = (sqrtPriceX96 / 2**96) ** 2
    adjusted = price * 10 ** (decimals0 - decimals1)
    return adjusted

# ETH/USDC: sqrtPriceX96 = 4_339_505_179_874_779_489_431_521
sqrtPriceX96_to_price(4_339_505_179_874_779_489_431_521, 18, 6)
# ≈ 3000.0

TickMath Library

Import: import {TickMath} from "v4-core/src/libraries/TickMath.sol";

Constants
solidity
int24 internal constant MIN_TICK = -887272;
int24 internal constant MAX_TICK = 887272;
int24 internal constant MIN_TICK_SPACING = 1;
int24 internal constant MAX_TICK_SPACING = type(int16).max; // 32767

uint160 internal constant MIN_SQRT_PRICE = 4295128739;
uint160 internal constant MAX_SQRT_PRICE =
    1461446703485210103287273052203988822378723970342;

MIN_SQRT_PRICE and MAX_SQRT_PRICE correspond to the prices at MIN_TICK and MAX_TICK. The pool's sqrtPriceX96 is always in (MIN_SQRT_PRICE, MAX_SQRT_PRICE) — strictly exclusive.

Core Functions
solidity
/// @notice Returns the sqrt price at the given tick as a Q64.96
/// @dev Reverts if |tick| > MAX_TICK
function getSqrtPriceAtTick(int24 tick)
    internal pure returns (uint160 sqrtPriceX96);

/// @notice Returns the tick at the given sqrt price
/// @dev Returns the largest tick whose price ≤ sqrtPriceX96
/// @dev sqrtPriceX96 must be in (MIN_SQRT_PRICE, MAX_SQRT_PRICE)
function getTickAtSqrtPrice(uint160 sqrtPriceX96)
    internal pure returns (int24 tick);

/// @notice Returns the maximum usable tick for a given tick spacing
function maxUsableTick(int24 tickSpacing)
    internal pure returns (int24);

/// @notice Returns the minimum usable tick for a given tick spacing
function minUsableTick(int24 tickSpacing)
    internal pure returns (int24);
Tick-Price Relationship

Each tick i maps to a price: P(i) = 1.0001ⁱ

Every tick is exactly 1 basis point (0.01%) away from its neighbors. This is the key invariant of concentrated liquidity — prices are spaced geometrically, not linearly.

tick = 0      → price = 1.0
tick = 1      → price = 1.0001
tick = -1     → price = 0.99990001...
tick = 100    → price ≈ 1.01005
tick = 10000  → price ≈ 2.71828  (≈ e)
tick = 23028  → price ≈ 10.0
tick = 46054  → price ≈ 100.0
tick = 69082  → price ≈ 1000.0
tick = -69082 → price ≈ 0.001
tick = 887272 → price ≈ 3.40 × 10³⁸  (near uint128 max)

Useful relationship: tick ≈ ln(price) / ln(1.0001) ≈ ln(price) × 10000

Tick Spacing

Only ticks divisible by tickSpacing can be initialized with liquidity positions. Common tick spacings:

Fee TierTick SpacingPrice Granularity
1 bps (0.01%)1Every tick — stablecoin pairs
5 bps (0.05%)100.10% between usable ticks
30 bps (0.30%)600.60% between usable ticks
100 bps (1.00%)2002.00% between usable ticks
solidity
// Usable ticks for tickSpacing = 60:
// ..., -120, -60, 0, 60, 120, 180, ...
int24 maxUsable = TickMath.maxUsableTick(60);  // 887220
int24 minUsable = TickMath.minUsableTick(60);  // -887220
getTickAtSqrtPrice Floor Behavior

getTickAtSqrtPrice returns the largest tick where getSqrtPriceAtTick(tick) <= sqrtPriceX96. This is a floor operation. The current tick always satisfies:

getSqrtPriceAtTick(tick) <= currentSqrtPrice < getSqrtPriceAtTick(tick + 1)

SqrtPriceMath Library

Import: import {SqrtPriceMath} from "v4-core/src/libraries/SqrtPriceMath.sol";

This library computes token amounts from liquidity and price changes, and computes new prices from token amounts. Every function is aware of rounding direction.

Amount Delta Functions
solidity
/// @notice Gets the token0 delta for a liquidity and price range
function getAmount0Delta(
    uint160 sqrtPriceAX96,
    uint160 sqrtPriceBX96,
    uint128 liquidity,
    bool roundUp
) internal pure returns (uint256 amount0);

/// @notice Gets the token1 delta for a liquidity and price range
function getAmount1Delta(
    uint160 sqrtPriceAX96,
    uint160 sqrtPriceBX96,
    uint128 liquidity,
    bool roundUp
) internal pure returns (uint256 amount1);

Signed overloads exist that accept int128 liquidity — positive for adding liquidity (user pays, round up), negative for removing (user receives, round down):

solidity
function getAmount0Delta(
    uint160 sqrtPriceAX96,
    uint160 sqrtPriceBX96,
    int128 liquidity
) internal pure returns (int256 amount0);

function getAmount1Delta(
    uint160 sqrtPriceAX96,
    uint160 sqrtPriceBX96,
    int128 liquidity
) internal pure returns (int256 amount1);
Core Formulas

For a position spanning [√P_a, √P_b] where √P_a < √P_b:

                 √P_b - √P_a
amount0 = L × ─────────────────
               √P_a × √P_b

amount1 = L × (√P_b - √P_a)

Equivalently:

amount0 = L × (1/√P_a - 1/√P_b)
amount1 = L × (√P_b - √P_a)

Intuition: token0 is the "x" asset in xy=k. As price rises (more token1 per token0), the position holds less token0 and more token1. At √P >= √P_b, the position is entirely token1. At √P <= √P_a, entirely token0.

Next Price Functions
solidity
/// @notice Gets next sqrt price given token0 input/output
function getNextSqrtPriceFromAmount0RoundingUp(
    uint160 sqrtPX96,
    uint128 liquidity,
    uint256 amount,
    bool add
) internal pure returns (uint160);

/// @notice Gets next sqrt price given token1 input/output
function getNextSqrtPriceFromAmount1RoundingDown(
    uint160 sqrtPX96,
    uint128 liquidity,
    uint256 amount,
    bool add
) internal pure returns (uint160);

/// @notice Gets next sqrt price from an exact input amount
function getNextSqrtPriceFromInput(
    uint160 sqrtPX96,
    uint128 liquidity,
    uint256 amountIn,
    bool zeroForOne
) internal pure returns (uint160);

/// @notice Gets next sqrt price from an exact output amount
function getNextSqrtPriceFromOutput(
    uint160 sqrtPX96,
    uint128 liquidity,
    uint256 amountOut,
    bool zeroForOne
) internal pure returns (uint160);

Next price from token0 amount:

When adding token0 (buying token1): price decreases
√P_next = L × √P / (L + amount0 × √P)

When removing token0 (selling token1): price increases
√P_next = L × √P / (L - amount0 × √P)

Next price from token1 amount:

When adding token1 (buying token0): price increases
√P_next = √P + amount1 / L

When removing token1 (selling token0): price decreases
√P_next = √P - amount1 / L
Rounding Convention
ScenarioAmount0Amount1Price
User pays (add liquidity, swap input)Round UPRound UPRound towards protocol benefit
User receives (remove liquidity, swap output)Round DOWNRound DOWNRound towards protocol benefit

The protocol must never undercharge or overpay. Every rounding decision favors the pool.

SwapMath Library

Import: import {SwapMath} from "v4-core/src/libraries/SwapMath.sol";

Constants
solidity
uint24 internal constant MAX_SWAP_FEE = 1e6; // 100% — denominated in hundredths of a bip

Fee is in units of hundredths of a basis point (1/100 of 0.01% = 0.0001%). So 3000 = 0.30%, 500 = 0.05%, 10000 = 1.00%.

Core Functions
solidity
/// @notice Returns the target sqrt price, clamped to the price limit
function getSqrtPriceTarget(
    bool zeroForOne,
    uint160 sqrtPriceNextX96,
    uint160 sqrtPriceLimitX96
) internal pure returns (uint160 sqrtPriceTargetX96);

/// @notice Computes a single step within a swap
function computeSwapStep(
    uint160 sqrtPriceCurrentX96,
    uint160 sqrtPriceTargetX96,
    uint128 liquidity,
    int256 amountRemaining,
    uint24 feePips
) internal pure returns (
    uint160 sqrtPriceNextX96,
    uint256 amountIn,
    uint256 amountOut,
    uint256 feeAmount
);
The Swap Loop

Every swap in Uniswap V3/V4 executes as a loop of steps across tick boundaries:

1. Start at current sqrtPrice and tick
2. LOOP:
   a. Find the next initialized tick in the swap direction (via TickBitmap)
   b. Clamp the target price to the user's price limit
   c. Call computeSwapStep(current, target, liquidity, remaining, fee)
   d. Update amountRemaining by subtracting amountIn + feeAmount (exact input)
      or amountOut (exact output)
   e. Accumulate fee growth: feeGrowthGlobal += feeAmount / liquidity
   f. If sqrtPriceNext reached the tick boundary:
      - Cross the tick: add/subtract the tick's liquidityNet from active liquidity
      - Update current tick
   g. If amountRemaining == 0 or sqrtPrice hits limit → exit loop
3. Update pool state: sqrtPrice, tick, liquidity, feeGrowthGlobal
computeSwapStep Internals

For exact input (amountRemaining > 0):

1. Calculate amountIn to move price from current to target
2. If amountIn + fee <= remaining:
   - Price reaches target: sqrtPriceNext = target
   - Fee = remaining - amountIn (entire remainder is fee, capped)
3. Else:
   - Only partial move: compute sqrtPriceNext from input (after fee deduction)
   - amountRemainingLessFee = amountRemaining * (1e6 - feePips) / 1e6
   - sqrtPriceNext = getNextSqrtPriceFromInput(current, liquidity, amountRemainingLessFee)
4. Compute amountOut from the actual price movement
5. Fee = amountIn calculated from movement, then:
   feeAmount = amountRemaining - amountIn (for exact input, fee is the delta)

For exact output (amountRemaining < 0):

1. Calculate amountOut to move price from current to target
2. If amountOut <= |remaining|:
   - Price reaches target
3. Else:
   - Partial move: compute sqrtPriceNext from output
4. Compute amountIn from the actual price movement
5. feeAmount = mulDivRoundingUp(amountIn, feePips, 1e6 - feePips)
zeroForOne Direction
zeroForOneDirectionPrice Movementtoken0token1
trueSell token0, buy token1Price decreases (√P goes down)InputOutput
falseSell token1, buy token0Price increases (√P goes up)OutputInput

FullMath Library

Import: import {FullMath} from "v4-core/src/libraries/FullMath.sol";

solidity
/// @notice 512-bit multiply then divide: (a × b) / denominator
/// @dev Will not overflow for any inputs where the result fits in uint256
function mulDiv(
    uint256 a,
    uint256 b,
    uint256 denominator
) internal pure returns (uint256 result);

/// @notice Same as mulDiv but rounds up
function mulDivRoundingUp(
    uint256 a,
    uint256 b,
    uint256 denominator
) internal pure returns (uint256 result);

FullMath.mulDiv computes (a * b) / d with a 512-bit intermediate product, preventing overflow when a * b > type(uint256).max. This is essential for Q64.96 math where multiplying two uint160 values can produce up to 320 bits.

Usage pattern in amount calculations:

solidity
// amount0 = liquidity * (sqrtPriceB - sqrtPriceA) / (sqrtPriceA * sqrtPriceB)
amount0 = FullMath.mulDiv(
    uint256(liquidity) << FixedPoint96.RESOLUTION,  // L * 2^96
    sqrtPriceBX96 - sqrtPriceAX96,
    sqrtPriceBX96
) / sqrtPriceAX96;
UnsafeMath

Import: import {UnsafeMath} from "v4-core/src/libraries/UnsafeMath.sol";

solidity
function divRoundingUp(uint256 x, uint256 d) internal pure returns (uint256);

Used internally where the caller has already validated inputs. Saves gas by skipping overflow checks.

TickBitmap Library

Import: import {TickBitmap} from "v4-core/src/libraries/TickBitmap.sol";

Ticks that have liquidity positions starting or ending at them are "initialized." The bitmap provides efficient lookup of the next initialized tick during swaps.

Storage Layout
The bitmap is a mapping(int16 => uint256):
  - The key (wordPos) is the tick index divided by 256
  - Each bit in the uint256 represents one compressed tick
  - Compressed tick = actual tick / tickSpacing

tick → compressed = tick / tickSpacing
compressed → wordPos = compressed >> 8   (arithmetic shift, so int16)
compressed → bitPos  = compressed % 256  (uint8, always positive modulo)
Functions
solidity
/// @notice Compresses a tick by the tick spacing
function compress(int24 tick, int24 tickSpacing)
    internal pure returns (int24 compressed);

/// @notice Returns word position and bit position within the word
function position(int24 tick)
    internal pure returns (int16 wordPos, uint8 bitPos);

/// @notice Toggles the initialized state of a tick
function flipTick(
    mapping(int16 => uint256) storage self,
    int24 tick,
    int24 tickSpacing
) internal;

/// @notice Finds the next initialized tick within the same word
function nextInitializedTickWithinOneWord(
    mapping(int16 => uint256) storage self,
    int24 tick,
    int24 tickSpacing,
    bool lte
) internal view returns (int24 next, bool initialized);
Search Behavior

When lte = true (selling token0, price decreasing):

  • Searches at and to the left of the current compressed tick
  • The current tick's bit IS included in the search

When lte = false (selling token1, price increasing):

  • Searches to the right of the current compressed tick
  • Starts at compressed + 1, so the current tick is excluded

If no initialized tick is found in the current word, returns the boundary of the word. The swap loop then advances to the next word.

Position Library

Import: import {Position} from "v4-core/src/libraries/Position.sol";

Position State
solidity
struct State {
    uint128 liquidity;
    uint256 feeGrowthInside0LastX128;
    uint256 feeGrowthInside1LastX128;
}
Position Key

In V4, positions are identified by a bytes32 key derived from owner, tick range, and salt:

solidity
function calculatePositionKey(
    address owner,
    int24 tickLower,
    int24 tickUpper,
    bytes32 salt
) internal pure returns (bytes32 positionKey);

The salt parameter (new in V4) allows a single address to hold multiple distinct positions at the same tick range. In V3, positionKey = keccak256(abi.encodePacked(owner, tickLower, tickUpper)).

Updating a Position
solidity
function update(
    State storage self,
    int128 liquidityDelta,
    uint256 feeGrowthInside0X128,
    uint256 feeGrowthInside1X128
) internal returns (uint256 feesOwed0, uint256 feesOwed1);

Collects accrued fees and applies the liquidity change. The returned feesOwed values represent tokens owed to the position owner.

Show full SKILL.md (560 more words)Show less

LiquidityAmounts (Periphery)

Import: import {LiquidityAmounts} from "v4-periphery/src/libraries/LiquidityAmounts.sol";

This is a periphery helper (not in core). It computes how much liquidity you get for a given token deposit, or how many tokens correspond to a given liquidity amount.

Functions
solidity
function getLiquidityForAmount0(
    uint160 sqrtPriceAX96,
    uint160 sqrtPriceBX96,
    uint256 amount0
) internal pure returns (uint128 liquidity);

function getLiquidityForAmount1(
    uint160 sqrtPriceAX96,
    uint160 sqrtPriceBX96,
    uint256 amount1
) internal pure returns (uint128 liquidity);

function getLiquidityForAmounts(
    uint160 sqrtPriceX96,
    uint160 sqrtPriceAX96,
    uint160 sqrtPriceBX96,
    uint256 amount0,
    uint256 amount1
) internal pure returns (uint128 liquidity);

function getAmount0ForLiquidity(
    uint160 sqrtPriceAX96,
    uint160 sqrtPriceBX96,
    uint128 liquidity
) internal pure returns (uint256 amount0);

function getAmount1ForLiquidity(
    uint160 sqrtPriceAX96,
    uint160 sqrtPriceBX96,
    uint128 liquidity
) internal pure returns (uint256 amount1);
Three Regimes for getLiquidityForAmounts

Given current price P, position range [P_a, P_b]:

Case 1: P < P_a — price is below range, position is entirely token0.

liquidity = getLiquidityForAmount0(√P_a, √P_b, amount0)
           = amount0 × √P_a × √P_b / (√P_b - √P_a)

Case 2: P_a ≤ P ≤ P_b — price is inside range, position holds both tokens.

L0 = getLiquidityForAmount0(√P, √P_b, amount0)
L1 = getLiquidityForAmount1(√P_a, √P, amount1)
liquidity = min(L0, L1)

The binding constraint determines the actual liquidity. Excess of the other token is not used.

Case 3: P > P_b — price is above range, position is entirely token1.

liquidity = getLiquidityForAmount1(√P_a, √P_b, amount1)
           = amount1 / (√P_b - √P_a)
Formulas
From token0:  L = amount0 × √P_a × √P_b / (√P_b - √P_a)
From token1:  L = amount1 / (√P_b - √P_a)

To token0:    amount0 = L × (√P_b - √P_a) / (√P_a × √P_b)
To token1:    amount1 = L × (√P_b - √P_a)

Fee Accounting

Global Fee Accumulators
solidity
uint256 feeGrowthGlobal0X128;  // cumulative fee per unit liquidity for token0
uint256 feeGrowthGlobal1X128;  // cumulative fee per unit liquidity for token1

These are Q128.128 fixed-point values that increase monotonically. Each swap adds:

feeGrowthGlobal0X128 += feeAmount0 × 2¹²⁸ / activeLiquidity
Per-Tick Fee Tracking

Each initialized tick stores feeGrowthOutside{0,1}X128. By convention, "outside" means the side that the current tick is NOT on relative to the tick in question.

feeGrowthBelow(tick_i):
  if currentTick >= tick_i:
    return tick_i.feeGrowthOutside
  else:
    return feeGrowthGlobal - tick_i.feeGrowthOutside

feeGrowthAbove(tick_i):
  if currentTick < tick_i:
    return tick_i.feeGrowthOutside
  else:
    return feeGrowthGlobal - tick_i.feeGrowthOutside
Fee Growth Inside a Range
feeGrowthInside[tickLower, tickUpper] =
    feeGrowthGlobal - feeGrowthBelow(tickLower) - feeGrowthAbove(tickUpper)
Fees Owed to a Position
solidity
feesOwed0 = (feeGrowthInside0X128 - position.feeGrowthInside0LastX128)
            * position.liquidity / 2**128;

feesOwed1 = (feeGrowthInside1X128 - position.feeGrowthInside1LastX128)
            * position.liquidity / 2**128;

The subtraction relies on uint256 wrapping — this works correctly even if feeGrowthInside has wrapped around, as long as fees accrued in a single position's lifetime don't exceed 2²⁵⁶.

Worked Examples

Example 1: sqrtPriceX96 for ETH/USDC at $3000
Assumptions:
  token0 = WETH (18 decimals)
  token1 = USDC (6 decimals)
  Human price: 1 ETH = 3000 USDC

Step 1: Raw price in token units
  price = 3000 × 10⁶ / 10¹⁸ = 3 × 10⁻⁹

Step 2: Square root
  √price = √(3 × 10⁻⁹) = √3 × 10⁻⁴·⁵ ≈ 5.47722558 × 10⁻⁵

Step 3: Scale by 2⁹⁶
  sqrtPriceX96 = 5.47722558 × 10⁻⁵ × 79228162514264337593543950336
               ≈ 4_339_505_179_874_779_489_431_521

Step 4: Corresponding tick
  tick = floor(log(3 × 10⁻⁹) / log(1.0001)) = floor(-196256.35) = -196257

Verification: TickMath.getSqrtPriceAtTick(-196257) should be ≈ sqrtPriceX96 above (slightly below it, since the tick is floored)
Example 2: Liquidity from Token Amounts
Scenario:
  Provide liquidity for ETH/USDC, range $2500-$3500
  Current price: $3000
  Deposit: 1 ETH + 3000 USDC

Step 1: Convert price bounds to ticks
  tickLower ≈ -198080  (corresponding to ~$2500)
  tickUpper ≈ -194715  (corresponding to ~$3500)

Step 2: Get sqrtPrices
  √P       = √(3000 × 10⁻¹²) × 2⁹⁶  (current, from Example 1)
  √P_lower = √(2500 × 10⁻¹²) × 2⁹⁶ ≈ 3_961_408_125_713_216_879_677_197
  √P_upper = √(3500 × 10⁻¹²) × 2⁹⁶ ≈ 4_687_201_305_027_700_927_646_043

Step 3: Compute L from each token
  L_from_ETH = amount0 × √P × √P_upper / (√P_upper - √P)
  L_from_USDC = amount1 / (√P - √P_lower) × 2⁹⁶

Step 4: Take the minimum
  liquidity = min(L_from_ETH, L_from_USDC)

Excess of the non-binding token is returned to the depositor.
Example 3: Swap Output Calculation
Scenario:
  Swap 1 WETH for USDC in the ETH/USDC pool
  zeroForOne = true (selling token0/WETH)
  Current sqrtPriceX96 corresponds to $3000
  Pool has 10_000_000 units of liquidity in the current tick range
  Fee: 3000 (0.30%)

Step 1: Deduct fee from input
  effectiveInput = 1e18 × (1_000_000 - 3000) / 1_000_000
                 = 1e18 × 997000 / 1000000
                 = 997 × 10¹⁵

Step 2: Compute new sqrtPrice after consuming effectiveInput of token0
  √P_new = L × √P_old / (L + effectiveInput × √P_old)
  (price decreases because we're adding token0)

Step 3: Compute token1 output
  amount1Out = L × (√P_old - √P_new)

Step 4: If √P_new crosses a tick boundary, split the computation:
  - Compute partial swap to the tick boundary
  - Cross tick (adjust liquidity by tick's liquidityNet)
  - Continue with remaining input and new liquidity
Example 4: Tick to Human-Readable Price
Given: tick = -196257, token0 = WETH (18 dec), token1 = USDC (6 dec)

Step 1: Raw price
  rawPrice = 1.0001^(-196257) ≈ 3.000 × 10⁻⁹

Step 2: Adjust for decimals
  humanPrice = rawPrice × 10^(decimals0 - decimals1)
             = 3.000 × 10⁻⁹ × 10^(18-6)
             = 3.000 × 10⁻⁹ × 10¹²
             = 3000

So tick -196257 ≈ $3000 ETH/USDC
python
import math
def tick_to_price(tick, decimals0, decimals1):
    raw = 1.0001 ** tick
    return raw * 10 ** (decimals0 - decimals1)

def price_to_tick(price, decimals0, decimals1):
    raw = price / 10 ** (decimals0 - decimals1)
    return math.floor(math.log(raw) / math.log(1.0001))
Example 5: Fee Accrual for an LP Position
Scenario:
  Position: liquidity = 5_000_000, range [tickLower, tickUpper]
  At position creation:
    position.feeGrowthInside0LastX128 = 100 × 2¹²⁸
    position.feeGrowthInside1LastX128 = 200 × 2¹²⁸

  After many swaps:
    feeGrowthInside0X128 = 150 × 2¹²⁸
    feeGrowthInside1X128 = 350 × 2¹²⁸

Fee calculation:
  feesOwed0 = (150 × 2¹²⁸ - 100 × 2¹²⁸) × 5_000_000 / 2¹²⁸
            = 50 × 5_000_000
            = 250_000_000  (in token0 smallest units)

  feesOwed1 = (350 × 2¹²⁸ - 200 × 2¹²⁸) × 5_000_000 / 2¹²⁸
            = 150 × 5_000_000
            = 750_000_000  (in token1 smallest units)

V4 Import Paths

solidity
// Core math libraries
import {TickMath} from "v4-core/src/libraries/TickMath.sol";
import {SqrtPriceMath} from "v4-core/src/libraries/SqrtPriceMath.sol";
import {SwapMath} from "v4-core/src/libraries/SwapMath.sol";
import {FullMath} from "v4-core/src/libraries/FullMath.sol";
import {FixedPoint96} from "v4-core/src/libraries/FixedPoint96.sol";
import {FixedPoint128} from "v4-core/src/libraries/FixedPoint128.sol";
import {TickBitmap} from "v4-core/src/libraries/TickBitmap.sol";
import {Position} from "v4-core/src/libraries/Position.sol";
import {UnsafeMath} from "v4-core/src/libraries/UnsafeMath.sol";
import {BitMath} from "v4-core/src/libraries/BitMath.sol";

// Periphery helpers
import {LiquidityAmounts} from "v4-periphery/src/libraries/LiquidityAmounts.sol";

// Types
import {PoolKey} from "v4-core/src/types/PoolKey.sol";
import {PoolId, PoolIdLibrary} from "v4-core/src/types/PoolId.sol";
import {BalanceDelta} from "v4-core/src/types/BalanceDelta.sol";
import {Currency} from "v4-core/src/types/Currency.sol";

FixedPoint96 and FixedPoint128

solidity
// v4-core/src/libraries/FixedPoint96.sol
uint8 internal constant RESOLUTION = 96;
uint256 internal constant Q96 = 0x1000000000000000000000000; // 2^96

// v4-core/src/libraries/FixedPoint128.sol
uint256 internal constant Q128 = 0x100000000000000000000000000000000; // 2^128
  • Q96 = 2⁹⁶ = 79228162514264337593543950336 — used for sqrtPriceX96
  • Q128 = 2¹²⁸ = 340282366920938463463374607431768211456 — used for fee growth accumulators

BitMath Library

Import: import {BitMath} from "v4-core/src/libraries/BitMath.sol";

solidity
function mostSignificantBit(uint256 x) internal pure returns (uint8 r);
function leastSignificantBit(uint256 x) internal pure returns (uint8 r);

Used internally by TickBitmap.nextInitializedTickWithinOneWord to find set bits efficiently. mostSignificantBit is also used in TickMath.getTickAtSqrtPrice for the initial approximation.

Common Pitfalls

1. Forgetting Decimal Normalization

Token0/token1 ordering and decimal differences change everything:

solidity
// WRONG: assuming 18 decimals for all tokens
uint256 priceInUSD = (sqrtPriceX96 * sqrtPriceX96) >> 192;

// RIGHT: account for decimal difference
// For WETH(18)/USDC(6): multiply result by 10^12
uint256 rawPrice = FullMath.mulDiv(sqrtPriceX96, sqrtPriceX96, 1 << 192);
uint256 priceInUSD = rawPrice * 10 ** (18 - 6);
2. Off-by-One in Tick Rounding

getTickAtSqrtPrice floors to the largest tick ≤ the price. When computing a position range from a human price, always round tickLower DOWN and tickUpper UP (to the nearest usable tick) to ensure the range contains the target price:

solidity
int24 rawTick = TickMath.getTickAtSqrtPrice(targetSqrtPrice);
int24 tickLower = (rawTick / tickSpacing) * tickSpacing;
if (rawTick < 0 && rawTick % tickSpacing != 0) {
    tickLower -= tickSpacing;  // round towards negative infinity
}
int24 tickUpper = tickLower + tickSpacing;
3. Rounding Direction Errors

Always match rounding to who benefits:

solidity
// Collecting fees — user receives, round DOWN
uint256 fees = FullMath.mulDiv(delta, liquidity, FixedPoint128.Q128);

// Charging fees — user pays, round UP
uint256 fees = FullMath.mulDivRoundingUp(delta, liquidity, FixedPoint128.Q128);
4. Overflow in Intermediate Calculations

Never multiply two uint160 or uint256 values directly — use FullMath.mulDiv:

solidity
// WRONG: overflows for large sqrtPriceX96 values
uint256 price = (uint256(sqrtPriceX96) * uint256(sqrtPriceX96)) / (1 << 192);

// RIGHT: 512-bit intermediate
uint256 price = FullMath.mulDiv(sqrtPriceX96, sqrtPriceX96, 1 << 192);
5. Tick Spacing Alignment

Positions can only be placed at ticks divisible by tickSpacing. Passing unaligned ticks to mint reverts:

solidity
// Verify alignment before creating positions
require(tickLower % tickSpacing == 0, "tickLower not aligned");
require(tickUpper % tickSpacing == 0, "tickUpper not aligned");
require(tickLower < tickUpper, "tickLower must be < tickUpper");
6. Liquidity Overflow

uint128 liquidity can overflow with very large positions. The maximum liquidity per tick is bounded by the pool's maxLiquidityPerTick, which depends on tick spacing:

solidity
// From Pool.tickSpacingToMaxLiquidityPerTick:
// tickSpacing=1   → maxLiq ≈ 1.918 × 10³²  ((2¹²⁸−1) / 1_774_545 ticks)
// tickSpacing=60  → maxLiq ≈ 1.151 × 10³⁴  ((2¹²⁸−1) / 29_575 ticks)
// tickSpacing=200 → maxLiq ≈ 3.835 × 10³⁴  ((2¹²⁸−1) / 8_873 ticks)
7. Fee Growth Wrapping

Fee growth values can wrap around for tokens with very small decimals or very high volume. The subtraction current - last works correctly due to unsigned integer underflow semantics, but only if total fees accrued in a position's lifetime stay under 2²⁵⁶. This is not a practical concern.

8. sqrtPrice Bounds

The pool's sqrtPriceX96 is always strictly within (MIN_SQRT_PRICE, MAX_SQRT_PRICE). Passing values at or outside these bounds to pool functions will revert:

solidity
// Valid price limits for swaps
uint160 priceLimit = zeroForOne
    ? TickMath.MIN_SQRT_PRICE + 1  // just above minimum
    : TickMath.MAX_SQRT_PRICE - 1; // just below maximum

Checklist

  • Decimal differences between token0 and token1 are accounted for in all price conversions
  • FullMath.mulDiv used for all intermediate multiplications that may overflow uint256
  • Rounding direction matches economic intent (pay UP, receive DOWN)
  • Tick values are aligned to pool's tick spacing before use
  • sqrtPriceLimitX96 is strictly within (MIN_SQRT_PRICE, MAX_SQRT_PRICE)
  • Fee accumulator math uses uint256 wrapping subtraction correctly
  • LiquidityAmounts regime (below/inside/above range) is handled for the current price
  • Token0/token1 ordering verified (token0 < token1 by address)
  • Position key includes salt parameter in V4 (not just owner + ticks)
  • getTickAtSqrtPrice floor behavior accounted for in range boundary calculations

© ccashwell, MIT. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file

Files

Just SKILL.md in skills/uniswap-math of ccashwell/evm-cortex.

Open the folder on GitHubat commit f8f3301

Compare with similar skills

Uniswap Math next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.

Uniswap Math compared with similar skills
SkillStarsUsed inTokensAuto-checkLicenceRepo updated
Uniswap Math this skillccashwell/evm-cortex131—~7.5kAutomated safety check: PassMIT
Notebook To Strategytradingstrategy-ai/trade-executor160—~1.1kAutomated safety check: PassCustom licence
Building Blocksaustintgriffith/ethskills295—~2.9kAutomated safety check: PassNone
Oracle Flashloan Analysisquillai-network/quillshield_skills130—~2.8kAutomated safety check: PassMIT
Octav APIinternet-court/internet-court-skill6.5k—~3.8kAutomated safety check: PassMIT
Uniswap SwapNethereum/Nethereum2.3k—~2.1kAutomated safety check: PassMIT

Similar skills

  • Notebook To Strategy

    tradingstrategy-ai/trade-executor

    Transfer code from a backtesting Jupyter notebook to a Trade Executor strategy module

    160 GitHub stars~1.1k tokensUpdated yesterday
    Business, Finance & HRAuto-check passed
  • Building Blocks

    austintgriffith/ethskills

    DeFi legos and protocol composability on Ethereum and L2s. An agent skill from austintgriffith/ethskills.

    295 GitHub stars~2.9k tokensUpdated 1 mo ago
    Business, Finance & HRAuto-check passed
  • Oracle Flashloan Analysis

    quillai-network/quillshield_skills

    Detects price oracle manipulation and flash loan attack vectors in DeFi smart contracts.

    130 GitHub stars~2.8k tokensUpdated 6 mo ago
    Business, Finance & HRAuto-check passed
  • Octav API

    internet-court/internet-court-skill

    Integrate with Octav API for cryptocurrency portfolio tracking, transaction history, and DeFi analytics across 50+ blockchain networks.

    6.5k GitHub stars~3.8k tokensUpdated 1 mo ago
    Business, Finance & HRAuto-check passed
  • Uniswap Swap

    Nethereum/Nethereum

    Swap tokens on Uniswap V2/V3/V4 using Nethereum (.NET/C). An agent skill from Nethereum/Nethereum.

    2.3k GitHub stars~2.1k tokensUpdated 4 days ago
    Business, Finance & HRAuto-check passed
  • Aomi Transact

    sickn33/agentic-awesome-skills

    Build natural-language crypto/DeFi agents and EVM MCP plugins (Claude Code, Cursor, Codex, Gemini).

    47k GitHub starsUsed in 1 repo~2.3k tokens
    Business, Finance & HRAuto-check passed

More from ccashwell/evm-cortex

All 89 skills in this repo
  • Xray Pre Audit

    ccashwell/evm-cortex

    A skill your agent uses when preparing for a security audit, performing reconnaissance on a new codebase, or creating a protocol overview.

    131 GitHub stars~25k tokensUpdated 9 days ago
    Auto-check passed
  • Aave Integration

    ccashwell/evm-cortex

    A skill your agent uses when integrating with Aave V3 for lending, borrowing, flash loans, or building on top of Aave markets.

    131 GitHub stars~1.3k tokensUpdated 9 days ago
    Auto-check passed
  • Access Control Patterns

    ccashwell/evm-cortex

    Access control design patterns for Solidity protocols. An agent skill from ccashwell/evm-cortex.

    131 GitHub stars~1.8k tokensUpdated 9 days ago
    Auto-check passed
  • Anvil Patterns

    ccashwell/evm-cortex

    A skill your agent uses when running a local Ethereum node with Anvil.

    131 GitHub stars~1.3k tokensUpdated 9 days ago
    Auto-check passed
  • Audit Breadth Scan

    ccashwell/evm-cortex

    A skill your agent uses when performing systematic breadth-first review of all contracts during a security audit.

    131 GitHub stars~1.4k tokensUpdated 9 days ago
    Auto-check passed
  • Audit Depth Analysis

    ccashwell/evm-cortex

    A skill your agent uses when performing deep analysis of specific findings or high-risk areas during a security audit.

    131 GitHub stars~1.6k tokensUpdated 9 days ago
    Auto-check passed

Works with

Questions about Uniswap Math

What does Uniswap Math do?

A skill your agent uses when working with Uniswap pricing math, tick calculations, liquidity formulas, or Q64.96 fixed-point arithmetic. Uniswap Math is an agent skill from ccashwell/evm-cortex.96 fixed-point arithmetic.

When should I use Uniswap Math?

Uniswap Math fits situations like: working with Uniswap pricing math; tick calculations; liquidity formulas; Q64.96 fixed-point arithmetic.

How do I install Uniswap Math in Claude Code?

Run `npx skills add ccashwell/evm-cortex --skill uniswap-math -a claude-code`. Or copy the skill folder (skills/uniswap-math in ccashwell/evm-cortex) into .claude/skills/uniswap-math in your project. Claude Code loads it when a task matches its description.

How do I install Uniswap Math in Codex?

Run `npx skills add ccashwell/evm-cortex --skill uniswap-math -a codex`. Or copy the skill folder (skills/uniswap-math in ccashwell/evm-cortex) into .agents/skills/uniswap-math in your project. Codex loads it when a task matches its description.

Can I use Uniswap Math in Cursor, Gemini CLI or GitHub Copilot?

Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add ccashwell/evm-cortex --skill uniswap-math -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/uniswap-math, .gemini/skills/uniswap-math, .github/skills/uniswap-math and .opencode/skills/uniswap-math in your project.

What does Uniswap Math need to run?

SKILL.md names no scripts, command-line tools or credentials: Uniswap Math is instructions for the agent only. Our summary lists: Python 3.

Does Uniswap Math access the network?

SKILL.md contains no URLs. Any network use would come from the scripts or tools the agent runs. This is read from the text; nothing was executed.

Is Uniswap Math safe to install?

Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. Review the folder before installing.

What licence does Uniswap Math use?

Uniswap Math is published under the MIT licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.

How many tokens does Uniswap Math use?

About 7.5k tokens (SKILL.md is roughly 30k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.

What are the alternatives to Uniswap Math?

Skills that share tags, products or a category with Uniswap Math: Notebook To Strategy (tradingstrategy-ai/trade-executor, 160 stars), Building Blocks (austintgriffith/ethskills, 295 stars), Oracle Flashloan Analysis (quillai-network/quillshield_skills, 130 stars) and Octav API (internet-court/internet-court-skill, 6.5k stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.

Who maintains Uniswap Math?

ccashwell (a GitHub user) maintains it in ccashwell/evm-cortex, which has 131 GitHub stars. The repository holds 89 skills in this directory. The repository was last updated on September 30, 2026.

Source: ccashwell/evm-cortex on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.