mcpField
S
swap-integration

swap-integration

github.com/pancakeswap开发52155 次安装34 周前更新
代码仓库

关于 swap-integration

swap-integration 是为前端、后端和智能合约集成 PancakeSwap 兑换的开发技能。它提供 Routing API、Smart Router SDK 与 Universal Router SDK,以及直接调用 V2 Router 的方案。

主要功能

  1. 01通过 Routing API 获取报价
  2. 02使用 SDK 查找兑换路线并编码交易
  3. 03直接调用 V2 Router 合约
  4. 04涵盖 V2、V3、StableSwap 和 Infinity

适用场景

  1. 01为前端或原型获取兑换报价
  2. 02在后端脚本或交易机器人中构建兑换
  3. 03在智能合约中集成简单的 V2 兑换

MCP 相关

此输入未说明与 MCP 的关系。

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/pancakeswap/pancakeswap-ai --skill swap-integration

安装后在 Claude Code 中输入/swap-integration确认已加载。

详细介绍

兑换集成

将 PancakeSwap 兑换集成到前端、后端和智能合约中。

快速选择指南

正在构建……使用此方法
快速获取报价或制作原型PancakeSwap Routing API(方法 1)
使用 React/Next.js 的前端Smart Router SDK + Universal Router(方法 2)
后端脚本或交易机器人Smart Router SDK + Universal Router(方法 2)
简单的 V2 兑换智能合约直接调用 V2 Router 合约(方法 3)
需要精确的 Universal Router 编码直接使用 Universal Router SDK(方法 2)
通过 Infinity(v4)CL 或 Bin 池兑换Routing API(方法 1),或使用 Infinity 池类型的 Smart Router SDK(方法 2)
协议类型
协议描述费率等级(基点)链
V2经典 AMM(xy=k),恒定乘积公式25(0.25%)仅限 BSC
V3集中流动性(兼容 Uniswap V3)1、5、25、100(0.01–1%)所有链
StableSwap为相关/锚定资产提供低滑点1、4(0.01–0.04%)仅限 BSC
Infinity CLv4 单例 PoolManager 中的集中流动性;支持用于自定义逻辑的钩子与 V3 相同的费率等级BSC、Base
Infinity Bin固定价格区间流动性(类似于 Trader Joe v2);区间紧凑,区间级价格可预测可配置BSC、Base
Mixed在上述任意协议组合之间拆分路由不适用(复合)主要为 BSC

Supported Chains

ChainChain IDV2V3StableSwapInfinity CLInfinity BinRPC
BNB Smart Chain56✅✅✅✅✅https://bsc-dataseed1.binance.org
BNB Smart Chain Testnet97✅❌❌❌❌https://bsc-testnet-rpc.publicnode.com or https://bsc-testnet.drpc.org
Ethereum1❌✅❌❌❌https://cloudflare-eth.com
Arbitrum One42161❌✅❌❌❌https://arb1.arbitrum.io/rpc
Base8453❌✅❌✅✅https://mainnet.base.org
Polygon137❌✅❌❌❌https://polygon-rpc.com
zkSync Era324❌✅❌❌❌https://mainnet.era.zksync.io
Linea59144❌✅❌❌❌https://rpc.linea.build
opBNB204❌✅❌❌❌https://opbnb-mainnet-rpc.bnbchain.org

For testing: Use BSC Testnet (chain ID 97). Get free testnet BNB from https://testnet.bnbchain.org/faucet-smart. The Smart Router SDK does not index testnet pools — use Method 3 (Direct V2 Router) on testnet.

Key Token Addresses

