Arbitrum Sepolia测试网Uniswap V3代币兑换失败问题排查
问题
在Arbitrum Sepolia测试网使用Uniswap V3 API执行WETH兑换MOR时,所有手续费层级(0.05%、0.3%、1%)均触发无原因回滚错误:
missing revert data in call exception; Transaction reverted without a reason string
交易执行日志片段:
Performing swap with account: 0x1237bbABAb2b9aC09a7dBC44A3e295D5fe2a318D WETH balance before swap: 0.0167 MOR balance before swap: 15.749987 Attempting swap with 0.05% fee... Quote failed for 0.05% fee: missing revert data in call exception Attempting swap with 0.3% fee... Quote failed for 0.3% fee: missing revert data in call exception Attempting swap with 1% fee... Quote failed for 1% fee: missing revert data in call exception
兑换未完成,代币余额无变化。相关执行脚本performSwap.js代码如下:
const { ethers } = require("hardhat"); async function main() { const [signer] = await ethers.getSigners(); console.log("Performing swap with account:", signer.address); const routerAddress = "0x101F443B4d1b059569D643917553c771E1b9663E"; const wethAddress = "0xb6c322FA3D8e0A60AfEB17512905eb2229CE7dA5"; const morAddress = "0xc1664f994Fd3991f98aE944bC16B9aED673eF5fD"; const poolFees = [500, 3000, 10000]; // 0.05%, 0.3%, 1% const routerABI = [ "function exactInputSingle((address tokenIn, address tokenOut, uint24 fee, address recipient, uint256 deadline, uint256 amountIn, uint256 amountOutMinimum, uint160 sqrtPriceLimitX96)) external payable returns (uint256 amountOut)", "function quoteExactInputSingle(address tokenIn, address tokenOut, uint24 fee, uint256 amountIn, uint160 sqrtPriceLimitX96) external view returns (uint256 amountOut)" ]; const erc20ABI = [ "function balanceOf(address account) external view returns (uint256)", "function approve(address spender, uint256 amount) external returns (bool)", "function allowance(address owner, address spender) external view returns (uint256)", "function decimals() external view returns (uint8)" ]; const router = new ethers.Contract(routerAddress, routerABI, signer); const weth = new ethers.Contract(wethAddress, erc20ABI, signer); const mor = new ethers.Contract(morAddress, erc20ABI, signer); const wethDecimals = await weth.decimals(); const morDecimals = await mor.decimals(); const wethBalance = await weth.balanceOf(signer.address); const morBalance = await mor.balanceOf(signer.address); const ethBalance = await signer.getBalance(); const allowance = await mor.allowance(signer.address, routerAddress); if (allowance.lt(ethers.utils.parseUnits("0.01", morDecimals))) { const approveTx = await mor.approve(routerAddress, ethers.constants.MaxUint256); await approveTx.wait(); } const amountIn = ethers.utils.parseUnits("0.01", morDecimals); const slippageTolerance = 50; const amountOutMinimum = 0; for (const poolFee of poolFees) { try { const quoteAmountOut = await router.quoteExactInputSingle(morAddress, wethAddress, poolFee, amountIn, 0); } catch (error) { continue; } const params = { tokenIn: morAddress, tokenOut: wethAddress, fee: poolFee, recipient: signer.address, deadline: Math.floor(Date.now() / 1000) + 60 * 20, amountIn: amountIn, amountOutMinimum: amountOutMinimum, sqrtPriceLimitX96: 0 }; try { const tx = await router.exactInputSingle(params, { gasLimit: 500000 }); const receipt = await tx.wait(); break; } catch (error) { console.error(`Swap failed for ${poolFee / 10000}% fee:`, error.message); } } const wethBalanceAfter = await weth.balanceOf(signer.address); const morBalanceAfter = await mor.balanceOf(signer.address); } main();
已尝试操作:
- 测试0.05%、0.3%、1%等多种手续费层级
- 检查兑换前后的授权额度与代币余额
- 设置合适的交易gas限制
- 验证Router、代币及资金池地址正确性
环境信息:
- 测试网:Arbitrum Sepolia
- 合约:Uniswap V3(Router、Quoter)
- 开发库:Hardhat、ethers.js
错误成因分析
- 资金池不存在或流动性为0:Uniswap V3的
quoteExactInputSingle调用失败,说明对应手续费层级的MOR-WETH资金池未创建,或池内流动性为0,导致合约执行时无数据可计算报价,直接回滚且无报错信息。 - Quoter合约调用错误:代码中直接用Router合约调用
quoteExactInputSingle,但该方法属于Uniswap V3 Quoter合约(而非Router),调用错误会触发合约无原因回滚。 - 代币排序与参数不匹配:Uniswap V3要求
tokenIn和tokenOut需符合资金池内的代币排序规则(按地址字典序排列),如果方向与池内设定不符,且sqrtPriceLimitX96设为0,会导致交易回滚。
调试与解决步骤
1. 确认资金池状态
打开Arbitrum Sepolia区块浏览器,搜索MOR和WETH的地址,查看是否存在对应手续费层级的资金池,以及池内是否有流动性。如果没有对应池,需先创建或等待流动性注入。
2. 修正Quoter合约调用
Uniswap V3的报价功能需要使用专门的Quoter合约,Arbitrum Sepolia的Quoter地址为0x61fFE014bA17989E743c5F6cB21bF9697530B21e,替换代码中的Router调用逻辑:
// 新增Quoter合约实例 const quoterAddress = "0x61fFE014bA17989E743c5F6cB21bF9697530B21e"; const quoter = new ethers.Contract(quoterAddress, routerABI, signer); // 替换原quote调用 const quoteAmountOut = await quoter.quoteExactInputSingle(morAddress, wethAddress, poolFee, amountIn, 0);
3. 验证代币排序与参数
- 比较MOR和WETH的地址字典序:将两个地址转为小写后对比,字典序小的为
token0,大的为token1。 - 如果
tokenIn是token1、tokenOut是token0,需将sqrtPriceLimitX96设为ethers.constants.MaxUint128(而非0),确保反向兑换的价格阈值合法。
4. 添加详细错误日志
在catch块中打印完整错误栈,而非仅message,便于定位具体问题:
catch (error) { console.error(`Quote failed for ${poolFee / 10000}% fee:`, error); }
5. 确认授权与余额有效性
在approve操作后打印授权额度,确保授权成功生效:
const approveTx = await mor.approve(routerAddress, ethers.constants.MaxUint256); await approveTx.wait(); const newAllowance = await mor.allowance(signer.address, routerAddress); console.log("New MOR allowance for router:", ethers.utils.formatUnits(newAllowance, morDecimals));
内容的提问来源于stack exchange,提问作者joeyzengazi
相关产品推荐
相关产品推荐

