-
-
Save nanom1t/b7ecb721fb29808af176a56587a5e756 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) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment