Skip to content

Instantly share code, notes, and snippets.

@crazygit
Last active May 9, 2024 13:22
Show Gist options
  • Star 28 You must be signed in to star a gist
  • Fork 15 You must be signed in to fork a gist
  • Save crazygit/9279a3b26461d7cb03e807a6362ec855 to your computer and use it in GitHub Desktop.
Save crazygit/9279a3b26461d7cb03e807a6362ec855 to your computer and use it in GitHub Desktop.
Parse Ethereum Transaction, Decode input data, Decode output data
package main
import (
"context"
"encoding/hex"
"fmt"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/go-resty/resty/v2"
"log"
"strings"
)
type (
RawABIResponse struct {
Status *string `json:"status"`
Message *string `json:"message"`
Result *string `json:"result"`
}
)
func GetContractRawABI(address string, apiKey string) (*RawABIResponse, error) {
client := resty.New()
rawABIResponse := &RawABIResponse{}
resp, err := client.R().
SetQueryParams(map[string]string{
"module": "contract",
"action": "getabi",
"address": address,
"apikey": apiKey,
}).
SetResult(rawABIResponse).
Get("https://api-ropsten.etherscan.io/api")
if err != nil {
return nil, err
}
if !resp.IsSuccess() {
return nil, fmt.Errorf(fmt.Sprintf("Get contract raw abi failed: %s\n", resp))
}
if *rawABIResponse.Status != "1" {
return nil, fmt.Errorf(fmt.Sprintf("Get contract raw abi failed: %s\n", *rawABIResponse.Result))
}
return rawABIResponse, nil
}
// refer
// https://github.com/ethereum/web3.py/blob/master/web3/contract.py#L435
func DecodeTransactionInputData(contractABI *abi.ABI, data []byte) {
methodSigData := data[:4]
inputsSigData := data[4:]
method, err := contractABI.MethodById(methodSigData)
if err != nil {
log.Fatal(err)
}
inputsMap := make(map[string]interface{})
if err := method.Inputs.UnpackIntoMap(inputsMap, inputsSigData); err != nil {
log.Fatal(err)
} else {
fmt.Println(inputsMap)
}
fmt.Printf("Method Name: %s\n", method.Name)
fmt.Printf("Method inputs: %v\n", inputsMap)
}
func GetTransactionMessage(tx *types.Transaction) types.Message {
msg, err := tx.AsMessage(types.LatestSignerForChainID(tx.ChainId()), nil)
if err != nil {
log.Fatal(err)
}
return msg
}
func ParseTransactionBaseInfo(tx *types.Transaction) {
fmt.Printf("Hash: %s\n", tx.Hash().Hex())
fmt.Printf("ChainId: %d\n", tx.ChainId())
fmt.Printf("Value: %s\n", tx.Value().String())
fmt.Printf("From: %s\n", GetTransactionMessage(tx).From().Hex())
fmt.Printf("To: %s\n", tx.To().Hex())
fmt.Printf("Gas: %d\n", tx.Gas())
fmt.Printf("Gas Price: %d\n", tx.GasPrice().Uint64())
fmt.Printf("Nonce: %d\n", tx.Nonce())
fmt.Printf("Transaction Data in hex: %s\n", hex.EncodeToString(tx.Data()))
}
func DecodeTransactionLogs(receipt *types.Receipt, contractABI *abi.ABI) {
for _, vLog := range receipt.Logs {
// topic[0] is the event name
event, err := contractABI.EventByID(vLog.Topics[0])
if err != nil {
log.Fatal(err)
}
fmt.Printf("Event Name: %s\n", event.Name)
// topic[1:] is other indexed params in event
if len(vLog.Topics) > 1 {
for i, param := range vLog.Topics[1:] {
fmt.Printf("Indexed params %d in hex: %s\n", i, param)
fmt.Printf("Indexed params %d decoded %s\n", i, common.HexToAddress(param.Hex()))
}
}
if len(vLog.Data) > 0 {
fmt.Printf("Log Data in Hex: %s\n", hex.EncodeToString(vLog.Data))
outputDataMap := make(map[string]interface{})
err = contractABI.UnpackIntoMap(outputDataMap, event.Name, vLog.Data)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Event outputs: %v\n", outputDataMap)
}
}
}
func GetContractABI(contractAddress, etherscanAPIKey string) *abi.ABI {
rawABIResponse, err := GetContractRawABI(contractAddress, etherscanAPIKey)
if err != nil {
log.Fatal(err)
}
contractABI, err := abi.JSON(strings.NewReader(*rawABIResponse.Result))
if err != nil {
log.Fatal(err)
}
return &contractABI
}
func GetTransactionReceipt(client *ethclient.Client, txHash common.Hash) *types.Receipt {
receipt, err := client.TransactionReceipt(context.Background(), txHash)
if err != nil {
log.Fatal(err)
}
return receipt
}
func main() {
// get etherscanAPIKEY from https://docs.etherscan.io/getting-started/viewing-api-usage-statistics
const etherscanAPIKEY = "M3SF4WTDC4NWQIIVNAZDFXBW1SW49QWDNZ"
const providerUrl = "https://ropsten.infura.io/v3/28d5693e8bee4b58a61f0c627d62331e"
client, err := ethclient.Dial(providerUrl)
if err != nil {
log.Fatal(err)
}
// https://ropsten.etherscan.io/tx/0x7e605f68ff30509eb2bf3238936ef65a01bfa25243488c007244aabe645d0ec9
txHash := common.HexToHash("0x7e605f68ff30509eb2bf3238936ef65a01bfa25243488c007244aabe645d0ec9")
tx, isPending, err := client.TransactionByHash(context.Background(), txHash)
if err != nil {
log.Fatal(err)
}
fmt.Printf("tx isPending: %t\n", isPending)
ParseTransactionBaseInfo(tx)
contractABI := GetContractABI(tx.To().String(), etherscanAPIKEY)
DecodeTransactionInputData(contractABI, tx.Data())
receipt := GetTransactionReceipt(client, txHash)
DecodeTransactionLogs(receipt, contractABI)
}
@crazygit
Copy link
Author

