白天搞智能合约,晚上撸前端代码,只要咖啡还续着,凌晨照样在线!会说 Solidity、PHP、Node.js,还有 Vue 和 HTML 的“方言”,代码不怕我不写,就怕写完跑太快。Bug?那都是小场面,一出手就搞定。我的宗旨是——交付准时,调试无敌,客户满意才是真理!
7. 检索事件
事件 Event
智能合约释放出的事件存储于以太坊虚拟机的日志中。日志分为两个主题topics和数据data部分,其中事件哈希和indexed变量存储在topics中,作为索引方便以后搜索;没有indexed变量存储在data中,不能被直接检索,但可以存储更复杂的数据结构。
以ERC20代币中的Transfer转账事件为例,在合约中它是这样声明的:
event Transfer(address indexed from, address indexed to, uint256 amount);
它共记录了3个变量from,to和amount,分别对应代币的发出地址,接收地址和转账数量,其中from和to前面带有indexed关键字。转账时,Transfer事件会被记录,可以在etherscan中查到。

从上图中可以看到,Transfer事件被记录到了EVM的日志中,其中Topics包含3个数据,分别对应事件哈希,发出地址from,和接收地址to;而Data中包含一个数据,对应转账数额amount。
检索事件
我们可以利用Ethers中合约类型的queryFilter()函数读取合约释放的事件。
const transferEvents = await contract.queryFilter('事件名', 起始区块, 结束区块)
queryFilter()包含3个参数,分别是事件名(必填),起始区块(选填),和结束区块(选填)。检索结果会以数组的方式返回。
注意:要检索的事件必须包含在合约的abi中。
例子:检索WETH合约中的Transfer事件
创建
provider。import { ethers } from "ethers"; // 利用Alchemy的rpc节点连接以太坊网络 const ALCHEMY_GOERLI_URL = 'https://eth-goerli.alchemyapi.io/v2/GlaeWuylnNM3uuOo-SAwJxuwTdqHaY5l'; const provider = new ethers.JsonRpcProvider(ALCHEMY_GOERLI_URL);创建包含检索事件的
abi。// WETH ABI,只包含我们关心的Transfer事件 const abiWETH = [ "event Transfer(address indexed from, address indexed to, uint amount)" ];声明
WETH合约实例。// 测试网WETH地址 const addressWETH = '0xb4fbf271143f4fbf7b91a5ded31805e42b2208d6' // 声明合约实例 const contract = new ethers.Contract(addressWETH, abiWETH, provider)获取过去10个区块内的
Transfer事件,并打印出1个。我们可以看到,topics中有3个数据,对应事件哈希,from,和to;而data中只有一个数据amount。另外,ethers还会根据ABI自动解析事件,结果显示在args成员中。// 得到当前block const block = await provider.getBlockNumber() console.log(`当前区块高度: ${block}`); console.log(`打印事件详情:`); const transferEvents = await contract.queryFilter('Transfer', block - 10, block) // 打印第1个Transfer事件 console.log(transferEvents[0])
读取事件的解析结果。
// 解析Transfer事件的数据(变量在args中) console.log("\n2. 解析事件:") const amount = ethers.formatUnits(ethers.getBigInt(transferEvents[0].args["amount"]), "ether"); console.log(`地址 ${transferEvents[0].args["from"]} 转账${amount} WETH 到地址 ${transferEvents[0].args["to"]}`)
完整代码
// 检索事件的方法:
// const transferEvents = await contract.queryFilter("事件名", [起始区块高度,结束区块高度])
// 其中起始区块高度和结束区块高度为选填参数。
import { ethers } from "ethers";
// playcode免费版不能安装ethers,用这条命令,需要从网络上import包(把上面这行注释掉)
// import { ethers } from "https://cdn-cors.ethers.io/lib/ethers-5.6.9.esm.min.js";
// 利用Alchemy的rpc节点连接以太坊网络
const ALCHEMY_GOERLI_URL = 'https://eth-goerli.alchemyapi.io/v2/GlaeWuylnNM3uuOo-SAwJxuwTdqHaY5l';
const provider = new ethers.JsonRpcProvider(ALCHEMY_GOERLI_URL);
// WETH ABI,只包含我们关心的Transfer事件
const abiWETH = [
"event Transfer(address indexed from, address indexed to, uint amount)"
];
// 测试网WETH地址
const addressWETH = '0xb4fbf271143f4fbf7b91a5ded31805e42b2208d6'
// 声明合约实例
const contract = new ethers.Contract(addressWETH, abiWETH, provider)
const main = async () => {
// 获取过去10个区块内的Transfer事件
console.log("\n1. 获取过去10个区块内的Transfer事件,并打印出1个");
// 得到当前block
const block = await provider.getBlockNumber()
console.log(`当前区块高度: ${block}`);
console.log(`打印事件详情:`);
const transferEvents = await contract.queryFilter('Transfer', block - 10, block)
// 打印第1个Transfer事件
console.log(transferEvents[0])
// 解析Transfer事件的数据(变量在args中)
console.log("\n2. 解析事件:")
const amount = ethers.formatUnits(ethers.getBigInt(transferEvents[0].args["amount"]), "ether");
console.log(`地址 ${transferEvents[0].args["from"]} 转账${amount} WETH 到地址 ${transferEvents[0].args["to"]}`)
}
main()
