前言
之前web3js监听过网络,发现其实还是挺复杂的,这次使用ethersjs来监听网络,应该会简单很多,也容易理解很多。本案例使用ethers发现和监听USDT合约。
目标
- 学会查看合约事件
- 编写ethersjs监听网络
- 监听USDT的转账事件
ethers
ethers入门的话可以看 GitHub - WTFAcademy/WTFEthers
官方文档:Documentation
如何通过ethers实现监听呢?在ethersjs中,我们可以通过合约对象来实现监听。合约对象有一个contract.on
的监听方法。
Contract 合约对象
合约是已部署到区块链上的代码的抽象。
一个合约可以被发送交易,这将触发其代码在交易数据的输入下运行。
创建合约对象
创建合约对象需要三个参数
address
:合约的地址
abi
:合约的abi
provider
:ethers的provider1
| 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
|
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
| const config = require('dotenv').config().parsed const {ethers} = require('ethers');
const provider = new ethers.providers.JsonRpcProvider(config.ALCHEMY_MAINNET_URL);
const contractAddress = '0xdac17f958d2ee523a2206206994597c13d831ec7'
const abi = [ "event Transfer(address indexed from, address indexed to, uint value)" ];
const contractUSDT = new ethers.Contract(contractAddress, abi, provider);
(async ()=>{ try{ 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'
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'); let filterBinanceIn = contractUSDT.filters.Transfer(null, balanceAccount); console.log(filterBinanceIn); 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