crazygit commented Jun 1, 2022

Here is the output of this script

tx isPending: false
Hash: 0x7e605f68ff30509eb2bf3238936ef65a01bfa25243488c007244aabe645d0ec9
ChainId: 3
Value: 0
From: 0x206AaB6b3e64e812479E287715fe40b2d7BDE67d
To: 0xf1EEfEE62A8651c3772cd8D7ba9031b7029316f7
Gas: 39403
Gas Price: 1500000016
Nonce: 1829
Transaction Data in hex: 191e2e83000000000000000000000000b00985da0555ee582ebdf24a1b72996ae77a72cd000000000000000000000000000000000000000000000000016345785d8a0000
map[amount:100000000000000000 receiver:0xb00985Da0555EE582ebdf24a1b72996AE77a72cD]
Method Name: withDraw
Method inputs: map[amount:100000000000000000 receiver:0xb00985Da0555EE582ebdf24a1b72996AE77a72cD]
Event Name: WithDrawEvent
Indexed params 0 in hex: 0x000000000000000000000000b00985da0555ee582ebdf24a1b72996ae77a72cd
Indexed params 0 decoded 0xb00985Da0555EE582ebdf24a1b72996AE77a72cD
Log Data in Hex: 000000000000000000000000000000000000000000000000016345785d8a0000
Event outputs: map[amount:100000000000000000]

@memochou1993
Copy link

This is great!

@rudian
Copy link

rudian commented Jul 5, 2022

This is cool!

@scDisorder
Copy link

thanks!

@padi-dev-dangpm
Copy link

padi-dev-dangpm commented Jan 27, 2023

This code only parse transaction if it is not contract-creation. To fix this you can see https://github.com/padi-dev-dangpm/sync_event_ethereum_with_go/blob/main/transactions/parse.go ( I use above code)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment