Agent skill

Uniswap V3 Expert

by ccashwell in ccashwell/evm-cortex

A skill your agent uses when building on, integrating with, or analyzing Uniswap V3.

MITAuto-check passedDevOps & Cloud

Install Uniswap V3 Expert

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

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

GitHub CLI
$ gh skill install ccashwell/evm-cortex uniswap-v3-expert --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-v3-expert .claude/skills/uniswap-v3-expert && 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-v3-expert
GitHub stars
131
Token cost
~9.6k tokens
SKILL.md length
1,723 words
Files
1
Skills in repo
89
Repo updated
First seen
Licence
MIT

At a glance

A skill your agent uses when building on, integrating with, or analyzing Uniswap V3.

  • Works in 4 steps: Oracle removal: V4 removed built-in… → Positions stay ERC-721: V4… → Callback → flash accounting: V3… → …
  • Integrating with
  • SKILL.md covers Architecture Overview, Concentrated Liquidity Model, Core Contract Functions and NonfungiblePositionManager, plus 3 more sections
  • Reaches developers.uniswap.org

What it does

Uniswap V3 Expert is an agent skill from ccashwell/evm-cortex. 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.

Its SKILL.md is about 9.6k 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 DevOps & Cloud. It works with Uniswap and Circle USDC. The repository describes itself as: Ethereum protocol engineering squad for AI coding assistants. The licence is MIT.

When your agent uses it

  • Integrating with
  • Analyzing Uniswap V3

Example prompts

  • “/uniswap-v3-expert”

Workflow steps

4 steps, taken from the first numbered list in SKILL.md.

  1. Oracle removal: V4 removed built-in oracles. If you depend on V3 TWAPs, either keep using V3 pools or implement an oracle hook in V4.
  2. Positions stay ERC-721: V4 PositionManager mints ERC-721 NFTs ("Uniswap v4 Positions NFT") but is driven by batched Actions via…
  3. Callback → flash accounting: V3 callbacks that push tokens to the pool are replaced by V4's settle() and take() within an unlock() context.
  4. Fee tiers → dynamic: V3 fixed fee tiers become fully configurable via hooks in V4. Custom fee logic (volatility-based, time-based) is…

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 bash).

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

  • Network

    Hosts in commands or code, which the agent is likely to contact:

    • developers.uniswap.org

    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 V3 Expert loads about 9.6k tokens when it runs. Until then it costs about 73 tokens; SKILL.md has 1,723 words of instructions outside code blocks.

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

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,723 words, ~9,555 tokens.

Download SKILL.mdSave it as .claude/skills/uniswap-v3-expert/SKILL.md (or your agent's skills folder).
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 (legacy v1) — single and multi-hop exact-input / exact-output swaps; Ethereum, Arbitrum, Optimism, Polygon only
├── SwapRouter02 — v2+v3 unified router with multicall
├── UniversalRouter — command-based router supporting v2, v3, permits, NFTs
├── Quoter — offchain 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 SpacingUse Case
0.01%1001Stable pairs (USDC/USDT, DAI/USDC)
0.05%50010Stable pairs, correlated assets
0.30%300060Standard pairs (ETH/USDC, WBTC/ETH)
1.00%10000200Exotic 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).

Protocol fee is live on Ethereum mainnet. Since the UNIfication governance proposal executed on 2025-12-28, mainnet V3 pools carry a protocol fee: slot0().feeProtocol is 68 on the 0.01% and 0.05% tiers (protocol takes 1/4 of LP fees) and 102 on the 0.30% tier (1/6). Decode it as token0 = feeProtocol % 16, token1 = feeProtocol >> 4, protocol share = 1/N; the feeGrowthGlobal*X128 accumulators already accrue net of that share, so volume-based LP fee-revenue estimates must be multiplied by (1 - protocol_share).

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 (legacy v1)