BSC Mainnet (Chain ID: 56)
TokenAddress
WBNB0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c
BUSD0xe9e7CEA3DedcA5984780Bafc599bD69ADd087D56
USDT0x55d398326f99059fF775485246999027B3197955
USDC0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d
CAKE0x0E09FaBB73Bd3Ade0a17ECC321fD13a19e81cE82
ETH0x2170Ed0880ac9A755fd29B2688956BD959F933F8
BTCB0x7130d2A12B9BCbFAe4f2634d864A1Ee1Ce3Ead9c
BSC Testnet (Chain ID: 97)
TokenAddressNotes
WBNB0xae13d989daC2f0dEbFf460aC112a837C89BAa7cd
CAKE0xFa60D973f7642b748046464E165A65B7323b0C73
BUSD0xeD24FC36d5Ee211Ea25A80239Fb8C4Cfd80f12Ee
V2Router0x9Ac64Cc6e4415144C455BD8E4837Fea55603e5c3PancakeSwap testnet
Universal Router Addresses
ChainChain IDUniversal Router Address
BNB Smart Chain560x3fC91A3afd70395Cd496C647d5a6CC9D4B2b7FAD
Ethereum10x3fC91A3afd70395Cd496C647d5a6CC9D4B2b7FAD
Arbitrum421610x3fC91A3afd70395Cd496C647d5a6CC9D4B2b7FAD
Base84530x198EF79F1F515F02dFE9e3115eD9fC07183f02fC
Polygon1370x3fC91A3afd70395Cd496C647d5a6CC9D4B2b7FAD
zkSync Era3240x3fC91A3afd70395Cd496C647d5a6CC9D4B2b7FAD
Linea591440x3fC91A3afd70395Cd496C647d5a6CC9D4B2b7FAD
opBNB2040x3fC91A3afd70395Cd496C647d5a6CC9D4B2b7FAD

方法 1:PancakeSwap Routing API(最简单)

适用于:快速获取报价、原型开发,以及不想自行管理链上池数据的场景。无需安装 SDK。

基础 URL:https://router.pancakeswap.finance/v0/quote

获取报价
# Exact input: 1 BNB → CAKE on BSC
curl -s "https://router.pancakeswap.finance/v0/quote?\
tokenInAddress=BNB\
&tokenInChainId=56\
&tokenOutAddress=0x0E09FaBB73Bd3Ade0a17ECC321fD13a19e81cE82\
&tokenOutChainId=56\
&amount=1000000000000000000\
&type=exactIn\
&maxHops=3\
&maxSplits=4" | jq '{
  amountOut: .trade.outputAmount,
  priceImpact: .trade.priceImpact,
  route: [.trade.routes[].type]
}'
API 参数
参数类型描述
tokenInAddressstring输入代币地址,或原生代币 "BNB" / "ETH"
tokenInChainIdnumber输入链 ID
tokenOutAddressstring输出代币地址
tokenOutChainIdnumber输出链 ID
amountstring原始单位的数量(18 位小数代币的单位为 wei)
typestring"exactIn" 或 "exactOut"
maxHopsnumber每条路由的最大跳数(默认值:3)
maxSplitsnumber路由拆分的最大数量(默认值:4)

Infinity(v4)支持:Routing API 会自动将 BSC 和 Base 上的 Infinity CL 和 Infinity Bin 池纳入考量,同时也会考虑 V2/V3/StableSwap。无需额外参数,路由器会从所有池类型中选择最佳路由。

TypeScript 获取示例
interface PancakeRouteQuote {
  trade: {
    inputAmount: string
    outputAmount: string
    priceImpact: string
    routes: Array<{ type: 'V2' | 'V3' | 'STABLE' | 'MIXED'; pools: unknown[] }>
    blockNumber: number
  }
}

async function getQuote(params: {
  tokenIn: string
  tokenOut: string
  chainId: number
  amount: bigint
  type: 'exactIn' | 'exactOut'
}): Promise<PancakeRouteQuote> {
  const url = new URL('https://router.pancakeswap.finance/v0/quote')
  url.searchParams.set('tokenInAddress', params.tokenIn)
  url.searchParams.set('tokenInChainId', String(params.chainId))
  url.searchParams.set('tokenOutAddress', params.tokenOut)
  url.searchParams.set('tokenOutChainId', String(params.chainId))
  url.searchParams.set('amount', String(params.amount))
  url.searchParams.set('type', params.type)
  url.searchParams.set('maxHops', '3')
  url.searchParams.set('maxSplits', '4')

  const res = await fetch(url.toString())
  if (!res.ok) throw new Error(`Routing API error: ${res.status} ${await res.text()}`)
  return res.json()
}

