Querying Transactions in Ethereum with Go

·

Understanding how to query transactions is a fundamental skill for developers building on the Ethereum blockchain. Whether you're analyzing transaction history, verifying user activity, or building a blockchain explorer, retrieving and interpreting transaction data is essential. In this guide, we'll explore how to use the Go programming language with the go-ethereum library to fetch and decode Ethereum transactions programmatically.

This tutorial assumes basic familiarity with Go and Ethereum concepts such as blocks, hashes, gas, and transaction structure.


Reading Transactions from a Block

In the previous chapter, we learned how to read a block and its data using a given block number. Once you have a block, you can retrieve all transactions within it by calling the Transactions() method, which returns a slice of Transaction objects.

Each transaction contains key information such as:

Here’s how to loop through transactions in a block:

for _, tx := range block.Transactions() {
    fmt.Println("Hash:", tx.Hash().Hex())
    fmt.Println("Value (wei):", tx.Value().String())
    fmt.Println("Gas Limit:", tx.Gas())
    fmt.Println("Gas Price (wei):", tx.GasPrice().Uint64())
    fmt.Println("Nonce:", tx.Nonce())
    fmt.Println("Data:", tx.Data())
    if tx.To() != nil {
        fmt.Println("To Address:", tx.To().Hex())
    } else {
        fmt.Println("To Address: Contract Creation")
    }
}

👉 Discover how to interact with live Ethereum data using powerful Go tools


Retrieving the Transaction Sender (From Address)

While the recipient (To) is directly accessible, the sender (From) isn't stored in the transaction itself due to Ethereum's cryptographic design. To derive the sender, you must recover the public key from the digital signature using a signer.

The AsMessage method allows this recovery. It requires an EIP155 signer, which needs the chain ID to prevent replay attacks across networks.

chainID, err := client.NetworkID(context.Background())
if err != nil {
    log.Fatal(err)
}

signer := types.NewEIP155Signer(chainID)
message, err := tx.AsMessage(signer)
if err != nil {
    log.Fatal(err)
}

fmt.Println("From Address:", message.From().Hex())

This step is crucial when you need to identify who initiated a transaction—especially useful for analytics, wallets, or audit tools.


Fetching Transaction Receipts

Every transaction generates a receipt upon execution. The receipt contains post-execution details including:

You can retrieve a receipt using the transaction hash:

receipt, err := client.TransactionReceipt(context.Background(), tx.Hash())
if err != nil {
    log.Fatal(err)
}

fmt.Println("Transaction Status:", receipt.Status)
fmt.Println("Gas Used:", receipt.GasUsed)
fmt.Println("Logs Count:", len(receipt.Logs))

Receipts are vital for confirming whether a transaction succeeded and extracting event data—such as token transfers or state changes in decentralized applications.


Querying Transactions Without Full Blocks

If you don’t need full block data, you can directly access transactions using their hash or position within a block.

By Block Hash and Index

Use TransactionInBlock to get a transaction at a specific index in a block:

blockHash := common.HexToHash("0x9e8751ebb5069389b855bba72d94902cc385042661498a415979b7b6ee9ba4b9")
count, err := client.TransactionCount(context.Background(), blockHash)
if err != nil {
    log.Fatal(err)
}

for idx := uint(0); idx < count; idx++ {
    tx, err := client.TransactionInBlock(context.Background(), blockHash, idx)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println("Tx Hash:", tx.Hash().Hex())
}

This approach is efficient when scanning specific blocks without loading all transactions upfront.

By Transaction Hash

To fetch a single transaction directly:

txHash := common.HexToHash("0x5d49fcaa394c97ec8a9c3e7bd9e8388d420fb050a52083ca52ff24b3b65bc9c2")
tx, isPending, err := client.TransactionByHash(context.Background(), txHash)
if err != nil {
    log.Fatal(err)
}

