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

  1. Fetch the chain ID from the connected client and check the error: cid, err := client.ChainID(ctx); if err != nil return err.
  2. Pass an explicit big.NewInt(<your chain id>) for known private/dev chains (e.g. 1337, 1).
  3. 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

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


AI-assisted analysis of ethereum/go-ethereum@6bb0588ad8 (2026-08-15). Data as JSON: /api/errors/c73b49edec411cc6. Report an issue: GitHub.