I'm fetching the logs using client.SubscribeFilterLogs
. The event parameters are encoded in types.Log.Data
. How can I decode it in go
?
-
Yes, the go-ethereum (the geth libraries) package received the contribution to add Event unpacking. I will write more about it in the answer below. – Robert Zaremba Nov 29 '17 at 19:04
go-ethereum abi package in Oct 2017 got the update to unpack event output. Initially it was only able to unpack method output.
All functionality is delivered through the abi.ABI
object. To use it you need to have Event ABI (JSON string). Then use func (abi *ABI) UnmarshalJSON to construct the ABI
object. From there you can use the Unpack
method using Data
from your log object.
Bear in mind that indexed attributes go into log Topic
rather than Data
.
For more usage details you can follow this examples:
NOTE: In the current implementation (2017-11-29) there is a bug with indexed attributes. I've submitted a PR for that and still waiting for the final approval.
Here's a full code example for anyone who's still confused (thanks to @Robert Zaremba's answer)
package main
import (
"context"
"log"
"math/big"
"strings"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
"github.com/myorg/myrepo/go-contracts/token"
"github.com/ethereum/go-ethereum/core/types"
)
func main() {
contractAddress := common.HexToAddress("0x0d8775f648430679a709e98d2b0cb6250d2887ef")
query := ethereum.FilterQuery{
Addresses: []common.Address{contractAddress},
}
var ch = make(chan types.Log)
ctx := context.Background()
sub, err := Client.SubscribeFilterLogs(ctx, query, ch)
if err != nil {
log.Fatal(err)
}
tokenAbi, err := abi.JSON(strings.NewReader(string(token.TokenABI)))
if err != nil {
log.Fatal(err)
}
for {
select {
case err := <-sub.Err():
log.Fatal(err)
case eventLog := <-ch:
var transferEvent struct {
From common.Address
To common.Address
Value *big.Int
}
err = tokenAbi.Unpack(&transferEvent, "Transfer", eventLog.Data)
if err != nil {
log.Println("Failed to unpack")
continue
}
transferEvent.From = common.BytesToAddress(eventLog.Topics[1].Bytes())
transferEvent.To = common.BytesToAddress(eventLog.Topics[2].Bytes())
log.Println("From", transferEvent.From.Hex())
log.Println("To", transferEvent.To.Hex())
log.Println("Value", transferEvent.Value)
}
}
}
-
One question: how do you know if the data contains a Transfer event, or a Approval event??? Because, you can receive both, or you can even receive other events, like
burn
ormint
, so how do you know in advance which type of event do you have to unpack? – Nulik Jun 23 '18 at 20:25 -
@Nulik great question; so topic[0] will be the event signature which is
keccak256(eventName(arg1,arg2))
so if it's a transfer event then topic[0] will bekeccak256(Transfer(address,address,uint256))
=>ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef
. So in short: you know the event type by filtering by topic[0] which is the hash of the event signature. Check out this Ethereum Development with Go guide for examples goethereumbook.org – Miguel Mota Jun 23 '18 at 20:32 -
@Nulik here's an example event log of a transfer event etherscan.io/tx/… . As you can see topic[0] is
keccak256(Transfer(address,address,uint256))
=>ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef
– Miguel Mota Jun 23 '18 at 20:34 -
@Nulik here's an example event log of an approval event etherscan.io/tx/… . As you can see topic[0] is
keccak256(Approval(address,address,uint256))
=>8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925
– Miguel Mota Jun 23 '18 at 20:36 -
Thanks, I have got it now. BTW, do you know of any other standard events? Transfer() and Approval() are the usual, but many tokens add minting or burning events. Do you know of any other events that are becoming standard too for erc20 tokens? – Nulik Jun 23 '18 at 20:58
Here's how I did it using the ABI:
path, _ := filepath.Abs("./resources/etherdelta.abi")
file, err := ioutil.ReadFile(path)
if err != nil {
fmt.Println("Failed to read file:", err)
}
edabi, err := abi.JSON(strings.NewReader(string(file)))
if err != nil {
fmt.Println("Invalid abi:", err)
}
var orderStruct struct {
TokenGet common.Address
AmountGet *big.Int
TokenGive common.Address
AmountGive *big.Int
Expires *big.Int
Nonce *big.Int
User common.Address
}
err = edabi.Unpack(&orderStruct, "Order", log.Data)
if err != nil {
fmt.Println("Failed to unpack:", err)
}
fmt.Println("TokenGet:", orderStruct.TokenGet.Hex())
fmt.Println("AmountGet:", orderStruct.AmountGet.Hex())
data := common.TrimLeftZeroes(log.Data)
hex := common.Bytes2Hex(data)
hex = TrimLeftZeroes(hex)
if hex != "" {
erc20Amount, err := hexutil.DecodeBig("0x" + hex)
}
func TrimLeftZeroes(hex string) string {
idx := 0
for ; idx < len(hex); idx++ {
if hex[idx] != '0' {
break
}
}
return hex[idx:]
}
-
2
-