> For the complete documentation index, see [llms.txt](https://docs.infraredtrading.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.infraredtrading.com/learn/protocols/velodrome/mechanics.md).

# Mechanics

How Velodrome/Aerodrome v2 pools work — the volatile and stable curves, LP mint/burn with fee separation, router quoting, swap-fee resolution, the pause surface, and the ve(3,3) flywheel.

This page covers the v2 AMM pool model in depth — the pool curves, the LP token's mint/burn/fee mechanics, and how to quote against them — plus the ve(3,3) governance flywheel that steers emissions. For addresses and roles, see [Deployments](/learn/protocols/velodrome/deployments.md); for the risk profile, see [Security](/learn/protocols/velodrome/security.md).

Both brands' v2 mechanics below were verified verbatim against the Velodrome and Aerodrome [`Pool.sol`](https://github.com/velodrome-finance/contracts/blob/main/contracts/Pool.sol) / [`Router.sol`](https://github.com/velodrome-finance/contracts/blob/main/contracts/Router.sol) source and against live on-chain reads on 2026-07-08. The code is functionally identical across the two brands (see the [parity verdict](/learn/protocols/velodrome/deployments.md)).

## Two pool products

* **v2 pools** — fungible-LP AMM pools in two flavors, volatile and stable (below). Everything on this page about LP composition applies to these.
* **Slipstream** — a Uniswap V3 fork with [concentrated liquidity](/learn/concepts/concentrated-liquidity.md): pools are identified by tick spacing rather than fee tier, fees are dynamic (delegated to a factory swap-fee module), and positions are **NFTs** (a NonfungiblePositionManager), not fungible LP tokens. Slipstream carries the large majority of Aerodrome's volume; v2 pools still held \~$106M TVL on Base as of 2026-08-19 (see [Ecosystem](/learn/protocols/velodrome/ecosystem.md)).

## Pool curves

* **Volatile:** invariant `k = x·y` — identical to Uniswap V2 pricing.
* **Stable:** invariant `k = x³y + xy³`, computed on decimal-normalized reserves (`_x = x·1e18/decimals0`, `_y = y·1e18/decimals1`, `k = (_x·_y/1e18)·((_x²+_y²)/1e18)/1e18`). Swap output solves the quartic via Newton iteration (up to 255 rounds) in `_get_y`; `getAmountOut` first deducts the fee from the input (`amountIn -= amountIn · getFee(pool, stable) / 10000`) and then walks the curve.
* The curve choice is **fixed at pool creation**: the `stable` flag is part of the pool's CREATE2 salt (`keccak256(abi.encodePacked(token0, token1, stable))`), so a volatile and a stable pool can coexist for the same token pair.

## LP composition mechanics (v2 pools)

### Burn / decompose

`Pool.burn(address to)` is **proportional by LP supply share, for both volatile and stable pools** — verbatim from source (identical in Velodrome and Aerodrome):

```solidity
uint256 _liquidity = balanceOf(address(this));   // LP sent to the pool first (Router does this)
uint256 _totalSupply = totalSupply();
amount0 = (_liquidity * _balance0) / _totalSupply;
amount1 = (_liquidity * _balance1) / _totalSupply;
if (amount0 == 0 || amount1 == 0) revert InsufficientLiquidityBurned();
```

* This is the **same formula as Uniswap V2** (`liquidity · balance / totalSupply`, computed on live token balances, not cached reserves). The stable curve plays **no role in burn** — the invariant appears only in swap pricing and the first-mint check. A stable-pool LP burn is exactly proportional by supply share.
* **Unlike Uniswap V2 there is no `kLast`/`feeOn` protocol-fee mint** on mint/burn — fee handling is fully externalized (next subsection), so burn output is exactly the reserve share with no dilution surprise.
* `Router.removeLiquidity(tokenA, tokenB, stable, liquidity, amountAMin, amountBMin, to, deadline)` transfers the LP to the pool, calls `burn(to)`, reorders amounts to (tokenA, tokenB), and enforces the minimums. `removeLiquidityETH` unwraps WETH; zap variants exist (`zapOut`).

### Fee separation — fees are never in the reserves

On every swap, the fee portion of the input is immediately **transferred out of the pool** to a dedicated per-pool [`PoolFees`](https://github.com/velodrome-finance/contracts/blob/main/contracts/PoolFees.sol) contract before reserves update — verbatim:

```solidity
function _update0(uint256 amount) internal {
    if (amount == 0) return;
    IERC20(token0).safeTransfer(poolFees, amount);          // fee leaves the pool
    uint256 _ratio = (amount * 1e18) / totalSupply();
    if (_ratio > 0) { index0 += _ratio; }
    emit Fees(msg.sender, amount, 0);
}
```

`PoolFees.sol`'s own header states the purpose: it *"Ensures curve does not need to be modified for LP shares"* — fee income never distorts the invariant. Consequences:

1. **Reserves + totalSupply value the LP token exactly.** There is no Uniswap-V2-style fee accretion inside reserves; `liquidity · reserve_i / totalSupply` is the precise burn entitlement (matches the live Base router wei-exactly, verified 2026-07-08).
2. **Fee income is a separate, per-address claim.** The indexes `index0`/`index1` (fee per LP unit, 1e18-scaled) drive `claimable0`/`claimable1[owner]` via `_updateFor`, which runs inside the LP token's ERC-20 transfer hook — so claim accounting follows every LP transfer, mint, and burn. Fees are collected with `Pool.claimFees()` (the pool pulls from its PoolFees contract). **`burn` does NOT claim fees** — a full exit from a fee-earning position is `claimFees()` + `removeLiquidity(...)`, two calls.
3. Empirically on Base (2026-07-08): the vAMM-WETH/USDC pool's PoolFees contract held \~3.45 WETH + \~8,295 USDC of unclaimed fees, entirely outside the pool's reserves.
4. Rounding dust: `claimable` shares are computed as `supplied · Δindex / 1e18` (floor), so a holder's claimable can lag the PoolFees balance by dust; unclaimed fees of past LPs simply sit in PoolFees.

### Mint / compose

`Pool.mint(address to)` (tokens must be transferred in first; the Router does this):

```solidity
if (_totalSupply == 0) {
    liquidity = Math.sqrt(_amount0 * _amount1) - MINIMUM_LIQUIDITY;   // MINIMUM_LIQUIDITY = 10**3
    _mint(address(1), MINIMUM_LIQUIDITY);                             // dead shares to address(1), not address(0)
    if (stable) {
        if ((_amount0 * 1e18) / decimals0 != (_amount1 * 1e18) / decimals1) revert DepositsNotEqual();
        if (_k(_amount0, _amount1) <= MINIMUM_K) revert BelowMinimumK();   // MINIMUM_K = 10**10
    }
} else {
    liquidity = Math.min((_amount0 * _totalSupply) / _reserve0, (_amount1 * _totalSupply) / _reserve1);
}
```

* **Subsequent deposits (the practical case): identical to Uniswap V2** — `min()` of the two proportional ratios, for both curves. Depositing at the current reserve ratio is optimal for stable pools too; the curve never enters mint math.
* **Stable-pool first deposit only** must be exactly balanced in normalized units (`DepositsNotEqual`) and large enough (`MINIMUM_K`). Existing pools impose no balance requirement beyond the `min()` penalty for ratio mismatch.
* `Router.addLiquidity(tokenA, tokenB, stable, amountADesired, amountBDesired, amountAMin, amountBMin, to, deadline)` computes the optimal pair via `quoteLiquidity(amountA, reserveA, reserveB) = amountA · reserveB / reserveA` — **the same for volatile and stable pools** (reserve-ratio matching, not price-ratio) — then transfers and calls `mint(to)`. `addLiquidityETH` wraps native; `zapIn` + `generateZapInParams` support single-token entry.

### Valuation preview

* `Router.quoteRemoveLiquidity(tokenA, tokenB, stable, _factory, liquidity)` exists on both deployed routers and returns exactly `liquidity · reserve_i / totalSupply` (returns `(0,0)` for a nonexistent pool). Verified wei-exact against live Base state on 2026-07-08.
* `Router.quoteAddLiquidity(tokenA, tokenB, stable, _factory, amountADesired, amountBDesired)` returns `(amountA, amountB, liquidity)` using the same optimal-amount + `min()` math as the real deposit. Verified on both chains 2026-07-08.
* Per-LP value from raw state: `getReserves()` + `totalSupply()` is sufficient **and exact** — the fee-separation design means there is no hidden fee premium inside reserves. The only skew vs `burn` is un-synced direct token donations to the pool (since `burn` uses live balances); donations only increase burn output.
* `Pool.metadata()` returns `(dec0, dec1, r0, r1, stable, t0, t1)` in one call — a convenient single state fetch for valuation.

### LP token properties

* **18 decimals always** (no `decimals()` override; ERC-20 default) — verified on Base for both a volatile (`vAMM-WETH/USDC`) and a stable (`sAMM-USDC/USDT`) pool, 2026-07-08. Underlying token decimals are absorbed by the normalized curve math, never by the LP token.
* ERC-20 + ERC-2612 permit (`Pool is IPool, ERC20Permit`). Freely transferable.
* **Gauge-staked LP is a different position**: staking deposits the LP token *into* the gauge contract, so the staker holds a gauge balance, not the LP ERC-20. Stakers "forgo their fee reward in exchange for a proportional distribution of emissions" — their trading fees are redirected to the pool's `FeesVotingReward` and paid to veAERO/veVELO voters in the next epoch ([SPECIFICATION.md](https://github.com/aerodrome-finance/contracts/blob/main/SPECIFICATION.md)). An LP ERC-20 held in a wallet is unaffected by any of this.

### Pause surface

`swap()` checks `IPoolFactory(factory).isPaused()` and reverts when paused; **`mint`, `burn`, and `claimFees` have no pause check**. [PERMISSIONS.md](https://github.com/aerodrome-finance/contracts/blob/main/PERMISSIONS.md) states it directly: the pauser "Controls pause state of swaps on UniswapV2 pools created by this factory. Users are still freely able to add/remove liquidity." The pauser is the protocol team multisig — the emergency council's kill-switch scope is gauges, not pools (see [Deployments](/learn/protocols/velodrome/deployments.md)). Live state 2026-07-08: `isPaused = false` on both chains. **Liquidity redemption (burn) can never be paused; only swaps can.**

## Swap fees (v2 pools)

`PoolFactory.getFee(pool, stable)` resolves: per-pool `customFee` override → else default `stableFee` / `volatileFee`. Values are basis points of `amountIn`. Live 2026-07-08 (both chains): `stableFee = 5` (0.05%), `volatileFee = 30` (0.30%). Caps diverge by brand: `MAX_FEE = 300` (3%) on Aerodrome/Base but `100` (1%) on Velodrome/Optimism. `ZERO_FEE_INDICATOR = 420` is a sentinel meaning "this pool's custom fee is exactly zero". The `feeManager` role can change defaults and per-pool custom fees at any time — **fee values must be read live, never hardcoded**. As of 2026-07-08 there is no dynamic (module-driven) fee on v2 pools — that exists only on Slipstream factories; the official docs describe a self-adjusting dynamic fee module as in progress (see [Ecosystem](/learn/protocols/velodrome/ecosystem.md)).

## The ve(3,3) flywheel

The emissions layer is a [vote-escrow](/learn/concepts/vote-escrow.md) system ([SPECIFICATION.md](https://github.com/aerodrome-finance/contracts/blob/main/SPECIFICATION.md)):

* **Epochs:** 1 week, starting Thursday 00:00 UTC.
* **veAERO / veVELO:** lock AERO/VELO for up to 4 years for a voting NFT; voting power scales linearly with lock duration (a 4-year lock is 1:1) and decays unless max-locked.
* **Voting:** ve-holders vote weekly to direct the epoch's AERO/VELO emissions across pool gauges; voters earn that pool's trading fees (redirected from staked LPs) plus any external incentives ("bribes"); ve-holders also receive rebases proportional to their locked share.
* **LPs:** unstaked LPs earn trading fees via `claimFees()`; gauge-staked LPs earn emissions instead.
* **Emissions:** minted per epoch by the Minter on a decaying schedule with a governance-adjustable tail — the repo SPECIFICATION.md describes 15M/epoch decaying 1% per epoch, reaching tail emissions around epoch 92 at 30bps of circulating supply, stepwise adjustable via the EpochGovernor.

The flywheel matters to trading and LP economics indirectly but powerfully: emissions placement drives where liquidity lives, and the 2026 emissions migration (begun July 2026 for legacy CL pools, ahead of the Aero launch targeted for September 2026) is actively moving it — see [Ecosystem](/learn/protocols/velodrome/ecosystem.md).

## Slipstream contract updates (April 2026)

Two behavioral additions landed in the [`aerodrome-finance/slipstream`](https://github.com/aerodrome-finance/slipstream) repo in April 2026:

* **DynamicSwapFeeModule `initialFee`** (commits `1e5e4b95` + `ec2799e4`, Apr 7, 2026). The Slipstream dynamic-fee module now exposes a configurable first-swap-in-block fee: `initialFeeEnabled` (bool) and `initialFee` (uint24) struct fields, with admin functions `setInitialFee()` and `disableInitialFee()`. When enabled, `getFee()` returns `initialFee` for the first oracle-update-eligible swap in a block — an MEV defense for high-frequency block environments. Integrators reading the pool's live `fee()` see the active fee in every case, whatever the `initialFeeEnabled` state.
* **CLGauge early-unstake penalty** (commit `618c88df`, Apr 10, 2026). CL gauges gained a configurable early-unstake penalty: `minStakeTime`, a `penaltyRate` in basis points (up to 100%), and a `gaugeStakeManager` role; penalty proceeds are transferred to the Minter, and `earned()` is penalty-adjusted. This affects gauge-staking economics only — swap and quote behavior of the pools themselves is unchanged.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.infraredtrading.com/learn/protocols/velodrome/mechanics.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
