Last active
April 6, 2025 08:22
-
-
Save crazygit/9279a3b26461d7cb03e807a6362ec855 to your computer and use it in GitHub Desktop.
Parse Ethereum Transaction, Decode input data, Decode output data
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | |
} |
This is great!
This is cool!
thanks!
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
Here is the output of this script