Ethereum

Ethereum

Go

go-ethereum — geth — is the reference Ethereum node, and the same module ships an ethclient package you import as a library. That gives Go the most complete and current EVM tooling around: the team maintaining the dominant execution client also maintains the API you call, so a new fork like Dencun lands in the bindings without a third-party catching up. Pull it in with go get github.com/ethereum/go-ethereum. It's stewarded by the Ethereum Foundation, which is why the types track the live protocol on chain 1.

go-ethereum is the official Go client, maintained by the Ethereum Foundation. For Go developers that means the most complete and up-to-date EVM implementation available — the library and the node move together, so what you build against matches what mainnet runs.

Go-Ethereum (geth)

ethclient.Dial(url) opens the connection to the Ethereum endpoint and returns a *ethclient.Client that speaks every JSON-RPC method as a typed Go call — BalanceAt returns a *big.Int of wei, SuggestGasPrice an eth_gasPrice reading. Wrapping that client in your own struct, as below, keeps the endpoint URL in one place and lets you swap in a mock client in tests instead of hitting mainnet from a unit test.

package ethereum
import (
"context"
"math/big"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
)
type EthereumClient struct {
client *ethclient.Client
}
func NewEthereumClient(url string) (*EthereumClient, error) {
client, err := ethclient.Dial(url)
if err != nil {
return nil, err
}
return &EthereumClient{client: client}, nil
}
func (ec *EthereumClient) GetBalance(address string) (*big.Float, error) {
account := common.HexToAddress(address)
balance, err := ec.client.BalanceAt(context.Background(), account, nil)
if err != nil {
return nil, err
}
fbalance := new(big.Float)
fbalance.SetString(balance.String())
ethValue := new(big.Float).Quo(fbalance, big.NewFloat(1e18))
return ethValue, nil
}
func (ec *EthereumClient) SendTransaction(from, to common.Address, value *big.Int) (*types.Transaction, error) {
nonce, err := ec.client.PendingNonceAt(context.Background(), from)
if err != nil {
return nil, err
}
gasPrice, err := ec.client.SuggestGasPrice(context.Background())
if err != nil {
return nil, err
}
tx := types.NewTransaction(nonce, to, value, 21000, gasPrice, nil)
return tx, nil
}
  • GitHub: https://github.com/ethereum/go-ethereum
  • Docs: https://geth.ethereum.org/
  • A full Ethereum node implementation you can also import as a library
  • Complete coverage of the Ethereum protocol, kept current with each upgrade
  • High-performance client tuned for production load
  • CLI tools (geth, abigen, clef) and mobile (Android/iOS) build targets
  • Typed contract bindings generated from Solidity ABIs via abigen

Smart Contract Integration

The idiomatic path for contracts is abigen: feed it a Solidity ABI and it emits a typed Go package where each contract method becomes a Go method, with the parameter and return types already decoded. Under that generated code sits bind.BoundContract, which you can also use directly — it pairs the parsed ABI with the client and exposes Call for gas-free reads against an Ethereum contract and Transact for signed state changes. Generating bindings is what turns an ERC-20's transfer into a compile-checked call rather than a hand-built calldata blob.

package contracts
import (
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
)
type SmartContractClient struct {
contract *bind.BoundContract
address common.Address
}
func NewContract(address common.Address, client *ethclient.Client) (*SmartContractClient, error) {
parsed, err := abi.JSON(strings.NewReader(ContractABI))
if err != nil {
return nil, err
}
contract := bind.NewBoundContract(address, parsed, client, client, client)
return &SmartContractClient{
contract: contract,
address: address,
}, nil
}
func (sc *SmartContractClient) CallMethod(method string, args ...interface{}) error {
opts := &bind.CallOpts{
Pending: false,
Context: context.Background(),
}
return sc.contract.Call(opts, method, args...)
}

Event Monitoring

SubscribeNewHead opens a live feed of block headers, but it only works over WebSocket — eth_subscribe can't ride a plain HTTP connection, so dial wss://ethereum.therpc.io/YOUR_API_KEY for this, not the HTTPS URL you use for one-off reads. Since a new Ethereum block arrives roughly every 12 seconds, you want the push rather than a polling loop. Run the receive logic in a goroutine and select over two channels: one for incoming headers, one for sub.Err(), so a dropped connection surfaces as an error you can reconnect on instead of a silent stall.

func (ec *EthereumClient) MonitorBlocks() (<-chan *types.Header, error) {
headers := make(chan *types.Header)
sub, err := ec.client.SubscribeNewHead(context.Background(), headers)
if err != nil {
return nil, err
}
go func() {
for {
select {
case err := <-sub.Err():
log.Fatal(err)
case header := <-headers:
block, err := ec.client.BlockByHash(context.Background(), header.Hash())
if err != nil {
log.Fatal(err)
}
fmt.Println("New block:", block.Number().Uint64())
}
}
}()
return headers, nil
}

Utils and Helpers

Wei values on Ethereum routinely exceed what a float64 or even a uint64 can hold — a single ETH is 10^18 wei, and balances stack well beyond that. Do every wei calculation in big.Int, and only drop to big.Float at the last step when you divide by 10^18 for display. Reach for float64 anywhere in the middle and you'll silently lose precision, which is how an off-by-a-few-wei rounding bug ends up in a payout.

package utils
import (
"math/big"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
)
// WeiToEther converts wei to ether
func WeiToEther(wei *big.Int) *big.Float {
return new(big.Float).Quo(
new(big.Float).SetInt(wei),
new(big.Float).SetInt(big.NewInt(1e18)),
)
}
// EtherToWei converts ether to wei
func EtherToWei(ether *big.Float) *big.Int {
truncInt, _ := new(big.Float).Mul(ether, big.NewFloat(1e18)).Int(nil)
return truncInt
}
// IsValidAddress checks if the address is valid
func IsValidAddress(address string) bool {
return common.IsHexAddress(address)
}

Ready to call this in production?

Free tier covers personal projects. Pay-as-you-go scales without a card.