Uniswap V3 SwapRouter.swapCallParameters报错:无法解析分数
问题
我按照Uniswap V3官方交易指南实现基础交易功能,已修改函数接收Token入、Token出、输入金额等参数,但执行交易时遇到报错。
相关代码
createTrade函数
export async function createTrade( tin: Token, tout: Token, amountIn: number, ): Promise<TokenTrade> { const poolInfo = await getPoolInfo(tin, tout); const pool = new Pool( tin, // tin tout, //tout FeeAmount.MEDIUM, poolInfo.sqrtPriceX96.toString(), poolInfo.liquidity.toString(), poolInfo.tick, ); const swapRoute = new Route( [pool], tin, // tin tout, //tout ); const amountOut = await getOutputQuote(swapRoute, amountIn, tin); const uncheckedTrade = Trade.createUncheckedTrade({ route: swapRoute, inputAmount: CurrencyAmount.fromRawAmount( tin, //tin fromReadableAmount( amountIn, // amount in tin.decimals, // tin.decimals ).toString(), ), outputAmount: CurrencyAmount.fromRawAmount( tout, //tout JSBI.toNumber(JSBI.BigInt(amountOut)), ), tradeType: TradeType.EXACT_INPUT, }); return uncheckedTrade; }
executeTrade函数
export async function executeTrade( trade: TokenTrade, tin: Token, ): Promise<TransactionState> { const walletAddress = getWalletAddress(); const provider = getProvider(); if (!walletAddress || !provider) { throw new Error('Cannot execute a trade without a connected wallet'); } // 授权路由器花费代币 const tokenApproval = await getTokenTransferApproval(tin); // 授权失败则终止交易 if (tokenApproval !== TransactionState.Sent) { return TransactionState.Failed; } const options: SwapOptions = { slippageTolerance: new Percent(50, 10_000), // 50个基点,即0.50% deadline: Math.floor(Date.now() / 1000) + 60 * 20, // 当前时间后20分钟 recipient: walletAddress, }; const methodParameters = SwapRouter.swapCallParameters([trade], options); const tx = { data: methodParameters.calldata, to: SWAP_ROUTER_ADDRESS, value: methodParameters.value, from: walletAddress, maxFeePerGas: MAX_FEE_PER_GAS, maxPriorityFeePerGas: MAX_PRIORITY_FEE_PER_GAS, }; const res = await sendTransaction(tx); return res; }
调用代码
import { createTrade, executeTrade } from './services/trade'; import { SUPPORTED_CHAINS, Token } from '@uniswap/sdk-core'; import { getCurrencyBalance, wrapETH } from './services/wallet'; import { getProvider, getWalletAddress } from './services/providers'; const run = async function () { const USDC_TOKEN = new Token( SUPPORTED_CHAINS[1], '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', 6, 'USDC', 'USD//C', ); const WETH_TOKEN = new Token( SUPPORTED_CHAINS[1], '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', 18, 'WETH', 'Wrapped Ether', ); console.log(await getCurrencyBalance(getProvider(), getWalletAddress(), WETH_TOKEN)); console.log(await wrapETH(1)); console.log(await getCurrencyBalance(getProvider(), getWalletAddress(), WETH_TOKEN)); const trade = await createTrade(WETH_TOKEN, USDC_TOKEN, 1); const execute = await executeTrade(trade, WETH_TOKEN); console.log(execute); console.log(await getCurrencyBalance(getProvider(), getWalletAddress(), USDC_TOKEN)); }; run();
报错信息
执行到const methodParameters = SwapRouter.swapCallParameters([trade], options);时触发错误:
Error: Could not parse fraction at Function.tryParseFraction (c:\Users\DixSon\Desktop\Frog\periscope\node_modules\.pnpm\@uniswap+sdk-core@4.0.6\node_modules\@uniswap\sdk-core\dist\sdk-core.cjs.development.js:317:11) at Percent2.lessThan (c:\Users\DixSon\Desktop\Frog\periscope\node_modules\.pnpm\@uniswap+sdk-core@4.0.6\node_modules\@uniswap\sdk-core\dist\sdk-core.cjs.development.js:348:32) at Trade2.minimumAmountOut (c:\Users\DixSon\Desktop\Frog\periscope\node_modules\.pnpm\@uniswap+v3-sdk@3.10.0_hardhat@2.17.0\node_modules\@uniswap\v3-sdk\dist\v3-sdk.cjs.development.js:3028:25) at <anonymous> (c:\Users\DixSon\Desktop\Frog\periscope\node_modules\.pnpm\@uniswap+v3-sdk@3.10.0_hardhat@2.17.0\node_modules\@uniswap\v3-sdk\dist\v3-sdk.cjs.development.js:3978:28) at Array.reduce (<anonymous>) at Function.swapCallParameters (c:\Users\DixSon\Desktop\Frog\periscope\node_modules\.pnpm\@uniswap+v3-sdk@3.10.0_hardhat@2.17.0\node_modules\@uniswap\v3-sdk\dist\v3-sdk.cjs.development.js:3977:33) at executeTrade (c:\Users\DixSon\Desktop\Frog\periscope\src\features\transaction\services\trade.ts:110:39) at processTicksAndRejections (node:internal/process/task_queues:96:5) at run (c:\Users\DixSon\Desktop\Frog\periscope\src\features\transaction\run.ts:35:19)
解决方案
错误根源
问题出在createTrade函数创建outputAmount的类型转换上:
amountOut应该是代币原始数值(即按小数位转换后的整数,比如USDC是6位小数,1 USDC对应1000000),且需要以字符串形式传入CurrencyAmount.fromRawAmount- 你当前用
JSBI.toNumber(JSBI.BigInt(amountOut))把它转成了普通数字,若amountOut是带小数的计算结果,转成number会丢失精度或变成非整数,导致后续滑点计算时无法解析分数。
修复步骤
1. 修正outputAmount创建逻辑
直接使用amountOut的字符串形式,不要做多余的类型转换:
const uncheckedTrade = Trade.createUncheckedTrade({ route: swapRoute, inputAmount: CurrencyAmount.fromRawAmount( tin, fromReadableAmount(amountIn, tin.decimals).toString(), ), // 直接传入amountOut的字符串,避免转成number outputAmount: CurrencyAmount.fromRawAmount(tout, amountOut.toString()), tradeType: TradeType.EXACT_INPUT, });
2. 验证getOutputQuote返回值
确保getOutputQuote返回的是代币原始数值(整数),而非可读的浮点数。如果它返回的是可读金额(比如1.23 USDC),需要先转换为原始数值:
const readableAmountOut = await getOutputQuote(swapRoute, amountIn, tin); const rawAmountOut = fromReadableAmount(readableAmountOut, tout.decimals).toString(); // 用rawAmountOut创建outputAmount
3. 金额类型规范
所有涉及代币金额的变量优先用JSBI或字符串存储,避免用普通number导致精度丢失,尤其是处理WETH这类18位小数的代币。
额外说明
- 调用
createTrade时传入的amountIn是可读金额,fromReadableAmount的转换逻辑没问题 - 滑点设置
new Percent(50, 10_000)是正确的(0.5%),无需修改
内容的提问来源于stack exchange,提问作者0xD1x0n
相关产品推荐
相关产品推荐

