description: Tutorial on how to read the 0x Protocol smart contract event logs with Go.

Reading 0x Protocol Event Logs

To read 0x Protocol event logs we must first compile the solidity smart contract to a Go package.

Install solc version 0.4.11

  1. npm i -g solc@0.4.11

Create the 0x protocol exchange smart contract interface for event logs as Exchange.sol:

  1. pragma solidity 0.4.11;
  2. contract Exchange {
  3. event LogFill(
  4. address indexed maker,
  5. address taker,
  6. address indexed feeRecipient,
  7. address makerToken,
  8. address takerToken,
  9. uint filledMakerTokenAmount,
  10. uint filledTakerTokenAmount,
  11. uint paidMakerFee,
  12. uint paidTakerFee,
  13. bytes32 indexed tokens, // keccak256(makerToken, takerToken), allows subscribing to a token pair
  14. bytes32 orderHash
  15. );
  16. event LogCancel(
  17. address indexed maker,
  18. address indexed feeRecipient,
  19. address makerToken,
  20. address takerToken,
  21. uint cancelledMakerTokenAmount,
  22. uint cancelledTakerTokenAmount,
  23. bytes32 indexed tokens,
  24. bytes32 orderHash
  25. );
  26. event LogError(uint8 indexed errorId, bytes32 indexed orderHash);
  27. }

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

  1. solc --abi Exchange.sol
  2. abigen --abi="Exchange.sol:Exchange.abi" --pkg=exchange --out=Exchange.go

Now in our Go application let’s create the struct types matching the types of the 0xProtocol event log signature:

  1. type LogFill struct {
  2. Maker common.Address
  3. Taker common.Address
  4. FeeRecipient common.Address
  5. MakerToken common.Address
  6. TakerToken common.Address
  7. FilledMakerTokenAmount *big.Int
  8. FilledTakerTokenAmount *big.Int
  9. PaidMakerFee *big.Int
  10. PaidTakerFee *big.Int
  11. Tokens [32]byte
  12. OrderHash [32]byte
  13. }
  14. type LogCancel struct {
  15. Maker common.Address
  16. FeeRecipient common.Address
  17. MakerToken common.Address
  18. TakerToken common.Address
  19. CancelledMakerTokenAmount *big.Int
  20. CancelledTakerTokenAmount *big.Int
  21. Tokens [32]byte
  22. OrderHash [32]byte
  23. }
  24. type LogError struct {
  25. ErrorID uint8
  26. OrderHash [32]byte
  27. }

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 0x Protocol smart contract address and the desired block range:

  1. // 0x Protocol Exchange smart contract address
  2. contractAddress := common.HexToAddress("0x12459C951127e0c374FF9105DdA097662A027093")
  3. query := ethereum.FilterQuery{
  4. FromBlock: big.NewInt(6383482),
  5. ToBlock: big.NewInt(6383488),
  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 to unpack the raw log data later:

  1. contractAbi, err := abi.JSON(strings.NewReader(string(exchange.ExchangeABI)))
  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:

  1. // NOTE: keccak256("LogFill(address,address,address,address,address,uint256,uint256,uint256,uint256,bytes32,bytes32)")
  2. logFillEvent := common.HexToHash("0d0b9391970d9a25552f37d436d2aae2925e2bfe1b2a923754bada030c498cb3")
  3. // NOTE: keccak256("LogCancel(address,address,address,address,uint256,uint256,bytes32,bytes32)")
  4. logCancelEvent := common.HexToHash("67d66f160bc93d925d05dae1794c90d2d6d6688b29b84ff069398a9b04587131")
  5. // NOTE: keccak256("LogError(uint8,bytes32)")
  6. logErrorEvent := common.HexToHash("36d86c59e00bd73dc19ba3adfe068e4b64ac7e92be35546adeddf1b956a87e90")

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 logFillEvent.Hex():
  6. //
  7. case logCancelEvent.Hex():
  8. //
  9. case logErrorEvent.Hex():
  10. //
  11. }
  12. }

Now to parse LogFill 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: LogFill\n")
  2. var fillEvent LogFill
  3. err := contractAbi.Unpack(&fillEvent, "LogFill", vLog.Data)
  4. if err != nil {
  5. log.Fatal(err)
  6. }
  7. fillEvent.Maker = common.HexToAddress(vLog.Topics[1].Hex())
  8. fillEvent.FeeRecipient = common.HexToAddress(vLog.Topics[2].Hex())
  9. fillEvent.Tokens = vLog.Topics[3]
  10. fmt.Printf("Maker: %s\n", fillEvent.Maker.Hex())
  11. fmt.Printf("Taker: %s\n", fillEvent.Taker.Hex())
  12. fmt.Printf("Fee Recipient: %s\n", fillEvent.FeeRecipient.Hex())
  13. fmt.Printf("Maker Token: %s\n", fillEvent.MakerToken.Hex())
  14. fmt.Printf("Taker Token: %s\n", fillEvent.TakerToken.Hex())
  15. fmt.Printf("Filled Maker Token Amount: %s\n", fillEvent.FilledMakerTokenAmount.String())
  16. fmt.Printf("Filled Taker Token Amount: %s\n", fillEvent.FilledTakerTokenAmount.String())
  17. fmt.Printf("Paid Maker Fee: %s\n", fillEvent.PaidMakerFee.String())
  18. fmt.Printf("Paid Taker Fee: %s\n", fillEvent.PaidTakerFee.String())
  19. fmt.Printf("Tokens: %s\n", hexutil.Encode(fillEvent.Tokens[:]))
  20. fmt.Printf("Order Hash: %s\n", hexutil.Encode(fillEvent.OrderHash[:]))

Similarly for LogCancel:

  1. fmt.Printf("Log Name: LogCancel\n")
  2. var cancelEvent LogCancel
  3. err := contractAbi.Unpack(&cancelEvent, "LogCancel", vLog.Data)
  4. if err != nil {
  5. log.Fatal(err)
  6. }
  7. cancelEvent.Maker = common.HexToAddress(vLog.Topics[1].Hex())
  8. cancelEvent.FeeRecipient = common.HexToAddress(vLog.Topics[2].Hex())
  9. cancelEvent.Tokens = vLog.Topics[3]
  10. fmt.Printf("Maker: %s\n", cancelEvent.Maker.Hex())
  11. fmt.Printf("Fee Recipient: %s\n", cancelEvent.FeeRecipient.Hex())
  12. fmt.Printf("Maker Token: %s\n", cancelEvent.MakerToken.Hex())
  13. fmt.Printf("Taker Token: %s\n", cancelEvent.TakerToken.Hex())
  14. fmt.Printf("Cancelled Maker Token Amount: %s\n", cancelEvent.CancelledMakerTokenAmount.String())
  15. fmt.Printf("Cancelled Taker Token Amount: %s\n", cancelEvent.CancelledTakerTokenAmount.String())
  16. fmt.Printf("Tokens: %s\n", hexutil.Encode(cancelEvent.Tokens[:]))
  17. fmt.Printf("Order Hash: %s\n", hexutil.Encode(cancelEvent.OrderHash[:]))

And finally for LogError:

  1. fmt.Printf("Log Name: LogError\n")
  2. errorID, err := strconv.ParseInt(vLog.Topics[1].Hex(), 16, 64)
  3. if err != nil {
  4. log.Fatal(err)
  5. }
  6. errorEvent := &LogError{
  7. ErrorID: uint8(errorID),
  8. OrderHash: vLog.Topics[2],
  9. }
  10. fmt.Printf("Error ID: %d\n", errorEvent.ErrorID)
  11. fmt.Printf("Order Hash: %s\n", hexutil.Encode(errorEvent.OrderHash[:]))

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

  1. Log Block Number: 6383482
  2. Log Index: 35
  3. Log Name: LogFill
  4. Maker: 0x8dd688660ec0BaBD0B8a2f2DE3232645F73cC5eb
  5. Taker: 0xe269E891A2Ec8585a378882fFA531141205e92E9
  6. Fee Recipient: 0xe269E891A2Ec8585a378882fFA531141205e92E9
  7. Maker Token: 0xD7732e3783b0047aa251928960063f863AD022D8
  8. Taker Token: 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2
  9. Filled Maker Token Amount: 240000000000000000000000
  10. Filled Taker Token Amount: 6930282000000000000
  11. Paid Maker Fee: 0
  12. Paid Taker Fee: 0
  13. Tokens: 0xf08499c9e419ea8c08c4b991f88632593fb36baf4124c62758acb21898711088
  14. Order Hash: 0x306a9a7ecbd9446559a2c650b4cfc16d1fb615aa2b3f4f63078da6d021268440
  15. Log Block Number: 6383482
  16. Log Index: 38
  17. Log Name: LogFill
  18. Maker: 0x04aa059b2e31B5898fAB5aB24761e67E8a196AB8
  19. Taker: 0xe269E891A2Ec8585a378882fFA531141205e92E9
  20. Fee Recipient: 0xe269E891A2Ec8585a378882fFA531141205e92E9
  21. Maker Token: 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2
  22. Taker Token: 0xD7732e3783b0047aa251928960063f863AD022D8
  23. Filled Maker Token Amount: 6941718000000000000
  24. Filled Taker Token Amount: 240000000000000000000000
  25. Paid Maker Fee: 0
  26. Paid Taker Fee: 0
  27. Tokens: 0x97ef123f2b566f36ab1e6f5d462a8079fbe34fa667b4eae67194b3f9cce60f2a
  28. Order Hash: 0xac270e88ce27b6bb78ee5b68ebaef666a77195020a6ab8922834f07bc9e0d524
  29. Log Block Number: 6383488
  30. Log Index: 43
  31. Log Name: LogCancel
  32. Maker: 0x0004E79C978B95974dCa16F56B516bE0c50CC652
  33. Fee Recipient: 0xA258b39954ceF5cB142fd567A46cDdB31a670124
  34. Maker Token: 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2
  35. Taker Token: 0x89d24A6b4CcB1B6fAA2625fE562bDD9a23260359
  36. Cancelled Maker Token Amount: 30000000000000000000
  37. Cancelled Taker Token Amount: 7274848425000000000000
  38. Tokens: 0x9dd48110dcc444fdc242510c09bbbbe21a5975cac061d82f7b843bce061ba391
  39. Order Hash: 0xe43eff38dc27af046bfbd431926926c072bbc7a509d56f6f1a7ae1f5ad7efe4f

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


Full code

Commands

  1. solc --abi Exchange.sol
  2. abigen --abi="Exchange.sol:Exchange.abi" --pkg=exchange --out=Exchange.go

Exchange.sol

  1. pragma solidity 0.4.11;
  2. contract Exchange {
  3. event LogFill(
  4. address indexed maker,
  5. address taker,
  6. address indexed feeRecipient,
  7. address makerToken,
  8. address takerToken,
  9. uint filledMakerTokenAmount,
  10. uint filledTakerTokenAmount,
  11. uint paidMakerFee,
  12. uint paidTakerFee,
  13. bytes32 indexed tokens, // keccak256(makerToken, takerToken), allows subscribing to a token pair
  14. bytes32 orderHash
  15. );
  16. event LogCancel(
  17. address indexed maker,
  18. address indexed feeRecipient,
  19. address makerToken,
  20. address takerToken,
  21. uint cancelledMakerTokenAmount,
  22. uint cancelledTakerTokenAmount,
  23. bytes32 indexed tokens,
  24. bytes32 orderHash
  25. );
  26. event LogError(uint8 indexed errorId, bytes32 indexed orderHash);
  27. }

event_read_0xprotocol.go

  1. package main
  2. import (
  3. "context"
  4. "fmt"
  5. "log"
  6. "math/big"
  7. "strconv"
  8. "strings"
  9. exchange "./contracts_0xprotocol" // for demo
  10. "github.com/ethereum/go-ethereum"
  11. "github.com/ethereum/go-ethereum/accounts/abi"
  12. "github.com/ethereum/go-ethereum/common"
  13. "github.com/ethereum/go-ethereum/common/hexutil"
  14. "github.com/ethereum/go-ethereum/ethclient"
  15. )
  16. // LogFill ...
  17. type LogFill struct {
  18. Maker common.Address
  19. Taker common.Address
  20. FeeRecipient common.Address
  21. MakerToken common.Address
  22. TakerToken common.Address
  23. FilledMakerTokenAmount *big.Int
  24. FilledTakerTokenAmount *big.Int
  25. PaidMakerFee *big.Int
  26. PaidTakerFee *big.Int
  27. Tokens [32]byte
  28. OrderHash [32]byte
  29. }
  30. // LogCancel ...
  31. type LogCancel struct {
  32. Maker common.Address
  33. FeeRecipient common.Address
  34. MakerToken common.Address
  35. TakerToken common.Address
  36. CancelledMakerTokenAmount *big.Int
  37. CancelledTakerTokenAmount *big.Int
  38. Tokens [32]byte
  39. OrderHash [32]byte
  40. }
  41. // LogError ...
  42. type LogError struct {
  43. ErrorID uint8
  44. OrderHash [32]byte
  45. }
  46. func main() {
  47. client, err := ethclient.Dial("https://mainnet.infura.io")
  48. if err != nil {
  49. log.Fatal(err)
  50. }
  51. // 0x Protocol Exchange smart contract address
  52. contractAddress := common.HexToAddress("0x12459C951127e0c374FF9105DdA097662A027093")
  53. query := ethereum.FilterQuery{
  54. FromBlock: big.NewInt(6383482),
  55. ToBlock: big.NewInt(6383488),
  56. Addresses: []common.Address{
  57. contractAddress,
  58. },
  59. }
  60. logs, err := client.FilterLogs(context.Background(), query)
  61. if err != nil {
  62. log.Fatal(err)
  63. }
  64. contractAbi, err := abi.JSON(strings.NewReader(string(exchange.ExchangeABI)))
  65. if err != nil {
  66. log.Fatal(err)
  67. }
  68. // NOTE: keccak256("LogFill(address,address,address,address,address,uint256,uint256,uint256,uint256,bytes32,bytes32)")
  69. logFillEvent := common.HexToHash("0d0b9391970d9a25552f37d436d2aae2925e2bfe1b2a923754bada030c498cb3")
  70. // NOTE: keccak256("LogCancel(address,address,address,address,uint256,uint256,bytes32,bytes32)")
  71. logCancelEvent := common.HexToHash("67d66f160bc93d925d05dae1794c90d2d6d6688b29b84ff069398a9b04587131")
  72. // NOTE: keccak256("LogError(uint8,bytes32)")
  73. logErrorEvent := common.HexToHash("36d86c59e00bd73dc19ba3adfe068e4b64ac7e92be35546adeddf1b956a87e90")
  74. for _, vLog := range logs {
  75. fmt.Printf("Log Block Number: %d\n", vLog.BlockNumber)
  76. fmt.Printf("Log Index: %d\n", vLog.Index)
  77. switch vLog.Topics[0].Hex() {
  78. case logFillEvent.Hex():
  79. fmt.Printf("Log Name: LogFill\n")
  80. var fillEvent LogFill
  81. err := contractAbi.Unpack(&fillEvent, "LogFill", vLog.Data)
  82. if err != nil {
  83. log.Fatal(err)
  84. }
  85. fillEvent.Maker = common.HexToAddress(vLog.Topics[1].Hex())
  86. fillEvent.FeeRecipient = common.HexToAddress(vLog.Topics[2].Hex())
  87. fillEvent.Tokens = vLog.Topics[3]
  88. fmt.Printf("Maker: %s\n", fillEvent.Maker.Hex())
  89. fmt.Printf("Taker: %s\n", fillEvent.Taker.Hex())
  90. fmt.Printf("Fee Recipient: %s\n", fillEvent.FeeRecipient.Hex())
  91. fmt.Printf("Maker Token: %s\n", fillEvent.MakerToken.Hex())
  92. fmt.Printf("Taker Token: %s\n", fillEvent.TakerToken.Hex())
  93. fmt.Printf("Filled Maker Token Amount: %s\n", fillEvent.FilledMakerTokenAmount.String())
  94. fmt.Printf("Filled Taker Token Amount: %s\n", fillEvent.FilledTakerTokenAmount.String())
  95. fmt.Printf("Paid Maker Fee: %s\n", fillEvent.PaidMakerFee.String())
  96. fmt.Printf("Paid Taker Fee: %s\n", fillEvent.PaidTakerFee.String())
  97. fmt.Printf("Tokens: %s\n", hexutil.Encode(fillEvent.Tokens[:]))
  98. fmt.Printf("Order Hash: %s\n", hexutil.Encode(fillEvent.OrderHash[:]))
  99. case logCancelEvent.Hex():
  100. fmt.Printf("Log Name: LogCancel\n")
  101. var cancelEvent LogCancel
  102. err := contractAbi.Unpack(&cancelEvent, "LogCancel", vLog.Data)
  103. if err != nil {
  104. log.Fatal(err)
  105. }
  106. cancelEvent.Maker = common.HexToAddress(vLog.Topics[1].Hex())
  107. cancelEvent.FeeRecipient = common.HexToAddress(vLog.Topics[2].Hex())
  108. cancelEvent.Tokens = vLog.Topics[3]
  109. fmt.Printf("Maker: %s\n", cancelEvent.Maker.Hex())
  110. fmt.Printf("Fee Recipient: %s\n", cancelEvent.FeeRecipient.Hex())
  111. fmt.Printf("Maker Token: %s\n", cancelEvent.MakerToken.Hex())
  112. fmt.Printf("Taker Token: %s\n", cancelEvent.TakerToken.Hex())
  113. fmt.Printf("Cancelled Maker Token Amount: %s\n", cancelEvent.CancelledMakerTokenAmount.String())
  114. fmt.Printf("Cancelled Taker Token Amount: %s\n", cancelEvent.CancelledTakerTokenAmount.String())
  115. fmt.Printf("Tokens: %s\n", hexutil.Encode(cancelEvent.Tokens[:]))
  116. fmt.Printf("Order Hash: %s\n", hexutil.Encode(cancelEvent.OrderHash[:]))
  117. case logErrorEvent.Hex():
  118. fmt.Printf("Log Name: LogError\n")
  119. errorID, err := strconv.ParseInt(vLog.Topics[1].Hex(), 16, 64)
  120. if err != nil {
  121. log.Fatal(err)
  122. }
  123. errorEvent := &LogError{
  124. ErrorID: uint8(errorID),
  125. OrderHash: vLog.Topics[2],
  126. }
  127. fmt.Printf("Error ID: %d\n", errorEvent.ErrorID)
  128. fmt.Printf("Order Hash: %s\n", hexutil.Encode(errorEvent.OrderHash[:]))
  129. }
  130. fmt.Printf("\n\n")
  131. }
  132. }

solc version used for these examples

  1. $ solc --version
  2. 0.4.11+commit.68ef5810.Emscripten.clang