ethclient wraps the low-level rpc.Client and exposes every standard eth_* method as a typed Go function. It is the recommended starting point for any Go application that needs to talk to an Ethereum node.
import "github.com/ethereum/go-ethereum/ethclient"
Connecting
client, err := ethclient.Dial("https://mainnet.infura.io/v3/<key>")
if err != nil {
log.Fatal(err)
}
defer client.Close()
Dial and DialContext return (*Client, error):
func Dial(rawurl string) (*Client, error)
func DialContext(ctx context.Context, rawurl string) (*Client, error)
Subscriptions (
SubscribeNewHead, SubscribeFilterLogs) require a persistent connection. Use WebSocket or IPC — HTTP connections do not support server-push notifications.Method reference
Blockchain
// Current chain ID (replay-protection identifier)
func (ec *Client) ChainID(ctx context.Context) (*big.Int, error)
// Most recent block number
func (ec *Client) BlockNumber(ctx context.Context) (uint64, error)
// Full block by number; pass nil for the latest block
func (ec *Client) BlockByNumber(ctx context.Context, number *big.Int) (*types.Block, error)
// Full block by hash
func (ec *Client) BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error)
// Block header only (cheaper — one RPC round-trip instead of two)
func (ec *Client) HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error)
func (ec *Client) HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error)
// All receipts for a block
func (ec *Client) BlockReceipts(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) ([]*types.Receipt, error)
// Current sync progress; returns nil if the node is fully synced
func (ec *Client) SyncProgress(ctx context.Context) (*ethereum.SyncProgress, error)
BlockByNumber and BlockByHash require two RPC calls: one for the header and one for uncles. Use HeaderByNumber when you do not need transaction data.*big.Int corresponding to rpc.BlockNumber constants:
| Constant | Meaning |
|---|---|
nil | latest |
rpc.LatestBlockNumber | latest |
rpc.SafeBlockNumber | safe head |
rpc.FinalizedBlockNumber | finalized |
rpc.PendingBlockNumber | pending |
rpc.EarliestBlockNumber | genesis |
Transactions
// Fetch a transaction; isPending is true if not yet mined
func (ec *Client) TransactionByHash(ctx context.Context, hash common.Hash) (tx *types.Transaction, isPending bool, err error)
// Receipt for a mined transaction (returns ethereum.NotFound for pending)
func (ec *Client) TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error)
// Inject a signed transaction into the pending pool
func (ec *Client) SendTransaction(ctx context.Context, tx *types.Transaction) error
// Number of transactions in a block
func (ec *Client) TransactionCount(ctx context.Context, blockHash common.Hash) (uint, error)
State
// Wei balance at a given block (nil = latest)
func (ec *Client) BalanceAt(ctx context.Context, account common.Address, blockNumber *big.Int) (*big.Int, error)
// Raw storage slot value
func (ec *Client) StorageAt(ctx context.Context, account common.Address, key common.Hash, blockNumber *big.Int) ([]byte, error)
// Contract bytecode
func (ec *Client) CodeAt(ctx context.Context, account common.Address, blockNumber *big.Int) ([]byte, error)
// Account nonce (use PendingNonceAt for the next available nonce)
func (ec *Client) NonceAt(ctx context.Context, account common.Address, blockNumber *big.Int) (uint64, error)
func (ec *Client) PendingNonceAt(ctx context.Context, account common.Address) (uint64, error)
Contract calls and gas
// Execute a read-only call against the EVM (eth_call)
func (ec *Client) CallContract(ctx context.Context, msg ethereum.CallMsg, blockNumber *big.Int) ([]byte, error)
// Estimate gas needed for a transaction
func (ec *Client) EstimateGas(ctx context.Context, msg ethereum.CallMsg) (uint64, error)
// Legacy gas price oracle
func (ec *Client) SuggestGasPrice(ctx context.Context) (*big.Int, error)
// EIP-1559 priority fee suggestion
func (ec *Client) SuggestGasTipCap(ctx context.Context) (*big.Int, error)
// EIP-4844 blob base fee
func (ec *Client) BlobBaseFee(ctx context.Context) (*big.Int, error)
// Fee history for the last N blocks
func (ec *Client) FeeHistory(ctx context.Context, blockCount uint64, lastBlock *big.Int, rewardPercentiles []float64) (*ethereum.FeeHistory, error)
Logs
// One-off log query
func (ec *Client) FilterLogs(ctx context.Context, q ethereum.FilterQuery) ([]types.Log, error)
// Streaming log subscription (WebSocket / IPC only)
func (ec *Client) SubscribeFilterLogs(ctx context.Context, q ethereum.FilterQuery, ch chan<- types.Log) (ethereum.Subscription, error)
ethereum.FilterQuery fields:
type FilterQuery struct {
BlockHash *common.Hash // pin to a single block, or...
FromBlock *big.Int // ...use a range (nil = genesis)
ToBlock *big.Int // nil = latest
Addresses []common.Address // restrict to these contracts
Topics [][]common.Hash // topic filter matrix
}
Subscriptions
// Receive new block headers as they arrive (WebSocket / IPC only)
func (ec *Client) SubscribeNewHead(ctx context.Context, ch chan<- *types.Header) (ethereum.Subscription, error)
ethereum.Subscription:
type Subscription interface {
Unsubscribe()
Err() <-chan error
}
Full example
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/crypto"
"github.com/ethereum/go-ethereum/ethclient"
)
func main() {
client, err := ethclient.Dial("https://mainnet.infura.io/v3/<key>")
if err != nil {
log.Fatal(err)
}
defer client.Close()
ctx := context.Background()
// Latest block number
blockNum, err := client.BlockNumber(ctx)
if err != nil {
log.Fatal(err)
}
fmt.Println("block:", blockNum)
// Full block
block, err := client.BlockByNumber(ctx, big.NewInt(int64(blockNum)))
if err != nil {
log.Fatal(err)
}
fmt.Printf("txs in block: %d\n", len(block.Transactions()))
// Balance of an address
addr := common.HexToAddress("0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045")
balance, err := client.BalanceAt(ctx, addr, nil) // nil = latest
if err != nil {
log.Fatal(err)
}
fmt.Println("balance (wei):", balance)
// Build and send an EIP-1559 transfer
privateKey, err := crypto.HexToECDSA("<hex-private-key>")
if err != nil {
log.Fatal(err)
}
chainID, _ := client.ChainID(ctx)
nonce, _ := client.PendingNonceAt(ctx, crypto.PubkeyToAddress(privateKey.PublicKey))
tip, _ := client.SuggestGasTipCap(ctx)
tx := types.NewTx(&types.DynamicFeeTx{
ChainID: chainID,
Nonce: nonce,
GasTipCap: tip,
GasFeeCap: new(big.Int).Add(tip, big.NewInt(2e9)), // tip + 2 gwei headroom
Gas: 21000,
To: &addr,
Value: big.NewInt(1e18), // 1 ETH in wei
})
signer := types.LatestSignerForChainID(chainID)
signedTx, err := types.SignTx(tx, signer, privateKey)
if err != nil {
log.Fatal(err)
}
if err := client.SendTransaction(ctx, signedTx); err != nil {
log.Fatal(err)
}
fmt.Println("sent:", signedTx.Hash())
}
Simulated backend for tests
Theethclient/simulated package provides an in-memory blockchain so you can write unit tests without running a real node.
import (
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/ethclient/simulated"
)
// Allocate 10 ETH to a test account at genesis
alloc := types.GenesisAlloc{
testAddr: {Balance: big.NewInt(10e18)},
}
// chainID is always 1337
backend := simulated.NewBackend(alloc)
defer backend.Close()
client := backend.Client() // implements the same interface as *ethclient.Client
// Mine a block explicitly
backend.Commit()
Backend methods:
func NewBackend(alloc types.GenesisAlloc, options ...func(*node.Config, *ethconfig.Config)) *Backend
func (n *Backend) Client() Client // returns the ethclient-compatible interface
func (n *Backend) Commit() common.Hash // seal a block and advance the chain
func (n *Backend) Rollback() // discard pending transactions
func (n *Backend) Fork(parentHash common.Hash) error // simulate a reorg
func (n *Backend) AdjustTime(adjustment time.Duration) error
func (n *Backend) Close() error
The simulated backend always uses chain ID 1337. Transactions must be signed with that chain ID.
