Loading...
Loading...
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.
npx skill4agent add uniswap/uniswap-ai v4-sdk-integrationApp-layer SDK for swaps, quotes, and liquidity. For Solidity hook contracts, use theskill. For Trading API or v3-centric swaps, use theuniswap-hooksskill.swap-integration
npm i @uniswap/v4-sdk @uniswap/sdk-core @uniswap/universal-router-sdk| 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 ( |
| Position NFTs | NonfungiblePositionManager | PositionManager + multicall |
| Fee collection | Explicit | 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 |
| Contract | Purpose |
|---|---|
| PoolManager | Singleton pool state |
| Universal Router | Swap execution entry point |
| Quoter | Offchain quote simulation (callStatic) |
| StateView | Pool state reads (getSlot0, getLiquidity) |
| PositionManager | LP position lifecycle |
| Permit2 | Token approval layer (same across chains: |
execute()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);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);SwapExactInSingleconst swapConfig = {
poolKey: { currency0, currency1, fee, tickSpacing, hooks },
zeroForOne,
amountIn,
amountOutMinimum,
hookData: '0x00',
};callStaticconst quote = await quoterContract.callStatic.quoteExactInputSingle({
poolKey,
zeroForOne,
exactAmount: amountIn,
hookData: '0x00',
});quoteExactInputSinglequoteExactInputquoteExactOutputSinglequoteExactOutputimport { 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 }// 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);valuePositionManager.multicall()| Operation | SDK Method |
|---|---|
| Add liquidity | |
| Remove liquidity | |
| Collect fees | |
| Create position | |
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),
});callStaticapproveEther.onChain(chainId)Pool.getPoolId()swap-integrationuniswap-hooks