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 | INLINECODE0 | INLINECODE1 |
| UniversalRouter |
0x6ff5693b99212da76ad316178a184ab56d299b43 |
0x66a9893cC07D91D95644AEDD05D03f95e1dBA8Af |
| Permit2 |
0x000000000022D473030F116dDEE9F6B43aC78BA3 |
0x000000000022D473030F116dDEE9F6B43aC78BA3 |
| StateView |
0xa3c0c9b65bad0b08107aa264b0f3db444b867a71 |
0x7ffe42c4a5deea5b0fec41c94c136cf115597227 |
| V4Quoter |
0x0d5e0f971ed27fbff6c2837bf31316121532048d |
0x52f0e24d1c21c8a0cb1e5a5dd6198556bd9e1203 |
Addresses from 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) - Get swap quote? →
src/quote.ts (free, uses on-chain V4Quoter) - Approve tokens? →
src/approve.ts (write, ~100K gas, needs PRIVATE_KEY) - Execute swap? →
src/swap.ts (write, ~300-350K gas, needs PRIVATE_KEY) - 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).
CODEBLOCK0
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.
CODEBLOCK1
Env: BASE_RPC_URL or INLINECODE26
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).
CODEBLOCK2
Env: PRIVATE_KEY (required), INLINECODE28
swap.ts — Execute Swap (write)
Exact-input swap via Universal Router. Quotes expected output first, applies slippage,
then sends the transaction.
CODEBLOCK3
With auto-approval (sets up Permit2 if needed):
CODEBLOCK4
Options: --slippage <bps> (default 50 = 0.5%), --recipient <addr>, --auto-approve, --json
Env: PRIVATE_KEY (required), INLINECODE34
Token Input
- -
ETH or eth → native ETH (address(0) in V4) - Contract address → ERC20 token
- Common Base tokens: USDC
0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913, WETH INLINECODE38
Environment Variables
| Variable | Used By | Required | Description |
|---|
| INLINECODE39 | approve, swap | Yes* | Wallet private key (never CLI!) |
| INLINECODE40 |
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 V4SWAP command
- Approvals: ERC20 → Permit2 → Universal Router (two-step)
- Pool ID: INLINECODE43
- Currency ordering:
currency0 < currency1 by numeric value. ETH = address(0) - Action sequence: SWAPEXACTINSINGLE (0x06) + SETTLEALL (0x0c) + TAKEALL (0x0f)
- See
references/v4-encoding.md for full encoding reference
Error Handling
| Error | Cause | Fix |
|---|
| INLINECODE46 | Pair not listed on V4 for this chain | Check token addresses |
| INLINECODE47 |
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
CODEBLOCK5
References
- - V4 encoding reference: INLINECODE61
- Contract addresses: INLINECODE62
- V4 architecture: INLINECODE63
Uniswap V4 🦄
通过通用路由器在Uniswap V4上交换代币并读取池状态。
支持的链: Base (8453)、以太坊 (1)、Base Sepolia (84532)
| 合约 | Base | 以太坊 |
|---|
| PoolManager | 0x498581fF718922c3f8e6A244956aF099B2652b2b | 0x000000000004444c5dc75cB358380D2e3dE08A90 |
| UniversalRouter |
0x6ff5693b99212da76ad316178a184ab56d299b43 | 0x66a9893cC07D91D95644AEDD05D03f95e1dBA8Af |
| Permit2 | 0x000000000022D473030F116dDEE9F6B43aC78BA3 | 0x000000000022D473030F116dDEE9F6B43aC78BA3 |
| StateView | 0xa3c0c9b65bad0b08107aa264b0f3db444b867a71 | 0x7ffe42c4a5deea5b0fec41c94c136cf115597227 |
| V4Quoter | 0x0d5e0f971ed27fbff6c2837bf31316121532048d | 0x52f0e24d1c21c8a0cb1e5a5dd6198556bd9e1203 |
地址来自 docs.uniswap.org/contracts/v4/deployments,已验证于2026-02-08。
决策树
- 1. 读取池状态? → src/pool-info.ts(免费,无Gas消耗,无需密钥)
- 获取兑换报价? → src/quote.ts(免费,使用链上V4Quoter)
- 授权代币? → src/approve.ts(写操作,约10万Gas,需要PRIVATEKEY)
- 执行兑换? → src/swap.ts(写操作,约30-35万Gas,需要PRIVATEKEY)
- 首次使用ERC20? → 先运行授权,或在兑换时使用--auto-approve
脚本参考
所有脚本位于src/目录。使用npx tsx运行。传递--help查看用法。
pool-info.ts — 读取池状态(免费)
返回池ID、sqrtPriceX96、tick、流动性、费用、代币符号/精度。
自动检测流动性最佳的池(或指定--fee/--tick-spacing)。
bash
npx tsx src/pool-info.ts --token0 ETH --token1 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913 --chain base --rpc $BASERPCURL
环境变量: BASERPCURL 或 ETHRPCURL(或传递--rpc)
quote.ts — 报价兑换金额(免费)
通过链上V4Quoter合约报价精确输入金额(模拟,无交易)。
返回预期输出金额和Gas估算。
bash
npx tsx src/quote.ts \
--token-in ETH \
--token-out 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913 \
--amount 10000000000000000 \
--chain base \
--rpc $BASERPCURL
环境变量: BASERPCURL 或 ETHRPCURL
approve.ts — 设置代币授权(写操作)
两步Permit2流程:ERC20 → Permit2,然后Permit2 → 通用路由器。
如果已授权则跳过。仅适用于ERC20代币(非ETH)。
bash
PRIVATE_KEY=0x... npx tsx src/approve.ts \
--token 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913 \
--chain base \
--rpc $BASERPCURL \
--json
环境变量: PRIVATEKEY(必需)、BASERPC_URL
swap.ts — 执行兑换(写操作)
通过通用路由器进行精确输入兑换。先报价预期输出,应用滑点,然后发送交易。
bash
PRIVATE_KEY=0x... npx tsx src/swap.ts \
--token-in ETH \
--token-out 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913 \
--amount 10000000000000000 \
--slippage 50 \
--chain base \
--rpc $BASERPCURL \
--json
带自动授权(如需则设置Permit2):
bash
PRIVATE_KEY=0x... npx tsx src/swap.ts \
--token-in 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913 \
--token-out ETH \
--amount 25000000 \
--slippage 100 \
--auto-approve \
--chain base \
--rpc $BASERPCURL
选项: --slippage <基点>(默认50 = 0.5%)、--recipient <地址>、--auto-approve、--json
环境变量: PRIVATEKEY(必需)、BASERPC_URL
代币输入
- - ETH 或 eth → 原生ETH(V4中地址为address(0))
- 合约地址 → ERC20代币
- 常见Base代币:USDC 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913、WETH 0x4200000000000000000000000000000000000006
环境变量
| 变量 | 使用方 | 必需 | 描述 |
|---|
| PRIVATEKEY | approve, swap | 是* | 钱包私钥(绝不在CLI中!) |
| BASERPC_URL |
全部(Base) | 否 | Base主网RPC URL |
| ETH
RPCURL | 全部(以太坊) | 否 | 以太坊主网RPC URL |
| BASE
SEPOLIARPC_URL | 全部(测试网) | 否 | Base Sepolia RPC URL |
\* 仅写操作需要。读操作(pool-info、quote)不需要密钥。
V4架构说明
- - 单例PoolManager 在一个合约中持有所有池
- 状态读取 通过StateView合约(封装PoolManager存储)
- 兑换: 通用路由器 → PoolManager,通过V4SWAP命令
- 授权: ERC20 → Permit2 → 通用路由器(两步)
- 池ID: keccak256(abi.encode(currency0, currency1, fee, tickSpacing, hooks))
- 货币排序: currency0 < currency1 按数值排序。ETH = address(0)
- 操作序列: SWAPEXACTINSINGLE (0x06) + SETTLEALL (0x0c) + TAKEALL (0x0f)
- 完整编码参考见 references/v4-encoding.md
错误处理
| 错误 | 原因 | 修复 |
|---|
| 未找到V4池 | 该链上V4未列出该交易对 | 检查代币地址 |
| 报价失败 |
池存在但无法模拟兑换 | 检查金额,池可能缺乏流动性 |
| 需要PRIVATE
KEY | 写操作缺少环境变量 | export PRIVATEKEY=0x... |
| 无RPC URL | 缺少RPC配置 | 传递--rpc或设置环境变量 |
| 交易回滚 | 余额不足、过期、滑点 | 检查余额,增加滑点 |
| uint128最大值 | 金额对V4过大 | 使用较小金额 |
安全
- - PRIVATE_KEY 必须仅通过环境变量或密钥管理器提供。
- 切勿在