Skip to main content

uniswap-v4-expert

Use when building on, integrating with, or analyzing Uniswap V4. Covers PoolManager singleton architecture, flash accounting via EIP-1153 transient storage, hook lifecycle, PoolKey structure, Currency type, dynamic fees, custom accounting, native ETH support, PositionManager (ERC-721 positions), and production deployment addresses.

Zur Installation springen

Quellinformationen

Repository
ccashwell/evm-cortex
Letzte Quellaktivität
10. April 2026 um 16:31
Erkannte Sprache von SKILL.md
Englisch
Sterne
131
Forks
18

Installationsoptionen

Standardmäßig ist der Prompt ausgewählt, der zuerst die Quelle prüft. Sie können zu einem direkten Befehl wechseln oder eine lokale Kopie herunterladen.

Quelldateien prüfen

Lesen Sie SKILL.md und alle von SkillsMP angezeigten Begleitdateien, bevor Sie sich für eine Installation entscheiden.

SKILL.md wird angezeigt

SKILL.md
Quellanweisungen · Schreibgeschützte Vorschau
name
uniswap-v4-expert
description
Use when building on, integrating with, or analyzing Uniswap V4. Covers PoolManager singleton architecture, flash accounting via EIP-1153 transient storage, hook lifecycle, PoolKey structure, Currency type, dynamic fees, custom accounting, native ETH support, PositionManager (ERC-721 positions), and production deployment addresses.
# Uniswap V4 Expert ## Architecture Overview Uniswap V4 replaces V3's factory-per-pool model with a **singleton PoolManager** — every pool lives inside a single contract. This eliminates redundant bytecode deployments and enables multi-hop swaps to settle only net token transfers. All state-changing operations use **flash accounting** via EIP-1153 transient storage: callers accumulate deltas during an `unlock()` callback and must zero out all balances before the callback returns. ### Singleton Design ``` ┌────────────────────────────────────────────┐ │ PoolManager │ │ ┌──────────┐ ┌───────────┐ ┌──────────┐ │ │ │ Pool A │ │ Pool B │ │ Pool C │ │ │ │ ETH/USDC │ │ WBTC/USDC │ │ ETH/DAI │ │ │ └──────────┘ └───────────┘ └──────────┘ │ │ │ │ Transient Storage (EIP-1153) │ │ ┌──────────────────────────────────────┐ │ │ │ currency → delta mapping (per lock) │ │ │ └──────────────────────────────────────┘ │ └────────────────────────────────────────────┘ ``` ### Flash Accounting Flow 1. Caller invokes `poolManager.unlock(data)` 2. PoolManager calls `IUnlockCallback(msg.sender).unlockCallback(data)` 3. Inside the callback, caller executes operations (swap, modifyLiquidity, donate) 4. Each operation updates transient storage deltas — no token transfers yet 5. Caller resolves deltas via `settle()` (pay tokens in) and `take()` (withdraw tokens out) 6. On return from `unlockCallback`, PoolManager verifies all currency deltas are zero 7. If any delta is nonzero, the transaction reverts with `CurrencyNotSettled()` This means multi-hop swaps (e.g., A→B→C) only require net token movements for A and C, saving gas on intermediate transfers. ### Functions Callable Outside unlock() Only two functions do NOT require the unlock context: - `initialize()` — creates a new pool (no balance changes) - `updateDynamicLPFee()` — called by hook contracts to set the current dynamic fee Everything else (`swap`, `modifyLiquidity`, `donate`, `take`, `settle`, `mint`, `burn`, `sync`, `clear`) requires being inside an active `unlockCallback`. ## Core Types ### PoolKey The unique identifier for a pool. Defined in `v4-core/src/types/PoolKey.sol`: ```solidity import {Currency} from "v4-core/src/types/Currency.sol"; import {IHooks} from "v4-core/src/interfaces/IHooks.sol"; struct PoolKey { /// @notice The lower currency of the pool, sorted numerically Currency currency0; /// @notice The higher currency of the pool, sorted numerically Currency currency1; /// @notice The pool LP fee, capped at 1_000_000. If the highest bit is 1, the pool has a dynamic fee and must be exactly equal to 0x800000 uint24 fee; /// @notice Ticks that involve positions must be a multiple of tick spacing int24 tickSpacing; /// @notice The hooks of the pool IHooks hooks; } ``` **Sorting invariant**: `currency0 < currency1` is enforced. The PoolManager reverts with `CurrenciesOutOfOrderOrEqual` if violated. When constructing a PoolKey, always sort currencies by address value. ### PoolId A `bytes32` hash of the PoolKey, used as the storage key for pool state. Defined in `v4-core/src/types/PoolId.sol`: ```solidity type PoolId is bytes32; library PoolIdLibrary { function toId(PoolKey memory poolKey) internal pure returns (PoolId poolId) { assembly ("memory-safe") { // 0xa0 = 5 slots × 32 bytes (total size of PoolKey struct) poolId := keccak256(poolKey, 0xa0) } } } ``` Usage: `using PoolIdLibrary for PoolKey;` then `key.toId()`. ### Currency An address wrapper where `address(0)` represents native ETH. Defined in `v4-core/src/types/Currency.sol`: ```solidity type Currency is address; library CurrencyLibrary { Currency public constant ADDRESS_ZERO = Currency.wrap(address(0)); function isAddressZero(Currency currency) internal pure returns (bool) { return Currency.unwrap(currency) == Currency.unwrap(ADDRESS_ZERO); } function transfer(Currency currency, address to, uint256 amount) internal { /* handles ETH vs ERC-20 */ } function balanceOfSelf(Currency currency) internal view returns (uint256) { /* handles ETH vs ERC-20 */ } } ``` Native ETH pools use `Currency.wrap(address(0))` as one of the currencies. No WETH wrapping required. ### BalanceDelta Two `int128` values packed into a single `int256`. Upper 128 bits = amount0, lower 128 bits = amount1. Defined in `v4-core/src/types/BalanceDelta.sol`: ```solidity type BalanceDelta is int256; library BalanceDeltaLibrary { BalanceDelta public constant ZERO_DELTA = BalanceDelta.wrap(0); function amount0(BalanceDelta balanceDelta) internal pure returns (int128 _amount0) { assembly ("memory-safe") { _amount0 := sar(128, balanceDelta) } } function amount1(BalanceDelta balanceDelta) internal pure returns (int128 _amount1) { assembly ("memory-safe") { _amount1 := signextend(15, balanceDelta) } } } ``` Delta semantics from the caller's perspective: - **Negative** delta = caller owes tokens to PoolManager (must `settle()`) - **Positive** delta = PoolManager owes tokens to caller (can `take()`) ### BeforeSwapDelta Return type of the `beforeSwap` hook. Upper 128 bits = delta in **specified** tokens, lower 128 bits = delta in **unspecified** tokens. Defined in `v4-core/src/types/BeforeSwapDelta.sol`: ```solidity type BeforeSwapDelta is int256; function toBeforeSwapDelta(int128 deltaSpecified, int128 deltaUnspecified) pure returns (BeforeSwapDelta beforeSwapDelta) { assembly ("memory-safe") { beforeSwapDelta := or(shl(128, deltaSpecified), and(sub(shl(128, 1), 1), deltaUnspecified)) } } library BeforeSwapDeltaLibrary { BeforeSwapDelta public constant ZERO_DELTA = BeforeSwapDelta.wrap(0); function getSpecifiedDelta(BeforeSwapDelta delta) internal pure returns (int128); function getUnspecifiedDelta(BeforeSwapDelta delta) internal pure returns (int128); } ``` ## PoolManager Interface Full interface from `v4-core/src/interfaces/IPoolManager.sol`. The PoolManager inherits `IProtocolFees`, `IERC6909Claims`, `IExtsload`, and `IExttload`. ### initialize ```solidity function initialize(PoolKey memory key, uint160 sqrtPriceX96) external returns (int24 tick); ``` Creates a new pool. Does NOT require the unlock context. Reverts if `currency0 >= currency1`, if `tickSpacing` is zero or exceeds `type(int16).max`, or if the pool already exists. Emits `Initialize` event. ### unlock ```solidity function unlock(bytes calldata data) external returns (bytes memory); ``` Entry point for all delta-accounting operations. Calls `IUnlockCallback(msg.sender).unlockCallback(data)`. After the callback returns, asserts all currency deltas are zero. ### swap ```solidity function swap(PoolKey memory key, SwapParams memory params, bytes calldata hookData) external returns (BalanceDelta swapDelta); ``` Executes a swap. Only callable inside `unlockCallback`. Invokes `beforeSwap` and `afterSwap` hooks if the pool's hook contract has those permissions. ### modifyLiquidity ```solidity function modifyLiquidity(PoolKey memory key, ModifyLiquidityParams memory params, bytes calldata hookData) external returns (BalanceDelta callerDelta, BalanceDelta feesAccrued); ``` Adds or removes liquidity. Returns both the principal delta and fees accrued. A zero `liquidityDelta` "pokes" the position to collect fees without changing liquidity. ### donate ```solidity function donate(PoolKey memory key, uint256 amount0, uint256 amount1, bytes calldata hookData) external returns (BalanceDelta); ``` Distributes tokens to in-range liquidity providers. Useful for hook-driven fee distribution or protocol reward injection. ### Settlement Functions ```solidity function settle() external payable returns (uint256 paid); function settleFor(address recipient) external payable returns (uint256 paid); function sync(Currency currency) external; function take(Currency currency, address to, uint256 amount) external; function clear(Currency currency, uint256 amount) external; ``` **settle()**: Pays what the caller owes. For ERC-20 tokens, the caller must first call `sync(currency)`, transfer tokens to the PoolManager, then call `settle()`. For native ETH, send value directly with `settle{value: amount}()`. Returns the amount credited. **sync(currency)**: Snapshots the PoolManager's current ERC-20 balance into transient storage. MUST be called before transferring ERC-20 tokens for settlement. Not needed for native ETH. **take(currency, to, amount)**: Withdraws tokens the PoolManager owes to the caller. Reverts if the caller's delta for that currency is insufficient. **clear(currency, amount)**: Zeros out a positive delta WITHOUT transferring tokens. The tokens are permanently locked in the PoolManager. Use only for dust amounts. ### ERC-6909 Claims ```solidity function mint(address to, uint256 id, uint256 amount) external; function burn(address from, uint256 id, uint256 amount) external; ``` Converts currency deltas into ERC-6909 claim tokens (and vice versa). The `id` is the currency address cast to `uint256`. Useful for holding balances inside the PoolManager across transactions without actual token transfers. ## Parameter Structs ### SwapParams Defined in `v4-core/src/types/PoolOperation.sol`: ```solidity struct SwapParams { /// Whether to swap token0 for token1 or vice versa bool zeroForOne; /// The desired input amount if negative (exactIn), or the desired output amount if positive (exactOut) int256 amountSpecified; /// The sqrt price at which, if reached, the swap will stop executing uint160 sqrtPriceLimitX96; } ``` **CRITICAL**: `amountSpecified` sign convention: - **Negative** = exact input (caller specifies how much to spend) - **Positive** = exact output (caller specifies how much to receive) Price limits: - `zeroForOne = true`: set `sqrtPriceLimitX96` to a value **less than** the current price (price decreases) - `zeroForOne = false`: set `sqrtPriceLimitX96` to a value **greater than** the current price (price increases) - Use `TickMath.MIN_SQRT_PRICE + 1` or `TickMath.MAX_SQRT_PRICE - 1` for unlimited slippage ### ModifyLiquidityParams Defined in `v4-core/src/types/PoolOperation.sol`: ```solidity struct ModifyLiquidityParams { int24 tickLower; int24 tickUpper; int256 liquidityDelta; bytes32 salt; } ``` - `liquidityDelta > 0`: add liquidity - `liquidityDelta < 0`: remove liquidity - `liquidityDelta == 0`: poke (collect accrued fees only) - `salt`: differentiates multiple positions at the same tick range from the same address ## Fee System ### Static Fees Set at pool creation via `PoolKey.fee`. Denominated in **hundredths of a basis point** (1/100th of 1/10000th): | PoolKey.fee | Effective Fee | |-------------|--------------| | 100 | 0.01% | | 500 | 0.05% | | 3000 | 0.30% | | 10000 | 1.00% | | 1000000 | 100% (MAX) | ### Dynamic Fees From `v4-core/src/libraries/LPFeeLibrary.sol`: ```solidity library LPFeeLibrary { uint24 public constant DYNAMIC_FEE_FLAG = 0x800000; uint24 public constant OVERRIDE_FEE_FLAG = 0x400000; uint24 public constant REMOVE_OVERRIDE_MASK = 0xBFFFFF; uint24 public constant MAX_LP_FEE = 1000000; // 100% } ``` To create a dynamic fee pool, set `PoolKey.fee = LPFeeLibrary.DYNAMIC_FEE_FLAG` (exactly `0x800000`). Two mechanisms for dynamic fee updates: 1. **Persistent update**: Hook calls `poolManager.updateDynamicLPFee(key, newFee)` (e.g., in `afterInitialize` or periodically). This sets the stored fee for subsequent swaps. 2. **Per-swap override**: `beforeSwap` returns a fee with the override flag set in the third return value (`uint24`). The returned fee is `desiredFee | LPFeeLibrary.OVERRIDE_FEE_FLAG`. This overrides the stored fee for that single swap only. ```solidity function beforeSwap(address, PoolKey calldata, IPoolManager.SwapParams calldata, bytes calldata) external override returns (bytes4, BeforeSwapDelta, uint24) { uint24 dynamicFee = _computeFee(); return (this.beforeSwap.selector, BeforeSwapDeltaLibrary.ZERO_DELTA, dynamicFee | LPFeeLibrary.OVERRIDE_FEE_FLAG); } ``` ### Protocol Fees Set by the PoolManager owner via `IProtocolFees.setProtocolFee(PoolKey, uint24)`. Protocol fees are taken as a percentage of LP fees. The protocol fee is a `uint24` where the upper 12 bits are the fee for token0 and the lower 12 bits are the fee for token1. ## PositionManager (Periphery) The `PositionManager` is the canonical periphery contract for managing liquidity positions as ERC-721 NFTs. Source: `v4-periphery/src/PositionManager.sol`. ```solidity contract PositionManager is IPositionManager, ERC721Permit_v4, // ERC-721 + EIP-4494 permit PoolInitializer_v4, Multicall_v4, DeltaResolver, ReentrancyLock, BaseActionsRouter, // action dispatch via unlock Notifier, // subscriber/notification pattern Permit2Forwarder, // Permit2 integration NativeWrapper // WETH wrapping/unwrapping { ... } ``` **NFT metadata**: Name = `"Uniswap v4 Positions NFT"`, Symbol = `"UNI-V4-POSM"`. ### Entry Point ```solidity function modifyLiquidities(bytes calldata unlockData, uint256 deadline) external payable; ``` The standard entry point. Encodes a sequence of actions and their parameters. The `unlockData` is ABI-encoded as `(bytes actions, bytes[] params)` where `actions` is a packed byte array of action codes. ### Action Codes From `v4-periphery/src/libraries/Actions.sol`: ```solidity library Actions { uint256 internal constant INCREASE_LIQUIDITY = 0x00; uint256 internal constant DECREASE_LIQUIDITY = 0x01; uint256 internal constant MINT_POSITION = 0x02; uint256 internal constant BURN_POSITION = 0x03; uint256 internal constant SWAP_EXACT_IN_SINGLE = 0x06; uint256 internal constant SWAP_EXACT_IN = 0x07; uint256 internal constant SWAP_EXACT_OUT_SINGLE = 0x08; uint256 internal constant SWAP_EXACT_OUT = 0x09; uint256 internal constant SETTLE = 0x0b; uint256 internal constant SETTLE_ALL = 0x0c; uint256 internal constant SETTLE_PAIR = 0x0d; uint256 internal constant TAKE = 0x0e; uint256 internal constant TAKE_ALL = 0x0f; uint256 internal constant TAKE_PORTION = 0x10; uint256 internal constant TAKE_PAIR = 0x11; uint256 internal constant CLOSE_CURRENCY = 0x12; uint256 internal constant CLEAR_OR_TAKE = 0x13; uint256 internal constant SWEEP = 0x14; uint256 internal constant WRAP = 0x15; uint256 internal constant UNWRAP = 0x16; } ``` **DEPRECATED** (vulnerable to sandwich attacks — lack slippage protection): - `INCREASE_LIQUIDITY_FROM_DELTAS` (0x04) - `MINT_POSITION_FROM_DELTAS` (0x05) ### Typical Action Sequences Mint a new position: ``` [MINT_POSITION, SETTLE_PAIR, SWEEP] // or CLOSE_CURRENCY for each ``` Increase liquidity on existing position: ``` [INCREASE_LIQUIDITY, SETTLE_PAIR, SWEEP] ``` Decrease liquidity and collect: ``` [DECREASE_LIQUIDITY, TAKE_PAIR] ``` Burn an empty position: ``` [BURN_POSITION] // position must have zero liquidity ``` ### Subscriber/Notification Pattern The `Notifier` base enables position subscribers — external contracts that receive callbacks when a position is modified. Subscribers implement `ISubscriber`: ```solidity interface ISubscriber { function notifySubscribe(uint256 tokenId, bytes memory data) external; function notifyUnsubscribe(uint256 tokenId) external; function notifyModifyLiquidity(uint256 tokenId, int256 liquidityChange, BalanceDelta feesAccrued) external; function notifyBurn(uint256 tokenId) external; } ``` Subscribe via `positionManager.subscribe(tokenId, subscriber, data)`. The subscriber is notified on every liquidity modification or burn. ## Production Deployment Addresses ### Ethereum Mainnet (Chain ID: 1) | Contract | Address | |----------|---------| | PoolManager | `0x000000000004444c5dc75cB358380D2e3dE08A90` | | Universal Router | `0x66a9893cC07D91D95644AEDD05D03f95e1dBA8Af` | | PositionManager | `0xbD216513d74C8cf14cf4747E6AaA6420FF64ee9e` |
Auf GitHub ansehen
Diese SKILL.md ist sehr gross, daher zeigt SkillsMP hier nur den ersten Abschnitt. Auf GitHub ansehen