Skip to main content

uniswap-v3-expert

Use when building on, integrating with, or analyzing Uniswap V3. Covers concentrated liquidity, tick-based pricing, UniswapV3Factory, UniswapV3Pool, NonfungiblePositionManager, SwapRouter, oracle observations, fee tiers, and production deployment addresses across all chains.

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-v3-expert
description
Use when building on, integrating with, or analyzing Uniswap V3. Covers concentrated liquidity, tick-based pricing, UniswapV3Factory, UniswapV3Pool, NonfungiblePositionManager, SwapRouter, oracle observations, fee tiers, and production deployment addresses across all chains.
# Uniswap V3 Expert ## Architecture Overview Uniswap V3 is a concentrated liquidity AMM where each (tokenA, tokenB, fee) triple gets its own `UniswapV3Pool` contract, deployed via `CREATE2` from a singleton `UniswapV3Factory`. LPs provide liquidity in discrete price ranges instead of across the full (0, infinity) curve, dramatically improving capital efficiency. ### Contract Hierarchy ``` UniswapV3Factory (singleton) ├── creates UniswapV3Pool contracts via CREATE2 (one per token pair + fee tier) │ ├── Core swap, mint, burn, collect, flash, observe logic │ └── Stores tick state, positions, observations, and protocol fees │ Periphery contracts (stateless routers / managers): ├── NonfungiblePositionManager — wraps LP positions as ERC-721 NFTs ├── SwapRouter — single and multi-hop exact-input / exact-output swaps ├── SwapRouter02 — v2+v3 unified router with multicall ├── UniversalRouter — command-based router supporting v2, v3, permits, NFTs ├── Quoter — off-chain swap simulation (reverts internally to return amounts) ├── QuoterV2 — returns sqrtPriceX96After, initializedTicksCrossed, gasEstimate └── TickLens — batch read initialized ticks for a pool ``` ### Token Ordering Uniswap V3 enforces `token0 < token1` (by address). The factory and pool reject misordered pairs. Always sort before calling factory or pool functions: ```solidity (address token0, address token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA); ``` ### Pool Address Derivation (CREATE2) Pool addresses are deterministic. You can compute them offchain without querying the factory: ```solidity address pool = address(uint160(uint256(keccak256(abi.encodePacked( hex"ff", factory, keccak256(abi.encode(token0, token1, fee)), POOL_INIT_CODE_HASH ))))); ``` The `POOL_INIT_CODE_HASH` for Uniswap V3 is: `0xe34f199b19b2b4f47f68442619d555527d244f78a3297ea89325f843f87b8b54` ## Concentrated Liquidity Model ### Ticks and Price Price is discretized into **ticks**. The price at tick `i` is: ``` P(i) = 1.0001^i ``` This gives ~1 basis point precision per tick. Ticks range from `MIN_TICK = -887272` to `MAX_TICK = 887272`. ### sqrtPriceX96 Uniswap V3 stores the square root of price as a Q64.96 fixed-point number: ``` sqrtPriceX96 = sqrt(token1 / token0) * 2^96 ``` Converting between tick and sqrtPriceX96: ```solidity import {TickMath} from "@uniswap/v3-core/contracts/libraries/TickMath.sol"; uint160 sqrtPriceX96 = TickMath.getSqrtRatioAtTick(tick); int24 tick = TickMath.getTickAtSqrtRatio(sqrtPriceX96); ``` Boundary values: - `TickMath.MIN_SQRT_RATIO = 4295128739` (tick -887272) - `TickMath.MAX_SQRT_RATIO = 1461446703485210103287273052203988822378723970342` (tick 887272) ### Fee Tiers and Tick Spacing | Fee (bps) | Fee (uint24) | Tick Spacing | Use Case | |-----------|-------------|-------------|----------| | 0.01% | 100 | 1 | Stable pairs (USDC/USDT, DAI/USDC) | | 0.05% | 500 | 10 | Stable pairs, correlated assets | | 0.30% | 3000 | 60 | Standard pairs (ETH/USDC, WBTC/ETH) | | 1.00% | 10000 | 200 | Exotic pairs, high volatility | Tick spacing means LPs can only place range boundaries at ticks divisible by the spacing. The 1 bps tier was added via governance (not in original deployment). ### Liquidity Math Within a single tick range, the V3 pool behaves like a constant-product AMM scaled by liquidity `L`: ``` x * y = L^2 (virtual reserves within the active range) ``` Real reserves required for a position between sqrtPriceA and sqrtPriceB with liquidity L: ``` amount0 = L * (1/sqrtPriceA - 1/sqrtPriceB) (when price < lower bound: all token0) amount1 = L * (sqrtPriceB - sqrtPriceA) (when price > upper bound: all token1) ``` When price is within the range, the position holds a mix of both tokens. ## Core Contract Functions ### UniswapV3Factory ```solidity interface IUniswapV3Factory { /// @notice Creates a pool for the given two tokens and fee /// @param tokenA One of the two tokens in the desired pool /// @param tokenB The other of the two tokens in the desired pool /// @param fee The desired fee for the pool (100, 500, 3000, or 10000) /// @return pool The address of the newly created pool function createPool( address tokenA, address tokenB, uint24 fee ) external returns (address pool); /// @notice Returns the pool address for a given pair of tokens and fee, or address(0) function getPool( address tokenA, address tokenB, uint24 fee ) external view returns (address pool); /// @notice Returns the tick spacing for a given fee amount function feeAmountTickSpacing(uint24 fee) external view returns (int24); /// @notice Returns the current protocol fee controller function owner() external view returns (address); /// @notice Enables a fee amount with the given tick spacing (governance only) function enableFeeAmount(uint24 fee, int24 tickSpacing) external; } ``` ### UniswapV3Pool ```solidity interface IUniswapV3Pool { /// @notice Sets the initial price for the pool. Can only be called once. /// @param sqrtPriceX96 The initial sqrt price as a Q64.96 value function initialize(uint160 sqrtPriceX96) external; /// @notice Adds liquidity for the given recipient/tickLower/tickUpper position /// @dev The caller of this method receives a callback (uniswapV3MintCallback) /// in which they must pay any token0 or token1 owed for the liquidity /// @param recipient The address for which the liquidity will be created /// @param tickLower The lower tick of the position /// @param tickUpper The upper tick of the position /// @param amount The amount of liquidity to mint /// @param data Any data to be passed through to the callback /// @return amount0 The amount of token0 that was paid to mint /// @return amount1 The amount of token1 that was paid to mint function mint( address recipient, int24 tickLower, int24 tickUpper, uint128 amount, bytes calldata data ) external returns (uint256 amount0, uint256 amount1); /// @notice Burns liquidity from the sender and accounts tokens owed /// @dev Does NOT transfer tokens — must call collect() afterward /// @param tickLower The lower tick of the position /// @param tickUpper The upper tick of the position /// @param amount The amount of liquidity to burn /// @return amount0 The amount of token0 owed to the position /// @return amount1 The amount of token1 owed to the position function burn( int24 tickLower, int24 tickUpper, uint128 amount ) external returns (uint256 amount0, uint256 amount1); /// @notice Collects tokens owed to a position /// @dev Must burn(0) first to update fee accounting if only collecting fees /// @param recipient The address which should receive the collected tokens /// @param tickLower The lower tick of the position /// @param tickUpper The upper tick of the position /// @param amount0Requested How much token0 should be withdrawn /// @param amount1Requested How much token1 should be withdrawn /// @return amount0 The amount of token0 collected /// @return amount1 The amount of token1 collected function collect( address recipient, int24 tickLower, int24 tickUpper, uint128 amount0Requested, uint128 amount1Requested ) external returns (uint128 amount0, uint128 amount1); /// @notice Swap token0 for token1, or token1 for token0 /// @param recipient The address to receive the output of the swap /// @param zeroForOne Direction: true = token0 → token1, false = token1 → token0 /// @param amountSpecified Positive = exact input, negative = exact output /// @param sqrtPriceLimitX96 Price limit — swap stops if crossed /// For zeroForOne: must be < current price and > MIN_SQRT_RATIO /// For oneForZero: must be > current price and < MAX_SQRT_RATIO /// @param data Callback data passed to uniswapV3SwapCallback /// @return amount0 Delta of token0 balance of the pool (positive = pool received) /// @return amount1 Delta of token1 balance of the pool function swap( address recipient, bool zeroForOne, int256 amountSpecified, uint160 sqrtPriceLimitX96, bytes calldata data ) external returns (int256 amount0, int256 amount1); /// @notice Flash loans both tokens /// @param recipient The address which will receive the token0 and token1 amounts /// @param amount0 The amount of token0 to flash /// @param amount1 The amount of token1 to flash /// @param data Callback data passed to uniswapV3FlashCallback function flash( address recipient, uint256 amount0, uint256 amount1, bytes calldata data ) external; /// @notice Returns cumulative tick and liquidity values at given seconds ago /// @param secondsAgos Array of seconds ago from current block timestamp /// @return tickCumulatives Cumulative tick values at each secondsAgo /// @return secondsPerLiquidityCumulativeX128s Cumulative seconds per liquidity function observe( uint32[] calldata secondsAgos ) external view returns ( int56[] memory tickCumulatives, uint160[] memory secondsPerLiquidityCumulativeX128s ); /// @notice Grows the observation buffer to support longer TWAPs /// @param observationCardinalityNext Minimum number of observations to store function increaseObservationCardinalityNext( uint16 observationCardinalityNext ) external; // --- State view functions --- function slot0() external view returns ( uint160 sqrtPriceX96, int24 tick, uint16 observationIndex, uint16 observationCardinality, uint16 observationCardinalityNext, uint8 feeProtocol, bool unlocked ); function liquidity() external view returns (uint128); function fee() external view returns (uint24); function token0() external view returns (address); function token1() external view returns (address); function tickSpacing() external view returns (int24); function maxLiquidityPerTick() external view returns (uint128); function ticks(int24 tick) external view returns ( uint128 liquidityGross, int128 liquidityNet, uint256 feeGrowthOutside0X128, uint256 feeGrowthOutside1X128, int56 tickCumulativeOutside, uint160 secondsPerLiquidityOutsideX128, uint32 secondsOutside, bool initialized ); function positions(bytes32 key) external view returns ( uint128 _liquidity, uint256 feeGrowthInside0LastX128, uint256 feeGrowthInside1LastX128, uint128 tokensOwed0, uint128 tokensOwed1 ); } ``` Position keys for the core pool contract use `keccak256(abi.encodePacked(owner, tickLower, tickUpper))`. ## NonfungiblePositionManager The `NonfungiblePositionManager` (NPM) wraps core pool positions as ERC-721 NFTs. Most LPs interact with V3 through the NPM rather than calling the pool directly. ```solidity interface INonfungiblePositionManager { struct MintParams { address token0; address token1; uint24 fee; int24 tickLower; int24 tickUpper; uint256 amount0Desired; uint256 amount1Desired; uint256 amount0Min; uint256 amount1Min; address recipient; uint256 deadline; } /// @notice Creates a new position wrapped in an NFT /// @return tokenId The ID of the minted NFT /// @return liquidity The amount of liquidity for this position /// @return amount0 The amount of token0 deposited /// @return amount1 The amount of token1 deposited function mint(MintParams calldata params) external payable returns (uint256 tokenId, uint128 liquidity, uint256 amount0, uint256 amount1); struct IncreaseLiquidityParams { uint256 tokenId; uint256 amount0Desired; uint256 amount1Desired; uint256 amount0Min; uint256 amount1Min; uint256 deadline; } function increaseLiquidity(IncreaseLiquidityParams calldata params) external payable returns (uint128 liquidity, uint256 amount0, uint256 amount1); struct DecreaseLiquidityParams { uint256 tokenId; uint128 liquidity; uint256 amount0Min; uint256 amount1Min; uint256 deadline; } function decreaseLiquidity(DecreaseLiquidityParams calldata params) external payable returns (uint256 amount0, uint256 amount1); struct CollectParams { uint256 tokenId; address recipient; uint128 amount0Max; uint128 amount1Max; } /// @notice Collects fees and principal owed to a position function collect(CollectParams calldata params) external payable returns (uint256 amount0, uint256 amount1); /// @notice Burns a token ID. Position must have 0 liquidity and 0 tokens owed. function burn(uint256 tokenId) external payable; /// @notice Returns the position data for a given token ID function positions(uint256 tokenId) external view returns ( uint96 nonce, address operator, address token0, address token1, uint24 fee, int24 tickLower, int24 tickUpper, uint128 liquidity, uint256 feeGrowthInside0LastX128, uint256 feeGrowthInside1LastX128, uint128 tokensOwed0, uint128 tokensOwed1 ); } ``` ### Collecting Fees To collect accrued trading fees without removing liquidity, call `decreaseLiquidity` with `liquidity = 0` (or simply use `collect` after calling `burn(0)` on the core pool). Through the NPM: ```solidity // Trigger fee accounting update via zero-amount decrease positionManager.decreaseLiquidity(INonfungiblePositionManager.DecreaseLiquidityParams({ tokenId: tokenId, liquidity: 0, amount0Min: 0, amount1Min: 0, deadline: block.timestamp })); // Collect all owed tokens (fees + any burned principal) positionManager.collect(INonfungiblePositionManager.CollectParams({ tokenId: tokenId, recipient: msg.sender, amount0Max: type(uint128).max, amount1Max: type(uint128).max })); ``` ### Full Position Lifecycle ```solidity // 1. Approve tokens to NPM IERC20(token0).approve(address(positionManager), amount0); IERC20(token1).approve(address(positionManager), amount1); // 2. Mint position (uint256 tokenId, uint128 liquidity, uint256 used0, uint256 used1) = positionManager.mint(INonfungiblePositionManager.MintParams({ token0: token0, token1: token1, fee: 3000, tickLower: -60, tickUpper: 60, amount0Desired: amount0, amount1Desired: amount1, amount0Min: 0, amount1Min: 0, recipient: msg.sender, deadline: block.timestamp })); // 3. Collect fees (anytime) positionManager.collect(INonfungiblePositionManager.CollectParams({ tokenId: tokenId, recipient: msg.sender, amount0Max: type(uint128).max, amount1Max: type(uint128).max })); // 4. Remove liquidity positionManager.decreaseLiquidity(INonfungiblePositionManager.DecreaseLiquidityParams({ tokenId: tokenId, liquidity: liquidity, amount0Min: 0, amount1Min: 0, deadline: block.timestamp })); // 5. Collect principal + remaining fees positionManager.collect(INonfungiblePositionManager.CollectParams({ tokenId: tokenId, recipient: msg.sender, amount0Max: type(uint128).max, amount1Max: type(uint128).max })); // 6. Burn NFT (optional, position must have 0 liquidity and 0 owed) positionManager.burn(tokenId); ``` ## SwapRouter Integration ### SwapRouter (original) ```solidity interface ISwapRouter { struct ExactInputSingleParams { address tokenIn; address tokenOut; uint24 fee; address recipient; uint256 deadline; uint256 amountIn; uint256 amountOutMinimum; uint160 sqrtPriceLimitX96; // 0 for no limit } /// @notice Swaps amountIn of one token for as much as possible of another token function exactInputSingle(ExactInputSingleParams calldata params) external payable returns (uint256 amountOut); struct ExactInputParams { bytes path; // abi.encodePacked(tokenIn, fee, ..., tokenOut) address recipient; uint256 deadline; uint256 amountIn; uint256 amountOutMinimum; } /// @notice Swaps along the specified multi-hop path function exactInput(ExactInputParams calldata params) external payable returns (uint256 amountOut); struct ExactOutputSingleParams { address tokenIn; address tokenOut; uint24 fee; address recipient; uint256 deadline; uint256 amountOut; uint256 amountInMaximum; uint160 sqrtPriceLimitX96; } /// @notice Swaps as little as possible of one token for amountOut of another function exactOutputSingle(ExactOutputSingleParams calldata params) external payable returns (uint256 amountIn); struct ExactOutputParams { bytes path; // abi.encodePacked(tokenOut, fee, ..., tokenIn) — REVERSED address recipient; uint256 deadline; uint256 amountOut; uint256 amountInMaximum; } /// @notice Swaps along a reversed path to get exact output amount function exactOutput(ExactOutputParams calldata params) external payable returns (uint256 amountIn); } ``` ### Multi-Hop Path Encoding Paths are tightly packed sequences of `(token, fee, token, fee, ..., token)`: ```solidity // Single hop: WETH → 0.3% → USDC bytes memory path = abi.encodePacked(WETH, uint24(3000), USDC); // Multi-hop: WETH → 0.3% → USDC → 0.01% → DAI bytes memory path = abi.encodePacked(WETH, uint24(3000), USDC, uint24(100), DAI); ``` For `exactOutput`, the path is **reversed** (starts with output token): ```solidity // Exact output multi-hop: want DAI, pay WETH // Path is: DAI → 0.01% → USDC → 0.3% → WETH (reversed order) bytes memory path = abi.encodePacked(DAI, uint24(100), USDC, uint24(3000), WETH); ``` ### SwapRouter02 (Unified V2+V3 Router)
Auf GitHub ansehen
Diese SKILL.md ist sehr gross, daher zeigt SkillsMP hier nur den ersten Abschnitt. Auf GitHub ansehen