As Ethereum continues to evolve, so do its underlying protocols and development tools. Developers working with the Go implementation of Ethereum—go-ethereum—often encounter challenges when retrieving transaction sender addresses (From field), especially as older methods become deprecated due to protocol upgrades. This guide walks you through the correct and up-to-date approach for extracting the From address from Ethereum transactions using go-ethereum v1.12.2, aligning with current network standards.
Whether you're building blockchain explorers, audit tools, or decentralized application (dApp) backends, understanding how to parse transaction origins is crucial. We’ll cover everything from connecting to an Ethereum node, fetching blocks and transactions, to properly decoding the sender address using dynamic signers based on network configuration.
Connect to an Ethereum Node
Before retrieving any data, you need a connection to the Ethereum blockchain. The ethclient package in go-ethereum allows Go applications to interact with Ethereum nodes via HTTP or WebSocket endpoints.
Here’s how to establish a secure connection using a public endpoint like Cloudflare’s Ethereum gateway:
package main
import (
"context"
"fmt"
"github.com/ethereum/go-ethereum/ethclient"
"log"
"math/big"
)
func main() {
ethereumNodeEndpoint := "https://cloudflare-eth.com"
client, err := ethclient.Dial(ethereumNodeEndpoint)
if err != nil {
log.Fatalf("Failed to connect to Ethereum node [%s]: %v", ethereumNodeEndpoint, err)
}
ctx := context.TODO()
latestBlockNumber, err := client.BlockNumber(ctx)
if err != nil {
log.Fatal("Failed to retrieve latest block number: ", err)
}
fmt.Println("Latest block number:", latestBlockNumber)
}This snippet connects to the Ethereum mainnet and retrieves the current highest block number—your entry point into the chain.
👉 Learn how blockchain data powers real-time trading insights
Fetch a Block and Its Transactions
Once connected, you can fetch a specific block by its number. Each block contains multiple transactions, which we can iterate over.
block, err := client.BlockByNumber(ctx, big.NewInt(int64(latestBlockNumber)))
if err != nil {
log.Fatal("Failed to retrieve latest block: ", err)
}
fmt.Println("Block hash:", block.Hash().Hex())
fmt.Println("Number of transactions:", len(block.Transactions()))Output:
Latest block number: 18054718
Block hash: 0xe1122f17b2d7e9f717bdedb7062cf0a2f44b3199e702754b97f9a5832622a87a
Number of transactions: 226You now have access to the full block data, including all included transactions.
Extract Basic Transaction Data
Let’s examine a specific transaction within the block—say, the second one (index 1):
tx := block.Transactions()[1]
fmt.Println("Transaction hash:", tx.Hash().Hex())
fmt.Println("Value (wei):", tx.Value().String())
fmt.Println("Gas limit:", tx.Gas())
fmt.Println("Gas price:", tx.GasPrice().Uint64())
if to := tx.To(); to != nil {
fmt.Println("Recipient address:", to.Hex())
}Sample output:
Transaction hash: 0xb9ac9b6a4eb857cfd410d0f3dc5604c5e52382e490acd00a14f9cf69c59391b0
Value (wei): 10000000000000000
Gas limit: 274920
Gas price: 59204457483
Recipient address: 0x7a250d5630B4cF539739dF2C5dAcb4c659F2488DNote that while fields like To, Value, and Gas are directly accessible, there's no From field in the transaction object. That’s because the sender isn't stored directly—it must be recovered from the digital signature.
Recover the Sender Address (From Field)
The From address is derived cryptographically from the transaction’s signature using a signer. Different Ethereum upgrades (e.g., Homestead, EIP-155, London, Cancun) use different signing algorithms. Therefore, you must use the correct signer based on the block’s configuration.
signer := types.MakeSigner(params.MainnetChainConfig, block.Number(), block.Time())
from, err := signer.Sender(tx)
if err != nil {
log.Fatal("Failed to recover sender address: ", err)
}
fmt.Println("Sender address:", from.Hex())Output:
Sender address: 0xCe1a934f373fb7355aFa2895d411A80e38384703How Signer Selection Works
The MakeSigner function dynamically selects the appropriate signing algorithm based on the chain configuration and block context:
func MakeSigner(config *params.ChainConfig, blockNumber *big.Int, blockTime uint64) Signer {
var signer Signer
switch {
case config.IsCancun(blockNumber, blockTime):
signer = NewCancunSigner(config.ChainID)
case config.IsLondon(blockNumber):
signer = NewLondonSigner(config.ChainID)
case config.IsBerlin(blockNumber):
signer = NewEIP2930Signer(config.ChainID)
case config.IsEIP155(blockNumber):
signer = NewEIP155Signer(config.ChainID)
case config.IsHomestead(blockNumber):
signer = HomesteadSigner{}
default:
signer = FrontierSigner{}
}
return signer
}This ensures compatibility across all Ethereum upgrade phases.
For private networks where chain ID and configuration differ, consider using types.LatestSignerForChainID(yourChainID) instead of relying on mainnet defaults.👉 Discover how accurate on-chain analysis enhances trading strategies
Complete Working Example
Here’s the full code combining all steps:
package main
import (
"context"
"fmt"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/params"
"log"
"math/big"
)
func main() {
client, err := ethclient.Dial("https://cloudflare-eth.com")
if err != nil {
log.Fatalf("Failed to connect: %v", err)
}
ctx := context.TODO()
header, err := client.HeaderByNumber(ctx, nil)
if err != nil {
log.Fatal("Failed to get latest header: ", err)
}
block, err := client.BlockByNumber(ctx, header.Number)
if err != nil {
log.Fatal("Failed to get block: ", err)
}
tx := block.Transactions()[1]
signer := types.MakeSigner(params.MainnetChainConfig, block.Number(), block.Time())
from, err := signer.Sender(tx)
if err != nil {
log.Fatal("Failed to recover sender: ", err)
}
fmt.Printf("Sender: %s\n", from.Hex())
fmt.Printf("Recipient: %s\n", tx.To().Hex())
fmt.Printf("Value: %s wei\n", tx.Value().String())
}FAQ
Q: Why doesn’t the transaction object have a From field?
A: The sender is not stored in the transaction data. It is recovered from the digital signature using elliptic curve cryptography and the appropriate signing scheme active at that block height.
Q: Can I use MakeSigner for private networks?
A: Not reliably with MainnetChainConfig. Instead, create a custom signer using types.NewEIP155Signer(chainID) or types.LatestSignerForChainID(chainID) tailored to your network.
Q: What happens if I use the wrong signer?
A: Signature recovery will fail or return an incorrect address. Always match the signer to the network and fork rules applicable at the block’s timestamp and number.
Q: Are there alternatives to recovering From via signing?
A: Yes. You can use debug APIs like trace_transaction or debug_traceTransaction with a callTracer, which also reveals internal transactions (e.g., those triggered by smart contracts).
Q: Is this method gas-efficient?
A: Yes—signature recovery runs locally and doesn't require on-chain computation. It’s ideal for backend services processing large volumes of transactions.
Core Keywords
- go-ethereum
- Ethereum transaction sender
- Recover From address
- Go Ethereum development
- Transaction signature recovery
- Ethereum blockchain parsing
- Golang smart contract interaction
- ETH transaction analysis
By understanding how transaction senders are derived in modern Ethereum clients, developers can build robust tools that remain compatible across network upgrades. Whether you're analyzing wallet activity or verifying dApp interactions, correctly implementing sender recovery ensures accurate and trustworthy results.
👉 Explore blockchain data tools that simplify developer workflows