Skip to main content

pool-finder

Use when discovering Uniswap pools, querying pool state, analyzing TVL/volume, finding optimal swap routes, or inspecting pool parameters. Covers V3 Factory queries, V4 PoolManager state, subgraph queries, and cast/forge inspection commands.

Jump to install

Source facts

Repository
ccashwell/evm-cortex
Last source activity
April 10, 2026 at 16:31
Detected SKILL.md language
English
Stars
131
Forks
18

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
pool-finder
description
Use when discovering Uniswap pools, querying pool state, analyzing TVL/volume, finding optimal swap routes, or inspecting pool parameters. Covers V3 Factory queries, V4 PoolManager state, subgraph queries, and cast/forge inspection commands.
# Uniswap Pool Discovery & Analysis ## Key Addresses (Ethereum Mainnet) | Contract | Address | |----------|---------| | V3 Factory | `0x1F98431c8aD98523631AE4a59f267346ea31F984` | | V3 QuoterV2 | `0x61fFE014bA17989E743c5F6cB21bF9697530B21e` | | V3 SwapRouter02 | `0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45` | | V3 NonfungiblePositionManager | `0xC36442b4a4522E871399CD717aBDD847Ab11FE88` | | V4 PoolManager | `0x000000000004444c5dc75cb358380d2e3de08a90` | ## V3 Pool Discovery ### Factory Query ```solidity import {IUniswapV3Factory} from "@uniswap/v3-core/contracts/interfaces/IUniswapV3Factory.sol"; IUniswapV3Factory factory = IUniswapV3Factory(0x1F98431c8aD98523631AE4a59f267346ea31F984); // Fee tiers and their tick spacings: // 100 (0.01%) — tickSpacing 1 — stablecoin pairs // 500 (0.05%) — tickSpacing 10 — correlated pairs, high volume // 3000 (0.30%) — tickSpacing 60 — standard pairs // 10000 (1.00%) — tickSpacing 200 — exotic / low-volume pairs address pool = factory.getPool(tokenA, tokenB, fee); ``` ### Using cast ```bash # Find ETH/USDC 0.3% pool on Ethereum mainnet cast call 0x1F98431c8aD98523631AE4a59f267346ea31F984 \ "getPool(address,address,uint24)(address)" \ 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 \ 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 \ 3000 # Read pool slot0 — returns (sqrtPriceX96, tick, observationIndex, observationCardinality, # observationCardinalityNext, feeProtocol, unlocked) cast call <pool_address> "slot0()(uint160,int24,uint16,uint16,uint16,uint8,bool)" # Active liquidity in the current tick range cast call <pool_address> "liquidity()(uint128)" # Pool configuration cast call <pool_address> "fee()(uint24)" cast call <pool_address> "tickSpacing()(int24)" cast call <pool_address> "token0()(address)" cast call <pool_address> "token1()(address)" # Oracle observation buffer size cast call <pool_address> "slot0()(uint160,int24,uint16,uint16,uint16,uint8,bool)" | \ awk '{print "observationCardinality:", $4}' ``` ### Scanning All Fee Tiers ```bash FACTORY=0x1F98431c8aD98523631AE4a59f267346ea31F984 TOKEN_A=0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 # WETH TOKEN_B=0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 # USDC for FEE in 100 500 3000 10000; do POOL=$(cast call $FACTORY "getPool(address,address,uint24)(address)" $TOKEN_A $TOKEN_B $FEE) if [ "$POOL" != "0x0000000000000000000000000000000000000000" ]; then LIQ=$(cast call $POOL "liquidity()(uint128)") echo "Fee: $FEE Pool: $POOL Liquidity: $LIQ" fi done ``` ## V3 High-Volume Pools (Ethereum Mainnet) ``` ETH/USDC 0.05%: 0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640 ETH/USDC 0.30%: 0x8ad599c3A0ff1De082011EFDDc58f1908eb6e6D8 ETH/USDT 0.05%: 0x11b815efB8f581194ae79006d24E0d814B7697F6 ETH/USDT 0.30%: 0x4e68Ccd3E89f51C3074ca5072bbAC773960dFa36 WBTC/ETH 0.30%: 0xCBCdF9626bC03E24f779434178A73a0B4bad62eD WBTC/ETH 0.05%: 0x4585FE77225b41b697C938B018E2Ac67Ac5a20c0 USDC/USDT 0.01%: 0x3416cF6C708Da44DB2624D63ea0AAef7113527C6 DAI/USDC 0.01%: 0x5777d92f208679DB4b9778590Fa3CAB3aC9e2168 DAI/USDC 0.05%: 0x6c6Bc977E13Df9b0de53b251522280BB72383700 ``` ## V4 Pool Discovery V4 pools do not have individual addresses. All pool state lives inside the singleton PoolManager. A pool is identified by its `PoolKey`, hashed into a `PoolId`. ### PoolKey Construction ```solidity import {PoolKey} from "v4-core/src/types/PoolKey.sol"; import {PoolId, PoolIdLibrary} from "v4-core/src/types/PoolId.sol"; import {Currency} from "v4-core/src/types/Currency.sol"; import {IHooks} from "v4-core/src/interfaces/IHooks.sol"; using PoolIdLibrary for PoolKey; // currency0 MUST be numerically less than currency1 PoolKey memory key = PoolKey({ currency0: Currency.wrap(token0), currency1: Currency.wrap(token1), fee: 3000, // or LPFeeLibrary.DYNAMIC_FEE_FLAG for hook-managed fees tickSpacing: 60, hooks: IHooks(hookAddress) // address(0) for vanilla pool }); PoolId id = key.toId(); // keccak256(abi.encode(key)) ``` ### Currency Ordering V4 enforces `currency0 < currency1`. Native ETH is represented as `Currency.wrap(address(0))`, which sorts below any ERC-20 address. For two ERC-20 tokens, the one with the numerically smaller address is `currency0`. ```solidity function orderCurrencies(address a, address b) pure returns (Currency c0, Currency c1) { (c0, c1) = a < b ? (Currency.wrap(a), Currency.wrap(b)) : (Currency.wrap(b), Currency.wrap(a)); } ``` ### Reading V4 Pool State with cast ```bash POOL_MANAGER=0x000000000004444c5dc75cb358380d2e3de08a90 # Get slot0: (sqrtPriceX96, tick, protocolFee, lpFee) cast call $POOL_MANAGER \ "getSlot0(bytes32)(uint160,int24,uint24,uint24)" \ <pool_id> # Get active liquidity cast call $POOL_MANAGER \ "getLiquidity(bytes32)(uint128)" \ <pool_id> # Get liquidity at a specific tick cast call $POOL_MANAGER \ "getTickLiquidity(bytes32,int24)(uint128,int128)" \ <pool_id> <tick> # Get tick bitmap word cast call $POOL_MANAGER \ "getTickBitmap(bytes32,int16)(uint256)" \ <pool_id> <word_position> ``` ### Computing a PoolId Offchain ```bash # Encode the PoolKey struct and hash it cast keccak $(cast abi-encode \ "(address,address,uint24,int24,address)" \ <currency0> <currency1> <fee> <tickSpacing> <hooks>) ``` ## Subgraph Queries ### V3 Subgraph Endpoints | Network | Subgraph ID | |---------|-------------| | Ethereum | `5zvR82QoaXYFyDEKLZ9t6v9adgnptxYpKpSbxtgVENFV` | | Base | `43Hwfi3dJSoGpyas9VwNoDAv55yjgGrPpNSmbQZArzMG` | | Arbitrum | `FbCGRftH4a3yZugY7TnbYgPJVEv2LvMT6oF1fxPe9aJM` | | Optimism | `Cghf4LfVqPiFw6fp6Y5X5Ubc8UpmUhSfJL82zwiBFLaj` | | Polygon | `3hCPRGf4z88VC5rsBKU5AA9FBBq5nF3jbKJG7VZCbhjm` | Endpoint format: `https://gateway.thegraph.com/api/[api-key]/subgraphs/id/<subgraph-id>` ### Top Pools by TVL ```graphql { pools(first: 10, orderBy: totalValueLockedUSD, orderDirection: desc) { id token0 { symbol decimals id } token1 { symbol decimals id } feeTier liquidity sqrtPrice tick totalValueLockedUSD volumeUSD txCount } } ``` ### Find Pools for a Specific Token ```graphql { pools( where: { or: [ { token0: "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48" } { token1: "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48" } ] } orderBy: totalValueLockedUSD orderDirection: desc first: 5 ) { id token0 { symbol } token1 { symbol } feeTier totalValueLockedUSD volumeUSD } } ``` ### Tick-Level Liquidity Distribution ```graphql { ticks( where: { pool: "<pool_id>" } first: 1000 orderBy: tickIdx ) { tickIdx liquidityNet liquidityGross } } ``` ### Active Positions ```graphql { positions( where: { pool: "<pool_id>", liquidity_gt: "0" } first: 100 orderBy: liquidity orderDirection: desc ) { id owner tickLower { tickIdx } tickUpper { tickIdx } liquidity depositedToken0 depositedToken1 collectedFeesToken0 collectedFeesToken1 } } ``` ### Historical Volume (Daily Snapshots) ```graphql { poolDayDatas( where: { pool: "<pool_id>" } orderBy: date orderDirection: desc first: 30 ) { date volumeUSD tvlUSD feesUSD liquidity sqrtPrice tick open high low close } } ``` ## Optimal Route Finding ### Comparing Fee Tiers Always query all fee tiers for a pair. The deepest liquidity at the current tick determines the best execution: ```solidity import {IQuoterV2} from "@uniswap/v3-periphery/contracts/interfaces/IQuoterV2.sol"; IQuoterV2 quoter = IQuoterV2(0x61fFE014bA17989E743c5F6cB21bF9697530B21e); uint24[4] memory fees = [uint24(100), 500, 3000, 10000]; uint256 bestOut; uint24 bestFee; for (uint256 i = 0; i < fees.length; i++) { try quoter.quoteExactInputSingle( IQuoterV2.QuoteExactInputSingleParams({ tokenIn: WETH, tokenOut: USDC, amountIn: 1 ether, fee: fees[i], sqrtPriceLimitX96: 0 }) ) returns (uint256 amountOut, uint160, uint32, uint256) { if (amountOut > bestOut) { bestOut = amountOut; bestFee = fees[i]; } } catch {} } ``` ### Multi-Hop Routing Direct swaps are not always optimal. Common intermediate hops: ``` ETH → WBTC may route as ETH → USDC → WBTC LINK → UNI may route as LINK → ETH → UNI Low-cap → Low-cap almost always routes through ETH or USDC ``` ```solidity // Multi-hop quote: tokenA -> WETH -> tokenB bytes memory path = abi.encodePacked( tokenA, uint24(3000), WETH, uint24(500), tokenB ); (uint256 amountOut, uint160[] memory sqrtPriceX96AfterList, uint32[] memory initializedTicksCrossedList, uint256 gasEstimate) = quoter.quoteExactInput(path, amountIn); ``` ### Price Impact Estimation ```solidity // sqrtPriceX96After from the quote tells you where price lands // Compare to current sqrtPriceX96 to estimate impact (uint160 sqrtPriceCurrent,,,,,,) = pool.slot0(); uint256 priceBefore = uint256(sqrtPriceCurrent) * uint256(sqrtPriceCurrent) / (1 << 192); uint256 priceAfter = uint256(sqrtPriceAfter) * uint256(sqrtPriceAfter) / (1 << 192); uint256 impactBps = (priceBefore - priceAfter) * 10_000 / priceBefore; ``` ## Pool Health Indicators | Metric | What It Means | Warning Sign | |--------|--------------|--------------| | TVL trend | Total value committed by LPs | Declining over 7+ days = liquidity flight | | Volume / TVL ratio | Fee generation efficiency | < 0.01 daily = stagnant pool | | Active liquidity concentration | How tightly LPs bracket current price | Wide spread = high slippage for traders | | Active position count | Number of LPs with in-range liquidity | < 5 positions = fragile liquidity | | Fee revenue vs IL | LP profitability | Negative = LPs losing money, expect exits | | Oracle cardinality | V3 observation buffer size (slot0 field 4) | Default 1 = no TWAP history | | Tick crossing frequency | How often price moves through tick boundaries | Very high = volatile, may deter LPs | ### Reading Oracle Cardinality ```bash # observationCardinalityNext is the 5th return value of slot0 cast call <pool_address> "slot0()(uint160,int24,uint16,uint16,uint16,uint8,bool)" # Increase cardinality for better TWAP support (anyone can call, pays gas) cast send <pool_address> "increaseObservationCardinalityNext(uint16)" 100 ``` ## Pool Creation ### V3 Pool Creation ```solidity IUniswapV3Factory factory = IUniswapV3Factory(0x1F98431c8aD98523631AE4a59f267346ea31F984); address pool = factory.createPool(tokenA, tokenB, fee); // Initialize with starting price (sqrtPriceX96 format) // For 1 token0 = 2000 token1 (e.g., 1 ETH = 2000 USDC with 18/6 decimals): // sqrtPriceX96 = sqrt(2000 * 1e6 / 1e18) * 2^96 uint160 sqrtPriceX96 = 3543191142285914205922034323215; // example IUniswapV3Pool(pool).initialize(sqrtPriceX96); ``` ### V4 Pool Initialization ```solidity import {IPoolManager} from "v4-core/src/interfaces/IPoolManager.sol"; IPoolManager poolManager = IPoolManager(0x000000000004444c5dc75cb358380d2e3de08a90); PoolKey memory key = PoolKey({ currency0: Currency.wrap(token0), currency1: Currency.wrap(token1), fee: 3000, tickSpacing: 60, hooks: IHooks(address(0)) }); poolManager.initialize(key, sqrtPriceX96); ``` ### sqrtPriceX96 Calculator ```solidity /// @notice Computes sqrtPriceX96 from a human-readable price ratio /// @param price The price of token0 in terms of token1 (e.g., 2000 for 1 ETH = 2000 USDC) /// @param decimals0 Decimals of token0 /// @param decimals1 Decimals of token1 function computeSqrtPriceX96( uint256 price, uint8 decimals0, uint8 decimals1 ) pure returns (uint160) { // adjustedPrice = price * 10^decimals0 / 10^decimals1 // sqrtPriceX96 = sqrt(adjustedPrice) * 2^96 uint256 adjustedPrice = price * (10 ** decimals1) / (10 ** decimals0); uint256 sqrtPrice = Math.sqrt(adjustedPrice); return uint160(sqrtPrice << 96); } ``` ## Forge Scripts for Pool Analysis ### Query All Fee Tiers for a Token Pair ```solidity // SPDX-License-Identifier: MIT pragma solidity ^0.8.24; import {Script, console2} from "forge-std/Script.sol"; import {IUniswapV3Factory} from "@uniswap/v3-core/contracts/interfaces/IUniswapV3Factory.sol"; import {IUniswapV3Pool} from "@uniswap/v3-core/contracts/interfaces/IUniswapV3Pool.sol"; contract PoolScanner is Script { IUniswapV3Factory constant FACTORY = IUniswapV3Factory(0x1F98431c8aD98523631AE4a59f267346ea31F984); function run(address tokenA, address tokenB) external view { uint24[4] memory fees = [uint24(100), 500, 3000, 10000]; string[4] memory labels = ["0.01%", "0.05%", "0.30%", "1.00%"]; for (uint256 i = 0; i < fees.length; i++) { address pool = FACTORY.getPool(tokenA, tokenB, fees[i]); if (pool != address(0)) { (uint160 sqrtPriceX96, int24 tick,,,,,) = IUniswapV3Pool(pool).slot0(); uint128 liquidity = IUniswapV3Pool(pool).liquidity();
View on GitHub
This SKILL.md is very large, so SkillsMP previews the first section here. View on GitHub