最近在学习web3所以根据自己的学习内容每天写一篇笔记出来,算是联系也算是巩固! 希望自己早日可以找到web3的工作。
在我看来ethers就是传统开发中的axios,他使得我们可以非常快速简单的访问区块链上的信息。那么ethers究竟为我们提供了哪些功能呢?让我们来一起学习一下吧!
今天我们来学习一下ethers中的event事件,首先我们梳理一下contract中的事件都有哪些呢
返回与event匹配的事件。
contract.queryFilter( event [ , fromBlockOrBlockHash [ , toBlock ] ) ⇒ Promise< Array< Event > >
返回订阅该event的监听器数量。如果没有提供event,则返回所有事件的总数。
contract.listenerCount( [ event ] ) ⇒ number
返回订阅该event的监听器列表。
contract.listeners( event ) ⇒ Array< Listener >
监听器取消订阅event事件。
contract.off( event , listener ) ⇒ this
监听event事件,当事件发生时,会调用listener函数。
contract.on( event , listener ) ⇒ this
监听event事件,当事件发生时,仅调用一次listener函数。
contract.once( event , listener ) ⇒ this
取消所有订阅event事件的监听器。如果未提供event事件,则取消订阅所有事件的监听。
contract.removeAllListeners( [ event ] ) ⇒ this
事件过滤器,返回EVENT_NAME的过滤器,可以通过增加其他约束进行过滤。只有indexed索引的事件参数可以被过滤。如果参数为空(或未提供),则该字段中的任何值都匹配。
contract.filters.EVENT_NAME( ...args ) ⇒ Filter
那么我们接下来一点一点来实现这些事件:
在My Web3文件夹中创建07_Event文件夹,并在其中创建文件Event.js,我们在这里实现一下如何检索区块内的Transfer事件
具体的代码和解释如下
// 检索事件的方法:
// const transferEvents = await contract.queryFilter("事件名", [起始区块高度,结束区块高度])
// 其中起始区块高度和结束区块高度为选填参数。
import { ethers } from "ethers";
// 利用Alchemy的rpc节点连接以太坊主网
const ALCHEMY_MAINNET_URL = '你申请的Connect to Alchemy URL(主网)';
const provider = new ethers.JsonRpcProvider(ALCHEMY_MAINNET_URL);
// WETH ABI,只包含我们关心的Transfer事件
const abiWETH = [
"event Transfer(address indexed from, address indexed to, uint amount)"
];
// WETH合约地址(主网)
const addressWETH = '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2'
// 声明合约实例
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()
执行这个看一下过去10个区块内的Transfer事件信息
接下来我们再来实现一下contract.on和contract.once,然后我们再在contract.on的监听回调中取消订阅。
在My Web3文件夹中创建08_ContractListener文件夹,并在其中创建文件ContractListener.js
实现代码如下
// 监听合约方法:
// 1. 持续监听
// contractUSDT.on("事件名", Listener)
// 2. 只监听一次
// contractUSDT.once("事件名", Listener)
import { ethers } from "ethers";
// 利用Alchemy的rpc节点连接以太坊主网
const ALCHEMY_MAINNET_URL = '你申请的Connect to Alchemy URL(主网)';
const provider = new ethers.JsonRpcProvider(ALCHEMY_MAINNET_URL);
// USDT的合约地址
const contractAddress = '0xdac17f958d2ee523a2206206994597c13d831ec7'
// 构建USDT的Transfer的ABI
const abi = [
"event Transfer(address indexed from, address indexed to, uint value)"
];
// 生成USDT合约对象
const contractUSDT = new ethers.Contract(contractAddress, abi, provider);
const main = async () => {
// 监听USDT合约的Transfer事件
try {
// 只监听一次
console.log("\n1. 利用contract.once(),监听一次Transfer事件");
contractUSDT.once('Transfer', (from, to, value) => {
// 打印结果
console.log('只监听一次')
console.log(
`${from} -> ${to} ${ethers.formatUnits(ethers.getBigInt(value), 6)}`
)
})
// 持续监听USDT合约
console.log("\n2. 利用contract.on(),持续监听Transfer事件");
let count = 1
contractUSDT.on('Transfer', (from, to, value) => {
console.log(`持续监听Transfer事件第${count}次回调`)
console.log(
// 打印结果
`${from} -> ${to} ${ethers.formatUnits(ethers.getBigInt(value), 6)}`
)
count++
if(count > 5) {
// 如果没有参数的话是默认异常所有的监听事件
contractUSDT.removeAllListeners(['Transfer'])
}
})
} catch (e) {
console.log(e);
}
}
main()
执行这个看一下监听合约获得的信息
一鼓作气我们接下来我们再过滤器,在我们学习过滤器之前我们应该先知道当合约创建日志(释放事件)时,它最多可以包含4条数据作为索引(indexed)。索引数据经过哈希处理并包含在布隆过滤器中,这是一种允许有效过滤的数据结构。因此,一个事件过滤器最多包含4个主题集,每个主题集是个条件,用于筛选目标事件。规则:
- 如果一个主题集为
null,则该位置的日志主题不会被过滤,任何值都匹配。 - 如果主题集是单个值,则该位置的日志主题必须与该值匹配。
- 如果主题集是数组,则该位置的日志主题至少与数组中其中一个匹配。
构建过滤器
ethers.js中的合约类提供了contract.filters来简化过滤器的创建:
const filter = contract.filters.EVENT_NAME( ...args )
其中EVENT_NAME为要过滤的事件名,..args为主题集/条件。前面的规则有一点抽象,下面举几个例子。
- 过滤来自
myAddress地址的Transfer事件
contract.filters.Transfer(myAddress)
- 过滤所有发给
myAddress地址的Transfer事件
contract.filters.Transfer(null, myAddress)
- 过滤所有从
myAddress发给otherAddress的Transfer事件
contract.filters.Transfer(myAddress, otherAddress)
- 过滤所有发给
myAddress或otherAddress的Transfer事件
contract.filters.Transfer(null, [ myAddress, otherAddress ])
监听交易所的USDT转账
我们需要先看懂交易日志Logs,包括事件的topics和data。
address:USDT合约地址topics[0]:事件哈希,keccak256("Transfer(address,address,uint256)")topics[1]:转出地址(币安交易所热钱包)。topics[2]转入地址。data:转账数量。
然后我们用代码来具体实现一下如果过滤监听交易所的USDT转账:
在My Web3文件夹中创建09_EventFilter文件夹,并在其中创建文件EventFilter.js
具体代码实现如下
import { ethers } from "ethers";
// 利用Alchemy的rpc节点连接以太坊网络
const ALCHEMY_MAINNET_URL = 'https://eth-mainnet.g.alchemy.com/v2/GTnW6zSipqJb0Nj0Gfsd9L9Oy4BUb0Y7';
const provider = new ethers.JsonRpcProvider(ALCHEMY_MAINNET_URL);
// 合约地址
const addressUSDT = '0xdac17f958d2ee523a2206206994597c13d831ec7'
// 交易所地址
const accountBinance = '0x28C6c06298d514Db089934071355E5743bf21d60'
// 构建ABI
const abi = [
"event Transfer(address indexed from, address indexed to, uint value)",
"function balanceOf(address) public view returns(uint)",
];
// 构建合约对象
const contractUSDT = new ethers.Contract(addressUSDT, abi, provider);
(async () => {
try {
// 1. 读取币安热钱包USDT余额
console.log("\n1. 读取币安热钱包USDT余额")
const balanceUSDT = await contractUSDT.balanceOf(accountBinance)
console.log(`USDT余额: ${ethers.formatUnits(balanceUSDT,6)}\n`)
// 2. 创建过滤器,监听转移USDT进交易所
console.log("\n2. 创建过滤器,监听USDT转进交易所")
let filterBinanceIn = contractUSDT.filters.Transfer(null, accountBinance);
console.log("过滤器详情:")
console.log(filterBinanceIn);
contractUSDT.on(filterBinanceIn, (res) => {
console.log('---------监听USDT进入交易所--------');
console.log(
`${res.args[0]} -> ${res.args[1]} ${ethers.formatUnits(res.args[2],6)}`
)
})
// 3. 创建过滤器,监听交易所转出USDT
let filterToBinanceOut = contractUSDT.filters.Transfer(accountBinance);
console.log("\n3. 创建过滤器,监听USDT转出交易所")
console.log("过滤器详情:")
console.log(filterToBinanceOut);
contractUSDT.on(filterToBinanceOut, (res) => {
console.log('---------监听USDT转出交易所--------');
console.log(
`${res.args[0]} -> ${res.args[1]} ${ethers.formatUnits(res.args[2],6)}`
)
}
);
} catch (e) {
console.log(e);
}
})()
好啦,让我们执行一下然后看一下运行结果。