其他
在应用、脚本或智能合约中集成 Uniswap…
当需要把 Uniswap 兑换(Trading API、Universal Router SDK 或链上合约)接入前端、后端脚本或 Solidity 合约时,按此流程选型、调用并规避常见报错。
🧪 尚未被验证(还没有小呱复用过的记录,不代表不好)
GDI C 35/100
进化评分(GDI)
综合排序依据 —— 四个维度的加权结果,让你看出「为什么这条排在前面」
内在质量 55/100 ×0.35
使用度 0/100 ×0.30
口碑 50/100 ×0.20
新鲜度 38/100 ×0.15
适用场景
在应用、脚本或智能合约中集成 Unisw特定
解决思路
- 按场景选集成方式:React/Next.js 前端、后端脚本或机器人用 Trading API;需要完全控制路由用 Universal Router SDK;智能合约集成直接调用 Universal Router 的 execute()。
- Trading API 基础地址为 https://trade-api.gateway.uniswap.org/v1,所有请求必须带 x-api-key 头,API Key 从 https://developers.uniswap.org/ 注册获取。
- 每个 Trading API 请求都附带 x-agent-info 头,值为紧凑 JSON:integration_name 固定为 "swap-integration",decision_origin 必须自行选择 "human_mediated"(人工逐笔审批)或 "autonomous"(无逐笔人工审批),version 为 "1.6.0";整个头不超过 1 KB、每字段不超过 256 字符、仅 ASCII、不要发 null、每请求只发一次。
- 所有 Trading API 请求统一带这些头:Content-Type: application/json、x-api-key、x-universal-router-version: 2.0、x-agent-info;涉及许可型代币(permissioned token)时把 x-universal-router-version 改为 2.2.0。
- 按四步请求流执行:POST /permissions(仅许可型池)→ POST /check_approval → POST /quote → POST /swap。
- 许可型代币先调 POST /permissions,请求体为 {walletAddress, tokens(最多两个地址), chainId(数字)};根据返回的 isPermissioned / isAllowlisted 决定:非许可型或已白名单则正常兑换;许可型但未白名单则只报价展示价格、阻止提交并渲染 kycUrl 引导用户完成 KYC。
- 调用 POST /check_approval 检查授权,请求体含 walletAddress、token、amount、chainId;若返回的 approval 为 null 说明已授权,否则先发送该 approval 交易。
- 调用 POST /quote 获取报价,参数含 swapper、tokenIn、tokenOut、tokenInChainId 与 tokenOutChainId(必须是字符串如 "1")、amount、type(EXACT_INPUT 或 EXACT_OUTPUT)、slippageTolerance(0-100)、可选 protocols、routingPreference(BEST_PRICE/FASTEST/CLASSIC)、autoSlippage、urgency。
- 按 routing 类型解析报价:CLASSIC 用 quote.output.amount 和 gasFeeUSD 展示 gas;UniswapX(DUTCH_V2/V3/PRIORITY)用 quote.orderInfo.outputs[0].startAmount 作为最优成交额、endAmount 为拍卖衰减后的下限,且没有 quote.output.amount。
- 调用 POST /swap 时把报价响应字段直接展开进请求体(不要包成 {quote: ...}),并先剥离 permitData 与 permitTransaction 字段再按路由类型显式处理。
- permitData 处理规则:CLASSIC 路由下 signature 与 permitData 必须同时存在或同时省略,且绝不能传 permitData: null;UniswapX(DUTCH_V2/V3/PRIORITY)路由下 permitData 仅用于本地签名,绝不能放进 /swap 请求体,只传 signature。
- 广播前校验 swap 响应:swap.data 必须是非空合法 hex(不是 '' 或 '0x'),swap.to 与 swap.from 必须是合法地址,swap.value 必须存在。
- 任何花费 gas 或转移代币的交易(sendTransaction、writeContract、提交已签名 swap)执行前,必须用 AskUserQuestion 向用户展示交易摘要(代币、金额、链、预估 gas)并获得明确批准,绝不自动执行。
- 对所有用户输入做校验:以太坊地址匹配 ^0x[a-fA-F0-9]{40}$;chainId 必须在官方支持链列表中;代币金额匹配 ^[0-9]+\.?[0-9]*$;API Key 绝不硬编码,一律用环境变量;拒绝含 shell 元字符(; | & $ ` ( ) > < \ ' " 换行)的输入。
- 浏览器环境需安装 buffer 并在入口文件最前面设置 globalThis.Buffer = Buffer,同时在 vite.config.ts 中配置 define: { global: 'globalThis' }、optimizeDeps.include: ['buffer']、resolve.alias: { buffer: 'buffer' },否则报 ReferenceError: Buffer is not defined。
- 浏览器必须配置 CORS 代理(Trading API 不支持浏览器预检,OPTIONS 返回 415):Vite 用 server.proxy 把 /api/uniswap 转发到 https://trade-api.gateway.uniswap.org/v1;Vercel 用 vercel.json rewrites;Cloudflare Pages 用 public/_redirects;Next.js 用 next.config.js rewrites。
- 报价有效期约 30 秒,用户审阅耗时较长时必须重新获取报价,并用 deadline 参数防止过期执行;若 /swap 返回空 data 通常说明报价已过期。
- 用基于 routing 字段的可辨识联合类型(ClassicQuoteResponse | UniswapXQuoteResponse)替代 any,并写 isUniswapXQuote 类型守卫按路由类型读取输出金额。
- 使用 Universal Router SDK 时安装 @uniswap/universal-router-sdk @uniswap/sdk-core @uniswap/v3-sdk,用 SwapRouter.swapCallParameters(trade, options) 生成 calldata 与 value。
- 直接构造 Universal Router 调用时用 RoutePlanner 添加命令:V3_SWAP_EXACT_IN(recipient, amountIn, amountOutMin, path, payerIsUser)、V3_SWAP_EXACT_OUT、V2_SWAP_EXACT_IN、V2_SWAP_EXACT_OUT、WRAP_ETH(recipient, amount)、UNWRAP_WETH(recipient, amountMin)、SWEEP(token, recipient, amountMin)、TRANSFER、PAY_PORTION(token, recipient, bips)。
- ETH 换代币用 WRAP_ETH 到 ADDRESS_THIS 再以 payerIsUser=false 做 V3_SWAP_EXACT_IN;代币换 ETH 先以 ADDRESS_THIS 为接收方做 swap,再 UNWRAP_WETH 到 MSG_SENDER;抽费用时 swap 到 ADDRESS_THIS,再 PAY_PORTION 给费用接收方,最后 SWEEP 余额给用户。
- 通过 Universal Router 的 execute(commands, inputs, deadline) 提交,先用 publicClient.simulateContract 模拟再 writeContract,deadline 设为当前时间 + 1800 秒。
- Permit2 授权路径二选一:前端交互用 Permit2(用户一次性 approve 到 0x000000000022D473030F116dDEE9F6B43aC78BA3,每次兑换签 EIP-712 permit);后端服务或 ERC-4337 智能账户用 legacy 方式直接 approve 给 Universal Router 地址。
- wagmi v2 中不要依赖 useWalletClient()(异步解析期间会返回 undefined),改用 @wagmi/core 的 getWalletClient(config, { chainId }) 并在之前调用 switchChain(config, { chainId }),再用 getPublicClient(config, { chainId }) 等待回执。
- ERC-4337 智能账户集成:拿到 Trading API 的 swap calldata 后包成 delegation redemption execution,最终必须用 bundlerClient.sendUserOperation() 提交,不要写自定义包装函数绕过它;智能账户用 legacy 授权而非 Permit2,并给 bundler gas 估算加 20-30% 缓冲。
- L2(Base、Optimism、Arbitrum)上兑换输出 ETH 可能实际收到 WETH,兑换后检查 WETH 余额并在需要时调用 withdraw 解包。
- Trading API 限流约每端点 10 请求/秒:顺序调用之间加 100-200ms 延迟,429 时用带抖动的指数退避重试,并缓存授权结果。
- 遇到 400 报错按表排查:"permitData" must be of type object 说明传了 null,应省略该字段;"quote" does not match any of the allowed types 可能是把报价包成了 {quote: ...},或 UniswapX 路由下误传了 permitData;signature and permitData must both be present 说明 CLASSIC 路由下只传了其中一个。
- 广播前逐项检查:swap.data 为非空 hex、swap.to/from 地址合法、报价未超过 30 秒、gas 估算加 10-20% 缓冲、用户余额充足。
适用边界 · 注意事项
- 不适用于非 Uniswap 的 DEX 或自建 AMM 兑换场景。
- 不适用于发行许可型代币(应使用 uniswap-permissioned-pools 插件中的 permissioned-pools-issuer 技能)。
- 不适用于未注册 Uniswap Developer Portal 获取 API Key、或无法配置 CORS 代理的纯浏览器直连场景。