All skills
uniswap avatar

/v4-sdk-integration

@5338d6e
by Uniswap Labsuniswap/uniswap-ai233 stars
39

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.

Use this Skill: https://skilld.dev/gh/uniswap/uniswap-ai/v4-sdk-integration

This session only. Nothing lands on disk.

SKILL.md

โ‰ˆ96 tokens always: the name and description. โ‰ˆ2.6k when used: this file.

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

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):

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 amount
  • quoteExactInput โ€” multi-hop, exact input amount
  • quoteExactOutputSingle โ€” single-hop, exact output amount
  • quoteExactOutput โ€” 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: 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


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)

Source: SKILL.md on GitHub

1 warning16d4 checks ยท Risk SAFE
  • Gen Agent Trust Hub16d

    This skill is a legitimate technical guide for integrating the Uniswap v4 SDK. It contains standard code templates, references to official npm packages, and links to official documentation. No malicious patterns, obfuscation, or security risks were detected.

  • Socket16d

    No alerts

  • Snyk16d

    Risk: MEDIUM ยท 1 issue

  • ZeroLeaks5mo

    Score: 93/100 ยท 2 sections analyzed

Signed by skilld at 5338d6e. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub yesterday.

Activeupdated 3 weeks ago
What it can do
Reads files Edits files Runs commands Network
Modelopus
model
opus
metadata
{
  "author": "uniswap",
  "version": "1.0.1"
}
All 10 allowed tools
ReadWriteEditGlobGrepBash(npm:*)Bash(npx:*)Bash(yarn:*)Bash(curl:*)WebFetch

README badge

README badge for uniswap/uniswap-ai/v4-sdk-integration