Uniswap V2 getAmountsOut查询极小面值代币价格返回0问题
问题原因
你遇到的返回0、计算偏差问题来自两个核心机制:
- Solidity本身只支持整数运算,所有除法默认向下取整。CAW、SHINJA这类极低单价代币,1个最小单位对应的ETH数量远小于1wei(ETH的最小单位,1wei=1e-18 ETH),用1单位代币走
getAmountsOut查询时,整数运算直接把小于1wei的部分截断,返回0。 getAmountsOut是交易场景下的报价函数,计算时会扣除Uniswap 0.3%的交易手续费,同时根据恒定乘积公式计算滑点。你把输入金额放大到10亿单位时,大额交易对应的滑点已经非常明显,用这个结果反推单币价格必然存在严重偏差,输入金额越大偏差越高。
正确实现方法
不要用路由合约的交易报价函数查现货价,直接读取交易对池内的储备金,通过恒定乘积公式计算无滑点现货价,这个方法不受代币单价高低、输入金额大小影响,结果和Uniswap官方信息页展示的价格完全一致。
实现步骤:
- 从路由合约读取工厂合约地址,通过工厂合约查询目标代币和WETH的交易对地址
- 实例化交易对合约,读取池内两种资产的储备量,同时确认两个代币的排列顺序(Uniswap V2交易对会按代币地址大小排序为token0、token1,不会和你传入的顺序一致)
- 把两种资产的储备量按照各自的小数位转换为统一单位,直接做除法得到单枚代币对应的ETH价格,计算时建议用
Decimal类型避免浮点数精度损失。
参考实现代码:
from web3 import Web3 from decimal import Decimal # 最小化Uniswap V2合约ABI pair_abi = [ {"inputs":[],"name":"getReserves","outputs":[{"internalType":"uint112","name":"_reserve0","type":"uint112"},{"internalType":"uint112","name":"_reserve1","type":"uint112"},{"internalType":"uint32","name":"_blockTimestampLast","type":"uint32"}],"stateMutability":"view","type":"function"}, {"inputs":[],"name":"token0","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"}, {"inputs":[],"name":"token1","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"} ] factory_abi = [ {"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"getPair","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"} ] # 初始化合约实例 router = w3.eth.contract(address=Web3.toChecksumAddress(IUNISWAP_V2_ROUTER), abi=router_contract_abi) factory_addr = router.functions.factory().call() factory = w3.eth.contract(address=Web3.toChecksumAddress(factory_addr), abi=factory_abi) token_addr = Web3.toChecksumAddress(token_metadata['address']) weth_addr = Web3.toChecksumAddress(WETH) # 校验交易对存在性 pair_addr = factory.functions.getPair(token_addr, weth_addr).call() if pair_addr == "0x0000000000000000000000000000000000000000": raise Exception("目标代币对应WETH交易对不存在") pair = w3.eth.contract(address=Web3.toChecksumAddress(pair_addr), abi=pair_abi) # 读取储备和代币顺序 res0, res1, _ = pair.functions.getReserves().call() t0 = pair.functions.token0().call() t1 = pair.functions.token1().call() # 单位转换后计算价格 token_decimals = int(token_metadata['decimals']) weth_decimals = tokens_decimals["WETH"] if t0 == token_addr: token_reserve = Decimal(res0) / Decimal(10 ** token_decimals) weth_reserve = Decimal(res1) / Decimal(10 ** weth_decimals) else: token_reserve = Decimal(res1) / Decimal(10 ** token_decimals) weth_reserve = Decimal(res0) / Decimal(10 ** weth_decimals) # 最终结果:1枚目标代币对应的ETH数量 token_eth_price = weth_reserve / token_reserve
如果需要得到USDC计价的代币价格,只需要用同样的方法读取WETH/USDC交易对的储备算出WETH价格,再和代币的ETH计价相乘即可。
不要用getAmountsOut计算现货基准价,这个函数仅适合估算实际交易时能拿到的到账金额,本身包含手续费和滑点计算,不适合作为定价依据。
内容的提问来源于stack exchange,提问作者AtiqGauri
相关产品推荐
相关产品推荐