// Usage
const quote = await getQuote({
  tokenIn: 'BNB',
  tokenOut: '0x0E09FaBB73Bd3Ade0a17ECC321fD13a19e81cE82', // CAKE
  chainId: 56,
  amount: BigInt('1000000000000000000'), // 1 BNB
  type: 'exactIn',
})

console.log('Output:', quote.trade.outputAmount, 'CAKE (raw)')
console.log('Price impact:', quote.trade.priceImpact, '%')

报价时效性:广播交易前,如果报价已超过约 15 秒,请重新获取。过期报价经常会因 INSUFFICIENT_OUTPUT_AMOUNT 而失败。


方法 2:Smart Router SDK + Universal Router SDK

适用于:需要完全通过代码控制路由和交易编码的前端与后端。完全在链上运行,不依赖外部 API。

安装
npm install @pancakeswap/smart-router @pancakeswap/sdk @pancakeswap/v3-sdk @pancakeswap/universal-router-sdk [email protected]
软件包作用
软件包作用
@pancakeswap/smart-router获取池信息 + 寻找最佳路由
@pancakeswap/sdk核心类型:Token、CurrencyAmount、Percent 等
@pancakeswap/v3-sdkV3 专用类型:FeeAmount、池编码
@pancakeswap/universal-router-sdk为 Universal Router 合约编码 calldata
[email protected]以太坊客户端(读取、写入、签名)——为确保兼容 PancakeSwap,请固定使用此版本
第 1 步:设置 Viem 客户端
import { createPublicClient, createWalletClient, http } from 'viem'
import { bsc } from 'viem/chains'
import { privateKeyToAccount } from 'viem/accounts'

const publicClient = createPublicClient({
  chain: bsc,
  transport: http('https://bsc-dataseed1.binance.org'),
})

