概述: 用Go来读取ERC20代币智能合约的教程。

读取ERC-20代币的事件日志

首先,创建ERC-20智能合约的事件日志的interface文件 erc20.sol:

  1. pragma solidity ^0.4.24;
  2. contract ERC20 {
  3. event Transfer(address indexed from, address indexed to, uint tokens);
  4. event Approval(address indexed tokenOwner, address indexed spender, uint tokens);
  5. }

然后在给定abi使用abigen创建Go包

  1. solc --abi erc20.sol
  2. abigen --abi=erc20_sol_ERC20.abi --pkg=token --out=erc20.go

现在在我们的Go应用程序中,让我们创建与ERC-20事件日志签名类型相匹配的结构类型:

  1. type LogTransfer struct {
  2. From common.Address
  3. To common.Address
  4. Tokens *big.Int
  5. }
  6. type LogApproval struct {
  7. TokenOwner common.Address
  8. Spender common.Address
  9. Tokens *big.Int
  10. }

初始化以太坊客户端

  1. client, err := ethclient.Dial("https://mainnet.infura.io")
  2. if err != nil {
  3. log.Fatal(err)
  4. }

按照ERC-20智能合约地址和所需的块范围创建一个“FilterQuery”。这个例子我们会用ZRX 代币:

  1. // 0x Protocol (ZRX) token address
  2. contractAddress := common.HexToAddress("0xe41d2489571d322189246dafa5ebde1f4699f498")
  3. query := ethereum.FilterQuery{
  4. FromBlock: big.NewInt(6383820),
  5. ToBlock: big.NewInt(6383840),
  6. Addresses: []common.Address{
  7. contractAddress,
  8. },
  9. }

FilterLogs来过滤日志:

  1. logs, err := client.FilterLogs(context.Background(), query)
  2. if err != nil {
  3. log.Fatal(err)
  4. }

接下来我们将解析JSON abi,稍后我们将使用解压缩原始日志数据:

  1. contractAbi, err := abi.JSON(strings.NewReader(string(token.TokenABI)))
  2. if err != nil {
  3. log.Fatal(err)
  4. }

为了按某种日志类型进行过滤,我们需要弄清楚每个事件日志函数签名的keccak256哈希值。 事件日志函数签名哈希始终是topic [0],我们很快就会看到。 以下是使用go-ethereumcrypto包计算keccak256哈希的方法:

  1. logTransferSig := []byte("Transfer(address,address,uint256)")
  2. LogApprovalSig := []byte("Approval(address,address,uint256)")
  3. logTransferSigHash := crypto.Keccak256Hash(logTransferSig)
  4. logApprovalSigHash := crypto.Keccak256Hash(LogApprovalSig)

现在我们将遍历所有日志并设置switch语句以按事件日志类型进行过滤:

  1. for _, vLog := range logs {
  2. fmt.Printf("Log Block Number: %d\n", vLog.BlockNumber)
  3. fmt.Printf("Log Index: %d\n", vLog.Index)
  4. switch vLog.Topics[0].Hex() {
  5. case logTransferSigHash.Hex():
  6. //
  7. case logApprovalSigHash.Hex():
  8. //
  9. }
  10. }

现在要解析Transfer事件日志,我们将使用abi.Unpack将原始日志数据解析为我们的日志类型结构。 解包不会解析indexed事件类型,因为它们存储在topics下,所以对于那些我们必须单独解析,如下例所示:

  1. fmt.Printf("Log Name: Transfer\n")
  2. var transferEvent LogTransfer
  3. err := contractAbi.Unpack(&transferEvent, "Transfer", vLog.Data)
  4. if err != nil {
  5. log.Fatal(err)
  6. }
  7. transferEvent.From = common.HexToAddress(vLog.Topics[1].Hex())
  8. transferEvent.To = common.HexToAddress(vLog.Topics[2].Hex())
  9. fmt.Printf("From: %s\n", transferEvent.From.Hex())
  10. fmt.Printf("To: %s\n", transferEvent.To.Hex())
  11. fmt.Printf("Tokens: %s\n", transferEvent.Tokens.String())

Approval 日志也是类似的方法:

  1. fmt.Printf("Log Name: Approval\n")
  2. var approvalEvent LogApproval
  3. err := contractAbi.Unpack(&approvalEvent, "Approval", vLog.Data)
  4. if err != nil {
  5. log.Fatal(err)
  6. }
  7. approvalEvent.TokenOwner = common.HexToAddress(vLog.Topics[1].Hex())
  8. approvalEvent.Spender = common.HexToAddress(vLog.Topics[2].Hex())
  9. fmt.Printf("Token Owner: %s\n", approvalEvent.TokenOwner.Hex())
  10. fmt.Printf("Spender: %s\n", approvalEvent.Spender.Hex())
  11. fmt.Printf("Tokens: %s\n", approvalEvent.Tokens.String())

