description: Tutorial on how to read ERC-20 Token smart contract events with Go.

Reading ERC-20 Token Event Logs

First create the ERC-20 smart contract interface for event logs as 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. }

Then use abigen to create the Go exchange package given the abi:

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

Now in our Go application let’s create the struct types matching the types of the ERC-20 event log signature:

  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. }

Initialize the ethereum client:

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

Create a FilterQuery passing the ERC-20 smart contract address and the desired block range. We’ll be using the ZRX token for this example:

  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. }

Query the logs with FilterLogs:

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

Next we’ll parse the JSON abi which we’ll use unpack the raw log data later:

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

In order to filter by certain log type, we need to figure out the keccak256 hash of each event log function signature. The event log function signature hash is always topic[0] as we’ll see soon. Here’s how to calculate the keccak256 hash using the go-ethereum crypto package:

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

Now we’ll iterate through all the logs and set up a switch statement to filter by event log type:

  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. }

Now to parse the Transfer event log we’ll use abi.Unpack to parse the raw log data into our log type struct. Unpack will not parse indexed event types because those are stored under topics, so for those we’ll have to parse separately as seen in the example below:

  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())

Similarly for the Approval event log:

  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())

Putting it all together and running it we’ll see the following output:

  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

Compare the parsed log output to what’s on etherscan: https://etherscan.io/tx/0x0c3b6cf604275c7e44dc7db400428c1a39f33f0c6cbc19ff625f6057a5cb32c0#eventlog

Full code

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