swap-integration について
swap-integrationは、フロントエンド、バックエンド、スマートコントラクトにPancakeSwapのスワップを組み込むための開発スキルです。Routing API、Smart RouterおよびUniversal Router SDK、V2 Routerの直接呼び出しを解説します。
主な機能
- 01Routing APIで見積もりを取得
- 02SDKでルート検索とトランザクションをエンコード
- 03V2 Routerコントラクトを直接呼び出し
- 04V2、V3、StableSwap、Infinityに対応
活用シーン
- 01フロントエンドやプロトタイプで見積もりを取得
- 02バックエンドスクリプトや取引ボットでスワップを構築
- 03スマートコントラクトにシンプルなV2スワップを統合
MCP 対応
入力にはMCPとの関係について記載されていません。
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ pancakeswap/ pancakeswap-ai --skill swap-integrationインストール後、Claude Code で/swap-integrationを入力して読み込まれたか確認します。
詳細
Swap Integration
フロントエンド、バックエンド、スマートコントラクトに PancakeSwap のスワップを統合します。
Quick Decision Guide
| 構築するもの | 使用する方法 |
|---|---|
| クイック見積もりまたはプロトタイプ | 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) |
Protocol Types
| プロトコル | 説明 | 手数料ティア(bps) | チェーン |
|---|---|---|---|
| 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 CL | v4 のシングルトン PoolManager 内の集中流動性。カスタムロジック用のフックに対応 | V3 と同じティア | BSC、Base |
| Infinity Bin | 固定価格ビン流動性(Trader Joe v2 に類似)。狭いレンジで、ビン単位の価格を予測可能 | 設定可能 | BSC、Base |
| Mixed | 上記のプロトコルを任意に組み合わせた分割ルート | 該当なし(複合) | 主に BSC |
Supported Chains
| Chain | Chain ID | V2 | V3 | StableSwap | Infinity CL | Infinity Bin | RPC |
|---|---|---|---|---|---|---|---|
| BNB Smart Chain | 56 | ✅ | ✅ | ✅ | ✅ | ✅ | https://bsc-dataseed1.binance.org |
| BNB Smart Chain Testnet | 97 | ✅ | ❌ | ❌ | ❌ | ❌ | https://bsc-testnet-rpc.publicnode.com or https://bsc-testnet.drpc.org |
| Ethereum | 1 | ❌ | ✅ | ❌ | ❌ | ❌ | https://cloudflare-eth.com |
| Arbitrum One | 42161 | ❌ | ✅ | ❌ | ❌ | ❌ | https://arb1.arbitrum.io/rpc |
| Base | 8453 | ❌ | ✅ | ❌ | ✅ | ✅ | https://mainnet.base.org |
| Polygon | 137 | ❌ | ✅ | ❌ | ❌ | ❌ | https://polygon-rpc.com |
| zkSync Era | 324 | ❌ | ✅ | ❌ | ❌ | ❌ | https://mainnet.era.zksync.io |
| Linea | 59144 | ❌ | ✅ | ❌ | ❌ | ❌ | https://rpc.linea.build |
| opBNB | 204 | ❌ | ✅ | ❌ | ❌ | ❌ | 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)
| Token | Address |
|---|---|
| WBNB | 0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c |
| BUSD | 0xe9e7CEA3DedcA5984780Bafc599bD69ADd087D56 |
| USDT | 0x55d398326f99059fF775485246999027B3197955 |
| USDC | 0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d |
| CAKE | 0x0E09FaBB73Bd3Ade0a17ECC321fD13a19e81cE82 |
| ETH | 0x2170Ed0880ac9A755fd29B2688956BD959F933F8 |
| BTCB | 0x7130d2A12B9BCbFAe4f2634d864A1Ee1Ce3Ead9c |
BSC Testnet (Chain ID: 97)
| Token | Address | Notes |
|---|---|---|
| WBNB | 0xae13d989daC2f0dEbFf460aC112a837C89BAa7cd | |
| CAKE | 0xFa60D973f7642b748046464E165A65B7323b0C73 | |
| BUSD | 0xeD24FC36d5Ee211Ea25A80239Fb8C4Cfd80f12Ee | |
| V2Router | 0x9Ac64Cc6e4415144C455BD8E4837Fea55603e5c3 | PancakeSwap testnet |
Universal Router Addresses
| Chain | Chain ID | Universal Router Address |
|---|---|---|
| BNB Smart Chain | 56 | 0x3fC91A3afd70395Cd496C647d5a6CC9D4B2b7FAD |
| Ethereum | 1 | 0x3fC91A3afd70395Cd496C647d5a6CC9D4B2b7FAD |
| Arbitrum | 42161 | 0x3fC91A3afd70395Cd496C647d5a6CC9D4B2b7FAD |
| Base | 8453 | 0x198EF79F1F515F02dFE9e3115eD9fC07183f02fC |
| Polygon | 137 | 0x3fC91A3afd70395Cd496C647d5a6CC9D4B2b7FAD |
| zkSync Era | 324 | 0x3fC91A3afd70395Cd496C647d5a6CC9D4B2b7FAD |
| Linea | 59144 | 0x3fC91A3afd70395Cd496C647d5a6CC9D4B2b7FAD |
| opBNB | 204 | 0x3fC91A3afd70395Cd496C647d5a6CC9D4B2b7FAD |
方法 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 パラメーター
| パラメーター | 型 | 説明 |
|---|---|---|
tokenInAddress | string | 入力トークンのアドレス、またはネイティブ通貨の場合は "BNB" / "ETH" |
tokenInChainId | number | 入力チェーン ID |
tokenOutAddress | string | 出力トークンのアドレス |
tokenOutChainId | number | 出力チェーン ID |
amount | string | 最小単位での数量(18 桁小数のトークンでは wei) |
type | string | "exactIn" または "exactOut" |
maxHops | number | ルートあたりの最大ホップ数(デフォルト:3) |
maxSplits | number | ルートの最大分割数(デフォルト:4) |
Infinity(v4)のサポート:Routing API は、V2/V3/StableSwap に加えて、BSC と Base の Infinity CL プールおよび Infinity Bin プールも自動的に考慮します。追加パラメーターは不要です。ルーターがすべてのプールタイプから最適なルートを選択します。
TypeScript の Fetch の例
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-sdk | V3 固有の型:FeeAmount、プールのエンコード |
@pancakeswap/universal-router-sdk | Universal Router コントラクト向け calldata のエンコード |
[email protected] | Ethereum クライアント(読み取り、書き込み、署名)— 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)
正確な出力額でのスワップ
正確な出力額(例:「USDT をちょうど 100 個受け取りたい」)と交換するには:
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 Router | トークン → Permit2 | トークンごとに1回 |
| Smart Router / Universal Router | Permit2 → Universal Router | セッションごと(または署名を使用) |
| Direct V2 Router | トークン → V2 Router | トークンごとに1回 |
| ネイティブBNB/ETHの入力 | 承認不要 | — |
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% |
| 新しいミームトークン | 5–15% |
本番環境ではスリッページを0%に設定しないでください。 ブロックごとの価格変動により、トランザクションがリバートします。
ネイティブBNB/ETHの処理
- 通貨として
Native.onChain(chainId)を渡してください。Smart RouterとUniversal Routerは、WRAP_ETH/UNWRAP_WETHコマンドを自動的に処理します。 - V2を直接呼び出す場合は、
value: amountInを指定してswapExactETHForTokensを使用してください。 - エンコードされたcalldataの
valueがゼロ以外になるのは、入力がネイティブ通貨の場合のみです。トランザクション送信時には必ず渡してください。
送金手数料ありトークン(リフレクショントークン)
- これらのトークンは送金ごとに手数料が差し引かれるため、ルーターが受け取る数量は
amountInより少なくなります。 - V2では、
swapExactTokensForTokensSupportingFeeOnTransferTokensまたはswapExactETHForTokensSupportingFeeOnTransferTokensを使用してください。 - Smart Routerはこれらのトークンを自動的に検出し、適切なルートを選択します。
- スリッページは、必ずトークンの送金手数料以上に設定してください(例:トークンの手数料が5%の場合は、スリッページを5%以上に設定)。
見積もりの有効期限切れ
- 送信前に、取得から15~30秒以上経過した取引見積もりは再取得してください。
- 古い見積もりでは、
INSUFFICIENT_OUTPUT_AMOUNTによるリバートが頻繁に発生します。
ガス見積もり
| スワップの種類 | 概算ガス量 |
|---|---|
| V2のシングルホップ | ~150,000 |
| V3のシングルホップ | ~180,000 |
| V2+V3の2ホップ | ~300,000 |
| 混合3ホップ | ~400,000–600,000 |
| Permit2の署名あり | +~40,000 |
本番環境では必ずpublicClient.estimateGas()を使用してください。固定値では、複雑なルートでガス量を過小評価する可能性があります。
エラー処理
よくあるリバート理由
| エラー文字列 | 原因 | 修正方法 |
|---|---|---|
INSUFFICIENT_OUTPUT_AMOUNT | スリッページ超過(価格変動) | slippageToleranceを増やすか、見積もりを再取得する |
EXCESSIVE_INPUT_AMOUNT | 正確な出力数量を指定したスワップでのスリッページ超過 | slippageToleranceを増やすか、見積もりを再取得する |
EXPIRED | deadlineのタイムスタンプが過去になっている | 新しい期限で見積もりを再取得する |
TRANSFER_FAILED | 送金手数料ありトークン、またはメソッドの誤り | SupportingFeeOnTransferTokensのバリアントを使用する |
STF(SafeTransferFromの失敗) | トークンがルーターまたは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 のフロントエンドでは、Smart Router SDK と併せて wagmi のフックを使用します。
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