Skip to content

Instantly share code, notes, and snippets.

@jerryan999
Last active September 23, 2023 13:00
Show Gist options
  • Save jerryan999/fb784e668e3f140619d22eaa391c3dd5 to your computer and use it in GitHub Desktop.
Save jerryan999/fb784e668e3f140619d22eaa391c3dd5 to your computer and use it in GitHub Desktop.
How to decode smart contract transation
[{"inputs":[{"internalType":"string","name":"name","type":"string"},{"internalType":"string","name":"symbol","type":"string"},{"internalType":"uint256","name":"maxNftSupply","type":"uint256"},{"internalType":"uint256","name":"saleStart","type":"uint256"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"approved","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"operator","type":"address"},{"indexed":false,"internalType":"bool","name":"approved","type":"bool"}],"name":"ApprovalForAll","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"Transfer","type":"event"},{"inputs":[],"name":"BAYC_PROVENANCE","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAX_APES","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"REVEAL_TIMESTAMP","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"apePrice","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"approve","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"baseURI","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"emergencySetStartingIndexBlock","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"flipSaleState","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"getApproved","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"operator","type":"address"}],"name":"isApprovedForAll","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"maxApePurchase","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"numberOfTokens","type":"uint256"}],"name":"mintApe","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"ownerOf","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"reserveApes","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"safeTransferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"bytes","name":"_data","type":"bytes"}],"name":"safeTransferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"saleIsActive","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"operator","type":"address"},{"internalType":"bool","name":"approved","type":"bool"}],"name":"setApprovalForAll","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"baseURI","type":"string"}],"name":"setBaseURI","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"provenanceHash","type":"string"}],"name":"setProvenanceHash","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"revealTimeStamp","type":"uint256"}],"name":"setRevealTimestamp","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"setStartingIndex","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"startingIndex","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"startingIndexBlock","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"index","type":"uint256"}],"name":"tokenByIndex","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"uint256","name":"index","type":"uint256"}],"name":"tokenOfOwnerByIndex","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"tokenURI","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"transferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"withdraw","outputs":[],"stateMutability":"nonpayable","type":"function"}]p
package main
import (
"context"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"log"
"os"
"strings"
"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"
)
func MapToJson(param map[string]interface{}) string {
dataType, _ := json.Marshal(param)
dataString := string(dataType)
return dataString
}
const PROVIDER_URL = "https://ethereum.publicnode.com"
func GetLocalABI(path string) string {
// read abi json from local file
abiFile, err := os.Open(path)
if err != nil {
log.Fatal(err)
}
defer abiFile.Close()
result, err := io.ReadAll(abiFile)
if err != nil {
log.Fatal(err)
}
return string(result)
}
// refer
// https://github.com/ethereum/web3.py/blob/master/web3/contract.py#L435
func DecodeTransactionInputData(contractABI *abi.ABI, data []byte) {
// The first 4 bytes of the txn represent the ID of the method in the ABI
methodSigData := data[:4]
method, err := contractABI.MethodById(methodSigData)
if err != nil {
log.Fatal(err)
}
// parse the inputs to this method
inputsSigData := data[4:]
inputsMap := make(map[string]interface{})
if err := method.Inputs.UnpackIntoMap(inputsMap, inputsSigData); err != nil {
log.Fatal(err)
}
fmt.Printf("Method Name: %s\n", method.Name)
fmt.Printf("Method inputs: %v\n", MapToJson(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()) // from field is not inside of transation
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()))
fmt.Print("\n")
}
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 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() {
// read parameter from the command line
txHashStr := os.Args[1]
// client wrappers for the Ethereum RPC API.
client, err := ethclient.Dial(PROVIDER_URL)
if err != nil {
log.Println(err)
continue
}
// TransactionByHash returns the transaction with the given hash.
txHash := common.HexToHash(txHashStr)
tx, isPending, err := client.TransactionByHash(context.Background(), txHash)
if err != nil {
log.Fatal(err)
}
fmt.Printf("tx isPending: %t\n", isPending)
// parse the transaction information
fmt.Println("\nStart to pase transation base info")
ParseTransactionBaseInfo(tx)
// get abi from local
contractABI, err := abi.JSON(strings.NewReader(GetLocalABI("boredape.json")))
if err != nil {
log.Fatal(err)
}
fmt.Println("\nStart to decode transation input data")
DecodeTransactionInputData(&contractABI, tx.Data())
fmt.Println("\nStart to decode transation log")
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