const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`)
const walletClient = createWalletClient({
  account,
  chain: bsc,
  transport: http('https://bsc-dataseed1.binance.org'),
})
第 2 步:定义代币
import { ChainId, Token } from '@pancakeswap/sdk'
import { Native } from '@pancakeswap/swap-sdk-evm'

const chainId = ChainId.BSC // 56

// Native BNB (no address)
const BNB = Native.onChain(chainId)

// ERC-20 tokens
const CAKE = new Token(
  chainId,
  '0x0E09FaBB73Bd3Ade0a17ECC321fD13a19e81cE82',
  18,
  'CAKE',
  'PancakeSwap Token',
)

const USDT = new Token(
  chainId,
  '0x55d398326f99059fF775485246999027B3197955',
  18,
  'USDT',
  'Tether USD',
)
第 3 步:获取候选池
import { SmartRouter, PoolType } from '@pancakeswap/smart-router'
import { TradeType } from '@pancakeswap/sdk'
import { CurrencyAmount } from '@pancakeswap/swap-sdk-core'

const amountIn = CurrencyAmount.fromRawAmount(
  BNB,
  BigInt('1000000000000000000'), // 1 BNB
)

// Fetch all relevant pool types in parallel
const [v2Pools, v3Pools, stablePools] = await Promise.all([
  SmartRouter.getV2CandidatePools({
    onChainProvider: () => publicClient,
    currencyA: BNB,
    currencyB: CAKE,
  }),
  SmartRouter.getV3CandidatePools({
    onChainProvider: () => publicClient,
    subgraphProvider: undefined, // optional — speeds up pool discovery
    currencyA: BNB,
    currencyB: CAKE,
  }),
  SmartRouter.getStableCandidatePools({
    onChainProvider: () => publicClient,
    currencyA: BNB,
    currencyB: CAKE,
  }),
])

const pools = [...v2Pools, ...v3Pools, ...stablePools]

性能提示:如果你正在构建 UI,可以考虑将池信息缓存 30–60 秒,并仅在用户更改代币或链时重新获取。

Infinity(v4)池访问:Smart Router SDK 的 Infinity 池集成功能仍在持续完善。若要可靠地访问 Infinity CL 和 Infinity Bin 池,目前请使用方法 1(Routing API)——它会自动纳入所有池类型,包括 BSC 和 Base 上的 Infinity 池,无需额外设置。

第 4 步:寻找最佳交易
const trade = await SmartRouter.getBestTrade(amountIn, CAKE, TradeType.EXACT_INPUT, {
  gasPriceWei: () => publicClient.getGasPrice(),
  maxHops: 3,
  maxSplits: 4,
  poolProvider: SmartRouter.createStaticPoolProvider(pools),
  quoteProvider: SmartRouter.createQuoteProvider({
    onChainProvider: () => publicClient,
  }),
  allowedPoolTypes: [PoolType.V2, PoolType.V3, PoolType.STABLE],
})

// Always check price impact before proceeding
if (parseFloat(trade.priceImpact.toSignificant(4)) > 2) {
  console.warn(`⚠️  High price impact: ${trade.priceImpact.toSignificant(4)}%`)
}

console.log('Output:', trade.outputAmount.toSignificant(6), CAKE.symbol)
console.log('Route:', trade.routes.map((r) => r.type).join(' + '))
第 5 步:授权代币

如果输入币种是原生 BNB/ETH,请跳过此步骤——原生币种无需授权。

import { erc20Abi } from 'viem'
import {
  PancakeSwapUniversalRouter,
  getUniversalRouterAddress,
} from '@pancakeswap/universal-router-sdk'

const PERMIT2_ADDRESS = '0x000000000022D473030F116dDEE9F6B43aC78BA3' as const

async function ensureTokenApproved(
  tokenAddress: `0x${string}`,
  owner: `0x${string}`,
  chainId: number,
) {
  // Step 1: Approve Permit2 contract (one-time per token, per wallet)
  const permit2Allowance = await publicClient.readContract({
    address: tokenAddress,
    abi: erc20Abi,
    functionName: 'allowance',
    args: [owner, PERMIT2_ADDRESS],
  })

  const MAX_UINT256 = BigInt('0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff')
  if (permit2Allowance < MAX_UINT256 / 2n) {
    const hash = await walletClient.writeContract({
      address: tokenAddress,
      abi: erc20Abi,
      functionName: 'approve',
      args: [PERMIT2_ADDRESS, MAX_UINT256],
    })
    await publicClient.waitForTransactionReceipt({ hash })
    console.log('✅ Permit2 approved')
  }

  // Step 2: Approve the Universal Router via Permit2 (per-swap, via signature OR transaction)
  // The Universal Router SDK handles this via inputTokenPermit in options.
  // For simplicity, approve the Universal Router directly instead:
  const routerAddress = getUniversalRouterAddress(chainId) as `0x${string}`
  const routerAllowance = await publicClient.readContract({
    address: tokenAddress,
    abi: erc20Abi,
    functionName: 'allowance',
    args: [owner, routerAddress],
  })

  if (routerAllowance < MAX_UINT256 / 2n) {
    const hash = await walletClient.writeContract({
      address: tokenAddress,
      abi: erc20Abi,
      functionName: 'approve',
      args: [routerAddress, MAX_UINT256],
    })
    await publicClient.waitForTransactionReceipt({ hash })
    console.log('✅ Universal Router approved')
  }
}
第 6 步:编码并发送交易
import {
  PancakeSwapUniversalRouter,
  getUniversalRouterAddress,
} from '@pancakeswap/universal-router-sdk'
import { Percent } from '@pancakeswap/sdk'

const slippage = new Percent(50, 10000) // 0.5%
const deadline = BigInt(Math.floor(Date.now() / 1000) + 60 * 20) // 20 min

// Encode the swap calldata
const { calldata, value } = PancakeSwapUniversalRouter.swapERC20CallParameters(trade, {
  slippageTolerance: slippage,
  recipient: account.address,
  deadlineOrPreviousBlockhash: deadline,
})

// Send the transaction
const hash = await walletClient.sendTransaction({
  to: getUniversalRouterAddress(chainId) as `0x${string}`,
  data: calldata as `0x${string}`,
  value: BigInt(value), // Non-zero only when input is native BNB/ETH
  gas: 400000n, // Overestimate — unspent gas is refunded
})

const receipt = await publicClient.waitForTransactionReceipt({ hash })
if (receipt.status === 'reverted') {
  throw new Error(`Swap reverted: ${receipt.transactionHash}`)
}
console.log('✅ Swap confirmed:', receipt.transactionHash)
精确输出兑换

如需兑换为指定的输出数量(例如,“我想要正好 100 USDT”):

const amountOut = CurrencyAmount.fromRawAmount(
  USDT,
  BigInt('100000000000000000000'), // 100 USDT (18 decimals)
)

const trade = await SmartRouter.getBestTrade(
  amountOut,
  BNB, // currencyIn — note: swapped argument order for EXACT_OUTPUT
  TradeType.EXACT_OUTPUT,
  {
    gasPriceWei: () => publicClient.getGasPrice(),
    maxHops: 3,
    maxSplits: 4,
    poolProvider: SmartRouter.createStaticPoolProvider(pools),
    quoteProvider: SmartRouter.createQuoteProvider({ onChainProvider: () => publicClient }),
  },
)

console.log('Max BNB to spend:', trade.inputAmount.toSignificant(6))

// Encode with maximumAmountIn applied automatically via slippageTolerance
const { calldata, value } = PancakeSwapUniversalRouter.swapERC20CallParameters(trade, {
  slippageTolerance: new Percent(50, 10000),
  recipient: account.address,
  deadlineOrPreviousBlockhash: deadline,
})

方法 3:直接使用 V2 Router 合约

适用于:简单的 BSC 兑换、Solidity 集成,或不想依赖任何 SDK 的场景。仅支持 V2 池,不支持 V3 或 StableSwap。

V2 Router 地址(BSC 主网)
0x10ED43C718714eb63d5aA57B78B54704E256024E
V2 Router ABI(子集)
const PANCAKE_V2_ROUTER_ABI = [
  {
    name: 'swapExactETHForTokens',
    type: 'function',
    stateMutability: 'payable',
    inputs: [
      { name: 'amountOutMin', type: 'uint256' },
      { name: 'path', type: 'address[]' },
      { name: 'to', type: 'address' },
      { name: 'deadline', type: 'uint256' },
    ],
    outputs: [{ name: 'amounts', type: 'uint256[]' }],
  },
  {
    name: 'swapExactTokensForETH',
    type: 'function',
    stateMutability: 'nonpayable',
    inputs: [
      { name: 'amountIn', type: 'uint256' },
      { name: 'amountOutMin', type: 'uint256' },
      { name: 'path', type: 'address[]' },
      { name: 'to', type: 'address' },
      { name: 'deadline', type: 'uint256' },
    ],
    outputs: [{ name: 'amounts', type: 'uint256[]' }],
  },
  {
    name: 'swapExactTokensForTokens',
    type: 'function',
    stateMutability: 'nonpayable',
    inputs: [
      { name: 'amountIn', type: 'uint256' },
      { name: 'amountOutMin', type: 'uint256' },
      { name: 'path', type: 'address[]' },
      { name: 'to', type: 'address' },
      { name: 'deadline', type: 'uint256' },
    ],
    outputs: [{ name: 'amounts', type: 'uint256[]' }],
  },
  // Fee-on-transfer variant (SafeMoon-style tokens)
  {
    name: 'swapExactTokensForTokensSupportingFeeOnTransferTokens',
    type: 'function',
    stateMutability: 'nonpayable',
    inputs: [
      { name: 'amountIn', type: 'uint256' },
      { name: 'amountOutMin', type: 'uint256' },
      { name: 'path', type: 'address[]' },
      { name: 'to', type: 'address' },
      { name: 'deadline', type: 'uint256' },
    ],
    outputs: [],
  },
  {
    name: 'getAmountsOut',
    type: 'function',
    stateMutability: 'view',
    inputs: [
      { name: 'amountIn', type: 'uint256' },
      { name: 'path', type: 'address[]' },
    ],
    outputs: [{ name: 'amounts', type: 'uint256[]' }],
  },
] as const

const V2_ROUTER = '0x10ED43C718714eb63d5aA57B78B54704E256024E' as const
const WBNB = '0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c' as const
const CAKE = '0x0E09FaBB73Bd3Ade0a17ECC321fD13a19e81cE82' as const
V2 兑换示例(BNB → CAKE)
import { parseEther } from 'viem'

const slippageBps = 50n // 0.5%
const deadline = BigInt(Math.floor(Date.now() / 1000) + 60 * 20)

// 1. Get quote
const amounts = await publicClient.readContract({
  address: V2_ROUTER,
  abi: PANCAKE_V2_ROUTER_ABI,
  functionName: 'getAmountsOut',
  args: [parseEther('0.1'), [WBNB, CAKE]],
})
const expectedOut = amounts[1]
const minOut = (expectedOut * (10000n - slippageBps)) / 10000n

// 2. Approve router for the input token (skip for BNB input)

// 3. Execute
const hash = await walletClient.writeContract({
  address: V2_ROUTER,
  abi: PANCAKE_V2_ROUTER_ABI,
  functionName: 'swapExactETHForTokens',
  args: [minOut, [WBNB, CAKE], account.address, deadline],
  value: parseEther('0.1'),
})
V2 多跳兑换(BNB → USDT → CAKE)
// Simply extend the path array
const amounts = await publicClient.readContract({
  address: V2_ROUTER,
  abi: PANCAKE_V2_ROUTER_ABI,
  functionName: 'getAmountsOut',
  args: [parseEther('0.1'), [WBNB, USDT_BSC, CAKE]],
})

代币授权参考

场景需要授权的内容位置
Smart Router / Universal RouterToken → Permit2每种代币授权一次
Smart Router / Universal RouterPermit2 → Universal Router每个会话一次(或使用签名)
Direct V2 RouterToken → V2 Router每种代币授权一次
Native BNB/ETH input无需授权—

V3 费率层级

import { FeeAmount } from '@pancakeswap/v3-sdk'

FeeAmount.LOWEST // 100   bps = 0.01%  — stablecoin pairs (USDT/USDC)
FeeAmount.LOW // 500   bps = 0.05%  — stable-ish pairs
FeeAmount.MEDIUM // 2500  bps = 0.25%  — most standard pairs (default)
FeeAmount.HIGH // 10000 bps = 1%     — exotic or highly volatile pairs

关键实现说明

TypeScript 导入约定

对于仅用作 TypeScript 类型、不会在运行时使用的导入,请使用 import type(或内联 type 修饰符)。这样可以保持打包文件整洁,并避免循环依赖问题。

// ✅ Correct — Currency is only used in a type annotation
import { type Currency, TradeType, Percent } from '@pancakeswap/sdk'
import type { SmartRouterTrade } from '@pancakeswap/smart-router'

// ❌ Wrong — Currency is only a type, don't import it as a value
import { Currency, TradeType, Percent } from '@pancakeswap/sdk'
滑点指南
代币类型建议滑点
稳定币(USDT/USDC/BUSD 交易对)0.01–0.1%
大盘代币(CAKE、BNB、ETH)0.3–0.5%
中小盘代币0.5–2%
转账收费 / 反射代币5–12%
新发行的 meme 代币5–15%

生产环境中绝不要将滑点设为 0%。 每个区块的价格变动都会导致交易回滚。

Native BNB/ETH 处理
  • 将 Native.onChain(chainId) 作为货币传入 — Smart Router 和 Universal Router 会自动处理 WRAP_ETH / UNWRAP_WETH 命令。
  • 对于直接 V2 调用:使用 swapExactETHForTokens,并设置 value: amountIn。
  • 编码后的调用数据中的 value 只有在输入为原生代币时才非零。发送交易时务必传入该值。
转账收费代币(反射代币)
  • 这些代币每次转账都会扣除费用,因此路由器收到的金额会少于 amountIn。
  • 在 V2 上:使用 swapExactTokensForTokensSupportingFeeOnTransferTokens 或 swapExactETHForTokensSupportingFeeOnTransferTokens。
  • Smart Router 会自动检测这些代币并采用相应路由。
  • 滑点始终应设为不低于代币的转账费率(例如,代币转账费率为 5% 时,滑点应设为 ≥5%)。
报价过时
  • 发送前,如果交易报价已超过 15–30 秒,请重新获取。
  • 过时的报价经常会因 INSUFFICIENT_OUTPUT_AMOUNT 而回滚。
Gas 估算
交换类型大约 Gas
V2 单跳~150,000
V3 单跳~180,000
V2+V3 两跳~300,000
混合 3 跳~400,000–600,000
使用 Permit2 签名+~40,000

生产环境中始终使用 publicClient.estimateGas();硬编码数值可能会低估复杂路由的 Gas 消耗。


错误处理

常见回滚原因
错误字符串原因修复方法
INSUFFICIENT_OUTPUT_AMOUNT超出滑点(价格发生变动)提高 slippageTolerance 或重新报价
EXCESSIVE_INPUT_AMOUNT精确输出交换超出滑点提高 slippageTolerance 或重新报价
EXPIREDdeadline 时间戳已过期使用新的截止时间重新获取报价
TRANSFER_FAILED转账收费代币,或使用了错误的方法使用 SupportingFeeOnTransferTokens 变体
STF (SafeTransferFrom failed)代币未授权给路由器或 Permit2先运行 ensureTokenApproved()
TransactionExecutionError一般链上故障使用下方的 publicClient.call() 解码
调试回滚
// Simulate the transaction to get the revert reason
try {
  await publicClient.call({
    to: getUniversalRouterAddress(chainId) as `0x${string}`,
    data: calldata as `0x${string}`,
    value: BigInt(value),
    account: account.address,
  })
} catch (err: unknown) {
  // viem throws with the decoded revert reason
  console.error('Revert reason:', (err as Error).message)
}

前端集成(React + wagmi)

对于 React 前端,可将 wagmi hooks 与 Smart Router SDK 搭配使用:

npm install [email protected] [email protected] @tanstack/react-query
import { useWalletClient, usePublicClient, useChainId } from 'wagmi'
import { useMutation } from '@tanstack/react-query'
import { type Currency, TradeType, Percent } from '@pancakeswap/sdk'
import { CurrencyAmount } from '@pancakeswap/swap-sdk-core'
import { SmartRouter } from '@pancakeswap/smart-router'
import {
  PancakeSwapUniversalRouter,
  getUniversalRouterAddress,
} from '@pancakeswap/universal-router-sdk'

type SwapVariables = { amountIn: bigint; tokenIn: Currency; tokenOut: Currency }

function useSwap() {
  const chainId = useChainId()
  const { data: walletClient } = useWalletClient()
  const publicClient = usePublicClient()

  return useMutation<`0x${string}`, Error, SwapVariables>({
    mutationFn: async ({ amountIn, tokenIn, tokenOut }) => {
      if (!walletClient || !publicClient) throw new Error('Wallet not connected')

      // 1. Fetch pools
      const pools = await fetchPancakePools(publicClient, tokenIn, tokenOut, chainId)

      // 2. Get best trade
      const trade = await SmartRouter.getBestTrade(
        CurrencyAmount.fromRawAmount(tokenIn, amountIn),
        tokenOut,
        TradeType.EXACT_INPUT,
        {
          gasPriceWei: () => publicClient.getGasPrice(),
          maxHops: 3,
          maxSplits: 4,
          poolProvider: SmartRouter.createStaticPoolProvider(pools),
          quoteProvider: SmartRouter.createQuoteProvider({ onChainProvider: () => publicClient }),
        },
      )
      if (!trade) throw new Error('No route found for this token pair')

      // 3. Encode
      const { calldata, value } = PancakeSwapUniversalRouter.swapERC20CallParameters(trade, {
        slippageTolerance: new Percent(50, 10000),
        recipient: walletClient.account.address,
        deadlineOrPreviousBlockhash: BigInt(Math.floor(Date.now() / 1000) + 1200),
      })

      // 4. Send
      return walletClient.sendTransaction({
        to: getUniversalRouterAddress(chainId) as `0x${string}`,
        data: calldata as `0x${string}`,
        value: BigInt(value),
      })
    },
  })
}

完整示例:BNB → CAKE

import { createPublicClient, createWalletClient, http } from 'viem'
import { bsc } from 'viem/chains'
import { privateKeyToAccount } from 'viem/accounts'
import { ChainId, Token, TradeType, Percent } from '@pancakeswap/sdk'
import { Native } from '@pancakeswap/swap-sdk-evm'
import { CurrencyAmount } from '@pancakeswap/swap-sdk-core'
import { SmartRouter, PoolType } from '@pancakeswap/smart-router'
import {
  PancakeSwapUniversalRouter,
  getUniversalRouterAddress,
} from '@pancakeswap/universal-router-sdk'

const chainId = ChainId.BSC
const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`)

