Skip to main content
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:
ConstantNPUse
StandardScryptN / StandardScryptP2621441Production
LightScryptN / LightScryptP40966Development / 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)

Build docs developers (and LLMs) love