Integrate Uniswap swaps into applications. Use when user says "integrate swaps", "uniswap", "trading api", "add swap functionality", "build a swap frontend", "create a swap script", "smart contract swap integration", "use Universal Router", "Trading API", or mentions swapping tokens via Uniswap.
Integrate Uniswap swaps into frontends, backends, and smart contracts.
This skill assumes familiarity with viem basics (client setup, account management, contract interactions, transaction signing). Install the uniswap-viem plugin for comprehensive viem/wagmi guidance: claude plugin add @uniswap/uniswap-viem
| Building... | Use This Method | | ------------------------------ | ----------------------------- | | Frontend with React/Next.js | Trading API | | Backend script or bot | Trading API | | Smart contract integration | Universal Router direct calls | | Need full control over routing | Universal Router SDK |
| Type | Description | Chains | | -------- | --------------------------------------- | ---------------------------------- | | CLASSIC | Standard AMM swap through Uniswap pools | All supported chains | | DUTCH_V2 | UniswapX Dutch auction V2 | Ethereum, Arbitrum, Base, Unichain | | PRIORITY | MEV-protected priority order | Base, Unichain | | WRAP | ETH to WETH conversion | All | | UNWRAP | WETH to ETH conversion | All |
See Routing Types for the complete list including DUTCH_V3, DUTCH_LIMIT, LIMIT_ORDER, BRIDGE, and QUICKROUTE.
Best for: Frontends, backends, scripts. Handles routing optimization automatically.
Base URL: https://trade-api.gateway.uniswap.org/v1
Authentication: x-api-key: <your-api-key> header required
Getting an API Key: The Trading API requires an API key for authentication. Visit the Uniswap Developer Portal to register and obtain your API key. Keys are typically available for immediate use after registration. Include it as an x-api-key header in all API requests.
Required Headers — Include these in ALL Trading API requests:
Content-Type: application/json
x-api-key: <your-api-key>
x-universal-router-version: 2.0
3-Step Flow:
1. POST /check_approval -> Check if token is approved
2. POST /quote -> Get executable quote with routing
3. POST /swap -> Get transaction to sign and submit
See the Trading API Reference section below for complete documentation.
Best for: Direct control over transaction construction.
Installation:
npm install @uniswap/universal-router-sdk @uniswap/sdk-core @uniswap/v3-sdk
Key Pattern:
import { SwapRouter } from '@uniswap/universal-router-sdk';
const { calldata, value } = SwapRouter.swapCallParameters(trade, options);
See the Universal Router Reference section below for complete documentation.
Best for: On-chain integrations, DeFi composability.
Interface: Call execute() on Universal Router with encoded commands.
See the Universal Router Reference section below for command encoding.
POST /check_approval
Request:
{
"walletAddress": "0x...",
"token": "0x...",
"amount": "1000000000",
"chainId": 1
}
Response:
{
"approval": {
"to": "0x...",
"from": "0x...",
"data": "0x...",
"value": "0",
"chainId": 1
}
}
If approval is null, token is already approved.
POST /quote
Request:
{
"swapper": "0x...",
"tokenIn": "0x...",
"tokenOut": "0x...",
"tokenInChainId": "1",
"tokenOutChainId": "1",
"amount": "1000000000000000000",
"type": "EXACT_INPUT",
"slippageTolerance": 0.5,
"routingPreference": "BEST_PRICE"
}
Note:
tokenInChainIdandtokenOutChainIdmust be strings (e.g.,"1"), not numbers.
Key Parameters:
| Parameter | Description |
| ------------------- | ----------------------------------------------------------------- |
| type | EXACT_INPUT or EXACT_OUTPUT |
| slippageTolerance | 0-100 percentage |
| protocols | Optional: ["V2", "V3", "V4"] |
| routingPreference | BEST_PRICE, FASTEST, CLASSIC |
| autoSlippage | true to auto-calculate slippage (overrides slippageTolerance) |
| urgency | normal or fast — affects UniswapX auction timing |
Response:
{
"routing": "CLASSIC",
"quote": {
"input": { "token": "0x...", "amount": "1000000000000000000" },
"output": { "token": "0x...", "amount": "999000000" },
"slippage": 0.5,
"route": [],
"gasFee": "5000000000000000",
"gasFeeUSD": "0.01",
"gasUseEstimate": "150000"
},
"permitData": {}
}
Display tip: Use
gasFeeUSD(a string with the USD value) for gas cost display. Do not manually convertgasFee(wei) using a hardcoded ETH price — this leads to wildly inaccurate estimates (e.g., ~$87 instead of ~$0.01).
POST /swap
Request - Spread the quote response directly into the body:
// CORRECT: Spread the quote response, strip null fields
const quoteResponse = await fetchQuote(params);
// Remove null permitData/permitTransaction (API rejects null values)
const { permitData, permitTransaction, ...cleanQuote } = quoteResponse;
const swapRequest = {
...cleanQuote,
// Only include permitData if it's a valid object (not null)
...(permitData && { permitData }),
};
// If using Permit2 signature, include BOTH signature and permitData
if (permit2Signature && permitData) {
swapRequest.signature = permit2Signature;
swapRequest.permitData = permitData;
}
Critical: Do NOT wrap the quote in {quote: quoteResponse}. The API expects the quote response fields spread into the request body.
Permit2 Rules:
signature and permitData must BOTH be present, or BOTH be absentpermitData: null - omit the field entirelypermitData: null - strip this before sendingResponse (ready-to-sign transaction):
{
"swap": {
"to": "0x...",
"from": "0x...",
"data": "0x...",
"value": "0",
"chainId": 1,
"gasLimit": "250000"
}
}
Response Validation - Always validate before broadcasting:
function validateSwapResponse(response: SwapResponse): void {
if (!response.swap?.data || response.swap.data === '' || response.swap.data === '0x') {
throw new Error('swap.data is empty - quote may have expired');
}
if (!isAddress(response.swap.to) || !isAddress(response.swap.from)) {
throw new Error('Invalid address in swap response');
}
}
| ID | Chain | ID | Chain | | ---- | -------- | ------- | ----------- | | 1 | Ethereum | 8453 | Base | | 10 | Optimism | 42161 | Arbitrum | | 56 | BNB | 42220 | Celo | | 130 | Unichain | 43114 | Avalanche | | 137 | Polygon | 81457 | Blast | | 196 | X Layer | 7777777 | Zora | | 324 | zkSync | 480 | World Chain | | 1868 | Soneium | 143 | Monad |
| Type | Description | | ----------- | --------------------------------------------- | | CLASSIC | Standard AMM swap through Uniswap pools | | DUTCH_V2 | UniswapX Dutch auction V2 | | DUTCH_V3 | UniswapX Dutch auction V3 | | PRIORITY | MEV-protected priority order (Base, Unichain) | | DUTCH_LIMIT | UniswapX Dutch limit order | | LIMIT_ORDER | Limit order | | WRAP | ETH to WETH conversion | | UNWRAP | WETH to ETH conversion | | BRIDGE | Cross-chain bridge | | QUICKROUTE | Fast approximation quote |
UniswapX availability: UniswapX V2 orders are supported on Ethereum (1), Arbitrum (42161), Base (8453), and Unichain (130). The auction mechanism varies by chain — see UniswapX Auction Types below.
These are common pitfalls discovered during real-world Trading API integration. Follow these rules to avoid on-chain reverts and API errors.
The /swap endpoint expects the quote response spread into the request body, not wrapped in a quote field.
// WRONG - causes "quote does not match any of the allowed types"
const badRequest = {
quote: quoteResponse, // Don't wrap!
signature: '0x...',
};
// CORRECT - spread the quote response
const goodRequest = {
...quoteResponse,
signature: '0x...', // Only if using Permit2
};
The API rejects permitData: null. Always strip null fields before sending:
function prepareSwapRequest(quoteResponse: QuoteResponse, signature?: string): object {
// Strip null values that the API rejects
const { permitData, permitTransaction, ...cleanQuote } = quoteResponse;
const request: Record<string, unknown> = { ...cleanQuote };
// Only include permitData if it's a valid object AND we have a signature
if (signature && permitData && typeof permitData === 'object') {
request.signature = signature;
request.permitData = permitData;
}
return request;
}
When using Permit2 for gasless approvals:
| Scenario | signature | permitData |
| -------------------------- | ----------- | ------------ |
| Standard swap (no Permit2) | Omit | Omit |
| Permit2 swap | Required | Required |
| Invalid | Present | Missing |
| Invalid | Missing | Present |
| Invalid (API error) | Any | null |
Always validate the swap response before sending to the blockchain:
import { isAddress, isHex } from 'viem';
function validateSwapBeforeBroadcast(swap: SwapTransaction): void {
// 1. data must be non-empty hex
if (!swap.data || swap.data === '' || swap.data === '0x') {
throw new Error('swap.data is empty - this will revert on-chain. Re-fetch the quote.');
}
if (!isHex(swap.data)) {
throw new Error('swap.data is not valid hex');
}
// 2. Addresses must be valid
if (!isAddress(swap.to)) {
throw new Error('swap.to is not a valid address');
}
if (!isAddress(swap.from)) {
throw new Error('swap.from is not a valid address');
}
// 3. Value must be present (can be "0" for non-ETH swaps)
if (swap.value === undefined || swap.value === null) {
throw new Error('swap.value is missing');
}
}
When using viem/wagmi in browser environments, you need Node.js polyfills:
Install buffer polyfill:
npm install buffer
Add to your entry file (before other imports):
// src/main.tsx or src/index.tsx
import { Buffer } from 'buffer';
globalThis.Buffer = Buffer;
// Then your other imports
import React from 'react';
import { WagmiProvider } from 'wagmi';
// ...
Vite configuration (vite.config.ts):
export default defineConfig({
define: {
global: 'globalThis',
},
optimizeDeps: {
include: ['buffer'],
},
resolve: {
alias: {
buffer: 'buffer',
},
},
});
Without this setup, you'll see: ReferenceError: Buffer is not defined
The Trading API does not support browser CORS preflight requests — OPTIONS requests return 415 Unsupported Media Type. Direct fetch() calls from a browser will always fail. You must proxy API requests through your own server or dev server.
Vite dev proxy (merge into the same vite.config.ts used for the Buffer polyfill above):
export default defineConfig({
server: {
proxy: {
'/api/uniswap': {
target: 'https://trade-api.gateway.uniswap.org/v1',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api\/uniswap/, ''),
},
},
},
});
Then use /api/uniswap/quote instead of the full URL in your frontend code.
Vercel production proxy (vercel.json):
{
"rewrites": [
{
"source": "/api/uniswap/:path*",
"destination": "https://trade-api.gateway.uniswap.org/v1/:path*"
}
]
}
Cloudflare Pages (public/_redirects):
/api/uniswap/* https://trade-api.gateway.uniswap.org/v1/:splat 200
Next.js (next.config.js):
module.exports = {
async rewrites() {
return [
{
source: '/api/uniswap/:path*',
destination: 'https://trade-api.gateway.uniswap.org/v1/:path*',
},
];
},
};
Without a proxy, you'll see: 415 Unsupported Media Type on preflight or CORS errors in the browser console.
deadline parameter to prevent stale execution/swap returns empty data, the quote likely expiredThe Universal Router is a unified interface for swapping across Uniswap v2, v3, and v4.
function execute(
bytes calldata commands,
bytes[] calldata inputs,
uint256 deadline
) external payable;
Each command is a single byte:
| Bits | Name | Purpose | | ---- | -------- | ----------------------------------- | | 0 | flag | Allow revert (1 = continue on fail) | | 1-2 | reserved | Use 0 | | 3-7 | command | Operation identifier |
| Code | Command | Description | | ---- | ----------------- | ------------------------- | | 0x00 | V3_SWAP_EXACT_IN | v3 swap with exact input | | 0x01 | V3_SWAP_EXACT_OUT | v3 swap with exact output | | 0x08 | V2_SWAP_EXACT_IN | v2 swap with exact input | | 0x09 | V2_SWAP_EXACT_OUT | v2 swap with exact output | | 0x10 | V4_SWAP | v4 swap |
| Code | Command | Description | | ---- | ----------- | -------------------------- | | 0x04 | SWEEP | Clear router token balance | | 0x05 | TRANSFER | Send specific amount | | 0x0b | WRAP_ETH | ETH to WETH | | 0x0c | UNWRAP_WETH | WETH to ETH |
| Code | Command | Description | | ---- | --------------------- | --------------------- | | 0x02 | PERMIT2_TRANSFER_FROM | Single token transfer | | 0x03 | PERMIT2_PERMIT_BATCH | Batch approval | | 0x0a | PERMIT2_PERMIT | Single approval |
import { SwapRouter, UniswapTrade } from '@uniswap/universal-router-sdk'
import { TradeType } from '@uniswap/sdk-core'
// Build trade using v3-sdk or router-sdk
const trade = new RouterTrade({
v3Routes: [...],
tradeType: TradeType.EXACT_INPUT
})
// Get calldata for Universal Router
const { calldata, value } = SwapRouter.swapCallParameters(trade, {
slippageTolerance: new Percent(50, 10000), // 0.5%
recipient: walletAddress,
deadline: Math.floor(Date.now() / 1000) + 1200 // 20 min
})
// Send transaction
const tx = await wallet.sendTransaction({
to: UNIVERSAL_ROUTER_ADDRESS,
data: calldata,
value
})
Permit2 enables signature-based token approvals instead of on-chain approve() calls.
There are two approval paths. Choose based on your integration type:
| Approach | Approve To | Per-Swap Auth | Best For | | --------------------------- | ---------------- | ------------------- | -------------------------------- | | Permit2 (recommended) | Permit2 contract | EIP-712 signature | Frontends with user interaction | | Legacy (direct approve) | Universal Router | None (pre-approved) | Backend services, smart accounts |
Permit2 flow (frontend with user signing):
Legacy flow (backend services, ERC-4337 smart accounts):
Use the Trading API's /check_approval endpoint — it returns the correct approval target based on the routing type.
| Mode | Description | | ----------------- | ------------------------------------------ | | SignatureTransfer | One-time signature, no on-chain state | | AllowanceTransfer | Time-limited allowance with on-chain state |
import { getContract, maxUint256, type Address } from 'viem';
const PERMIT2_ADDRESS = '0x000000000022D473030F116dDEE9F6B43aC78BA3' as const;
// Check if Permit2 approval exists
const allowance = await publicClient.readContract({
address: PERMIT2_ADDRESS,
abi: permit2Abi,
functionName: 'allowance',
args: [userAddress, tokenAddress, spenderAddress],
});
// If not approved, user must approve Permit2 first
if (allowance.amount < requiredAmount) {
const hash = await walletClient.writeContract({
address: tokenAddress,
abi: erc20Abi,
functionName: 'approve',
args: [PERMIT2_ADDRESS, maxUint256],
});
await publicClient.waitForTransactionReceipt({ hash });
}
// Then sign permit for the swap
const permitSignature = await signPermit(...);
UniswapX routes swaps through off-chain fillers who compete to execute orders at better prices than on-chain AMMs. The auction mechanism varies by chain.
Trading API routing type: DUTCH_V2 or DUTCH_V3
Trading API routing type: DUTCH_V2
Trading API routing type: PRIORITY
For more detail, see the UniswapX Auction Types documentation.
For direct Universal Router integration without the Trading API, use the SDK's high-level API.
npm install @uniswap/universal-router-sdk @uniswap/router-sdk @uniswap/sdk-core @uniswap/v3-sdk viem
Use RouterTrade + SwapRouter.swapCallParameters() for automatic command building:
import { SwapRouter } from '@uniswap/universal-router-sdk';
import { Trade as RouterTrade } from '@uniswap/router-sdk';
import { TradeType, Percent } from '@uniswap/sdk-core';
import { Route as V3Route, Pool } from '@uniswap/v3-sdk';
// 1. Fetch pool data (required to construct routes)
// Using viem to read on-chain pool state:
const slot0 = await publicClient.readContract({
address: poolAddress,
abi: [
{
name: 'slot0',
type: 'function',
stateMutability: 'view',
inputs: [],
outputs: [
{ name: 'sqrtPriceX96', type: 'uint160' },
{ name: 'tick', type: 'int24' },
{ name: 'observationIndex', type: 'uint16' },
{ name: 'observationCardinality', type: 'uint16' },
{ name: 'observationCardinalityNext', type: 'uint16' },
{ name: 'feeProtocol', type: 'uint8' },
{ name: 'unlocked', type: 'bool' },
],
},
],
functionName: 'slot0',
});
const liquidity = await publicClient.readContract({
address: poolAddress,
abi: [
{
name: 'liquidity',
type: 'function',
stateMutability: 'view',
inputs: [],
outputs: [{ type: 'uint128' }],
},
],
functionName: 'liquidity',
});
const pool = new Pool(tokenIn, tokenOut, fee, slot0[0].toString(), liquidity.toString(), slot0[1]);
// 2. Build route and trade
const route = new V3Route([pool], tokenIn, tokenOut);
const trade = RouterTrade.createUncheckedTrade({
route,
inputAmount: amountIn,
outputAmount: expectedOut,
tradeType: TradeType.EXACT_INPUT,
});
// 3. Get calldata
const { calldata, value } = SwapRouter.swapCallParameters(trade, {
slippageTolerance: new Percent(50, 10000), // 0.5%
recipient: walletAddress,
deadline: Math.floor(Date.now() / 1000) + 1800,
});
// 4. Execute with viem
const hash = await walletClient.sendTransaction({
to: UNIVERSAL_ROUTER_ADDRESS,
data: calldata,
value: BigInt(value),
});
For custom flows (fee collection, complex routing), use RoutePlanner directly:
import { RoutePlanner, CommandType, ROUTER_AS_RECIPIENT } from '@uniswap/universal-router-sdk';
import { encodeRouteToPath } from '@uniswap/v3-sdk';
// Special addresses
const MSG_SENDER = '0x0000000000000000000000000000000000000001';
const ADDRESS_THIS = '0x0000000000000000000000000000000000000002';
<!-- Content truncated for initial SEO render. Open the source file tab for the full file. -->
npx skills add simulatedrealty/swap-integration下载完整 Skill 目录,包含 SKILL.md 及所有相关文件
Search for places (restaurants, cafes, etc.) via Google Places API proxy on localhost.
Interact with GitHub using the `gh` CLI. Use `gh issue`, `gh pr`, `gh run`, and `gh api` for issues, PRs, CI runs, and advanced queries.
Create or update AgentSkills. Use when designing, structuring, or packaging skills with scripts, references, and assets.
Start voice calls via the OpenClaw voice-call plugin.
Notion API for creating and managing pages, databases, and blocks.
Gemini CLI for one-shot Q&A, summaries, and generation.
Category:developer