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

Dart调用Uniswap V3 QuoterV2 quoteExactOutput()模拟查询报错求助

问题:Dart调用Uniswap V3 QuoterV2的quoteExactOutput()函数报错(execution reverted)

问题背景

我尝试用Dart调用Uniswap V3 QuoterV2合约的quoteExactOutput()函数,该函数并非view函数,但我仅需模拟调用(不向以太坊发送交易),目标是获取WETH与UNI代币的兑换比例(例如1.0 WETH对应多少UNI)。

我的Dart代码

import 'dart:convert';
import 'dart:io';

import 'package:token_price_uniswap/utils/constant.dart';
import 'package:web3dart/web3dart.dart';
import 'package:http/http.dart';

Future<DeployedContract> loadContract() async {
  final contract = DeployedContract(
      ContractAbi.fromJson(uniswapV3QuoterV2Abi, "UniswapV3Quoter2"),
      EthereumAddress.fromHex(uniswapV3QuoterV2Address));
  return contract;
}

Future<String> sendTx(
  String funcname,
  List<dynamic> args,
  Web3Client ethClient,
  String privateKey,
) async {
  EthPrivateKey credentials = EthPrivateKey.fromHex(privateKey);
  DeployedContract contract = await loadContract();
  final ethFunction = contract.function(funcname);
  final result = await ethClient.sendTransaction(
      credentials,
      Transaction.callContract(
        contract: contract,
        function: ethFunction,
        parameters: args,
      ),
      chainId: null,
      fetchChainIdFromNetworkId: true);
  return result;
}

Future<List<dynamic>> fetchFromContract(
    String funcName, List<dynamic> args, Web3Client ethClient) async {
  final contract = await loadContract();
  final calledFunction = contract.function(funcName);
  final result = ethClient.call(
    contract: contract,
    function: calledFunction,
    params: args,
  );

  return result;
}

Future<List> getWeth(Web3Client ethClient) async {
  List<dynamic> result = await fetchFromContract("WETH9", [], ethClient);

  print("called Uniswap V3 WETH");
  return result;
}

Future<List<dynamic>> quoteExactOutput(
  Web3Client ethClient,
  List<int> path,
  String amountIn,
) async {
  final amountInBigInt = BigInt.parse(amountIn);

  final params = [
    path,
    amountInBigInt,
  ];
  print("params: $path");

  final result = await fetchFromContract('quoteExactOutput', params, ethClient);

  return result;
}

Future<void> main(List<String> arguments) async {
  Web3Client? ethClient;
  Client? httpClient;

  httpClient = Client();
  ethClient = Web3Client(rpcUrl, httpClient);

  String wethAmount = '1000000000000000000'; // 1 WETH
  final path = [uniswapTokenAddress, 3000, wethTokenAddress];

  // call quote exact input
  final encodedPath = encode(path);
  var result = await quoteExactOutput(ethClient, encodedPath, wethAmount);
  print("1 WETH is equal to ${result[0]} UNI");
}

报错信息

Unhandled exception:
RPCError: got code -32000 with msg "execution reverted".
#0      JsonRPC.call (package:web3dart/json_rpc.dart:71:7)
<asynchronous suspension>
#1      Web3Client.makeRPCCall (package:web3dart/src/core/client.dart:53:20)
<asynchronous suspension>
#2      Web3Client.call (package:web3dart/src/core/client.dart:408:27)
<asynchronous suspension>
#3      quoteExactOutput(package:token_price_uniswap/services/uniswap_v3_functions.dart:108:18)
<asynchronous suspension>
#4      main (file:///D:/Eight/Programs/token_price_uniswap/bin/token_price_uniswap.dart:40:7)
<asynchronous suspension>

问题排查

我怀疑错误源于path参数的编码不正确:Solidity中通过abi.encodePacked()生成该参数,但我不清楚Dart中的实现方式。我已用Python实现相同功能,通过eth_abi.packed.encode_packed()处理path参数后调用成功。

可行的Python代码

from web3 import Web3
import eth_abi.packed
import json

# setup our account and chain connection - we will use infura here
rpc_endpoint = "https://mainnet.infura.io/v3/INFURA_ID" # 
web3 = Web3(Web3.HTTPProvider(rpc_endpoint))

