Two packages handle the account layer in go-ethereum:
accounts/abi — encode method calls, decode return values, and parse event logs.
accounts/keystore — create, import, unlock, and sign with password-encrypted key files.
accounts/abi
import "github.com/ethereum/go-ethereum/accounts/abi"
The ABI type
type ABI struct {
Constructor Method
Methods map[string]Method
Events map[string]Event
Errors map[string]Error
Fallback Method
Receive Method
}
Parsing an ABI
func JSON(reader io.Reader) (ABI, error)
parsedABI, err := abi.JSON(strings.NewReader(abiJSON))
if err != nil {
log.Fatal(err)
}
Encoding a method call
Pack prepends the 4-byte selector and ABI-encodes all arguments:
func (abi ABI) Pack(name string, args ...interface{}) ([]byte, error)
toAddr := common.HexToAddress("0xRecipient")
amount := big.NewInt(1e18)
calldata, err := parsedABI.Pack("transfer", toAddr, amount)
if err != nil {
log.Fatal(err)
}
// calldata = [selector (4 bytes)] + [ABI-encoded args]
Pass an empty name to encode constructor arguments only (no selector):
calldata, err := parsedABI.Pack("", constructorArg1, constructorArg2)
Decoding return values
Unpack returns []interface{}; UnpackIntoInterface maps values into a struct or slice:
func (abi ABI) Unpack(name string, data []byte) ([]interface{}, error)
func (abi ABI) UnpackIntoInterface(v interface{}, name string, data []byte) error
func (abi ABI) UnpackIntoMap(v map[string]interface{}, name string, data []byte) error
// Using UnpackIntoInterface — v must be a pointer
var balance *big.Int
if err := parsedABI.UnpackIntoInterface(&balance, "balanceOf", returnData); err != nil {
log.Fatal(err)
}
fmt.Println("balance:", balance)
// Using Unpack — returns []interface{}
results, err := parsedABI.Unpack("balanceOf", returnData)
if err != nil {
log.Fatal(err)
}
balance := results[0].(*big.Int)
Parsing event logs
Events are accessed through abi.Events:
// Get the event definition
transferEvent := parsedABI.Events["Transfer"]
// The first topic is always the event ID (keccak256 of the signature)
fmt.Println(transferEvent.ID.Hex())
// Unpack non-indexed data from log.Data
values, err := transferEvent.Inputs.UnpackValues(log.Data)
if err != nil {
log.Fatal(err)
}
Indexed event parameters are packed into log.Topics (32 bytes each), not into log.Data. Non-indexed parameters are ABI-encoded in log.Data.
Looking up methods and events by selector
func (abi *ABI) MethodById(sigdata []byte) (*Method, error)
func (abi *ABI) EventByID(topic common.Hash) (*Event, error)
func (abi *ABI) ErrorByID(sigdata [4]byte) (*Error, error)
Decoding revert reasons
func UnpackRevert(data []byte) (string, error)
Passes raw revert data (from CallContract errors or receipt logs) and returns the human-readable reason string from a Solidity revert("...") or a panic code description.
Full example — call + decode
package main
import (
"context"
"fmt"
"log"
"math/big"
"strings"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethclient"
)
const erc20ABI = `[{"inputs":[{"name":"account","type":"address"}],"name":"balanceOf","outputs":[{"name":"","type":"uint256"}],"stateMutability":"view","type":"function"}]`
func main() {
client, err := ethclient.Dial("https://mainnet.infura.io/v3/<key>")
if err != nil {
log.Fatal(err)
}
parsedABI, err := abi.JSON(strings.NewReader(erc20ABI))
if err != nil {
log.Fatal(err)
}
token := common.HexToAddress("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48") // USDC
account := common.HexToAddress("0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045")
calldata, err := parsedABI.Pack("balanceOf", account)
if err != nil {
log.Fatal(err)
}
result, err := client.CallContract(context.Background(), ethereum.CallMsg{
To: &token,
Data: calldata,
}, nil)
if err != nil {
log.Fatal(err)
}
var balance *big.Int
if err := parsedABI.UnpackIntoInterface(&balance, "balanceOf", result); err != nil {
log.Fatal(err)
}
fmt.Println("USDC balance (units):", balance)
}
accounts/keystore
import "github.com/ethereum/go-ethereum/accounts/keystore"
Keys are stored as encrypted JSON files using the Web3 Secret Storage specification.
Scrypt parameters
Two predefined cost settings trade security for speed:
| Constant | N | P | Use |
|---|
StandardScryptN / StandardScryptP | 262144 | 1 | Production |
LightScryptN / LightScryptP | 4096 | 6 | Development / testing |
Creating a keystore
func NewKeyStore(keydir string, scryptN, scryptP int) *KeyStore
ks := keystore.NewKeyStore("/path/to/keystore", keystore.StandardScryptN, keystore.StandardScryptP)
Creating an account
func (ks *KeyStore) NewAccount(passphrase string) (accounts.Account, error)
account, err := ks.NewAccount("my-passphrase")
if err != nil {
log.Fatal(err)
}
fmt.Println("address:", account.Address.Hex())
Importing a raw private key
func (ks *KeyStore) ImportECDSA(priv *ecdsa.PrivateKey, passphrase string) (accounts.Account, error)
privKey, err := crypto.HexToECDSA("<hex-private-key>")
if err != nil {
log.Fatal(err)
}
account, err := ks.ImportECDSA(privKey, "my-passphrase")
Unlocking and signing a transaction
// Unlock indefinitely (timeout = 0)
func (ks *KeyStore) Unlock(a accounts.Account, passphrase string) error
// Unlock for a fixed duration
func (ks *KeyStore) TimedUnlock(a accounts.Account, passphrase string, timeout time.Duration) error
// Sign a transaction with the unlocked key
func (ks *KeyStore) SignTx(a accounts.Account, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error)
if err := ks.Unlock(account, "my-passphrase"); err != nil {
log.Fatal(err)
}
tx := types.NewTx(&types.DynamicFeeTx{
ChainID: chainID,
Nonce: nonce,
GasTipCap: tip,
GasFeeCap: feeCap,
Gas: 21000,
To: &recipient,
Value: big.NewInt(1e18),
})
signedTx, err := ks.SignTx(account, tx, chainID)
if err != nil {
log.Fatal(err)
}
If you do not want to keep the key unlocked in memory, use SignTxWithPassphrase instead — it decrypts the key on demand and zeroes it immediately after signing.
Sign without unlocking
func (ks *KeyStore) SignTxWithPassphrase(
a accounts.Account, passphrase string,
tx *types.Transaction, chainID *big.Int,
) (*types.Transaction, error)
Listing and locking accounts
func (ks *KeyStore) Accounts() []accounts.Account
func (ks *KeyStore) Lock(addr common.Address) error
func (ks *KeyStore) HasAddress(addr common.Address) bool
Importing / exporting encrypted JSON keys
// Import an existing Web3 Secret Storage JSON key
func (ks *KeyStore) Import(keyJSON []byte, passphrase, newPassphrase string) (accounts.Account, error)
// Export to a JSON key encrypted with newPassphrase
func (ks *KeyStore) Export(a accounts.Account, passphrase, newPassphrase string) (keyJSON []byte, err error)