前言

之前web3js监听过网络,发现其实还是挺复杂的,这次使用ethersjs来监听网络,应该会简单很多,也容易理解很多。本案例使用ethers发现和监听USDT合约。

目标

  • 学会查看合约事件
  • 编写ethersjs监听网络
  • 监听USDT的转账事件

ethers

ethers入门的话可以看 GitHub - WTFAcademy/WTFEthers
官方文档:Documentation
如何通过ethers实现监听呢?在ethersjs中,我们可以通过合约对象来实现监听。合约对象有一个contract.on的监听方法。

Contract 合约对象

合约是已部署到区块链上的代码的抽象。
一个合约可以被发送交易,这将触发其代码在交易数据的输入下运行。

创建合约对象

创建合约对象需要三个参数

  • address :合约的地址
  • abi:合约的abi
  • provider:ethers的provider
    1
    new ethers.Contract( address , abi , signerOrProvider )

合约类的事件

合约类有很多事件,具体的可以自己直接看文档:Contract - events

这次主要了解一下这个就好了,只需要一个合约对象,然后就可以监听对应的事件了。

1
contract.on( event , listener )

查找ABI

通过etherscan.io查找abi

因为我们新建contract对象对时候需要三个参数,其中provider和address我们都可以很容易获得,ABI参数有2种方法可以获得。

一、通过开源合约代码获得ABI

首先打开Tether: USDT Stablecoin | Address 0xdac17f958d2ee523a2206206994597c13d831ec7 | EtherscanUSDT的合约地址,切换到合约Contract

搜索合约代码中的监听event Transfer

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* @title ERC20Basic
* @dev Simpler version of ERC20 interface
* @dev see https://github.com/ethereum/EIPs/issues/20
*/
contract ERC20Basic {
uint public _totalSupply;
function totalSupply() public constant returns (uint);
function balanceOf(address who) public constant returns (uint);
function transfer(address to, uint value) public;
event Transfer(address indexed from, address indexed to, uint value);
}

可以看到Transfer事件,那么我们就可以直接拿来嵌入ethersjs生成对应的ABI

1
2
3
const abi = [
"event Transfer(address indexed from, address indexed to, uint value)"
];

二、通过etherscan网站获得abi

或者你可以通过etherscan获得具体的ABI

实现监听完整代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 获取alchealchemy的节点
const config = require('dotenv').config().parsed
const {ethers} = require('ethers');

// 连接主网的提供者
const provider = new ethers.providers.JsonRpcProvider(config.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);


(async ()=>{
try{
// 监听USDT合约
contractUSDT.on('Transfer', (from, to, value)=>{
console.log(
`${from} -> ${to} ${ethers.BigNumber.from(value).toString()}`
)
})
}catch(e){
console.log(e);
}
})()

实现布隆过滤

默认的监听是会监听全网所有跟该合约地址交互的信息,但是有时候我们就只想监听固定的from或者to地址的操作,这个时候就可以使用创建过滤器来实现了。
详细的过滤可以看官方文档:Events
这里我列举几个常用的方式

构建过滤器

你可以通过如下方法构建一个过滤器
1.过滤来自myAddress地址的线上广播事件

1
contract.filters.Transfer(myAddress)

2.过滤所有发给 myAddress地址的线上广播事件

1
contract.filters.Transfer(null, myAddress)

3.过滤所有由 myAddress地址发给otherAddress的线上广播事件

1
contract.filters.Transfer(myAddress, otherAddress)

4.过滤的时候你可以传入数组,过滤监听多个账号

1
contract.filters.Transfer(null, [ myAddress, otherAddress ])

监听交易所地址的USDT信息

监听ethers之前,我们需要先看懂一条hash的交易状态,以及如何看懂该hash的topics,我们是通过topics的结构来创建过滤器的。

通过etherscan查看事件

查看该hash0xab1f7b575600c4517a2e479e46e3af98a95ee84dd3f46824e02ff4618523fff5

该条hash做了一件事:从 binance14 这个地址 将USDT转给了 0x354de44bedba213d612e92d3248b899de17b0c58 这个地址

查看该事件日志信息
address 为USDT合约地址
topics[0]为keccak256(“Transfer(address,address,uint256)”)
topics[1] 为from地址 就是 binance14交易所的地址
topics[2] 为to地址 就是接受USDT的地址
data 为发送的数量

通过ethers查看一条交易信息

已知一条交易hash为:0xab1f7b575600c4517a2e479e46e3af98a95ee84dd3f46824e02ff4618523fff5
通过代码从区块链上看该代码的信息