The original SwapRouter (0xE592427A0AEce92De3Edee1F18E0157C05861564) is a legacy contract: it is deployed only on Ethereum, Arbitrum One, Optimism and Polygon and is absent from Base, Unichain, BNB, Avalanche and Celo (on Base and Unichain that address holds an unrelated contract). The official docs name UniversalRouter as the preferred swap entrypoint, with SwapRouter02 as the V3-native alternative; the interface below is kept for reference and for reading existing integrations.

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)

SwapRouter02 at 0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45 on Ethereum mainnet unifies Uniswap V2 and V3 swaps under one interface with multicall support. The deadline parameter is removed from individual swap structs — use the checkDeadline multicall wrapper instead.

Using the Quoter for Off-Chain Price Estimates

The Quoter simulates a swap and reverts internally to return the result. Never call it onchain (wastes gas with guaranteed revert).

solidity
interface IQuoterV2 {
    struct QuoteExactInputSingleParams {
        address tokenIn;
        address tokenOut;
        uint256 amountIn;
        uint24 fee;
        uint160 sqrtPriceLimitX96;
    }

    function quoteExactInputSingle(QuoteExactInputSingleParams memory params)
        external
        returns (
            uint256 amountOut,
            uint160 sqrtPriceX96After,
            uint32 initializedTicksCrossed,
            uint256 gasEstimate
        );
}

Use QuoterV2 over the original Quoter — it returns additional data (sqrtPriceX96After, initializedTicksCrossed, gasEstimate) useful for routing.

Oracle System

Built-In TWAP

Every V3 pool stores an array of (blockTimestamp, tickCumulative, secondsPerLiquidityCumulative) observations. The pool writes one observation per block in which a swap occurs.

Reading the Oracle
solidity
// Get the 30-minute TWAP tick
uint32[] memory secondsAgos = new uint32[](2);
secondsAgos[0] = 1800; // 30 minutes ago
secondsAgos[1] = 0;    // now

(int56[] memory tickCumulatives, ) = pool.observe(secondsAgos);

int24 twapTick = int24((tickCumulatives[1] - tickCumulatives[0]) / 1800);

// Convert tick to price
uint160 sqrtPriceX96 = TickMath.getSqrtRatioAtTick(twapTick);
Observation Cardinality
  • Default cardinality = 1 — only the latest observation is stored
  • Must call increaseObservationCardinalityNext() to enable longer TWAPs
  • Each observation costs ~20k gas to write (one per block on first swap)
  • To support a 1-hour TWAP at 12s blocks, need at least 300 observations
solidity
// Expand observation buffer to support 1-hour TWAP
pool.increaseObservationCardinalityNext(350); // some buffer
Oracle Security Considerations
  • Geometric mean TWAP is manipulation-resistant over multiple blocks
  • Single-block manipulation is limited by pool liquidity and block timing
  • Use longer TWAP windows (>= 30 minutes) for critical price feeds
  • V3 TWAPs are geometric (tick-based), not arithmetic — immune to flash loan manipulation within a single block
  • Always verify observation cardinality is sufficient before relying on TWAP
  • Combine TWAP with Chainlink for defense in depth

Callback Pattern

V3 uses a pull-based token collection model. The pool calls back into the caller to collect tokens owed.

