# 解码交易详情
这一讲,我们以未决交易(Pending Transaction)为例,介绍如何解码交易详情。
# 未决交易
未决交易是用户发出但没被矿工打包上链的交易,在mempool(交易内存池)中出现。对于mempool
的更多介绍可以看第19讲:监听Mempool
下面是一个利用Uniswap V3 Router
合约交易代币的未决交易,你可以在etherscan (opens new window)上查看交易详情:
红框中是这个交易的input data
,看似杂乱无章的十六进制数据,实际上编码了这笔交易的内容:包括调用的函数,以及输入的参数。我们在etherscan点击Decode Input Data按钮,就可以解码这段数据:
解码之后,我们可以看到这笔交易调用了合约中的exactInputSingle()
函数(Uniswap V3
路由合约中的一个交易函数)以及输入的参数。
# Interface类
ethers.js
提供了Interface
类方便解码交易数据。声明Interface
类型和声明abi
的方法差不多,例如:
const iface = ethers.utils.Interface([
"function balanceOf(address) public view returns(uint)",
"function transfer(address, uint) public returns (bool)",
"function approve(address, uint256) public returns (bool)"
]);
如果输入参数是solidity
的struct
结构体类型时,我们要做特殊处理,在javascript
中改为tuple
元组类型。上面的exactInputSingle()
函数的参数为ExactInputSingleParams
结构体:
struct ExactInputSingleParams {
address tokenIn;
address tokenOut;
uint24 fee;
address recipient;
uint256 deadline;
uint256 amountIn;
uint256 amountOutMinimum;
uint160 sqrtPriceLimitX96;
}
在js
中我们要写为:
const iface = new utils.Interface([
"function exactInputSingle(tuple(address tokenIn, address tokenOut, uint24 fee, address recipient, uint deadline, uint amountIn, uint amountOutMinimum, uint160 sqrtPriceLimitX96) calldata) external payable returns (uint amountOut)",
])
# 解码交易数据
下面我们写一个解码未决交易数据的脚本。
创建
provider
和wallet
,监听交易时候推荐用wss
连接而不是http
。// 准备 alchemy API 可以参考/solidity/p/Alchemy/ const ALCHEMY_MAINNET_WSSURL = 'wss://eth-mainnet.g.alchemy.com/v2/oKmOQKbneVkxgHZfibs-iFhIlIAl6HDN'; const provider = new ethers.providers.WebSocketProvider(ALCHEMY_MAINNET_WSSURL); let network = provider.getNetwork() network.then(res => console.log(`[${(new Date).toLocaleTimeString()}] 连接到 chain ID ${res.chainId}`));
创建
Interface
对象,用于解码交易详情。const iface = new utils.Interface([ "function exactInputSingle(tuple(address tokenIn, address tokenOut, uint24 fee, address recipient, uint deadline, uint amountIn, uint amountOutMinimum, uint160 sqrtPriceLimitX96) calldata) external payable returns (uint amountOut)", ])
限制访问
rpc
速率,不然调用频率会超出限制,报错。function throttle(fn, delay) { let timer; return function(){ if(!timer) { fn.apply(this, arguments) timer = setTimeout(()=>{ clearTimeout(timer) timer = null },delay) } } }
监听
pending
的uniswapV3
交易,获取交易详情并解码。这里只解码了通过Uniswap V3
路由合约的exactInputSingle()
函数的交易。网络不活跃的时候,可能需要等待几分钟才能监听到一笔。provider.on("pending", throttle(async (txHash) => { if (txHash) { // 获取tx详情 let tx = await provider.getTransaction(txHash); if (tx) { // filter pendingTx.data if (tx.data.indexOf(iface.getSighash("exactInputSingle")) !== -1) { // 打印txHash console.log(`\n[${(new Date).toLocaleTimeString()}] 监听Pending交易: ${txHash} \r`); // 打印解码的交易详情 let parsedTx = iface.parseTransaction(tx) console.log("pending交易详情解码:") console.log(parsedTx); // Input data解码 console.log("Input Data解码:") console.log(parsedTx.args); } } } }, 100));
交易参数解码:
# 总结
这一讲,我们介绍了ethers.js
的Interface
类,并利用它解码了mempool
中的Uniswap
交易。