1
2
3
4
5
6
7
8
const provider = new ethers.providers.JsonRpcProvider(config.ALCHEMY_MAINNET_URL);
const receipt = await provider.getTransactionReceipt('0xab1f7b575600c4517a2e479e46e3af98a95ee84dd3f46824e02ff4618523fff5');
console.group('receipt');
console.log(receipt);
console.groupEnd();
console.group('receipt.logs');
console.log(receipt.logs);
console.groupEnd();

通过上述代码得到如下结构:

receipt的详细信息
看如下信息:
from:为发起地址
to: 为接收地址,一般是erc20的合约地址,如果是nft的话,就是nft的合约地址

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
receipt
{
to: '0xdAC17F958D2ee523a2206206994597C13D831ec7',
from: '0x28C6c06298d514Db089934071355E5743bf21d60',
contractAddress: null,
transactionIndex: 93,
gasUsed: BigNumber { _hex: '0xf6e9', _isBigNumber: true },
logsBloom: '0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000010000000000000000000000000000010000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000802000010000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000080000000000000000000000000000000020000000000000002000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000',
blockHash: '0x36c70a48ba9cff240854327f0d373d6bd295d9798b26dcf5cfff88d93d356de4',
transactionHash: '0xab1f7b575600c4517a2e479e46e3af98a95ee84dd3f46824e02ff4618523fff5',
logs: [
{
transactionIndex: 93,
blockNumber: 15382657,
transactionHash: '0xab1f7b575600c4517a2e479e46e3af98a95ee84dd3f46824e02ff4618523fff5',
address: '0xdAC17F958D2ee523a2206206994597C13D831ec7',
topics: [Array],
data: '0x00000000000000000000000000000000000000000000000000000000b1ae7340',
logIndex: 84,
blockHash: '0x36c70a48ba9cff240854327f0d373d6bd295d9798b26dcf5cfff88d93d356de4'
}
],
blockNumber: 15382657,
confirmations: 224,
cumulativeGasUsed: BigNumber { _hex: '0x4163fe', _isBigNumber: true },
effectiveGasPrice: BigNumber { _hex: '0xf1a1e025', _isBigNumber: true },
status: 1,
type: 2,
byzantium: true
}

logs中的信息展开
address 为USDT合约地址
topics[0]为keccak256(“Transfer(address,address,uint256)”)
topics[1] 为from地址 就是 binance14交易所的地址
topics[2] 为to地址 就是接受USDT的地址
data 为发送的数量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
receipt.logs  ============= logs 信息
[
{
transactionIndex: 93,
blockNumber: 15382657,
transactionHash: '0xab1f7b575600c4517a2e479e46e3af98a95ee84dd3f46824e02ff4618523fff5',
address: '0xdAC17F958D2ee523a2206206994597C13D831ec7',
topics: [
'0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef',
'0x00000000000000000000000028c6c06298d514db089934071355e5743bf21d60',
'0x000000000000000000000000354de44bedba213d612e92d3248b899de17b0c58'
],
data: '0x00000000000000000000000000000000000000000000000000000000b1ae7340',
logIndex: 84,
blockHash: '0x36c70a48ba9cff240854327f0d373d6bd295d9798b26dcf5cfff88d93d356de4'
}
]

完整代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
const config = require('dotenv').config().parsed
const { ethers } = require('ethers');

const provider = new ethers.providers.JsonRpcProvider(config.ALCHEMY_MAINNET_URL);
// 合约地址
const contractAddress = '0xdac17f958d2ee523a2206206994597c13d831ec7'
// 交易所地址
const balanceAccount = '0x28C6c06298d514Db089934071355E5743bf21d60'
// 构建ABI
const abi = [
"event Transfer(address indexed from, address indexed to, uint value)"
];
// 构建合约对象
const contractUSDT = new ethers.Contract(contractAddress, abi, provider);


(async () => {
try {
console.log('start');
// 创建过滤器,监听转移USDT进交易所
let filterBinanceIn = contractUSDT.filters.Transfer(null, balanceAccount);
console.log(filterBinanceIn);
// 创建过滤器,监听交易所转出USDT
let filterToBinanceOut = contractUSDT.filters.Transfer(balanceAccount, null);
console.log(filterToBinanceOut);
console.log('In');
contractUSDT.on(filterBinanceIn, (from, to, value) => {
console.log('---------监听USDT进入交易所--------');
console.log(
`${from} -> ${to} ${ethers.BigNumber.from(value).toString()}`
)
}).on('error', (error) => {
console.log(error)
})
console.log('out');
contractUSDT.on(filterToBinanceOut, (from, to, value) => {
console.log('---------监听USDT转出交易所--------');
console.log(
`${from} -> ${to} ${ethers.BigNumber.from(value).toString()}`
)
}
).on('error', (error) => {
console.log(error)
});
} catch (e) {
console.log(e);
}
})()

参考

How to listen to contract events using ethers.js? - Ethereum Stack Exchange
Events