v4-sdk-integration

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Uniswap v4 SDK Integration

Uniswap v4 SDK 集成

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.
用于兑换、报价、流动性操作的应用层SDK。如果要开发Solidity hook合约,请使用
uniswap-hooks
技能。如果需要对接Trading API或以v3为核心的兑换功能,请使用
swap-integration
技能。

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)
  • 代币兑换UI(单跳或多跳)
  • 交易执行前的报价/价格展示
  • 流动性头寸管理(添加/移除/提取收益)
  • 池状态读取(价格、tick、流动性)

Packages

安装依赖包

bash
npm i @uniswap/v4-sdk @uniswap/sdk-core @uniswap/universal-router-sdk

bash
npm i @uniswap/v4-sdk @uniswap/sdk-core @uniswap/universal-router-sdk

v4 vs v3 Decision Table

v4 与 v3 对比决策表

Aspectv3v4
Swap executionSwapRouter directlyUniversal Router required (V4Planner)
Pool architectureOne contract per poolSingleton PoolManager
Pool state readsDirect pool contractStateView contract
Native ETHWrap to WETHNative support (
Ether.onChain(chainId)
)
Position NFTsNonfungiblePositionManagerPositionManager + multicall
Fee collectionExplicit
collect()
Automatic on position modification
Position discoveryOnchain enumerationOffchain event indexing
Token approvalsDirect approvePermit2 required
Contract addressesSame across chainsDifferent per chain — verify from deployments

对比项v3v4
兑换执行直接调用SwapRouter需使用Universal Router(搭配V4Planner)
池架构每个池对应独立合约单例PoolManager
池状态读取直接调用池合约通过StateView合约
原生ETH需要包装为WETH原生支持(
Ether.onChain(chainId)
头寸NFTNonfungiblePositionManagerPositionManager + 批量调用(multicall)
手续费收取显式调用
collect()
修改头寸时自动收取
头寸查询链上枚举链下事件索引
代币授权直接授权必须使用Permit2
合约地址跨链统一各链不同——需从部署页面核实

Core Contracts (Per Chain)

核心合约(按链区分)

Look up addresses at https://docs.uniswap.org/contracts/v4/deployments — they differ per chain.
ContractPurpose
PoolManagerSingleton pool state
Universal RouterSwap execution entry point
QuoterOffchain quote simulation (callStatic)
StateViewPool state reads (getSlot0, getLiquidity)
PositionManagerLP position lifecycle
Permit2Token approval layer (same across chains:
0x000000000022D473030F116dDEE9F6B43aC78BA3
)

可在 https://docs.uniswap.org/contracts/v4/deployments 查询各链对应的合约地址,不同链地址存在差异。
合约用途
PoolManager单例池状态存储
Universal Router兑换执行入口
Quoter链下报价模拟(callStatic调用)
StateView池状态读取(getSlot0、getLiquidity)
PositionManager流动性提供者头寸全生命周期管理
Permit2代币授权层(全链统一地址:
0x000000000022D473030F116dDEE9F6B43aC78BA3

Swap Pattern (Universal Router)

兑换实现模式(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',
};

所有兑换都遵循:V4Planner -> RoutePlanner -> Universal Router
execute()
的流程。
单跳(固定输入金额):
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);
多跳(固定输入金额):
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

报价实现模式

Use the Quoter contract with
callStatic
— this simulates the swap offchain without executing it or spending gas.
typescript
const quote = 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

使用Quoter合约搭配
callStatic
调用——这会在链下模拟兑换操作,不会实际执行也不会消耗Gas。
typescript
const quote = await quoterContract.callStatic.quoteExactInputSingle({
  poolKey,
  zeroForOne,
  exactAmount: amountIn,
  hookData: '0x00',
});
四个可用方法:
  • quoteExactInputSingle
    — 单跳、固定输入金额
  • quoteExactInput
    — 多跳、固定输入金额
  • quoteExactOutputSingle
    — 单跳、固定输出金额
  • quoteExactOutput
    — 多跳、固定输出金额

Pool State Reads (StateView)

池状态读取(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 }

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 授权流程(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.

ERC20代币兑换需要两步授权——代币合约授权给Permit2,再将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);
原生ETH兑换不需要这两步授权——直接在交易参数中传入
value
即可。

Position Management (PositionManager)

头寸管理(PositionManager)

All operations use
PositionManager.multicall()
:
OperationSDK 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),
});

所有操作都使用
PositionManager.multicall()
操作SDK 方法
添加流动性
V4PositionManager.addCallParameters(position, options)
移除流动性
V4PositionManager.removeCallParameters(position, options)
提取手续费
V4PositionManager.collectCallParameters(options)
创建头寸
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 call Quoter onchain (gas expensive) — ALWAYS use
    callStatic
    for offchain simulation.
  • 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.

  • 绝对不要直接调用PoolManager执行兑换——必须通过Universal Router路由。
  • 绝对不要假设合约地址跨链统一——请从部署页面查询对应链的地址。
  • 绝对不要在链上调用Quoter(Gas成本极高)——必须使用
    callStatic
    进行链下模拟。
  • 绝对不要在ERC20兑换中跳过Permit2——直接授权给Universal Router无法正常工作。
  • 必须为兑换和流动性操作设置截止时间。
  • 在v4池场景下必须使用
    Ether.onChain(chainId)
    处理原生ETH,不要使用WETH。
  • 必须使用
    Pool.getPoolId()
    计算池ID——不要手动构造。

Links

相关链接

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)
  • swap-integration
    ——对接Trading API和以v3为核心的兑换集成(非直接使用v4 SDK)
  • uniswap-hooks
    ——Solidity Hook合约生成(非应用层SDK)