前言
之前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