v4-sdk-integration — Claude Code skill: Uniswap v4 swaps and liquidity
A skill that walks the agent through the Uniswap v4 SDK: swaps via Universal Router, quotes, pool state reads and LP position handling.
npx skills add Uniswap/uniswap-ai --skill v4-sdk-integration -a claude-codeWhat v4-sdk-integration does
The skill kicks in when you ask the agent to build a token swap or liquidity flow straight through the SDK instead of the Trading API. It carries a reference for @uniswap/v4-sdk, @uniswap/sdk-core and @uniswap/universal-router-sdk, a v3 vs v4 comparison table, and notes on the core contracts: PoolManager, Universal Router, Quoter, StateView, PositionManager, Permit2.
Two patterns get the most room. A swap is built as V4Planner → RoutePlanner → Universal Router execute(), with examples for a single pool and for a multi-hop path. Quoting goes through eth_call: quoteExactInputSingle returns two values, amountOut and gasEstimate, and only the first belongs in amountOutMinimum. Sending a quote as a transaction wastes gas and hands nothing back to your code.
Contract addresses differ per chain in v4, so check them against the deployments list before shipping. Token approvals need Permit2; a plain approve will not do. Solidity hooks are a different skill, uniswap-hooks.
The skill targets the opus model and asks for Read, Write, Edit, Glob, Grep plus npm, npx, yarn and curl access.
What it helps with
- build a token swap through Universal Router
- show a quote and price before the trade is signed
- add liquidity to a position and withdraw it later
- read pool state: price, tick, liquidity
- route a swap across several pools
Example prompts for the agent
- Build a USDC to WETH swap on Uniswap v4 with V4Planner
- How do I read v4 pool state through StateView?
- Get me an exact input single quote and set amountOutMinimum
How to install v4-sdk-integration
1Skills CLI (Vercel)
npx skills add Uniswap/uniswap-ai --skill v4-sdk-integration -a claude-code2Manually: put the files in place
~/.claude/skills/v4-sdk-integration/SKILL.mdCopy the whole skill folder; for one project use .claude/skills/v4-sdk-integration/
1Skills CLI (Vercel)
npx skills add Uniswap/uniswap-ai --skill v4-sdk-integration -a codex2Manually: put the files in place
.agents/skills/v4-sdk-integration/SKILL.mdFor all projects use ~/.agents/skills/
1Skills CLI (Vercel)
npx skills add Uniswap/uniswap-ai --skill v4-sdk-integration -a cursor2Manually: put the files in place
.cursor/skills/v4-sdk-integration/SKILL.mdCursor also reads .agents/skills/ and .claude/skills/
1GitHub CLI
gh skill install Uniswap/uniswap-ai v4-sdk-integration2Manually: put the files in place
.github/skills/v4-sdk-integration/SKILL.md1Skills CLI (Vercel)
npx skills add Uniswap/uniswap-ai --skill v4-sdk-integration -a gemini-cli2Platform CLI
gemini skills install https://github.com/Uniswap/uniswap-ai --path packages/plugins/uniswap-trading/skills/v4-sdk-integrationManually: put the files in place
.devin/skills/v4-sdk-integration/SKILL.mdThe legacy .windsurf/skills/ path still works
Skills CLI (Vercel)
npx skills add Uniswap/uniswap-ai --skill v4-sdk-integration -a opencodeClaude.ai
Zip the folder and upload it in Settings → Capabilities → Skills
Step by step, with commands for each platform: How to install skills in Claude Code: five ways
Questions about v4-sdk-integration
How do I install v4-sdk-integration in Claude?
Run npx skills add Uniswap/uniswap-ai --skill v4-sdk-integration -a claude-code. The skill file then sits at ~/.claude/skills/v4-sdk-integration/SKILL.md.
What does v4-sdk-integration do?
It gives the agent a Uniswap v4 SDK reference: swap patterns through Universal Router, quoting, pool state reads and LP position work.
How does v4 differ from v3 here?
In v4 swaps go only through Universal Router with V4Planner, pool state is read via StateView, and token approvals require Permit2. Contract addresses vary per chain.
Source text
SKILL.md · MIT
---
name: v4-sdk-integration
description: >
App-layer SDK guide for building swap and liquidity experiences directly with the Uniswap v4 SDK.
Use when user asks about "v4 sdk", "uniswap v4", "v4 swap", "v4 liquidity", "PoolManager",
"V4Planner", "StateView", "PositionManager", "pool state", "v4 position", "uniswap sdk",
or when building swap/liquidity UX directly with SDKs rather than via the Trading API.
allowed-tools: Read, Write, Edit, Glob, Grep, Bash(npm:*), Bash(npx:*), Bash(yarn:*), Bash(curl:*), WebFetch
model: opus
license: MIT
metadata:
author: uniswap
version: '1.0.1'
---
# Uniswap v4 SDK Integration
> App-layer SDK for swaps, quotes, and liquidity. For Solidity hook contracts, use the
> `uniswap-hooks` skill. For Trading API or v3-centric swaps, use the `swap-integration` skill.
## When to Use
- Token swap UI (single-hop or multi-hop)
- Quote/price display before executing a trade
- Liquidity position management (add/remove/collect)
- Pool state reads (price, tick, liquidity)
## Packages
```bash
npm i @uniswap/v4-sdk @uniswap/sdk-core @uniswap/universal-router-sdk
```
---
## v4 vs v3 Decision Table
| Aspect | v3 | v4 |
| ------------------ | -------------------------- | --------------------------------------------- |
| Swap execution | SwapRouter directly | Universal Router required (V4Planner) |
| Pool architecture | One contract per pool | Singleton PoolManager |
| Pool state reads | Direct pool contract | StateView contract |
| Native ETH | Wrap to WETH | Native support (`Ether.onChain(chainId)`) |
| Position NFTs | NonfungiblePositionManager | PositionManager + multicall |
| Fee collection | Explicit `collect()` | Automatic on position modification |
| Position discovery | Onchain enumeration | Offchain event indexing |
| Token approvals | Direct approve | Permit2 required |
| Contract addresses | Same across chains | Different per chain — verify from deployments |
---
## Core Contracts (Per Chain)
Look up addresses at <https://docs.uniswap.org/contracts/v4/deployments> — they differ per chain.
| Contract | Purpose |
| ---------------- | --------------------------------------------------------------------------------------- |
| PoolManager | Singleton pool state |
| Universal Router | Swap execution entry point |
| Quoter | Offchain quote simulation (`eth_call`, never a transaction) |
| StateView | Pool state reads (getSlot0, getLiquidity) |
| PositionManager | LP position lifecycle |
| Permit2 | Token approval layer (same across chains: `0x000000000022D473030F116dDEE9F6B43aC78BA3`) |
---
## Swap Pattern (Universal Router)
All swaps use: V4Planner -> RoutePlanner -> Universal Router `execute()`.
**Single-hop (exact input):**
```typescript
import { Actions, V4Planner } from '@uniswap/v4-sdk';
import { CommandType, RoutePlanner } from '@uniswap/universal-router-sdk';
const v4Planner = new V4Planner();
v4Planner.addAction(Actions.SWAP_EXACT_IN_SINGLE, [swapConfig]);
v4Planner.addAction(Actions.SETTLE_ALL, [inputCurrency, amountIn]);
v4Planner.addAction(Actions.TAKE_ALL, [outputCurrency, amountOutMinimum]);
const routePlanner = new RoutePlanner();
routePlanner.addCommand(CommandType.V4_SWAP, [v4Planner.actions, v4Planner.params]);
const deadline = Math.floor(Date.now() / 1000) + 3600;
// Note: universalRouter.execute() is pseudocode for the viem call pattern.
// With viem, use: walletClient.writeContract({ address: UNIVERSAL_ROUTER_ADDRESS, abi: universalRouterAbi, functionName: 'execute', args: [routePlanner.commands, [v4Planner.finalize()], deadline], ...txOptions })
await universalRouter.execute(routePlanner.commands, [v4Planner.finalize()], deadline, txOptions);
```
**Multi-hop (exact input):**
```typescript
import { Actions, V4Planner, encodeMultihopExactInPath } from '@uniswap/v4-sdk';
import { CommandType, RoutePlanner } from '@uniswap/universal-router-sdk';
const v4Planner = new V4Planner();
// Build multi-hop path: tokenA -> tokenB -> tokenC
const path = encodeMultihopExactInPath([poolKeyAB, poolKeyBC], tokenA);
v4Planner.addAction(Actions.SWAP_EXACT_IN, [{ path, amountIn, amountOutMinimum }]);
// SETTLE_ALL uses first pool's input currency; TAKE_ALL uses last pool's output currency
v4Planner.addAction(Actions.SETTLE_ALL, [tokenA, amountIn]);
v4Planner.addAction(Actions.TAKE_ALL, [tokenC, amountOutMinimum]);
const routePlanner = new RoutePlanner();
routePlanner.addCommand(CommandType.V4_SWAP, [v4Planner.actions, v4Planner.params]);
const deadline = Math.floor(Date.now() / 1000) + 3600;
// Note: universalRouter.execute() is pseudocode for the viem call pattern.
// With viem, use: walletClient.writeContract({ address: UNIVERSAL_ROUTER_ADDRESS, abi: universalRouterAbi, functionName: 'execute', args: [routePlanner.commands, [v4Planner.finalize()], deadline], ...txOptions })
await universalRouter.execute(routePlanner.commands, [v4Planner.finalize()], deadline, txOptions);
```
**SwapConfig** (`SwapExactInSingle`):
```typescript
const swapConfig = {
poolKey: { currency0, currency1, fee, tickSpacing, hooks },
zeroForOne,
amountIn,
amountOutMinimum,
hookData: '0x00',
};
```
---
## Quoting Pattern
The Quoter is **not** a `view` function. Uniswap's own guide explains why: the v4 Quoter contracts
"rely on state-changing calls designed to be reverted to return the desired data"
(<https://developers.uniswap.org/docs/sdks/v4/guides/swapping/quoting>). Sending it as a real
transaction both wastes gas and cannot hand the value back to your code.
**The rule, independent of library: simulate the quote through `eth_call`. Never send it as a
transaction.** Each library spells that simulation differently — pick the one matching your stack,
not the one you saw in a guide.
`quoteExactInputSingle` returns **two** values — `(uint256 amountOut, uint256 gasEstimate)` — so
destructure it. Only `amountOut` feeds `amountOutMinimum`; passing the whole tuple is a bug.
```typescript
// viem — the stack the rest of this skill assumes.
// simulateContract, not readContract: the quote method is nonpayable, not view.
const {
result: [amountOut, gasEstimate],
} = await publicClient.simulateContract({
address: QUOTER_ADDRESS,
abi: quoterAbi,
functionName: 'quoteExactInputSingle',
args: [{ poolKey, zeroForOne, exactAmount: amountIn, hookData: '0x00' }],
});
// ethers v6 — `.staticCall` hangs off the method itself
const [amountOut, gasEstimate] = await quoterContract.quoteExactInputSingle.staticCall({
poolKey,
zeroForOne,
exactAmount: amountIn,
hookData: '0x00',
});
// ethers v5 only — `callStatic` was removed in v6
const [amountOut, gasEstimate] = await quoterContract.callStatic.quoteExactInputSingle({
poolKey,
zeroForOne,
exactAmount: amountIn,
hookData: '0x00',
});
```
Four available methods:
- `quoteExactInputSingle` — single-hop, exact input amount
- `quoteExactInput` — multi-hop, exact input amount
- `quoteExactOutputSingle` — single-hop, exact output amount
- `quoteExactOutput` — multi-hop, exact output amount
---
## Pool State Reads (StateView)
```typescript
import { Pool } from '@uniswap/v4-sdk';
const poolId = Pool.getPoolId(currency0, currency1, fee, tickSpacing, hooks);
const [slot0, liquidity] = await Promise.all([
stateViewContract.getSlot0(poolId),
stateViewContract.getLiquidity(poolId),
]);
// slot0 → { sqrtPriceX96, tick, protocolFee, lpFee }
```
---
## ERC20 Approval Flow (Permit2)
ERC20 swaps require two approvals — token -> Permit2, then Permit2 -> Universal Router:
```typescript
// Step 1: Approve Permit2 on the token contract
await erc20Contract.approve(PERMIT2_ADDRESS, MaxUint256);
// Step 2: Approve Universal Router on Permit2
await permit2Contract.approve(tokenAddress, UNIVERSAL_ROUTER_ADDRESS, MAX_UINT160, deadline);
```
Native ETH swaps bypass both approvals — pass `value` in the transaction options instead.
---
## Position Management (PositionManager)
All operations use `PositionManager.multicall()`:
| Operation | SDK Method |
| ---------------- | ----------------------------------------------------------- |
| Add liquidity | `V4PositionManager.addCallParameters(position, options)` |
| Remove liquidity | `V4PositionManager.removeCallParameters(position, options)` |
| Collect fees | `V4PositionManager.collectCallParameters(options)` |
| Create position | `V4PositionManager.createCallParameters(position, options)` |
```typescript
const { calldata, value } = V4PositionManager.addCallParameters(position, {
slippageTolerance: new Percent(50, 10_000),
deadline: deadline.toString(),
tokenId: tokenId.toString(),
useNative: token0.isNative ? Ether.onChain(chainId) : undefined,
batchPermit,
hookData: '0x',
});
await walletClient.writeContract({
address: POSITION_MANAGER_ADDRESS,
functionName: 'multicall',
args: [[calldata]],
value: BigInt(value),
});
```
---
## Strict Rules
- NEVER call PoolManager directly for swaps — ALWAYS route through Universal Router.
- NEVER assume contract addresses are the same across chains — look up from the deployments page.
- NEVER send the Quoter as a transaction (gas expensive, and it cannot return the value) — ALWAYS
simulate it through `eth_call`: viem `simulateContract`, ethers v6 `.staticCall`, ethers v5
`callStatic`.
- NEVER skip Permit2 for ERC20 swaps — direct `approve` to Universal Router will not work.
- ALWAYS set a deadline on swaps and LP operations.
- ALWAYS handle native ETH with `Ether.onChain(chainId)`, not WETH, in v4 pool contexts.
- ALWAYS use `Pool.getPoolId()` to compute pool identifiers — do not construct manually.
---
## Links
- SDK overview: <https://docs.uniswap.org/sdk/v4/overview>
- Swap guides: <https://docs.uniswap.org/sdk/v4/guides/swaps/quoting>
- Liquidity guide: <https://docs.uniswap.org/sdk/v4/guides/liquidity/add-remove-liquidity>
- Pool data: <https://docs.uniswap.org/sdk/v4/guides/advanced/pool-data>
- Deployments: <https://docs.uniswap.org/contracts/v4/deployments>
- npm: <https://www.npmjs.com/package/@uniswap/v4-sdk>
---
## Related Skills
- `swap-integration` — Trading API and v3-centric swap integration (not direct v4 SDK)
- `uniswap-hooks` — Solidity hook contract generation (not app-layer SDK)
© uniswap · MIT · Uniswap/uniswap-ai