Swap Callback
solidity
/// @notice Called by the pool after executing a swap
/// @dev Must pay the pool the tokens owed for the swap.
///      Positive delta = tokens owed TO the pool.
function uniswapV3SwapCallback(
    int256 amount0Delta,
    int256 amount1Delta,
    bytes calldata data
) external {
    // CRITICAL: Verify the caller is the expected pool
    require(msg.sender == address(pool), "unauthorized callback");

    // Decode any data passed through the swap
    address payer = abi.decode(data, (address));

    // Pay whichever token is owed (positive delta = owed to pool)
    if (amount0Delta > 0) {
        IERC20(pool.token0()).safeTransferFrom(payer, msg.sender, uint256(amount0Delta));
    }
    if (amount1Delta > 0) {
        IERC20(pool.token1()).safeTransferFrom(payer, msg.sender, uint256(amount1Delta));
    }
}
Mint Callback
solidity
/// @notice Called by the pool when minting liquidity
/// @dev Must pay the pool both token0 and token1 owed
function uniswapV3MintCallback(
    uint256 amount0Owed,
    uint256 amount1Owed,
    bytes calldata data
) external {
    require(msg.sender == address(pool), "unauthorized callback");
    address payer = abi.decode(data, (address));

    if (amount0Owed > 0) {
        IERC20(pool.token0()).safeTransferFrom(payer, msg.sender, amount0Owed);
    }
    if (amount1Owed > 0) {
        IERC20(pool.token1()).safeTransferFrom(payer, msg.sender, amount1Owed);
    }
}
Flash Callback
solidity
/// @notice Called by the pool after a flash loan
/// @dev Must repay the flash-loaned amount plus fees
function uniswapV3FlashCallback(
    uint256 fee0,
    uint256 fee1,
    bytes calldata data
) external {
    require(msg.sender == address(pool), "unauthorized callback");

    // Perform arbitrage or other operations here

    // Repay principal + fee
    if (fee0 > 0 || amount0 > 0) {
        IERC20(pool.token0()).safeTransfer(msg.sender, amount0 + fee0);
    }
    if (fee1 > 0 || amount1 > 0) {
        IERC20(pool.token1()).safeTransfer(msg.sender, amount1 + fee1);
    }
}

Callback security: Always verify msg.sender is the expected pool. Compute the expected pool address via CREATE2 rather than storing it, or validate against the factory:

solidity
function _verifyCallback(address tokenA, address tokenB, uint24 fee) internal view {
    address expected = IUniswapV3Factory(factory).getPool(tokenA, tokenB, fee);
    require(msg.sender == expected, "unauthorized callback");
}

Production Deployment Addresses

Ethereum Mainnet
ContractAddress
UniswapV3Factory0x1F98431c8aD98523631AE4a59f267346ea31F984
NonfungiblePositionManager0xC36442b4a4522E871399CD717aBDD847Ab11FE88
SwapRouter (legacy v1)0xE592427A0AEce92De3Edee1F18E0157C05861564
SwapRouter020x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45
Quoter0xb27308f9F90D607463bb33eA1BeBb41C27CE5AB6
QuoterV20x61fFE014bA17989E743c5F6cB21bF9697530B21e
UniversalRouter V20x66a9893cC07D91D95644AEDD05D03f95e1dBA8Af
UniversalRouter V2.1.10x4C82D1fBFe28C977cBB58D8C7FF8FCF9F70a2cCA
UniversalRouter V2.1.20x23617e59A5925b2A4Bf75d73ff6711cD0b29De85
TickLens0xbfd8137f7d1516D3ea5cA83523914859ec47F573
Cross-Chain Deployments

The original CREATE2 set (Factory 0x1F98…F984, NPM 0xC364…FE88, SwapRouter 0xE592…1564, SwapRouter02 0x68b3…Fc45, QuoterV2 0x61fF…B21e) is shared only by Ethereum, Arbitrum One, Optimism and Polygon (verified by cast code); every later chain has its own addresses:

