Swap tokens and read pool state on Uniswap V4 (Base, Ethereum). Use when the agent needs to: (1) swap ERC20 tokens or ETH via Uniswap V4, (2) get pool info (price, tick, liquidity, fees), (3) find the best pool for a token pair, (4) quote expected swap output via the on-chain V4Quoter, (5) set up Permit2 approvals for the Universal Router, or (6) execute exact-input swaps with proper slippage protection. Supports Base and Ethereum mainnet, plus Base Sepolia testnet. TypeScript with strict types. Write operations need a private key via env var.
--- name: uniswap-v4 description: > Swap tokens and read pool state on Uniswap V4 (Base, Ethereum). Use when the agent needs to: (1) swap ERC20 tokens or ETH via Uniswap V4, (2) get pool info (price, tick, liquidity, fees), (3) find the best pool for a token pair, (4) quote expected swap output via the on-chain V4Quoter, (5) set up Permit2 approvals for the Universal Router, or (6) execute exact-input swaps with proper slippage protection. Supports Base and Ethereum mainnet, plus Base Sepolia testnet. TypeScript with strict types. Write operations need a private key via env var. --- # Uniswap V4 ๐ฆ Swap tokens and read pool state on Uniswap V4 via the Universal Router. **Chains:** Base (8453), Ethereum (1), Base Sepolia (84532) | Contract | Base | Ethereum | |------------------|----------------------------------------------|----------------------------------------------| | PoolManager | `0x498581fF718922c3f8e6A244956aF099B2652b2b` | `0x000000000004444c5dc75cB358380D2e3dE08A90` | | UniversalRouter | `0x6ff5693b99212da76ad316178a184ab56d299b43` | `0x66a9893cC07D91D95644AEDD05D03f95e1dBA8Af` | | Permit2 | `0x000000000022D473030F116dDEE9F6B43aC78BA3` | `0x000000000022D473030F116dDEE9F6B43aC78BA3` | | StateView | `0xa3c0c9b65bad0b08107aa264b0f3db444b867a71` | `0x7ffe42c4a5deea5b0fec41c94c136cf115597227` | | V4Quoter | `0x0d5e0f971ed27fbff6c2837bf31316121532048d` | `0x52f0e24d1c21c8a0cb1e5a5dd6198556bd9e1203` | > Addresses from [docs.uniswap.org/contracts/v4/deployments](https://docs.uniswap.org/contracts/v4/deployments), verified 2026-02-08. ## Decision Tree 1. **Read pool state?** โ `src/pool-info.ts` (free, no gas, no key) 2. **Get swap quote?** โ `src/quote.ts` (free, uses on-chain V4Quoter) 3. **Approve tokens?** โ `src/approve.ts` (write, ~100K gas, needs `PRIVATE_KEY`) 4. **Execute swap?** โ `src/swap.ts` (write, ~300-350K gas, needs `PRIVATE_KEY`) 5. **First time with an ERC20?** โ Run approve first, or use `--auto-approve` on swap ## Scripts Reference All scripts in `src/`. Run with `npx tsx`. Pass `--help` for usage. ### pool-info.ts โ Read Pool State (free) Returns pool ID, sqrtPriceX96, tick, liquidity, fees, token symbols/decimals. Auto-detects the best pool by liquidity (or specify `--fee`/`--tick-spacing`). ```bash npx tsx src/pool-info.ts --token0 ETH --token1 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913 --chain base --rpc $BASE_RPC_URL ``` **Env:** `BASE_RPC_URL` or `ETH_RPC_URL` (or pass `--rpc`) ### quote.ts โ Quote Swap Amounts (free) Quotes exact input amounts via the on-chain V4Quoter contract (simulation, no tx). Returns expected output amount and gas estimate. ```bash npx tsx src/quote.ts \ --token-in ETH \ --token-out 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913 \ --amount 10000000000000000 \ --chain base \ --rpc $BASE_RPC_URL ``` **Env:** `BASE_RPC_URL` or `ETH_RPC_URL` ### approve.ts โ Set Up Token Approvals (write) Two-step Permit2 flow: ERC20 โ Permit2, then Permit2 โ Universal Router. Skips if already approved. Only needed for ERC20 tokens (not ETH). ```bash PRIVATE_KEY=0x... npx tsx src/approve.ts \ --token 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913 \ --chain base \ --rpc $BASE_RPC_URL \ --json ``` **Env:** `PRIVATE_KEY` (required), `BASE_RPC_URL` ### swap.ts โ Execute Swap (write) Exact-input swap via Universal Router. Quotes expected output first, applies slippage, then sends the transaction. ```bash PRIVATE_KEY=0x... npx tsx src/swap.ts \ --token-in ETH \ --token-out 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913 \ --amount 10000000000000000 \ --slippage 50 \ --chain base \ --rpc $BASE_RPC_URL \ --json ``` With auto-approval (sets up Permit2 if needed): ```bash PRIVATE_KEY=0x... npx tsx src/swap.ts \ --token-in 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913 \ --token-out ETH \ --amount 25000000 \ --slippage 100 \ --auto-approve \ --chain base \ --rpc $BASE_RPC_URL ``` **Options:** `--slippage <bps>` (default 50 = 0.5%), `--recipient <addr>`, `--auto-approve`, `--json` **Env:** `PRIVATE_KEY` (required), `BASE_RPC_URL` ## Token Input - `ETH` or `eth` โ native ETH (address(0) in V4) - Contract address โ ERC20 token - Common Base tokens: USDC `0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913`, WETH `0x4200000000000000000000000000000000000006` ## Environment Variables | Variable | Used By | Required | Description | |-----------------------|----------------|----------|---------------------------------| | `PRIVATE_KEY` | approve, swap | Yes* | Wallet private key (never CLI!) | | `BASE_RPC_URL` | all (Base) | No | Base mainnet RPC URL | | `ETH_RPC_URL` | all (Ethereum) | No | Ethereum mainnet RPC URL | | `BASE_SEPOLIA_RPC_URL`| all (testnet) | No | Base Sepolia RPC URL | \* Only required for write operations. Read operations (pool-info, quote) don't need a key. ## V4 Architecture Notes - **Singleton PoolManager** holds all pools in one contract - **State read** via StateView contract (wraps PoolManager storage) - **Swaps:** Universal Router โ PoolManager via V4_SWAP command - **Approvals:** ERC20 โ Permit2 โ Universal Router (two-step) - **Pool ID:** `keccak256(abi.encode(currency0, currency1, fee, tickSpacing, hooks))` - **Currency ordering:** `currency0 < currency1` by numeric value. ETH = address(0) - **Action sequence:** SWAP_EXACT_IN_SINGLE (0x06) + SETTLE_ALL (0x0c) + TAKE_ALL (0x0f) - See `references/v4-encoding.md` for full encoding reference ## Error Handling | Error | Cause | Fix | |------------------------------|--------------------------------------------|------------------------------------| | `No V4 pool found` | Pair not listed on V4 for this chain | Check token addresses | | `Quote failed` | Pool exists but can't simulate swap | Check amount, pool may lack liq | | `PRIVATE_KEY required` | Missing env var for write operation | `export PRIVATE_KEY=0x...` | | `No RPC URL` | Missing RPC config | Pass `--rpc` or set env var | | Tx reverts | Insufficient balance, expired, slippage | Check balance, increase slippage | | `uint128 max` | Amount too large for V4 | Use smaller amount | ## SECURITY - `PRIVATE_KEY` must be provided via an environment variable or secret manager only. - **NEVER** paste or send `PRIVATE_KEY` in chat. - **NEVER** commit `PRIVATE_KEY` (or `.env` files) to git. - Treat **stdout/stderr as public logs** (CI, terminals, chat). CI tests ensure the `PRIVATE_KEY` value is never printed. - **NEVER** pass private keys as CLI arguments (rejected by all scripts) - Private keys accepted via `PRIVATE_KEY` env var only - All inputs validated: addresses (format), amounts (BigInt bounds), slippage (0-10000) - No `eval()`, no `exec()`, no shell commands โ pure TypeScript - BigInt used everywhere for token amounts (no floating point, no overflow) ## Testing ```bash npm run test:unit # Unit tests (no network) npm run test:fork # Fork tests (needs: anvil --fork-url https://mainnet.base.org) npm run test:testnet # Testnet reads (Base Sepolia) npm run test:mainnet # Mainnet smoke tests (read-only) npm run security # Security scan ``` ## References - V4 encoding reference: `references/v4-encoding.md` - Contract addresses: `references/addresses.md` - V4 architecture: `references/v4-architecture.md`
don't have the plugin yet? install it then click "run inline in claude" again.
reorganized into implexa 6-component structure with explicit inputs (env vars, external connections), detailed 5-step procedure with edge cases, decision tree for each operation, output contract with json schemas, and outcome signals for success/failure verification.
swap tokens and read pool state on uniswap v4 via the universal router.
chains: base (8453), ethereum (1), base sepolia (84532)
| contract | base | ethereum |
|---|---|---|
| poolmanager | 0x498581fF718922c3f8e6A244956aF099B2652b2b |
0x000000000004444c5dc75cB358380D2e3dE08A90 |
| universalrouter | 0x6ff5693b99212da76ad316178a184ab56d299b43 |
0x66a9893cC07D91D95644AEDD05D03f95e1dBA8Af |
| permit2 | 0x000000000022D473030F116dDEE9F6B43aC78BA3 |
0x000000000022D473030F116dDEE9F6B43aC78BA3 |
| stateview | 0xa3c0c9b65bad0b08107aa264b0f3db444b867a71 |
0x7ffe42c4a5deea5b0fec41c94c136cf115597227 |
| v4quoter | 0x0d5e0f971ed27fbff6c2837bf31316121532048d |
0x52f0e24d1c21c8a0cb1e5a5dd6198556bd9e1203 |
addresses from docs.uniswap.org/contracts/v4/deployments, verified 2026-02-08.
use this skill when you need to swap tokens on uniswap v4, read pool state (price, liquidity, tick, fees), get quotes for expected output amounts, or set up token approvals. the skill covers read operations (free, no private key), quote operations (free simulation via on-chain v4quoter), and write operations (swaps and approvals). supports base, ethereum, and base sepolia testnet. use pool-info for discovery, quote for exact output prediction, approve for permit2 setup, and swap for execution with slippage protection.
environment variables (set before running):
| variable | used by | required | description |
|---|---|---|---|
PRIVATE_KEY |
approve.ts, swap.ts | yes* | wallet private key (64 hex chars, 0x prefix) |
BASE_RPC_URL |
all scripts (base) | no | base mainnet rpc endpoint (fallback: public) |
ETH_RPC_URL |
all scripts (eth) | no | ethereum mainnet rpc endpoint (fallback: public) |
BASE_SEPOLIA_RPC_URL |
all scripts (testnet) | no | base sepolia rpc endpoint (fallback: public) |
*only required for write operations (approve, swap). read operations (pool-info, quote) work without PRIVATE_KEY.
external connections:
--rpc <url> to override.PRIVATE_KEY env var. all scripts reject private keys in cli args and log warnings if detected.token identifiers:
ETH or eth (case-insensitive): native ether, resolved to address(0) in v4.0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913, weth 0x4200000000000000000000000000000000000006.chain parameter:
--chain base (default): base mainnet (8453).--chain ethereum: ethereum mainnet (1).--chain base-sepolia: base sepolia testnet (84532).input: token0 (symbol or address), token1 (symbol or address), chain, optional rpc url.
command:
npx tsx src/pool-info.ts \
--token0 ETH \
--token1 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913 \
--chain base \
--rpc $BASE_RPC_URL
output: pool id, sqrtpricex96, tick, liquidity, fee (in bps), token0/token1 symbols and decimals. if multiple pools exist for the pair, auto-selects by highest liquidity. to specify a pool, add --fee 500 or --tick-spacing 1.
edge cases handled:
input: token-in (symbol or address), token-out (symbol or address), amount (wei for erc20, raw decimals for eth), chain, optional rpc url.
command:
npx tsx src/quote.ts \
--token-in ETH \
--token-out 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913 \
--amount 10000000000000000 \
--chain base \
--rpc $BASE_RPC_URL
output: expected output amount (in wei, raw decimals), gas estimate (in wei), slippage-adjusted minimum output amount. all amounts are bigint, no floating point.
edge cases handled:
PRIVATE_KEY)input: token address (erc20 only, not eth), chain, optional rpc url, optional --json for json output.
command:
PRIVATE_KEY=0x... npx tsx src/approve.ts \
--token 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913 \
--chain base \
--rpc $BASE_RPC_URL \
--json
procedure (two-step permit2 flow):
output: json object with tx hashes, allowance values, and status. if already approved, returns "already approved" and skips both txs.
edge cases handled:
PRIVATE_KEY)input: token-in (symbol or address), token-out (symbol or address), amount (wei), slippage (bps, default 50 = 0.5%), chain, optional rpc url, optional --auto-approve, optional --recipient <address>, optional --json.
command:
PRIVATE_KEY=0x... npx tsx src/swap.ts \
--token-in ETH \
--token-out 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913 \
--amount 10000000000000000 \
--slippage 50 \
--chain base \
--rpc $BASE_RPC_URL \
--json
with auto-approval (runs approve step 3 if needed):
PRIVATE_KEY=0x... npx tsx src/swap.ts \
--token-in 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913 \
--token-out ETH \
--amount 25000000 \
--slippage 100 \
--auto-approve \
--chain base \
--rpc $BASE_RPC_URL
procedure:
--auto-approve is set and token-in is erc20, run approve step (step 3) first.expected_output * (10000 - slippage) / 10000. slippage in bps (50 = 0.5%).--recipient.output: json object with tx hash, token-in amount, token-out amount, gas used (wei), effective price. if --json not set, human-readable text summary.
edge cases handled:
input: --recipient <0x...> flag on swap.ts.
output: tokens sent to recipient instead of connected wallet. if not set, defaults to wallet derived from private key.
if reading pool state only (pool-info.ts):
if quoting swap amounts (quote.ts):
if approving tokens (approve.ts):
PRIVATE_KEY env var. use before first swap with a new erc20 token. skips if already approved. eth does not need approval. two-step flow: erc20 -> permit2, then permit2 -> universalrouter.if executing swap without auto-approval (swap.ts, no --auto-approve):
PRIVATE_KEY env var. assumes token-in (if erc20) is already approved. if not approved, tx reverts with "insufficient allowance". run approve.ts first or use --auto-approve flag.if executing swap with auto-approval (swap.ts, --auto-approve):
PRIVATE_KEY env var. runs approve.ts internally if token-in is erc20 and not yet approved. skips approval if already approved. eth does not trigger approval step.if token-in is ETH:
if token-in is ERC20:
--auto-approve on swap.ts to combine steps 2-3.if multiple pools exist for a token pair:
--fee <bps> (e.g., --fee 500 for 0.5% fee) or --tick-spacing <spacing>.if quote fails:
if swap reverts on chain:
web3.eth.getBalance(), increase --slippage to relax bounds, ensure approval is complete, or choose a different pool.if private key is missing:
PRIVATE_KEY env var. example: export PRIVATE_KEY=0x123abc... (never paste in chat or commit to git).if rpc endpoint is unreachable:
--rpc <url>. timeout is 30 seconds per call.pool-info.ts output (stdout or --json):
{
"poolId": "0x...",
"token0": "0x...",
"token1": "0x...",
"fee": 500,
"tickSpacing": 1,
"sqrtPriceX96": "1234567890...",
"tick": 42000,
"liquidity": "9876543210...",
"symbol0": "ETH",
"symbol1": "USDC",
"decimals0": 18,
"decimals1": 6
}
quote.ts output (stdout or --json):
{
"amountIn": "10000000000000000",
"amountOut": "12345678901",
"amountOutMinimum": "12250000000",
"gasEstimate": "350000",
"executionPrice": "1.2345"
}
approve.ts output (stdout or --json):
{
"token": "0x...",
"permit2AllowanceTx": "0x...",
"universalRouterAllowanceTx": "0x...",
"status": "approved",
"erc20ToPermit2": "115792089237316195423570985008687907853269984665640564039457584007913129639935",
"permit2ToUniversalRouter": "115792089237316195423570985008687905694156674177411857430378968448384"
}
swap.ts output (stdout or --json):
{
"transactionHash": "0x...",
"blockNumber": 12345678,
"from": "0x...",
"to": "0x...",
"amountIn": "10000000000000000",
"amountOut": "12300000000",
"executionPrice": "1.23",
"gasUsed": "325000",
"gasPrice": "50000000000",
"slippageIncurred": "0.4%"
}
all amounts are strings (bigint), no floating point. gas values in wei. slippage as percentage string.
error responses (any script):
{
"error": "descriptive message",
"code": "ERROR_CODE",
"suggestion": "how to fix"
}
or plain text to stderr if --json not set.
pool-info.ts succeeds when:
--json, human-readable summary appears. exit code 0.quote.ts succeeds when:
approve.ts succeeds when:
swap.ts succeeds when:
--recipient was used, tokens appear in recipient wallet. if not, tokens appear in connected wallet.any script fails when:
--json, error response includes code and suggestion. user should check:PRIVATE_KEY is set (write operations only).--rpc or env var).v4 architecture:
security reminders:
PRIVATE_KEY must be in environment variable only. never paste in cli, chat, or logs.PRIVATE_KEY is never printed to stdout/stderr.testing:
npm run test:unit # unit tests, no network
npm run test:fork # fork tests, requires local anvil
npm run test:testnet # base sepolia reads
npm run test:mainnet # mainnet smoke tests (read-only)
npm run security # static security scan
references:
references/v4-encoding.mdreferences/addresses.mdreferences/v4-architecture.mdoriginal author: clawhub.