A CLI tool for managing EVM chain configurations
- Interactive chain discovery and configuration (backed by chainlist, cached locally)
- Short chain names with aliases and prefix matching (
chainz exec eth) - RPC health checking (
chainz doctor, with--fixfailover to healthy RPCs) - Safe-by-default private key management (OS keyring, encrypted, 1Password, explicit plaintext)
- Multiple RPC support per chain and a configurable default chain
- RPC-only chain configurations when signing is not needed
- Environment variable interpolation
- Command execution with chain-specific variable expansion
- Shell completions and
--jsonoutput for scripting
Requires Rust 1.88+.
cargo install chainz --lockedTo install the latest development version instead:
git clone https://github.com/marktoda/chainz.git
cd chainz
cargo install --path .# Initialize with interactive wizard
chainz init
# Add a new chain
chainz add
# List configured chains
chainz list
# Inspect one chain in detail
chainz show ethereum
# Removal requires an exact primary name or chain ID
chainz remove ethereum
# Execute a command for a given chain (prefix matching works)
chainz exec eth -- cast block-number
21532741
# Set a default chain, then omit it
chainz use ethereum
chainz exec -- cast block-number
# Check config and RPC health; switch dead RPCs to healthy ones
chainz doctor --fix
# Open a subshell with chain environment (prompt shows the chain)
chainz shell ethereum
# Install shell completions (zsh example)
chainz completions zsh > ~/.zfunc/_chainzChainz provides an interactive wizard for adding chains:
> chainz add
Chain Selection
══════════════════════════════════════════════════════
? Type to search and select a chain
> ethereum (1)
optimism (10)
arbitrum (42161)
polygon (137)
...
RPC Configuration
══════════════════════════════════════════════════════
Testing RPCs...
13 of 18 RPCs healthy
? Select an RPC
> RPC 1 · https://…alchemy.com/… (120ms)
RPC 2 · https://…llamarpc.com (184ms)
Key Configuration
══════════════════════════════════════════════════════
? Select a key
> default (0x123...789)
deployer (0xabc...def)
Add new key
Chain added: ethereum (ChainId: 1)list is a compact index; the active RPC is redacted so credential-bearing
URLs are safe to display. The * marks the default chain:
> chainz list
CHAIN ID RPC KEY
* ethereum 1 https://…llamarpc.com default
optimism 10 https://…optimism.io —
* default chainUse show for one chain's details, or list --verbose for all details.
Both support the explicit --show-secrets escape hatch:
chainz show ethereum
chainz show ethereum --json
chainz list --verboseUpdate interactively, or target a chain and make a direct change:
> chainz update
? Select chain to update
? What would you like to update?
> RPC URL
Key
Verification
Rename
Save and finish
> chainz update ethereum --name eth --no-key
> chainz update eth --rpc-url https://eth.llamarpc.comChains may omit a key entirely. This is useful for read-only RPC commands;
@wallet, @key, and --expose-key fail with a clear message until a key is
attached.
Execute commands with chain-specific variables expanded. Chains can be referenced by name, alias, unambiguous prefix, or chain ID:
> chainz exec 1 -- cast block-number
21532741
> chainz exec eth -- cast balance @wallet
1.5 ETH
> chainz exec 10 -- forge script DeploySet a default chain to omit the chain argument entirely:
> chainz use ethereum
Default chain set to 'ethereum'
> chainz exec -- cast block-number
21532741Available expansions:
@wallet— Wallet address@rpc— RPC URL@chainid— Chain ID@chainname— Chain name@key— Private key
@key is deprecated and planned for removal at 1.0 because it exposes the
private key in child process arguments. Prefer env-only exposure when a tool
accepts environment variables:
chainz exec ethereum --expose-key -- sh -c 'PRIVATE_KEY="$RAW_PRIVATE_KEY" forge script Deploy'Chainz resolves a key once and injects only what was requested: using
@wallet does not put RAW_PRIVATE_KEY in the child environment. If a shell
expands $RAW_PRIVATE_KEY into a downstream command argument, that downstream
argument can still be visible to other processes.
Override the key for a single command:
> chainz exec ethereum -k deployer -- forge script Deploychainz shell [chain] opens your $SHELL with the chain's environment
(ETH_RPC_URL, CHAIN_ID, CHAIN_NAME, VERIFIER_*, CHAINZ_CHAIN) —
private keys are never injected. Shells that honor an inherited PS1 get
a (⛓ ethereum) prefix, but interactive shells often reset PS1 from rc
files, so this isn't reliable everywhere. For a prompt that always works
(zsh, starship, or a customized bash), key off CHAINZ_CHAIN instead, e.g.:
# starship.toml
[env_var.CHAINZ_CHAIN]
format = "\\(⛓ $env_value\\) "Add and manage private keys. Without --type, Chainz uses the OS keyring when
available and otherwise uses password-encrypted storage. Plaintext requires an
explicit --type private-key:
> chainz key add deployer
Enter private key: ****
Added key 'deployer'
> chainz key list
Stored keys:
- default (0x123...789, keyring)
- deployer (0xabc...def, encrypted)Read key material from stdin in scripts so it does not enter shell history or process listings:
> printf '%s\n' "$PRIVATE_KEY" | chainz key add ci-key --stdin
Added key 'ci-key'On a headless machine without an OS keyring, the safe encrypted fallback needs an interactive password prompt and exits clearly instead of hanging. Scripts that deliberately accept plaintext storage can opt in explicitly:
printf '%s\n' "$PRIVATE_KEY" | chainz key add ci-key --stdin --type private-keyMigrate one key, or every plaintext key, into safe storage:
chainz key migrate default
chainz key migrate --all --to encryptedRemoving an attached key is blocked by default. Use --force to detach it
from every referencing chain before removal.
chainz doctor checks key storage, key references, and RPC connectivity for
every chain (concurrently). With --fix, any dead selected RPC is switched to
a healthy alternative from that chain's RPC list. Exits nonzero when failures
are found, so it can gate scripts. Interactive RPC tests and doctor probes
time out after 4 seconds per endpoint; results stream in live and pickers
list healthy endpoints fastest-first:
> chainz doctor --fix
Configuration
✓ configuration invariants hold
Keys
⚠ 'default' is stored as a plaintext private key — migrate with `chainz key migrate default`
Key references
✓ all chains reference existing keys
RPC health
✓ ethereum (https://eth.llamarpc.com)
✗ optimism (https://dead.example.com)
Fixing RPCs
✓ optimism: switched to https://mainnet.optimism.iolist, show, and key list support --json. Credential-bearing URLs are redacted
and key material is never included by default. list --show-secrets is the
explicit escape hatch for trusted interactive use:
> chainz list --json | jq '.[].name'
> chainz show ethereum --json | jq '.selected_rpc'
> chainz key list --json | jq '.[] | {name, address}'Set and use custom variables for RPC URL interpolation. Stdin avoids placing values in shell history; values are redacted from ordinary output:
> printf '%s\n' "$ALCHEMY_KEY" | chainz var set ALCHEMY_KEY --stdin
> printf '%s\n' "$ETHERSCAN_KEY" | chainz var set ETHERSCAN_KEY --stdin
> chainz var list
Variables:
ALCHEMY_KEY = [REDACTED]
ETHERSCAN_KEY = [REDACTED]
> chainz var get ALCHEMY_KEY --show
ALCHEMY_KEY = abc123chainz var list --json is an explicitly revealing machine-readable form;
use it only in trusted scripting contexts.
Configs are stored at $XDG_CONFIG_HOME/chainz/config.json (defaulting to
~/.config/chainz/config.json) with owner-only permissions (0600), since
the file can contain private keys. A legacy ~/.chainz.json is migrated to
the new location automatically on first run.
{
"chains": [
{
"name": "ethereum",
"aliases": ["Ethereum Mainnet"],
"chain_id": 1,
"rpc_urls": [
"https://eth-mainnet.g.alchemy.com/v2/${ALCHEMY_KEY}",
"https://eth.llamarpc.com"
],
"selected_rpc": "https://eth.llamarpc.com",
"verification_api_key": "abc123",
"key_name": "default"
}
],
"variables": {
"ALCHEMY_KEY": "def456"
},
"keys": {
"default": {
"address": "0x0123456789abcdef0123456789abcdef01234567",
"type": "EncryptedKey",
"value": "<base64>",
"nonce": "<base64>",
"salt": "<base64>",
"version": 1,
"kdf_memory_kib": 19456,
"kdf_iterations": 2,
"kdf_parallelism": 1
}
},
"default_chain": "ethereum"
}The chainlist used by chainz add is cached at ~/.cache/chainz/chains.json
for 24 hours; pass --refresh to add/update to force a re-download.
MIT — see LICENSE.