You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.14 17:15:24