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

使用Uniswap V2 SDK在Base Layer2执行Swap时合约回滚报错求助

Uniswap V2 SDK在Base Layer2执行Swap时合约执行Revert问题排查

在Node.js环境中使用Uniswap V2 SDK执行Swap操作时,触发合约执行回滚错误:Warning! Error encountered during contract execution [execution reverted] (Base Layer2),怀疑问题出在数据编码或交易配置上。

初始代码

async function createPair() {
  const pairAddress = Pair.getAddress(TOKEN, WETH9[TOKEN.chainId]);

  const pairContract = new ethers.Contract(pairAddress, UniswapV2PoolAbi, provider);

  const reserves = await pairContract['getReserves']();
  const [reserve0, reserve1] = reserves;

  const reserve0Parsed = BigInt(reserve0.toString()).toString();
  const reserve1Parsed = BigInt(reserve1.toString()).toString();

  const tokens = [TOKEN, WETH9[TOKEN.chainId]];
  const [token0, token1] = tokens[0].sortsBefore(tokens[1]) ? tokens : [tokens[1], tokens[0]];

  const pair = new Pair(
    CurrencyAmount.fromRawAmount(token0, reserve0Parsed),
    CurrencyAmount.fromRawAmount(token1, reserve1Parsed)
  );

  return pair;
}

async function swap() {
  const amountIn = ethers.parseEther('0.00001').toString();
  const slippageTolerance = new Percent('50', '10000');
  const to = WALLET_ADDRESS;
  const signer = new ethers.Wallet(WALLET_SECRET);
  const account = signer.connect(provider);
  const command = '0x08';
  const deadline = 2 * 10 ** 10;
  const fromEoa = true;

  const uniswap = new ethers.Contract(
    swapRouterAddress,
    [
      'function execute(bytes calldata commands, bytes[] calldata inputs, uint256 deadline) external payable returns (bytes[] memory outputs)',
    ],
    account
  );

  const pair = await createPair();
  const route = new Route([pair], WETH9[TOKEN.chainId], TOKEN);
  const trade = new Trade(
    route,
    CurrencyAmount.fromRawAmount(WETH9[TOKEN.chainId], amountIn),
    TradeType.EXACT_INPUT
  );

  const amountOutMin = trade.minimumAmountOut(slippageTolerance).toExact();

  const amoutOut = ethers.parseEther(amountOutMin);

  const pathData = ethers.solidityPacked(
    ['address', 'address'],
    [WETH9[ChainId.BASE].address, TOKEN.address]
  );

  const v2Calldata = ethers.AbiCoder.defaultAbiCoder().encode(
    ['address', 'uint256', 'uint256', 'bytes', 'bool'],
    [to, amountIn, amoutOut, pathData, fromEoa]
  );

  const tx = await uniswap.execute(command, [v2Calldata], deadline, {
    value: amountIn,
    gasPrice: ethers.parseUnits('0.15', 'gwei'),
    gasLimit: 100000,
    chainId: ChainId.BASE,
    from: WALLET_ADDRESS,
  });

  console.log(`Transaction hash: ${tx.hash}`);
  const receipt = await tx.wait();

  console.log(`Transaction was mined in block ${receipt.blockNumber}`);
}

代码逻辑概述

  • 导入Uniswap V2 SDK组件与ethers.js
  • 通过QuickNode HTTP端点配置provider
  • 定义钱包地址与密钥
  • 声明swapRouterAddress与目标TOKEN
  • 实现swap函数,包含交易参数设置、合约实例创建、交易细节计算、数据编码及execute方法调用

更新后代码(仍报错)

async function swap() {
const swapRouterAddress = '0x3fc91a3afd70395cd496c647d5a6cc9d4b2b7fad';
const TOKEN = new Token(ChainId.BASE, '0xBC45647eA894030a4E9801Ec03479739FA2485F0', 18);
const amountIn = ethers.parseEther('0.00001').toString();
  const to = WALLET_ADDRESS;
  const signer = new ethers.Wallet(WALLET_SECRET);
  const account = signer.connect(provider);
  const command = '0x0b08';
  const deadline = 2 * 10 ** 10;
  const fromEoa = false;
  const FEE = 500;

  const uniswap = new ethers.Contract(
    swapRouterAddress,
    [
      'function execute(bytes calldata commands, bytes[] calldata inputs) external payable returns (bytes[] memory outputs)',
    ],
    account
  );

  const pathData = ethers.AbiCoder.defaultAbiCoder().encode(
    ['address', 'address'],
    [WETH9[ChainId.BASE].address, TOKEN.address]
  );

  const wrap_calldata = ethers.AbiCoder.defaultAbiCoder().encode(
    ['address', 'uint256'],
    [swapRouterAddress, amountIn]
  );

  const v2Calldata = ethers.AbiCoder.defaultAbiCoder().encode(
    ['address', 'uint256', 'uint256', 'bytes', 'bool'],
    [to, amountIn, 0, pathData, fromEoa]
  );

  const tx = await uniswap.execute(command, [wrap_calldata, v2Calldata], {
    value: amountIn,
    gasPrice: ethers.parseUnits('0.15', 'gwei'),
    gasLimit: 150000,
    chainId: ChainId.BASE,
    from: WALLET_ADDRESS,
  });

  console.log(`Transaction hash: ${tx.hash}`);
  const receipt = await tx.wait();

  console.log(`Transaction was mined in block ${receipt.blockNumber}`);
}

