ethereum/go-ethereum · critical
nil chainID
Error message
nil chainID
What it means
NewKeyStoreTransactor in accounts/abi/bind/v2 builds a transaction signer from a keystore account and requires a non-nil EIP-155 chainID to select the latest signer for that chain. Passing a nil *big.Int would later dereference nil inside the signer; the constructor fails fast with panic("nil chainID") instead.
Source
Thrown at accounts/abi/bind/v2/auth.go:40
"errors"
"math/big"
"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/accounts/external"
"github.com/ethereum/go-ethereum/accounts/keystore"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
)
// ErrNotAuthorized is returned when an account is not properly unlocked.
var ErrNotAuthorized = errors.New("not authorized to sign this account")
// NewKeyStoreTransactor is a utility method to easily create a transaction signer from
// a decrypted key from a keystore.
func NewKeyStoreTransactor(keystore *keystore.KeyStore, account accounts.Account, chainID *big.Int) *TransactOpts {
if chainID == nil {
panic("nil chainID")
}
signer := types.LatestSignerForChainID(chainID)
return &TransactOpts{
From: account.Address,
Signer: func(address common.Address, tx *types.Transaction) (*types.Transaction, error) {
if address != account.Address {
return nil, ErrNotAuthorized
}
signature, err := keystore.SignHash(account, signer.Hash(tx).Bytes())
if err != nil {
return nil, err
}
return tx.WithSignature(signer, signature)
},
Context: context.Background(),
}
}
View on GitHub (pinned to 6bb0588ad8)
Solutions
- Fetch the chain ID from the connected client and check the error: cid, err := client.ChainID(ctx); if err != nil return err.
- Pass an explicit big.NewInt(<your chain id>) for known private/dev chains (e.g. 1337, 1).
- If nil can reach this call by design, guard before calling and return a proper error instead of panicking.
Example fix
// before
opts := bind.NewKeyStoreTransactor(ks, acct, nil) // panics
// after
chainID, err := client.ChainID(ctx)
if err != nil { return err }
opts := bind.NewKeyStoreTransactor(ks, acct, chainID) Defensive patterns
Strategy: validation
Validate before calling
func mustChainID(ctx context.Context, client *ethclient.Client) (*big.Int, error) {
cid, err := client.ChainID(ctx)
if err != nil {
return nil, fmt.Errorf("fetching chain id: %w", err)
}
return cid, nil
}
// opts, err := mustChainID(ctx, client); if err == nil { bind.NewKeyStoreTransactor(ks, acct, cid) } Type guard
func hasChainID(cid *big.Int) bool { return cid != nil && cid.Sign() >= 0 } Try / catch
defer func() {
if r := recover(); r != nil {
if msg, _ := r.(string); strings.Contains(msg, "nil chainID") {
return nil, errors.New("chain id missing: connect client and fetch ChainID first")
}
panic(r)
}
}() Prevention
- Always pair client.ChainID with its error check.
- For known chains, pass a constant big.NewInt(chainID).
- Wrap transactor constructors in your own helper that validates first.
When it happens
Trigger: Calling bind v2's NewKeyStoreTransactor(keystore, account, nil) — e.g. the chain ID variable was never assigned, a config field left empty, or a nil was propagated from a lookup like ethclient.ChainID() error path where the returned value was used despite an error.
Common situations: Migrating from the old bind API (where chainID was inside bind.TransactOpts) to v2 and forgetting the new parameter; using a helper that returns (chainID, err) and ignoring err, leaving chainID nil; tests that construct transactors without setting the chain.
Related errors
- error decoding contract deployer hex %s: %v
- Could not create random uuid: %v
- key generation: could not read from random source:
- key generation: ecdsa.GenerateKey failed:
- reading from crypto/rand failed:
AI-assisted analysis of ethereum/go-ethereum@6bb0588ad8 (2026-08-15).
Data as JSON: /api/errors/c73b49edec411cc6.
Report an issue: GitHub.