const publicClient = createPublicClient({
  chain: bsc,
  transport: http('https://bsc-dataseed1.binance.org'),
})
const walletClient = createWalletClient({
  account,
  chain: bsc,
  transport: http('https://bsc-dataseed1.binance.org'),
})

const BNB = Native.onChain(chainId)
const CAKE = new Token(chainId, '0x0E09FaBB73Bd3Ade0a17ECC321fD13a19e81cE82', 18, 'CAKE')

async function swapBNBforCAKE(bnbAmountWei: bigint) {
  const amountIn = CurrencyAmount.fromRawAmount(BNB, bnbAmountWei)

  // 1. Fetch candidate pools (V2 + V3; stable not relevant for BNB/CAKE)
  const [v2Pools, v3Pools] = await Promise.all([
    SmartRouter.getV2CandidatePools({
      onChainProvider: () => publicClient,
      currencyA: BNB,
      currencyB: CAKE,
    }),
    SmartRouter.getV3CandidatePools({
      onChainProvider: () => publicClient,
      subgraphProvider: undefined,
      currencyA: BNB,
      currencyB: CAKE,
    }),
  ])

  // 2. Find best route
  const trade = await SmartRouter.getBestTrade(amountIn, CAKE, TradeType.EXACT_INPUT, {
    gasPriceWei: () => publicClient.getGasPrice(),
    maxHops: 3,
    maxSplits: 4,
    poolProvider: SmartRouter.createStaticPoolProvider([...v2Pools, ...v3Pools]),
    quoteProvider: SmartRouter.createQuoteProvider({ onChainProvider: () => publicClient }),
    allowedPoolTypes: [PoolType.V2, PoolType.V3],
  })

  const impact = parseFloat(trade.priceImpact.toSignificant(4))
  if (impact > 2) console.warn(`⚠️  High price impact: ${impact}%`)

  console.log(
    `Swapping ${amountIn.toSignificant(4)} BNB → ~${trade.outputAmount.toSignificant(4)} CAKE`,
  )

  // 3. Encode calldata
  const { calldata, value } = PancakeSwapUniversalRouter.swapERC20CallParameters(trade, {
    slippageTolerance: new Percent(50, 10000), // 0.5%
    recipient: account.address,
    deadlineOrPreviousBlockhash: BigInt(Math.floor(Date.now() / 1000) + 1200),
  })

  // 4. Send (no token approval needed — input is native BNB)
  const hash = await walletClient.sendTransaction({
    to: getUniversalRouterAddress(chainId) as `0x${string}`,
    data: calldata as `0x${string}`,
    value: BigInt(value),
    gas: 400000n,
  })

  const receipt = await publicClient.waitForTransactionReceipt({ hash })
  if (receipt.status === 'reverted') throw new Error(`Swap reverted: ${hash}`)

  console.log('✅ Confirmed:', receipt.transactionHash)
  return receipt
}

await swapBNBforCAKE(BigInt('100000000000000000')) // 0.1 BNB

评论

支持 Markdown