This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
chiliz-cli is a hybrid CLI+TUI application for managing multi-chain EVM transactions. Built with Cobra (CLI), Bubble Tea (TUI), and go-ethereum, it provides both scriptable CLI commands and an interactive keyboard-driven interface for balance checks, transaction queries, and token transfers across EVM-compatible chains.
Currently configured for Chiliz Chain with PEPPER token. New chains/tokens are added via JSON config files with zero code changes.
Distributed via Homebrew (brew tap GrapeInTheTree/tap && brew install chiliz), go install, and GitHub Releases.
# Run (TUI mode)
go run ./cmd/chiliz
# Run (CLI mode)
go run ./cmd/chiliz address 0x1234...
go run ./cmd/chiliz tx 0xabcd... --json
go run ./cmd/chiliz block latest
go run ./cmd/chiliz chain-info --json
go run ./cmd/chiliz call 0x1234... "totalSupply()(uint256)"
# Build
go build -o chiliz ./cmd/chiliz
# Test
go test ./...
# Lint
go vet ./...
golangci-lint run
# Logs (TUI mode writes to chiliz.log)
tail -f chiliz.log
# Regenerate ERC-20 bindings after ABI changes
abigen --abi internal/infra/ethereum/abi/erc20.json \
--pkg contracts --type ERC20 \
--out internal/infra/ethereum/contracts/erc20.go
# Release (tag triggers GoReleaser via GitHub Actions)
git tag v0.3.0
git push --tagschiliz # Interactive TUI mode (no args)
chiliz address <addr> # Address info: balance, nonce, tx history, tokens
chiliz tx <hash> # Transaction details with receipt
chiliz block [number|latest] # Block information
chiliz chain-info # Chain status: latest block, gas price
chiliz call <contract> <sig> [args] # Generic read-only contract call (eth_call)
chiliz validators # Chiliz validator set + staking status
chiliz staking <addr> # Personal staking positions + rewards
chiliz token <contract> # Token metadata via Chiliscan API
chiliz contract <addr> # Contract source, compiler, deployer
chiliz holders <token> # Top token holders + count
chiliz logs --address --event # On-chain event logs (eth_getLogs)
chiliz rpc <method> [params] # Raw JSON-RPC escape hatch
chiliz init # Initialize config in ~/.chiliz/
chiliz version # Build version and commit hash
Global flags:
--chain <name> Blockchain network (default: first in chains.json)
--json Machine-readable JSON output
--config <path> Config directory path
Three-layer clean architecture under internal/, with Cobra CLI routing on top:
cmd/chiliz/
main.go Entry point: Cobra Execute()
cmd/
root.go Cobra root command + global flags + PersistentPreRunE
Resolves config, chain, and explorer client
appCtx struct holds shared state for all subcommands
slog silenced in CLI mode (TUI sets up file logger)
tui.go TUI launcher (no-args fallback)
Logging to chiliz.log with 0600 permissions
address.go `chiliz address <addr>` — parallel RPC + Explorer fetch
5 concurrent goroutines: balance, nonce, code, txlist, tokens
tx.go `chiliz tx <hash>` — tx + receipt lookup
Derives sender via LatestSignerForChainID
Gets block timestamp for time display
block.go `chiliz block [number]` — block by number
Supports "latest" keyword or specific block number
chaininfo.go `chiliz chain-info` — chain status
Parallel: block number + gas price
call.go `chiliz call <contract> <sig> [args]` — generic eth_call
Uses abi_helper.go for encoding/decoding
Supports cast-style sig: "name(inputs)(outputs)"
validators.go `chiliz validators` — Chiliz staking + governance query
getValidators() + parallel getValidatorStatus() per validator
Voting Power from Governance(0x7002), APY estimated on-chain
Semaphore (max 4 concurrent) + retry for RPC rate limits
init.go `chiliz init` — creates ~/.chiliz/ with default config
Chiliz Mainnet + Spicy Testnet pre-configured
version.go `chiliz version` — ldflags-injected version/commit
internal/
domain/
models.go Pure domain structs (Chain, Token, Wallet, Contact)
Chain.ExplorerAPIURL for per-chain explorer API
Token.IsNative() detects native vs ERC-20
output.go CLI output types: AddressInfo, TxDetail, BlockInfo,
ChainStatus, TokenBalance, TxSummary
All JSON-serializable with stable field names
Numeric values that exceed JS Number.MAX_SAFE_INTEGER are strings
infra/
config/config.go Loads chains.json, tokens.json, contacts.json, .env
ResolveConfigDir() cascade: --config > env > ~/.chiliz/ > CWD
configPath() joins configDir + filename (empty configDir = relative)
GetPrivateKey() reads key from env only at signing time
ResolveAddress() resolves contact names or validates 0x addresses
Wallets hardcoded: Main Wallet + Test Wallet
ethereum/
client.go RPC queries (each function creates/closes its own ethclient):
GetBalance, GetNonce, GetCode, GetChainID,
GetGasPrice, GetLatestBlockNumber, GetTransaction,
GetTransactionReceipt, GetBlock, CallContract,
SendTransaction (EIP-1559),
FormatBalance, ParseAmount, GetAddressFromPrivateKey
abi_helper.go Dynamic ABI encoding/decoding without abigen:
ParseCallSignature — splits "name(in)(out)" via paren depth
BuildCalldata — ParseSelector + Pack → 4byte selector + args
ConvertArg — CLI string → Go type (address, uint, bool, etc.)
DecodeOutputs — Unpack + FormatValue → human strings
Reusable for validators, staking, logs commands
erc20.go ERC-20: GetTokenBalance, SendTokenTransaction,
FormatTokenBalance, ParseTokenAmount
pow10() uses big.Int (safe for any decimal count)
abi/erc20.json Standard ERC-20 ABI
contracts/erc20.go Auto-generated Go bindings (abigen). Do not edit.
explorer/
etherscan.go Chiliscan/Etherscan-compatible API client (Routescan)
GetTxList, GetTokenBalances, GetTokenTxList
Rate-limited (2 req/sec via time.Tick), no API key needed
Base URL per chain via Chain.ExplorerAPIURL
Graceful degradation: "No transactions found" is not an error
Internal types (rawTxListEntry) → domain types conversion
output/
formatter.go Print(jsonMode, v) — type switch for human/JSON output
Human: fmt.Fprintf formatted tables to stdout
JSON: json.Encoder with indent to stdout
relativeTime() for "4d ago" style timestamps
shortenHash() for "0xabcd...ef12" display
tui/
app.go Router model: manages currentPage, holds shared data
(wallets, chains, tokens, contacts), routes messages
to active page sub-model
Init() loads config asynchronously via tea.Cmd
style/style.go Shared Lipgloss styles (Title, Selected, Error, Success, etc.)
pages/
mainmenu/model.go Menu: Send Transaction, Check Balance, Exit
balance/model.go Balance check flow (4-state machine):
Wallet -> Chain -> Token -> fetch balance -> result
send/model.go Send flow (8-state machine):
Wallet -> Chain -> Token -> Recipient -> Amount
-> Confirm -> send tx -> result
Hybrid CLI+TUI: Cobra routes commands. No args = TUI (Bubbletea). Subcommands = CLI with stdout output. Single binary, same infra layer shared.
Dual data sources: RPC for real-time chain state (balance, nonce, blocks, tx by hash). Explorer API (Chiliscan/Routescan) for indexed data (tx history by address, token discovery). Explorer is optional — graceful degradation if unavailable.
Nested Models (TUI): Each TUI page is a self-contained Bubble Tea model with its own Init, Update, View. The router (app.go) delegates to the active page.
Type-safe contract bindings: ERC-20 interactions use abigen-generated Go code, not manual ABI encoding.
Dynamic ABI encoding: chiliz call uses abi.ParseSelector + abi.Arguments.Pack/Unpack for runtime encoding without JSON ABI files. The abi_helper.go module is designed for reuse by future commands (validators, staking, logs).
Config-driven extensibility: Chains, tokens, and contacts are pure JSON. Both TUI and CLI dynamically use these at startup.
Async blockchain calls: RPC operations run as concurrent goroutines (CLI uses sync.WaitGroup, TUI uses Bubble Tea commands).
Package-level appContext: CLI commands share resolved config/chain/explorer via a package-level struct in root.go. Appropriate for a CLI (single execution path, no concurrency at command level).
| Data | Source | Notes |
|---|---|---|
| Native balance | RPC eth_getBalance |
|
| ERC-20 balance | RPC eth_call (balanceOf) |
Known tokens only |
| Nonce, code, gas price | RPC | |
| Tx by hash, receipt | RPC | |
| Block by number | RPC | |
| Tx history by address | Explorer API | RPC cannot do this — no eth_getTransactionsByAddress exists |
| All token holdings | Explorer API | Token discovery requires indexer |
| Arbitrary contract reads | RPC eth_call |
via chiliz call with dynamic ABI encoding |
--config /path/to/dirflagCHILIZ_CONFIG_DIRenvironment variable~/.chiliz/ifchains.jsonexists there- Current working directory (default, backward compatible)
up/k,down/j: cursor movemententer: confirm selectionesc: back to main menuctrl+c: quit- Amount entry:
0-9,.,backspace
| What | File |
|---|---|
| Add EVM chain | chains.json (include explorer_api_url for tx history support) |
| Add ERC-20 token | tokens.json (decimals must match contract) |
| Add contact | contacts.json |
| What | Where |
|---|---|
| Add CLI command | Create cmd/chiliz/cmd/<name>.go, register in root.go init() |
| Add wallet | internal/infra/config/config.go LoadWallets() + .env |
| Add TUI page | Create internal/tui/pages/<name>/model.go, register in app.go |
| Add blockchain query | Add to internal/infra/ethereum/client.go |
| Add explorer query | Add to internal/infra/explorer/etherscan.go |
| Add output type | Add struct to internal/domain/output.go, add case in internal/output/formatter.go |
| Add contract type | Place ABI in abi/, run abigen, use bindings in new internal/infra/ethereum/<name>.go |
Releases are automated via GoReleaser + GitHub Actions.
git tag v0.3.0
git push --tags
# GitHub Actions builds linux/darwin x amd64/arm64, creates GitHub Release,
# and pushes Homebrew formula to GrapeInTheTree/homebrew-tapConfig files:
.goreleaser.yml— build matrix, archive format, Homebrew tap config.github/workflows/release.yml— triggered onv*tag push- Secrets required:
HOMEBREW_TAP_TOKEN(fine-grained PAT with repo scope on homebrew-tap)
Install methods after release:
brew tap GrapeInTheTree/tap && brew install chilizgo install github.com/GrapeInTheTree/chiliz-cli/cmd/chiliz@latest- Download binary from GitHub Releases page
cobrav1.10.2 - CLI frameworkbubbleteav1.3.10 - TUI frameworklipglossv1.1.0 - TUI stylinggo-ethereumv1.16.7 - Ethereum client, signing, ABI bindingsgodotenvv1.5.1 - .env loader
Private keys live only in .env (gitignored). They are loaded via config.GetPrivateKey(envKey) at the moment of transaction signing and never cached. The .env.example template shows the expected variable names. CLI read-only commands never access private keys.
Git history has been audited: no secrets have ever been committed.
- Native vs ERC-20 detection:
token.IsNative()checks if address is empty/zero - Native transfers use fixed 21000 gas; ERC-20 transfers use
EstimateGas()+ 20% buffer - All gas pricing is EIP-1559 (
SuggestGasTipCap+SuggestGasPricewith 1.2x buffer) - Decimals are dynamic per token (18 for most, 6 for USDC/USDT, 8 for WBTC)
pow10()usesbig.Int— safe for any decimal count (no int64 overflow)
- Base URL:
https://api.routescan.io/v2/network/mainnet/evm/88888/etherscan/api - Free tier: no API key, 2 req/sec, 10,000 calls/day
- Etherscan-compatible format (module/action query params)
- Set per chain via
explorer_api_urlinchains.json - Confirmed working endpoints:
txlist,addresstokenbalance,tokentx,balance,gasoracle