fmt.Println("Found Transaction:", tx.Hash().Hex())
fmt.Println("Pending?", isPending) // false if already mined

This method is ideal for real-time lookups—such as tracking user deposits or monitoring smart contract calls.


Complete Working Example

Below is a full Go program that demonstrates querying transactions from a block, extracting sender addresses, fetching receipts, and looking up individual transactions:

package main

import (
    "context"
    "fmt"
    "log"
    "math/big"

    "github.com/ethereum/go-ethereum/common"
    "github.com/ethereum/go-ethereum/core/types"
    "github.com/ethereum/go-ethereum/ethclient"
)

func main() {
    client, err := ethclient.Dial("https://cloudflare-eth.com")
    if err != nil {
        log.Fatal(err)
    }

    // Fetch block by number
    blockNumber := big.NewInt(5671744)
    block, err := client.BlockByNumber(context.Background(), blockNumber)
    if err != nil {
        log.Fatal(err)
    }

    // Process each transaction
    for _, tx := range block.Transactions() {
        fmt.Println("Hash:", tx.Hash().Hex())
        fmt.Println("Value:", tx.Value().String())
        fmt.Println("Gas:", tx.Gas())
        fmt.Println("Gas Price:", tx.GasPrice().Uint64())
        fmt.Println("Nonce:", tx.Nonce())
        fmt.Println("Data:", tx.Data())
        if tx.To() != nil {
            fmt.Println("To:", tx.To().Hex())
        }

        // Get sender address
        chainID, _ := client.NetworkID(context.Background())
        signer := types.NewEIP155Signer(chainID)
        if msg, err := tx.AsMessage(signer); err == nil {
            fmt.Println("From:", msg.From().Hex())
        }

        // Get receipt
        receipt, _ := client.TransactionReceipt(context.Background(), tx.Hash())
        fmt.Println("Status:", receipt.Status)
        fmt.Println("---")
    }

    // Query by block hash and index
    blockHash := common.HexToHash("0x9e8751ebb5069389b855bba72d94902cc385042661498a415979b7b6ee9ba4b9")
    count, _ := client.TransactionCount(context.Background(), blockHash)
    for idx := uint(0); idx < count; idx++ {
        tx, _ := client.TransactionInBlock(context.Background(), blockHash, idx)
        fmt.Println("Indexed Tx:", tx.Hash().Hex())
    }

    // Direct lookup by hash
    txHash := common.HexToHash("0x5d49fcaa394c97ec8a9c3e7bd9e8388d420fb050a52083ca52ff24b3b65bc9c2")
    tx, isPending, _ := client.TransactionByHash(context.Background(), txHash)
    fmt.Println("Direct Lookup - Hash:", tx.Hash().Hex(), "Pending?", isPending)
}

👉 See how professional developers streamline Ethereum queries with advanced tooling


Frequently Asked Questions

How do I check if a transaction was successful?

Check the transaction receipt's Status field. A value of 1 means success; 0 indicates failure or revert.

Can I get pending transactions?

Yes. When using TransactionByHash, the second return value (isPending) tells you if the transaction hasn’t been mined yet.

Why can’t I get the sender address directly from the transaction?

The sender isn't stored on-chain. It must be cryptographically recovered using the signature and chain ID via AsMessage.

What’s the difference between TransactionInBlock and TransactionByHash?

TransactionInBlock retrieves a transaction by its position in a known block. TransactionByHash fetches it directly using its unique hash—regardless of block location.

Is it expensive to query many transactions?

It depends on your node provider. Public endpoints like Cloudflare's may rate-limit heavy usage. For large-scale analysis, consider running your own node or using archival services.

Can I decode input data from smart contract calls?

Yes. You can parse the Data() field using ABI decoding methods provided by go-ethereum. This allows interpretation of function calls and parameters.


Core Keywords: Ethereum transactions, Go blockchain development, query Ethereum data, transaction receipt, sender address recovery, Geth Go client, blockchain analytics