ChainFactoryNPMSwapRouter02QuoterV2
Arbitrum0x1F98431c...F9840xC36442b4...FE880x68b34658...Fc450x61fFE014...B21e
Optimism0x1F98431c...F9840xC36442b4...FE880x68b34658...Fc450x61fFE014...B21e
Polygon0x1F98431c...F9840xC36442b4...FE880x68b34658...Fc450x61fFE014...B21e
Base0x33128a8fC17869897dcE68Ed026d694621f6FDfD0x03a520b32C04BF3bEEf7BEb72E919cf822Ed34f10x2626664c2603336E57B271c5C0b26F421741e4810x3d4e44Eb1374240CE5F1B871ab261CD16335B76a
Unichain0x1F984000000000000000000000000000000000030x943e6e07a7E8E791dAFC44083e54041D743C46E90x73855d06DE49d0fe4A9c42636Ba96c62da12FF9C0x385A5cf5F83e99f7BB2852b6A19C3538b9FA7658
BNB Chain0xdB1d10011AD0Ff90774D0C6Bb92e5C5c8b4461F70x7b8A01B39D58278b5DE7e48c8449c9f4F51706130xB971eF87ede563556b2ED4b1C0b0019111Dd85d20x78D78E420Da98ad378D7799bE8f4AF69033EB077
Avalanche0x740b1c1de25031C31FF4fC9A62f554A55cdC1baD0x655C406EBFa14EE2006250925e54ec43AD184f8B0xbb00FF08d01D300023C629E8fFfFcb65A5a578cE0xbe0F5544EC67e9B3b2D979aaA43f18Fd87E6257F
Celo0xAfE208a311B21f13EF87E33A90049fC17A7acDEc0x3d79EdAaBC0EaB6F08ED885C05Fc0B014290D95A0x5615CDAb10dc425a742d643d949a7F474C01abc40x82825d0554fA07f7FC52Ab63c961F330fdEFa8E8

CRITICAL: Addresses on Base, BNB, Avalanche, Celo, and newer chains differ from the canonical set. Always verify with cast code <address> --rpc-url <chain> or check the official per-chain deployment pages at https://developers.uniswap.org/docs/protocols/v3/deployments/<chain> (the former docs.uniswap.org URLs redirect there) before integrating.

Foundry Setup

Install Dependencies
bash
forge install Uniswap/v3-core
forge install Uniswap/v3-periphery
forge install OpenZeppelin/openzeppelin-contracts
Remappings (foundry.toml or remappings.txt)
@uniswap/v3-core/=lib/v3-core/
@uniswap/v3-periphery/=lib/v3-periphery/
@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/
Fork Testing Against Mainnet Pools

This test targets the legacy SwapRouter v1, so it only works on Ethereum, Arbitrum One, Optimism and Polygon forks. To target SwapRouter02 instead (0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45 on Ethereum; per-chain addresses in the table above), use IV3SwapRouter from Uniswap/swap-router-contracts — its ExactInputSingleParams has no deadline field, so drop that line and wrap the call in multicall(uint256 deadline, bytes[] data) if you need a deadline.

solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import {Test} from "forge-std/Test.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {ISwapRouter} from "@uniswap/v3-periphery/contracts/interfaces/ISwapRouter.sol";

contract UniswapV3ForkTest is Test {
    ISwapRouter constant ROUTER = ISwapRouter(0xE592427A0AEce92De3Edee1F18E0157C05861564);
    address constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
    address constant USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48;

    function setUp() public {
        vm.createSelectFork("mainnet");
    }

    function test_exactInputSingle() public {
        uint256 amountIn = 1 ether;
        deal(WETH, address(this), amountIn);
        IERC20(WETH).approve(address(ROUTER), amountIn);

        uint256 amountOut = ROUTER.exactInputSingle(
            ISwapRouter.ExactInputSingleParams({
                tokenIn: WETH,
                tokenOut: USDC,
                fee: 3000,
                recipient: address(this),
                deadline: block.timestamp,
                amountIn: amountIn,
                amountOutMinimum: 0,
                sqrtPriceLimitX96: 0
            })
        );

        assertGt(amountOut, 0, "swap returned zero");
        assertGt(IERC20(USDC).balanceOf(address(this)), 0, "no USDC received");
    }
}

Run with:

bash
forge test --fork-url $ETH_RPC_URL --match-contract UniswapV3ForkTest -vvv

Common V3 Patterns

Direct Pool Swap (No Router)

For maximum gas efficiency or custom routing, call the pool directly:

solidity
// Swap 1 WETH for USDC via the WETH/USDC 0.3% pool
IUniswapV3Pool pool = IUniswapV3Pool(factory.getPool(WETH, USDC, 3000));

// zeroForOne = true means token0 → token1
// amountSpecified > 0 means exact input
(int256 amount0, int256 amount1) = pool.swap(
    recipient,
    true,                              // zeroForOne
    int256(1 ether),                   // exact input
    TickMath.MIN_SQRT_RATIO + 1,       // price limit (min for zeroForOne)
    abi.encode(msg.sender)             // callback data
);
Show full SKILL.md (683 more words)Show less
Price Limit Constants
solidity
// For zeroForOne swaps (token0 → token1), price decreases
uint160 sqrtPriceLimitX96 = TickMath.MIN_SQRT_RATIO + 1;

// For oneForZero swaps (token1 → token0), price increases
uint160 sqrtPriceLimitX96 = TickMath.MAX_SQRT_RATIO - 1;
Computing Position Fees Offchain
solidity
// Read position from NPM
(, , , , , int24 tickLower, int24 tickUpper, uint128 liquidity,
 uint256 feeGrowthInside0Last, uint256 feeGrowthInside1Last,
 uint128 tokensOwed0, uint128 tokensOwed1) = npm.positions(tokenId);

// Read current fee growth from pool
(uint256 feeGrowthGlobal0, uint256 feeGrowthGlobal1) =
    (pool.feeGrowthGlobal0X128(), pool.feeGrowthGlobal1X128());

// Calculate uncollected fees (simplified — needs tick fee growth subtraction)
uint256 fees0 = tokensOwed0 + (feeGrowthInside0Current - feeGrowthInside0Last) * liquidity / (1 << 128);
uint256 fees1 = tokensOwed1 + (feeGrowthInside1Current - feeGrowthInside1Last) * liquidity / (1 << 128);
Just-In-Time (JIT) Liquidity Pattern

JIT liquidity is a MEV strategy where a searcher mints a tight range position just before a large swap, earns concentrated fees, then removes the position in the same or next block:

Block N:
1. Observe pending large swap in mempool
2. Mint concentrated position around current tick
3. Large swap executes — LP earns concentrated fees
4. Remove liquidity and collect fees

This is profitable because concentrated liquidity in a tiny range captures nearly all fees from the swap. However, it requires mempool access and priority gas auctions.

V3 vs V4 Migration Notes

AspectUniswap V3Uniswap V4
Pool architectureOne contract per poolSingleton PoolManager
Pool creationfactory.createPool()poolManager.initialize()
Token transfersCallbacks (pull pattern)Flash accounting with settle() / take()
LP positionsNonfungiblePositionManager (ERC-721)PositionManager (ERC-721, Permit2, action-batched)
Swap routingSwapRouter / SwapRouter02UniversalRouter or custom routers
ExtensibilityNoneHooks at every lifecycle point
Fee modelFixed fee tiersDynamic fees via hooks
Flash loanspool.flash()Free via flash accounting (take + settle within unlock)
Pool IDContract addresskeccak256(abi.encode(PoolKey))
Gas (single swap)~130k-180k~100k-140k (singleton savings)
OracleBuilt-in observation arrayRemoved from core — implement via hooks
Key Migration Considerations
  1. Oracle removal: V4 removed built-in oracles. If you depend on V3 TWAPs, either keep using V3 pools or implement an oracle hook in V4.
  2. Positions stay ERC-721: V4 PositionManager mints ERC-721 NFTs ("Uniswap v4 Positions NFT") but is driven by batched Actions via modifyLiquidities; ERC-6909 in V4 is the PoolManager's claim-token standard (mint/burn), not LP positions.
  3. Callback → flash accounting: V3 callbacks that push tokens to the pool are replaced by V4's settle() and take() within an unlock() context.
  4. Fee tiers → dynamic: V3 fixed fee tiers become fully configurable via hooks in V4. Custom fee logic (volatility-based, time-based) is possible.

Security Considerations

Slippage Protection

Always set meaningful amountOutMinimum (exact input) or amountInMaximum (exact output). Using 0 invites sandwich attacks.

solidity
// Calculate minimum output with 0.5% slippage tolerance
uint256 amountOutMin = (expectedAmountOut * 995) / 1000;
Deadline Protection

Always use a reasonable deadline parameter. Setting block.timestamp is useless onchain (always passes). Use block.timestamp + 300 (5 minutes) or pass a user-specified deadline.

Callback Verification

Unverified callbacks are the most common V3 integration vulnerability. Always verify:

solidity
// Option A: Check against factory
require(msg.sender == IUniswapV3Factory(FACTORY).getPool(token0, token1, fee));

// Option B: Recompute CREATE2 address (saves an external call)
address expected = address(uint160(uint256(keccak256(abi.encodePacked(
    hex"ff",
    FACTORY,
    keccak256(abi.encode(token0, token1, fee)),
    POOL_INIT_CODE_HASH
)))));
require(msg.sender == expected);
Price Manipulation Resistance
  • Never use slot0().sqrtPriceX96 as a price oracle — it reflects the instantaneous spot price and is trivially manipulable within a transaction
  • Use observe() for TWAP over multiple blocks
  • For critical operations, combine V3 TWAP with Chainlink as a secondary oracle
  • Flash loans cannot manipulate the geometric mean TWAP within a single block
Reentrancy

V3 pools have a slot0.unlocked mutex that prevents reentrancy into the pool during swaps and mints. However, your own contracts receiving callbacks should still use nonReentrant guards for defense in depth.

Integration Checklist

Swaps
  • Using SafeERC20 for all token transfers
  • amountOutMinimum / amountInMaximum set to meaningful values (never 0 in production)
  • Deadline is user-supplied or uses a reasonable future timestamp (not block.timestamp)
  • sqrtPriceLimitX96 set correctly for swap direction or 0 for no limit
  • Callback verifies msg.sender is the expected pool via CREATE2 or factory lookup
  • Multi-hop path encoding is correct (packed token, fee, token, ...)
  • For exactOutput, path is reversed (output token first)
  • Unused input tokens are refunded to the user on exact output swaps
Liquidity Provision
  • Token pair is correctly ordered (token0 < token1)
  • Tick bounds are multiples of the pool's tickSpacing
  • amount0Min / amount1Min protect against sandwich attacks during mint
  • Fee collection calls decreaseLiquidity(0) or equivalent to update accounting
  • Position is fully withdrawn (decreaseLiquidity + collect) before calling burn
  • Handling the case where a position goes fully out of range (100% one token)
Oracle Usage
  • Observation cardinality increased before relying on TWAP
  • TWAP window is >= 30 minutes for security-critical price feeds
  • Never using slot0().sqrtPriceX96 as a price oracle
  • Handling OLD observation errors (requested time exceeds oldest observation)
  • TWAP combined with secondary oracle for critical operations
Flash Loans
  • Flash callback verifies msg.sender is the pool
  • Repayment amount includes the pool fee (amount + fee)
  • Flash loan is profitable after fees, gas, and all swap costs
  • Access control prevents unauthorized triggering
General
  • All addresses verified on the target chain (not assumed from mainnet)
  • POOL_INIT_CODE_HASH matches the V3 factory on the target chain
  • Token decimal differences handled correctly (USDC = 6, WBTC = 8, most = 18)
  • Fee-on-transfer tokens use balance-before/after pattern if supported
  • Tested on a mainnet fork with forge test --fork-url
  • Gas profiled with forge test --gas-report

© 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-v3-expert of ccashwell/evm-cortex.

Open the folder on GitHubat commit f8f3301

Compare with similar skills

Uniswap V3 Expert 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 V3 Expert compared with similar skills
SkillStarsUsed inTokensAuto-checkLicenceRepo updated
Uniswap V3 Expert this skillccashwell/evm-cortex131—~9.6kAutomated safety check: PassMIT
Uniswap V3 Swapkeypo-us/keypo-cli182—~3.2kAutomated safety check: PassMIT
Okx Dapp Discoveryinternet-court/internet-court-skill6.6k1 repos~6.9kAutomated safety check: PassMIT
Trading Evmalsk1992/CloddsBot3k—~1.9kAutomated safety check: PassMIT
Cortx Reliabilityaeonfun/aeon770—~1.1kAutomated safety check: PassMIT
Okx Dapp Discoveryaiskillstore/marketplace4331 repos~5.6kAutomated safety check: PassMIT

Similar skills

  • Uniswap V3 Swap

    keypo-us/keypo-cli

    A skill your agent uses when the user wants to swap tokens on Base Sepolia (or Base mainnet) using Uniswap V3.

    182 GitHub stars~3.2k tokensUpdated 6 mo ago
    Backend & APIsAuto-check passed
  • Okx Dapp Discovery

    internet-court/internet-court-skill

    Plugin router for 20 third-party DeFi protocols (Polymarket, Aave, Hyperliquid, PancakeSwap, Morpho, Raydium, Curve, Compound, Pendle, Lido, ether.fi, GMX, Kamino, Orca, Meteora, Clanker, pump.fun…

    6.6k GitHub starsUsed in 1 repo~6.9k tokens
    Backend & APIsAuto-check passed
  • Trading Evm

    alsk1992/CloddsBot

    Trade tokens on EVM chains - Uniswap V3, 1inch on Ethereum, Arbitrum, Optimism, Base, Polygon

    3k GitHub stars~1.9k tokensUpdated 8 days ago
    Backend & APIsAuto-check passed
  • Cortx Reliability

    aeonfun/aeon

    Check whether an x402 payment endpoint is reliably delivering value before spending USDC on it.

    770 GitHub stars~1.1k tokensUpdated yesterday
    DevOps & CloudAuto-check passed
  • Okx Dapp Discovery

    aiskillstore/marketplace

    Plugin router for 20 third-party DeFi protocols (Polymarket, Aave, Hyperliquid, PancakeSwap, Morpho, Raydium, Curve, Compound, Pendle, Lido, ether.fi, GMX, Kamino, Orca, Meteora, Clanker, pump.fun…

    433 GitHub starsUsed in 1 repo~5.6k tokens
    Backend & APIsAuto-check passed
  • Addresses

    austintgriffith/ethskills

    Verified contract addresses for major Ethereum protocols across mainnet and L2s.

    295 GitHub stars~7.1k tokensUpdated 1 mo ago
    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 11 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 11 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 11 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 11 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 11 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 11 days ago
    Auto-check passed

Categories

Questions about Uniswap V3 Expert

What does Uniswap V3 Expert do?

A skill your agent uses when building on, integrating with, or analyzing Uniswap V3. Uniswap V3 Expert is an agent skill from ccashwell/evm-cortex. Use when building on, integrating with, or analyzing Uniswap V3.

When should I use Uniswap V3 Expert?

Uniswap V3 Expert fits situations like: integrating with; analyzing Uniswap V3.

How do I install Uniswap V3 Expert in Claude Code?

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

How do I install Uniswap V3 Expert in Codex?

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

Can I use Uniswap V3 Expert 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-v3-expert -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-v3-expert, .gemini/skills/uniswap-v3-expert, .github/skills/uniswap-v3-expert and .opencode/skills/uniswap-v3-expert in your project.

What does Uniswap V3 Expert need to run?

SKILL.md names no scripts, command-line tools or credentials: Uniswap V3 Expert is instructions for the agent only.

Does Uniswap V3 Expert access the network?

SKILL.md names 1 domain. In commands or code: developers.uniswap.org; the agent is likely to contact it when it follows the instructions. This is read from the text; nothing was executed.

Is Uniswap V3 Expert 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 V3 Expert use?

Uniswap V3 Expert 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 V3 Expert use?

About 9.6k tokens (SKILL.md is roughly 38k 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 V3 Expert?

Skills that share tags, products or a category with Uniswap V3 Expert: Uniswap V3 Swap (keypo-us/keypo-cli, 182 stars), Okx Dapp Discovery (internet-court/internet-court-skill, 6.6k stars), Trading Evm (alsk1992/CloddsBot, 3k stars) and Cortx Reliability (aeonfun/aeon, 770 stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.

Who maintains Uniswap V3 Expert?

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.