错误核心原因

  1. 合约ABI与调用参数不匹配:更新后代码移除了execute函数的deadline参数,但Uniswap V2 Router的execute签名要求该参数,导致调用格式错误触发revert。
  2. 路径编码错误:Uniswap V2的path需要用solidityPacked直接拼接地址,而非ABI编码数组,更新后代码的路径编码方式不符合合约要求。
  3. 命令逻辑冗余且参数错误:手动添加0x0b(WRAP_ETH)命令,但参数编码不符合合约规范,且Router的0x08命令可自动处理ETH转WETH,无需额外操作。
  4. amountOutMin设置为0:违反Uniswap滑点保护逻辑,部分合约会拒绝此类参数。
  5. Gas Limit不足:Base链上Swap操作需要更高的Gas上限,手动设置的150000可能不足以覆盖执行成本。

修复后的代码

async function createPair() {
  const pairAddress = Pair.getAddress(TOKEN, WETH9[TOKEN.chainId]);
  const pairContract = new ethers.Contract(pairAddress, UniswapV2PoolAbi, provider);
  const reserves = await pairContract.getReserves();
  const [reserve0, reserve1] = reserves;

  const tokens = [TOKEN, WETH9[TOKEN.chainId]];
  const [token0, token1] = tokens[0].sortsBefore(tokens[1]) ? tokens : [tokens[1], tokens[0]];

  const pair = new Pair(
    CurrencyAmount.fromRawAmount(token0, reserve0.toString()),
    CurrencyAmount.fromRawAmount(token1, reserve1.toString())
  );

  return pair;
}

async function swap() {
  const swapRouterAddress = '0x3fc91a3afd70395cd496c647d5a6cc9d4b2b7fad';
  const TOKEN = new Token(ChainId.BASE, '0xBC45647eA894030a4E9801Ec03479739FA2485F0', 18);
  const amountIn = ethers.parseEther('0.00001');
  const slippageTolerance = new Percent('50', '10000');
  const to = WALLET_ADDRESS;
  const signer = new ethers.Wallet(WALLET_SECRET);
  const account = signer.connect(provider);
  const command = '0x08';
  // 设置合理的过期时间:当前时间+30分钟
  const deadline = Math.floor(Date.now() / 1000) + 1800;
  const fromEoa = true;

  const uniswap = new ethers.Contract(
    swapRouterAddress,
    [
      'function execute(bytes calldata commands, bytes[] calldata inputs, uint256 deadline) external payable returns (bytes[] memory outputs)',
    ],
    account
  );

  // 计算交易路径与最小输出量
  const pair = await createPair();
  const route = new Route([pair], WETH9[ChainId.BASE], TOKEN);
  const trade = new Trade(
    route,
    CurrencyAmount.fromRawAmount(WETH9[ChainId.BASE], amountIn.toString()),
    TradeType.EXACT_INPUT
  );
  const amountOutMin = trade.minimumAmountOut(slippageTolerance).quotient;

  // 正确编码路径:用solidityPacked拼接地址
  const pathData = ethers.solidityPacked(
    ['address', 'address'],
    [WETH9[ChainId.BASE].address, TOKEN.address]
  );

  // 编码V2_EXACT_INPUT参数
  const v2Calldata = ethers.AbiCoder.defaultAbiCoder().encode(
    ['address', 'uint256', 'uint256', 'bytes', 'bool'],
    [to, amountIn, amountOutMin, pathData, fromEoa]
  );

  // 发送交易,设置足够的Gas上限
  const tx = await uniswap.execute(command, [v2Calldata], deadline, {
    value: amountIn,
    gasPrice: ethers.parseUnits('0.15', 'gwei'),
    gasLimit: ethers.toBigInt(300000),
    chainId: ChainId.BASE,
    from: WALLET_ADDRESS,
  });

  console.log(`Transaction hash: ${tx.hash}`);
  const receipt = await tx.wait();
  console.log(`Transaction was mined in block ${receipt.blockNumber}`);
}

核心修复点

  • 恢复execute函数的完整签名,添加deadline参数并设置合理值
  • 用ethers.solidityPacked正确编码交易路径
  • 恢复通过Trade对象计算amountOutMin,保留滑点保护
  • 简化命令逻辑,直接使用0x08命令由Router自动处理ETH转WETH
  • 提高Gas上限至300000,确保覆盖执行成本

内容的提问来源于stack exchange,提问作者Ghassane Aboughazaouat

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.26 01:53:11