跳到主要内容

Ethers极简入门: 7. 检索事件

我最近在重新学ethers.js,巩固一下细节,也写一个WTF Ethers极简入门,供小白们使用。

推特@0xAA_Science

WTF Academy社群: 官网 wtf.academy | WTF Solidity教程 | discord | 微信群申请

所有代码和教程开源在github: github.com/WTFAcademy/WTFEthers


提示:本教程基于ethers.js 6.3.0 ,如果你使用的是v5,可以参考ethers.js v5文档

这一讲,我们将介绍如何使用ethers.js读取智能合约释放的事件。如果你不了解Solidity的事件,可以阅读WTF Solidity极简教程中第12讲:事件

具体可参考ethers.js文档

事件 Event

智能合约释放出的事件存储于以太坊虚拟机的日志中。日志分为两个主题topics和数据data部分,其中事件哈希和indexed变量存储在topics中,作为索引方便以后搜索;没有indexed变量存储在data中,不能被直接检索,但可以存储更复杂的数据结构。

以ERC20代币中的Transfer转账事件为例,在合约中它是这样声明的:

event Transfer(address indexed from, address indexed to, uint256 amount);

它共记录了3个变量fromtoamount,分别对应代币的发出地址,接收地址和转账数量,其中fromto前面带有indexed关键字。转账时,Transfer事件会被记录,可以在etherscan查到

Transfer事件

从上图中可以看到,Transfer事件被记录到了EVM的日志中,其中Topics包含3个数据,分别对应事件哈希,发出地址from,和接收地址to;而Data中包含一个数据,对应转账数额amount

检索事件

我们可以利用Ethers中合约类型的queryFilter()函数读取合约释放的事件。

const transferEvents = await contract.queryFilter('事件名', 起始区块, 结束区块)

queryFilter()包含3个参数,分别是事件名(必填),起始区块(选填),和结束区块(选填)。检索结果会以数组的方式返回。

注意:要检索的事件必须包含在合约的abi中。

例子:检索WETH合约中的Transfer事件

  1. 创建provider

    import { ethers } from "ethers";
    // 利用Alchemy的rpc节点连接以太坊网络
    // 准备 alchemy API 可以参考https://github.com/AmazingAng/WTFSolidity/blob/main/Topics/Tools/TOOL04_Alchemy/readme.md
    const ALCHEMY_GOERLI_URL = 'https://eth-goerli.alchemyapi.io/v2/GlaeWuylnNM3uuOo-SAwJxuwTdqHaY5l';
    const provider = new ethers.JsonRpcProvider(ALCHEMY_GOERLI_URL);
  2. 创建包含检索事件的abi

    // WETH ABI,只包含我们关心的Transfer事件
    const abiWETH = [
    "event Transfer(address indexed from, address indexed to, uint amount)"
    ];
  3. 声明WETH合约实例。

    // 测试网WETH地址
    const addressWETH = '0xb4fbf271143f4fbf7b91a5ded31805e42b2208d6'
    // 声明合约实例
    const contract = new ethers.Contract(addressWETH, abiWETH, provider)
  4. 获取过去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])

    打印事件

  5. 读取事件的解析结果。

    // 解析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"]}`)

    解析事件

总结

这一讲,我们回顾了Solidity中的事件,并介绍如何用ethers检索智能合约释放的事件。要注意的一点:要检索的事件必须包含在合约abi中。