Skill: openclaw-consensus-bot/uniswap-v4 (version 2.0.4)

--- 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 | [REDACTED_SECRET] | [REDACTED_SECRET] | | UniversalRouter | [REDACTED_SECRET] | [REDACTED_SECRET] | | Permit2 | [REDACTED_SECRET] | [REDACTED_SECRET] | | StateView | [REDACTED_SECRET] | [REDACTED_SECRET] | | V4Quoter | [REDACTED_SECRET] | [REDACTED_SECRET] | > 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 [REDACTED_SECRET], WETH [REDACTED_SECRET] ## 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`

path: references/addresses.md
content: # Uniswap V4 Contract Addresses (Reference)

This file is **informational**.

- **Canonical source of truth (used by the code):** `src/lib/addresses.ts`
- **Official deployments reference:** https://docs.uniswap.org/contracts/v4/deployments

## Base Mainnet (chainId: 8453)
- **PoolManager**: [REDACTED_SECRET]
- **UniversalRouter**: `0x6ff5693b99212da76ad316178a184ab56d299b43`
- **Permit2 (universal)**: [REDACTED_SECRET]
- **StateView**: `0xa3c0c9b65bad0b08107aa264b0f3db444b867a71`
- **V4Quoter**: `0x0d5e0f971ed27fbff6c2837bf31316121532048d`
- **WETH**: `0x4200000000000000000000000000000000000006`
- **USDC**: [REDACTED_SECRET]

## Ethereum Mainnet (chainId: 1)
- **PoolManager**: [REDACTED_SECRET]
- **UniversalRouter**: [REDACTED_SECRET]
- **Permit2 (universal)**: [REDACTED_SECRET]
- **StateView**: `0x7ffe42c4a5deea5b0fec41c94c136cf115597227`
- **V4Quoter**: `0x52f0e24d1c21c8a0cb1e5a5dd6198556bd9e1203`
- **WETH**: [REDACTED_SECRET]

## Base (Common Tokens)
- **USDC**: [REDACTED_SECRET]
- **DAI**: [REDACTED_SECRET]
- **cbBTC**: [REDACTED_SECRET]

## RPC Endpoints
Avoid public RPCs for anything important. Prefer a paid endpoint (Alchemy).
- Base: `https://mainnet.base.org` (public)
- Ethereum: `https://eth.llamarpc.com` (public)


Security scan verdicts — ClawScan: suspicious | static analysis: suspicious | VirusTotal: clean | SkillSpector: suspicious (CRITICAL)