v4-sdk-integration
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.
Resumen
Uniswap v4 SDK Integration
App-layer SDK for swaps, quotes, and liquidity. For Solidity hook contracts, use the
uniswap-hooksskill. For Trading API or v3-centric swaps, use theswap-integrationskill.
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
npm i @uniswap/v4-sdk @uniswap/sdk-core @uniswap/universal-router-sdkv4 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):
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):
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):
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.
// 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 amountquoteExactInput— multi-hop, exact input amountquoteExactOutputSingle— single-hop, exact output amountquoteExactOutput— multi-hop, exact output amount
Pool State Reads (StateView)
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:
// 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) |
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: viemsimulateContract, ethers v6.staticCall, ethers v5callStatic. - NEVER skip Permit2 for ERC20 swaps — direct
approveto 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)
Archivos 1 archivo
Instálala a mano
Deska ejecuta esto por ti, para cada agente que elijas. Para ejecutarlo tú en una terminal:
npx skills add uniswap/uniswap-ai --skill v4-sdk-integrationMás de uniswap
- configuratoruniswapConfigure CCA (Continuous Clearing Auction) smart contract parameters through an interactive bulk form flow. Use when user says "configure auction", "cca auction", "setup token auction", "auction configuration", "continuous auction", or mentions CCA contracts.1 instalaciones en skills.sh0
- copy-tradeuniswapThis skill should be used when the user asks to "copy trades from" a wallet, "mirror a wallet", "follow this address", set up "copy trading", "track and replicate a trader", or mirror another account's swaps bounded by guardrails. Watches a target wallet and mirrors its trades, filtered by chain, asset match, position size, and the follower's own portfolio state.1 instalaciones en skills.sh0
- dca-botuniswapThis skill should be used when the user wants to "dca into" a token, "buy X every day", set up a "recurring buy", "dollar cost average" into an asset, "schedule a buy", or "auto-buy on a dip". Buys a fixed amount into a token on a schedule, optionally only when a condition holds (for example only when ETH is below a price threshold). The host agent's scheduler wakes the skill on a cadence; each wake is one self-contained run.1 instalaciones en skills.sh0
- deployeruniswapDeploy CCA (Continuous Clearing Auction) smart contracts using the Factory pattern. Use when user says "deploy auction", "deploy cca", "factory deployment", or wants to deploy a configured auction.1 instalaciones en skills.sh0
- index-botuniswapThis skill should be used when the user asks to "create an index", "build a basket of top assets", "buy a weighted basket", "make a portfolio of assets", "equal-weight basket", "rebalance my portfolio", "track the top N tokens", or wants an automated, weighted multi-asset basket that buys in one pass and rebalances on a cadence. Builds the basket spec, delegates each buy and rebalance swap to the swap-integration Trading API flow, and records target weights in state.1 instalaciones en skills.sh0