# some addresses first
UNISWAP_v3_QUOTER2_ADDRESS = "0x61fFE014bA17989E743c5F6cB21bF9697530B21e"
UNISWAP_TOKEN_ADDRESS = "0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984"
WETH_TOKEN_ADDRESS = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"

# Load abi
f = open('./assets/uniswap_v3_quoter2.json')
UNISWAP_V3_QUOTER2_ABI = json.load(f)

# load the contracts
quoter2_contract = web3.eth.contract(address=UNISWAP_v3_QUOTER2_ADDRESS, abi=UNISWAP_V3_QUOTER2_ABI)

# prepare the function call parameters
path = eth_abi.packed.encode_packed(['address','uint24','address'], [WETH_TOKEN_ADDRESS, 3000, UNISWAP_TOKEN_ADDRESS])
amount_to_buy_for = 1 * 10**18

# call quote exact output
amount_out, sqrtPriceX96After, initializedTicksCrossed, gasEstimate = quoter2_contract.functions.quoteExactOutput(path, amount_to_buy_for).call()
print(f"for {amount_to_buy_for/10**18} WETH you would get {amount_out/10**18} UNI token")

Python代码输出

for 1.0 UNI token you have to pay 284.3230485269558 WETH

解决方案:Dart中实现abi.encodePacked编码path参数

在Dart中,你可以手动实现abi.encodePacked的逻辑来处理path参数——因为path的结构是[address, uint24, address],对应20字节地址 + 3字节手续费 + 20字节地址,直接拼接这些字节即可。

添加以下编码函数:

Uint8List encodePackedPath(String tokenAddress1, int fee, String tokenAddress2) {
  // 转换第一个地址为20字节Uint8List
  final addr1Bytes = EthereumAddress.fromHex(tokenAddress1).bytes;
  // 转换uint24手续费为3字节大端序Uint8List
  final feeBytes = Uint8List(3)..buffer.asByteData().setUint24(0, fee, Endian.big);
  // 转换第二个地址为20字节Uint8List
  final addr2Bytes = EthereumAddress.fromHex(tokenAddress2).bytes;

  // 拼接所有字节
  final combined = Uint8List(addr1Bytes.length + feeBytes.length + addr2Bytes.length);
  var offset = 0;
  combined.setRange(offset, offset + addr1Bytes.length, addr1Bytes);
  offset += addr1Bytes.length;
  combined.setRange(offset, offset + feeBytes.length, feeBytes);
  offset += feeBytes.length;
  combined.setRange(offset, offset + addr2Bytes.length, addr2Bytes);

  return combined;
}

然后修改main函数中的path处理逻辑:

Future<void> main(List<String> arguments) async {
  Web3Client? ethClient;
  Client? httpClient;

  httpClient = Client();
  ethClient = Web3Client(rpcUrl, httpClient);

  String wethAmount = '1000000000000000000'; // 1 WETH
  // 注意:quoteExactOutput的path顺序是「输出代币 → 手续费 → 输入代币」,如果要计算1 WETH能换多少UNI,path应该是UNI → 3000 → WETH
  final encodedPath = encodePackedPath(uniswapTokenAddress, 3000, wethTokenAddress);
  
  var result = await quoteExactOutput(ethClient, encodedPath, wethAmount);
  // 注意:quoteExactOutput返回的第一个值是需要的输入金额,如果你传的是要得到的UNI数量,这里才是对应的WETH数量;如果要算1 WETH换多少UNI,需要调整参数为amountOut是UNI的数量,或者改用quoteExactInput
  print("1 WETH is equal to ${BigInt.parse(result[0].toString()) / BigInt.from(10).pow(18)} UNI");
}

另外需要注意:quoteExactOutput的参数定义是function quoteExactOutput(bytes calldata path, uint256 amountOut) external returns (uint256 amountIn, ...),即你传入要得到的amountOut(比如1 UNI),返回需要的amountIn(对应的WETH数量)。如果要计算1 WETH能换多少UNI,应该调用quoteExactInput函数,或者调整quoteExactOutput的参数为amountOut是你想得到的UNI数量,path为WETH → 3000 → UNI。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.27 09:27:04