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:
- Hash: Unique identifier of the transaction
- Value: Amount of Ether transferred (in wei)
- Gas: Gas limit set by the sender
- Gas Price: Price per unit of gas (in wei)
- Nonce: Sequence number for the sender’s address
- Data: Optional field used for smart contract interactions
- To: Recipient address (or contract creation if nil)
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:
- Status:
1for success,0for failure - Logs: Events emitted by smart contracts
- Gas Used: Actual gas consumed
- Contract Address: If a contract was created
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 minedThis 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