最后,把所有的步骤放一起:

  1. Log Block Number: 6383829
  2. Log Index: 20
  3. Log Name: Transfer
  4. From: 0xd03dB9CF89A9b1f856a8E1650cFD78FAF2338eB2
  5. To: 0x924CD9b60F4173DCDd5254ddD38C4F9CAB68FE6b
  6. Tokens: 2804000000000000000000
  7. Log Block Number: 6383831
  8. Log Index: 62
  9. Log Name: Approval
  10. Token Owner: 0xDD3b9186Da521AbE707B48B8f805Fb3Cd5EEe0EE
  11. Spender: 0xCf67d7A481CEEca0a77f658991A00366FED558F7
  12. Tokens: 10000000000000000000000000000000000000000000000000000000000000000
  13. Log Block Number: 6383838
  14. Log Index: 13
  15. Log Name: Transfer
  16. From: 0xBA826fEc90CEFdf6706858E5FbaFcb27A290Fbe0
  17. To: 0x4aEE792A88eDDA29932254099b9d1e06D537883f
  18. Tokens: 2863452144424379687066

我们可以把解析的日志与etherscan的数据对比: https://etherscan.io/tx/0x0c3b6cf604275c7e44dc7db400428c1a39f33f0c6cbc19ff625f6057a5cb32c0#eventlog

完整代码

Commands

  1. solc --abi erc20.sol
  2. abigen --abi=erc20_sol_ERC20.abi --pkg=token --out=erc20.go

erc20.sol

  1. pragma solidity ^0.4.24;
  2. contract ERC20 {
  3. event Transfer(address indexed from, address indexed to, uint tokens);
  4. event Approval(address indexed tokenOwner, address indexed spender, uint tokens);
  5. }

event_read_erc20.go

  1. package main
  2. import (
  3. "context"
  4. "fmt"
  5. "log"
  6. "math/big"
  7. "strings"
  8. token "./contracts_erc20" // for demo
  9. "github.com/ethereum/go-ethereum"
  10. "github.com/ethereum/go-ethereum/accounts/abi"
  11. "github.com/ethereum/go-ethereum/common"
  12. "github.com/ethereum/go-ethereum/crypto"
  13. "github.com/ethereum/go-ethereum/ethclient"
  14. )
  15. // LogTransfer ..
  16. type LogTransfer struct {
  17. From common.Address
  18. To common.Address
  19. Tokens *big.Int
  20. }
  21. // LogApproval ..
  22. type LogApproval struct {
  23. TokenOwner common.Address
  24. Spender common.Address
  25. Tokens *big.Int
  26. }
  27. func main() {
  28. client, err := ethclient.Dial("https://mainnet.infura.io")
  29. if err != nil {
  30. log.Fatal(err)
  31. }
  32. // 0x Protocol (ZRX) token address
  33. contractAddress := common.HexToAddress("0xe41d2489571d322189246dafa5ebde1f4699f498")
  34. query := ethereum.FilterQuery{
  35. FromBlock: big.NewInt(6383820),
  36. ToBlock: big.NewInt(6383840),
  37. Addresses: []common.Address{
  38. contractAddress,
  39. },
  40. }
  41. logs, err := client.FilterLogs(context.Background(), query)
  42. if err != nil {
  43. log.Fatal(err)
  44. }
  45. contractAbi, err := abi.JSON(strings.NewReader(string(token.TokenABI)))
  46. if err != nil {
  47. log.Fatal(err)
  48. }
  49. logTransferSig := []byte("Transfer(address,address,uint256)")
  50. LogApprovalSig := []byte("Approval(address,address,uint256)")
  51. logTransferSigHash := crypto.Keccak256Hash(logTransferSig)
  52. logApprovalSigHash := crypto.Keccak256Hash(LogApprovalSig)
  53. for _, vLog := range logs {
  54. fmt.Printf("Log Block Number: %d\n", vLog.BlockNumber)
  55. fmt.Printf("Log Index: %d\n", vLog.Index)
  56. switch vLog.Topics[0].Hex() {
  57. case logTransferSigHash.Hex():
  58. fmt.Printf("Log Name: Transfer\n")
  59. var transferEvent LogTransfer
  60. err := contractAbi.Unpack(&transferEvent, "Transfer", vLog.Data)
  61. if err != nil {
  62. log.Fatal(err)
  63. }
  64. transferEvent.From = common.HexToAddress(vLog.Topics[1].Hex())
  65. transferEvent.To = common.HexToAddress(vLog.Topics[2].Hex())
  66. fmt.Printf("From: %s\n", transferEvent.From.Hex())
  67. fmt.Printf("To: %s\n", transferEvent.To.Hex())
  68. fmt.Printf("Tokens: %s\n", transferEvent.Tokens.String())
  69. case logApprovalSigHash.Hex():
  70. fmt.Printf("Log Name: Approval\n")
  71. var approvalEvent LogApproval
  72. err := contractAbi.Unpack(&approvalEvent, "Approval", vLog.Data)
  73. if err != nil {
  74. log.Fatal(err)
  75. }
  76. approvalEvent.TokenOwner = common.HexToAddress(vLog.Topics[1].Hex())
  77. approvalEvent.Spender = common.HexToAddress(vLog.Topics[2].Hex())
  78. fmt.Printf("Token Owner: %s\n", approvalEvent.TokenOwner.Hex())
  79. fmt.Printf("Spender: %s\n", approvalEvent.Spender.Hex())
  80. fmt.Printf("Tokens: %s\n", approvalEvent.Tokens.String())
  81. }
  82. fmt.Printf("\n\n")
  83. }
  84. }

solc version used for these examples

  1. $ solc --version
  2. 0.4.24+commit.e67f0147.Emscripten.clang