Introduction
Working with ERC-20 token event logs is a fundamental skill for Ethereum developers. This guide walks you through the process of reading and parsing ERC-20 token event logs using Go, covering everything from smart contract interfaces to log filtering and data extraction.
Setting Up the ERC-20 Smart Contract Interface
First, create the ERC-20 smart contract interface for event logs in a file named erc20.sol:
pragma solidity ^0.4.24;
contract ERC20 {
event Transfer(address indexed from, address indexed to, uint tokens);
event Approval(address indexed tokenOwner, address indexed spender, uint tokens);
}Generating Go Bindings
Use abigen to create the Go exchange package from the ABI:
solc --abi erc20.sol
abigen --abi=erc20_sol_ERC20.abi --pkg=token --out=erc20.goStruct Types for Event Logs
Define struct types in Go that match the types of the ERC-20 event log signatures:
type LogTransfer struct {
From common.Address
To common.Address
Tokens *big.Int
}
type LogApproval struct {
TokenOwner common.Address
Spender common.Address
Tokens *big.Int
}Initializing the Ethereum Client
Initialize the Ethereum client to connect to the network:
client, err := ethclient.Dial("https://mainnet.infura.io")
if err != nil {
log.Fatal(err)
}Creating a Filter Query
Set up a FilterQuery to specify the contract address and block range. We'll use the ZRX token as an example:
contractAddress := common.HexToAddress("0xe41d2489571d322189246dafa5ebde1f4699f498")
query := ethereum.FilterQuery{
FromBlock: big.NewInt(6383820),
ToBlock: big.NewInt(6383840),
Addresses: []common.Address{
contractAddress,
},
}Querying the Logs
Fetch the logs using FilterLogs:
logs, err := client.FilterLogs(context.Background(), query)
if err != nil {
log.Fatal(err)
}Parsing the JSON ABI
Parse the JSON ABI to unpack the raw log data:
contractAbi, err := abi.JSON(strings.NewReader(string(token.TokenABI)))
if err != nil {
log.Fatal(err)
}Calculating Event Log Signatures
Calculate the keccak256 hash of each event log function signature:
logTransferSig := []byte("Transfer(address,address,uint256)")
LogApprovalSig := []byte("Approval(address,address,uint256)")
logTransferSigHash := crypto.Keccak256Hash(logTransferSig)
logApprovalSigHash := crypto.Keccak256Hash(LogApprovalSig)Filtering and Parsing Logs
Iterate through the logs and filter by event type:
for _, vLog := range logs {
fmt.Printf("Log Block Number: %d\n", vLog.BlockNumber)
fmt.Printf("Log Index: %d\n", vLog.Index)
switch vLog.Topics[0].Hex() {
case logTransferSigHash.Hex():
fmt.Printf("Log Name: Transfer\n")
var transferEvent LogTransfer
err := contractAbi.Unpack(&transferEvent, "Transfer", vLog.Data)
if err != nil {
log.Fatal(err)
}
transferEvent.From = common.HexToAddress(vLog.Topics[1].Hex())
transferEvent.To = common.HexToAddress(vLog.Topics[2].Hex())
fmt.Printf("From: %s\n", transferEvent.From.Hex())
fmt.Printf("To: %s\n", transferEvent.To.Hex())
fmt.Printf("Tokens: %s\n", transferEvent.Tokens.String())
case logApprovalSigHash.Hex():
fmt.Printf("Log Name: Approval\n")
var approvalEvent LogApproval
err := contractAbi.Unpack(&approvalEvent, "Approval", vLog.Data)
if err != nil {
log.Fatal(err)
}
approvalEvent.TokenOwner = common.HexToAddress(vLog.Topics[1].Hex())
approvalEvent.Spender = common.HexToAddress(vLog.Topics[2].Hex())
fmt.Printf("Token Owner: %s\n", approvalEvent.TokenOwner.Hex())
fmt.Printf("Spender: %s\n", approvalEvent.Spender.Hex())
fmt.Printf("Tokens: %s\n", approvalEvent.Tokens.String())
}
}Example Output
Running the code produces output like this:
Log Block Number: 6383829
Log Index: 20
Log Name: Transfer
From: 0xd03dB9CF89A9b1f856a8E1650cFD78FAF2338eB2
To: 0x924CD9b60F4173DCDd5254ddD38C4F9CAB68FE6b
Tokens: 2804000000000000000000
Log Block Number: 6383831
Log Index: 62
Log Name: Approval
Token Owner: 0xDD3b9186Da521AbE707B48B8f805Fb3Cd5EEe0EE
Spender: 0xCf67d7A481CEEca0a77f658991A00366FED558F7
Tokens: 10000000000000000000000000000000000000000000000000000000000000000
Log Block Number: 6383838
Log Index: 13
Log Name: Transfer
From: 0xBA826fEc90CEFdf6706858E5FbaFcb27A290Fbe0
To: 0x4aEE792A88eDDA29932254099b9d1e06D537883f
Tokens: 2863452144424379687066👉 Compare these results with Etherscan for verification.
Full Code Implementation
Commands
solc --abi erc20.sol
abigen --abi=erc20_sol_ERC20.abi --pkg=token --out=erc20.goERC-20 Contract Interface
pragma solidity ^0.4.24;
contract ERC20 {
event Transfer(address indexed from, address indexed to, uint tokens);
event Approval(address indexed tokenOwner, address indexed spender, uint tokens);
}Main Go File
package main
import (
"context"
"fmt"
"log"
"math/big"
"strings"
token "./contracts_erc20" // for demo
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethclient"
)
// LogTransfer ..
type LogTransfer struct {
From common.Address
To common.Address
Tokens *big.Int
}
// LogApproval ..
type LogApproval struct {
TokenOwner common.Address
Spender common.Address
Tokens *big.Int
}
func main() {
client, err := ethclient.Dial("https://mainnet.infura.io")
if err != nil {
log.Fatal(err)
}
// 0x Protocol (ZRX) token address
contractAddress := common.HexToAddress("0xe41d2489571d322189246dafa5ebde1f4699f498")
query := ethereum.FilterQuery{
FromBlock: big.NewInt(6383820),
ToBlock: big.NewInt(6383840),
Addresses: []common.Address{
contractAddress,
},
}
logs, err := client.FilterLogs(context.Background(), query)
if err != nil {
log.Fatal(err)
}
contractAbi, err := abi.JSON(strings.NewReader(string(token.TokenABI)))
if err != nil {
log.Fatal(err)
}
logTransferSig := []byte("Transfer(address,address,uint256)")
LogApprovalSig := []byte("Approval(address,address,uint256)")
logTransferSigHash := crypto.Keccak256Hash(logTransferSig)
logApprovalSigHash := crypto.Keccak256Hash(LogApprovalSig)
for _, vLog := range logs {
fmt.Printf("Log Block Number: %d\n", vLog.BlockNumber)
fmt.Printf("Log Index: %d\n", vLog.Index)
switch vLog.Topics[0].Hex() {
case logTransferSigHash.Hex():
fmt.Printf("Log Name: Transfer\n")
var transferEvent LogTransfer
err := contractAbi.Unpack(&transferEvent, "Transfer", vLog.Data)
if err != nil {
log.Fatal(err)
}
transferEvent.From = common.HexToAddress(vLog.Topics[1].Hex())
transferEvent.To = common.HexToAddress(vLog.Topics[2].Hex())
fmt.Printf("From: %s\n", transferEvent.From.Hex())
fmt.Printf("To: %s\n", transferEvent.To.Hex())
fmt.Printf("Tokens: %s\n", transferEvent.Tokens.String())
case logApprovalSigHash.Hex():
fmt.Printf("Log Name: Approval\n")
var approvalEvent LogApproval
err := contractAbi.Unpack(&approvalEvent, "Approval", vLog.Data)
if err != nil {
log.Fatal(err)
}
approvalEvent.TokenOwner = common.HexToAddress(vLog.Topics[1].Hex())
approvalEvent.Spender = common.HexToAddress(vLog.Topics[2].Hex())
fmt.Printf("Token Owner: %s\n", approvalEvent.TokenOwner.Hex())
fmt.Printf("Spender: %s\n", approvalEvent.Spender.Hex())
fmt.Printf("Tokens: %s\n", approvalEvent.Tokens.String())
}
fmt.Printf("\n\n")
}
}Solc Version Information
$ solc --version
0.4.24+commit.e67f0147.Emscripten.clangFAQs
What is an ERC-20 token?
ERC-20 is a technical standard used for smart contracts on the Ethereum blockchain for implementing tokens. It defines a common list of rules that Ethereum tokens must follow.
Why do we need to parse event logs?
Event logs provide a way to track contract activities without requiring constant blockchain queries. Parsing these logs allows applications to react to contract events efficiently.
👉 Learn more about Ethereum development and how to leverage these techniques in your projects.
How can I verify my parsed logs?
You can compare your parsed logs with transaction details on Etherscan by searching for the transaction hash or contract address involved in the events.