description: Tutorial on how to read smart contract events with Go.

Reading Event Logs

A smart contract may optionally emit “events” which get stored as logs as part of the transaction receipt. Reading these events is pretty simple. First we need to construct a filter query. We import the FilterQuery struct from the go-ethereum package and initialize it with filter options. We tell it the range of blocks that we want to filter through and specify the contract address to read these logs from. In this example we’ll be reading all the logs from a particular block, from the smart contract we created in the smart contract sections.

  1. query := ethereum.FilterQuery{
  2. FromBlock: big.NewInt(2394201),
  3. ToBlock: big.NewInt(2394201),
  4. Addresses: []common.Address{
  5. contractAddress,
  6. },
  7. }

The next is step is to call FilterLogs from the ethclient that takes in our query and will return all the matching event logs.

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

All the logs returned will be ABI encoded so by themselves they won’t be very readable. In order to decode the logs we’ll need to import our smart contract ABI. To do that, we import our compiled smart contract Go package which will contain an external property in the name format <ContractName>ABI containing our ABI. Afterwards we use the abi.JSON function from the go-ethereum accounts/abi go-ethereum package to return a parsed ABI interface that we can use in our Go application.

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

Now we can interate through the logs and decode them into a type we can use. If you recall the logs that our sample contract emitted were of type bytes32 in Solidity, so the equivalent in Go would be [32]byte. We can create an anonymous struct with these types and pass a pointer as the first argument to the Unpack function of the parsed ABI interface to decode the raw log data. The second argument is the name of the event we’re trying to decode and the last argument is the encoded log data.

  1. for _, vLog := range logs {
  2. event := struct {
  3. Key [32]byte
  4. Value [32]byte
  5. }{}
  6. err := contractAbi.Unpack(&event, "ItemSet", vLog.Data)
  7. if err != nil {
  8. log.Fatal(err)
  9. }
  10. fmt.Println(string(event.Key[:])) // foo
  11. fmt.Println(string(event.Value[:])) // bar
  12. }

Also, the log struct contains additional information such as the block hash, block number, and transaction hash.

  1. fmt.Println(vLog.BlockHash.Hex()) // 0x3404b8c050aa0aacd0223e91b5c32fee6400f357764771d0684fa7b3f448f1a8
  2. fmt.Println(vLog.BlockNumber) // 2394201
  3. fmt.Println(vLog.TxHash.Hex()) // 0x280201eda63c9ff6f305fcee51d5eb86167fab40ca3108ec784e8652a0e2b1a6

Topics

If your solidity event contains indexed event types, then they become a topic rather than part of the data property of the log. In solidity you may only have up to 4 topics but only 3 indexed event types. The first topic is always the signature of the event. Our example contract didn’t contain indexed events, but if it did this is how to read the event topics.

  1. var topics [4]string
  2. for i := range vLog.Topics {
  3. topics[i] = vLog.Topics[i].Hex()
  4. }
  5. fmt.Println(topics[0]) // 0xe79e73da417710ae99aa2088575580a60415d359acfad9cdd3382d59c80281d4

As you can see here the first topic is just the hashed event signature.

  1. eventSignature := []byte("ItemSet(bytes32,bytes32)")
  2. hash := crypto.Keccak256Hash(eventSignature)
  3. fmt.Println(hash.Hex()) // 0xe79e73da417710ae99aa2088575580a60415d359acfad9cdd3382d59c80281d4

That’s all there is to reading and parsing logs. To learn how to subscribe to logs, read the previous section.

Full code

Commands

  1. solc --abi Store.sol
  2. solc --bin Store.sol
  3. abigen --bin=Store_sol_Store.bin --abi=Store_sol_Store.abi --pkg=store --out=Store.go

Store.sol

  1. pragma solidity ^0.4.24;
  2. contract Store {
  3. event ItemSet(bytes32 key, bytes32 value);
  4. string public version;
  5. mapping (bytes32 => bytes32) public items;
  6. constructor(string _version) public {
  7. version = _version;
  8. }
  9. function setItem(bytes32 key, bytes32 value) external {
  10. items[key] = value;
  11. emit ItemSet(key, value);
  12. }
  13. }

event_read.go

  1. package main
  2. import (
  3. "context"
  4. "fmt"
  5. "log"
  6. "math/big"
  7. "strings"
  8. "github.com/ethereum/go-ethereum"
  9. "github.com/ethereum/go-ethereum/accounts/abi"
  10. "github.com/ethereum/go-ethereum/common"
  11. "github.com/ethereum/go-ethereum/crypto"
  12. "github.com/ethereum/go-ethereum/ethclient"
  13. store "./contracts" // for demo
  14. )
  15. func main() {
  16. client, err := ethclient.Dial("wss://rinkeby.infura.io/ws")
  17. if err != nil {
  18. log.Fatal(err)
  19. }
  20. contractAddress := common.HexToAddress("0x147B8eb97fD247D06C4006D269c90C1908Fb5D54")
  21. query := ethereum.FilterQuery{
  22. FromBlock: big.NewInt(2394201),
  23. ToBlock: big.NewInt(2394201),
  24. Addresses: []common.Address{
  25. contractAddress,
  26. },
  27. }
  28. logs, err := client.FilterLogs(context.Background(), query)
  29. if err != nil {
  30. log.Fatal(err)
  31. }
  32. contractAbi, err := abi.JSON(strings.NewReader(string(store.StoreABI)))
  33. if err != nil {
  34. log.Fatal(err)
  35. }
  36. for _, vLog := range logs {
  37. fmt.Println(vLog.BlockHash.Hex()) // 0x3404b8c050aa0aacd0223e91b5c32fee6400f357764771d0684fa7b3f448f1a8
  38. fmt.Println(vLog.BlockNumber) // 2394201
  39. fmt.Println(vLog.TxHash.Hex()) // 0x280201eda63c9ff6f305fcee51d5eb86167fab40ca3108ec784e8652a0e2b1a6
  40. event := struct {
  41. Key [32]byte
  42. Value [32]byte
  43. }{}
  44. err := contractAbi.Unpack(&event, "ItemSet", vLog.Data)
  45. if err != nil {
  46. log.Fatal(err)
  47. }
  48. fmt.Println(string(event.Key[:])) // foo
  49. fmt.Println(string(event.Value[:])) // bar
  50. var topics [4]string
  51. for i := range vLog.Topics {
  52. topics[i] = vLog.Topics[i].Hex()
  53. }
  54. fmt.Println(topics[0]) // 0xe79e73da417710ae99aa2088575580a60415d359acfad9cdd3382d59c80281d4
  55. }
  56. eventSignature := []byte("ItemSet(bytes32,bytes32)")
  57. hash := crypto.Keccak256Hash(eventSignature)
  58. fmt.Println(hash.Hex()) // 0xe79e73da417710ae99aa2088575580a60415d359acfad9cdd3382d59c80281d4
  59. }
  1. $ solc --version
  2. 0.4.24+commit.e67f0147.Emscripten.clang