Skip to content

feat: merge wallet CLI into unified zerion-cli#5

Merged
graysonhyc merged 59 commits intomainfrom
merge-wallet-cli
Apr 16, 2026
Merged

feat: merge wallet CLI into unified zerion-cli#5
graysonhyc merged 59 commits intomainfrom
merge-wallet-cli

Conversation

@graysonhyc
Copy link
Copy Markdown
Collaborator

@graysonhyc graysonhyc commented Apr 1, 2026

Summary

Merges zerion-wallet-cli into a unified zerion CLI — single entry point for wallets, trading, agent tokens, policies, and analytics.

Replaces monolithic cli/zerion-cli.js (247 lines) with a domain-grouped router (cli/router.js) + 28 modular command handlers across 4 domains.

Commands added

Domain Commands
wallet create, import, list, backup, delete, fund, watch, sync
trading swap, bridge, send, search, list-tokens, chains
agent create-token, revoke-token, list-tokens, use-token, create-policy, delete-policy, list-policies, show-policy
analytics portfolio, positions, pnl, activity, history, overview
config set, get, list, reset

Shared libraries (cli/lib/)

  • api/ — HTTP client with timeouts, URL encoding, null guards; x402 payment support
  • chain/ — chain registry (14 chains, CAIP-2 IDs), Solana helpers
  • trading/ — token resolution, swap execution, transaction handling, trade guards
  • wallet/ — encrypted keystore, ENS/address resolution, watchlist, policy picker
  • util/ — flags, formatting, output, prompts, validation, analysis, migration

Local policy engine (cli/policies/)

  • allowlist.mjs — restrict destinations to known addresses
  • deny-approvals.mjs — block unlimited token approvals
  • deny-transfers.mjs — prevent transfers above threshold
  • run-policies.mjs — orchestrates policy evaluation before signing
  • Policies enforced at CLI level, filtered by wallet, fail-closed on missing policy

Security hardening

  • Interactive passphrase required for swap/bridge without agent token
  • Agent tokens require security policy before creation
  • OWS validates agent tokens at sign time
  • CAIP-2 chain IDs passed to OWS for correct policy enforcement
  • Timeouts, URL encoding, and null guards on all API calls
  • Every bare catch replaced with explicit error handling
  • Path traversal check for macOS (/etc/private/etc)
  • Agent tokens redacted in structured stderr output

MCP tools

8 tool definitions: wallet-create, swap, search, chains-list, wallet-portfolio, wallet-positions, wallet-pnl, wallet-transactions

Skills

4 skill packages: zerion (full CLI reference), wallet-analysis, wallet-trading, chains

Tests

Suite Covers
cli-routing Command dispatch, argument parsing, routing fallback
cli End-to-end CLI invocation
consistency Cross-file naming consistency
integration API integration paths
tool-catalog MCP tool schema consistency
unit Utility functions (validation, formatting, analysis)
wallet-config Config file read/write
wallet-flags Flag parsing edge cases
wallet-ows OWS signing integration
skills Skill file structure
examples Example file validation

190 tests total — 176 pass, 0 fail, 14 skipped

Stats

96 files changed, 8281 insertions, 1244 deletions

Test plan

  • npm test — 176 pass, 0 fail
  • zerion wallet create --name test
  • zerion swap --from ETH --to USDC --amount 0.01 --wallet test
  • zerion agent create-token --wallet test
  • zerion send --to <addr> --amount 0.01 --wallet test
  • Verify policy enforcement blocks unauthorized transfers

🤖 Generated with Claude Code

graysonhyc and others added 26 commits March 31, 2026 15:31
Combine wallet analysis (read) and trading (write) capabilities into a
single CLI distributed via npm, MCP, and the skills marketplace.

- Add 26 trading commands: swap, bridge, buy/sell, wallet management,
  agent tokens, and security policies (from zerion-wallet-cli)
- Adopt modular router architecture with separate command handlers
- Merge API client with x402 pay-per-call support on all endpoints
- Add wallet-trading skill for AI agent integration
- Add MCP tool definitions for swap, wallet-create, and search
- Lazy-load trading deps (OWS, Solana, qrcode) for lightweight reads
- Port wallet-cli tests; all 139 tests pass

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Delete cli/lib.mjs — all functions moved to proper modules:
  - parseFlags → cli/lib/flags.js
  - basicAuthHeader → cli/lib/api-client.js (now exported)
  - validateChain, validatePositions, CHAIN_IDS, POSITION_FILTERS → cli/lib/validate.js (new)
  - summarizeAnalyze → cli/lib/analyze.js (new)
- Create dedicated wallet-positions.js with --positions all|simple|defi
  and --chain filtering (was incorrectly aliased to portfolio handler)
- Update wallet-analyze.js to import shared summarizeAnalyze
- Repoint unit.test.mjs and consistency.test.mjs to new modules
- Fix consistency tests to skip auth/method checks on CLI tool definitions

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The chains command now uses local chain data instead of calling the API,
so the "invalid API key returns error" test needs to use a command that
actually hits the API (wallet pnl).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Restructure --help into categorized sections (wallet_management,
  wallet_analysis, trading, agent_tokens, agent_policies, watchlist)
- Add policy flags, env vars, and supported chains to help output
- Document ZERION_AGENT_TOKEN usage in help
- Rewrite zerion-cli skill with end-to-end agent wallet setup flow,
  key management details, and comprehensive command reference

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Critical:
- Fix chain ID mismatch: validate.js now imports SUPPORTED_CHAINS
  from chains.js instead of maintaining a separate set with wrong names

Security:
- wallet-export: require --yes or interactive confirmation before
  displaying seed phrase; output to stderr to prevent accidental piping
- x402.js: read WALLET_PRIVATE_KEY at call time, not module load

Code quality:
- Extract resolveAddressOrWallet() shared helper (DRY 4 commands)
- Lazy-init ENS client in resolve-wallet.js
- Fix getApiKey() called twice per API request in fetchAPI
- Import basicAuthHeader in simulate.js instead of inlining
- Remove dead code: errors.js, MAX_UINT256 in tx.js,
  resolveOutputMode in output.js

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…on, and QA plan

- Wallet create/import now always prompt for passphrase interactively
  with masked input (*) — the --passphrase flag is ignored to prevent
  exposure in shell history and process listings
- Wallet export requires TTY, blocks agent mode, writes mnemonic to
  stderr only (never stdout), and prompts passphrase interactively
- Add wallet delete command with passphrase verification + "Type DELETE"
  confirmation, agent-mode blocking, and TTY requirement
- Add --limit, --offset, --search pagination to wallet list (default 20)
- Remove unused simulate.js (Blockaid integration not wired in)
- Add comprehensive QA-PLAN.md with 80+ test cases

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Move flat command and lib files into domain subdirectories for
scalability. Remove buy/sell convenience commands (use swap instead).
Rename ambiguous files for clarity.

Commands: wallet/, trading/, agent/, analytics/ + config.js
Lib: util/, api/, chain/, wallet/, trading/ + config.js, analyze.js

Key renames:
- export → wallet/backup (seed phrase) + wallet/sync (Zerion app QR)
- ows.js → keystore.js, tx.js → transaction.js
- stdin.js → prompt.js, chains.js → registry.js
- swap-tokens → list-tokens, analyze → activity/overview

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Rename walletExport→walletBackup, update error messages from
  "wallet export" to "wallet backup" in backup.js
- Rename exportCmd→walletSync, export_error→sync_error in sync.js
- Move lib/analyze.js→lib/util/analyze.js (no more orphans at lib root)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
CRITICAL: create-policy.js used __dirname + ".." which now resolves
to commands/ instead of cli/ after the directory restructure. This
silently disabled all executable security policies (deny-transfers,
deny-approvals, allowlist) at transaction time. Fixed to "../..".

Also hardens pre-existing bare catch patterns:
- revokeAgentToken: only fall through on "not found" errors
- wallet delete: distinguish passphrase errors from keystore errors

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- run-policies.mjs: only catch module-not-found, rethrow real errors
- wallet/create.js: log warning when listWallets fails in generateName
- api/client.js: preserve raw response text on JSON parse failure
- trading/swap.js: log fungible lookup failures instead of swallowing
- trading/transaction.js: log gas estimation failures with fallback
- wallet/resolve.js: distinguish "not found" from keystore errors

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
getAgentToken() previously just read the env var without checking
if the token was still valid. A revoked token would still be passed
to OWS as a passphrase, potentially allowing signing to succeed.

Now validates the token against the active token list before
returning it. Throws a clear error if the token is revoked.

Also fixes suggestion strings to use "zerion-cli" consistently.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Previously, swap and bridge commands passed `undefined` as passphrase
when no ZERION_AGENT_TOKEN was set, allowing wallets without
passphrases to sign transactions without any authentication.

Now both commands prompt interactively via readPassphrase() when
no agent token is present, ensuring every trade requires explicit
authentication.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The token validation was comparing against listApiKeys() which
doesn't return the token secret (only shown once at creation).
Every valid token was being rejected.

OWS already validates tokens when signing — if a token is revoked,
ows.signTransaction() will fail with an auth error. No need to
duplicate validation at the CLI layer.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
OWS returns "API key not found" when a revoked agent token is used
for signing, which is confusing. Now swap/bridge catch this and
show: "Agent token is revoked or invalid" with instructions to
unset or create a new token.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The Zerion API swap response includes chain_id which was being
parsed and used for the transaction. This could return a different
chain (e.g. Ethereum) than what the user specified with --chain,
causing policy violations like "chain eip155:1 not in allowlist"
even when --chain base was passed.

Now always uses the chain ID derived from the user's --chain flag
via getViemChain(), ensuring the transaction matches the user's
intent and policy rules.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
OWS was checking chain policies against the wallet's default account
chain (eip155:1 = Ethereum) instead of the transaction's target chain.
A "base-only" policy would always fail because OWS saw eip155:1.

Now passes the correct CAIP-2 chain ID (e.g. eip155:8453 for Base)
to ows.signTransaction(), so policies enforce against the actual
target chain. Applies to both swap and ERC-20 approval signing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add 30s AbortController timeout to all API fetch calls
- Add 120s timeout to waitForTransactionReceipt (prevents hang on congested chains)
- URL-encode wallet addresses in all API endpoint paths (ENS safety)
- Add optional chaining for tx.attributes in history.js
- Add optional chaining for attributes in resolve-token.js

From API contract specialist review.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…orts

Critical security fixes from specialist review:
- create-token now requires interactive passphrase (was accepting via
  --passphrase flag or defaulting to empty string)
- Redact token from stderr usage example (prevent log leakage)
- Add path traversal guard for --key-file/--mnemonic-file in import

Performance fix:
- Cache config.json reads (was re-reading disk on every getConfigValue)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Remove unused Transaction/VersionedTransaction imports from solana.js
- Remove unused txBase64 variable from solana.js
- Remove dead validateChain from registry.js (validate.js is the canonical one)
- Remove dead printQuiet from output.js (never called)

From maintainability specialist review.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
… codebase

Also removes QA-PLAN.md (no longer needed).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- cli/README.md: rewritten with all commands, flags, env vars, error table
- mcp/README.md: trimmed duplicated auth section, points to root README
- skills/zerion-cli/SKILL.md: added positions shorthand and ENS note

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds every flag variant for wallet list, analyze, swap, bridge, search,
agent tokens, policies, watchlist, config, and shorthands. Adds global
flags table.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The npm package, binary, config directory, and all references are now
"zerion" instead of "zerion-cli". A migration helper auto-moves
~/.zerion-cli to ~/.zerion on first run and notifies upgrading users.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@graysonhyc graysonhyc marked this pull request as ready for review April 1, 2026 14:22
graysonhyc and others added 3 commits April 2, 2026 10:21
…curity hardening

Based on QA feedback testing the CLI with AI agents (Codex, Claude):

- Add `zerion send` command for native and ERC-20 transfers
- Add `wallet create --agent` for zero-prompt wallet + token setup
- Auto-save agent tokens to config — no more env var export needed
- Require agent token for all trading (swap/bridge/send) — no passphrase prompts
- Add `--passphrase-file` for automated wallet setup commands
- Add `--timeout` flag and progress output for bridge operations
- Add bridge delivery polling via positions API (snapshot + poll)
- Surface per-position 24h changes in `wallet positions` output
- Separate broadcast failure from confirmation timeout in error handling
- Add ERC-20 balance check (balanceOf) before send to prevent gas waste
- Harden path traversal: lstatSync rejects symlinks, realpathSync resolves before blocklist
- Fix operator precedence bug in import.js path guard
- Atomic recovery file write (O_CREAT|O_EXCL), wallet name sanitization
- Remove agent token from stdout/stderr — only saved to config
- Extract shared guards.js (requireAgentToken, parseTimeout, handleTradingError)
- Extract offer-agent-token.js shared module
- Update all docs and skills for new commands and auth flow

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
realpathSync resolves /etc/passwd to /private/etc/passwd on macOS.
Updated regex to match both /etc/ and /private/etc/ (and /proc, /sys, /dev).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@zebastieneth
Copy link
Copy Markdown
Contributor

🔴 High
The new agent-token flow saves a live trading credential to config, but the config command only redacts apiKey, not agentToken. That means zerion config list / zerion config get agentToken can print the signing token in cleartext, which is a credential leak for unattended trading. See /tmp/zerion-pr5/cli/commands/agent/create-token.js:48 and /tmp/zerion-pr5/cli/commands/config.js:12.

🟡 Medium
The rename migration writes a plain-text banner to stderr on startup when ~/.zerion-cli exists. Elsewhere the CLI contract assumes JSON on stderr for failures, so this can break machine consumers by mixing human-readable text with structured error output. See /tmp/zerion-pr5/cli/lib/util/migrate.js:14 and /tmp/zerion-pr5/cli/zerion.js:13.

🟡 Medium
Home-directory handling is inconsistent on Windows. Migration resolves home via HOME || USERPROFILE, but config storage is derived from HOME only, so environments without HOME will resolve config under an invalid path. See /tmp/zerion-pr5/cli/lib/util/constants.js:2 and /tmp/zerion-pr5/cli/lib/util/migrate.js:10.

🔵 Low
After installing @open-wallet-standard/core and rerunning npm test, the remaining failures are all in /tmp/zerion-pr5/tests/integration.test.mjs. Those assertions still expect the old raw json.data response shape and the pre-resolution ENS query value, so they look stale relative to the current CLI output contract.

graysonhyc and others added 23 commits April 3, 2026 16:26
…tent HOME

- Redact agentToken alongside apiKey in config list/get/set (credential leak fix)
- Replace plain-text migration banner with JSON stderr notices
- Unify HOME resolution via constants.js (os.homedir fallback for Windows)
- Update integration tests to match current CLI output contract

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…y picker

- Agent tokens now always require a security policy (no policy-less tokens)
- Interactive 3-tier picker: Standard (deny-transfers + expiry), Strict
  (+ chain restriction), Custom (existing policy)
- Expiry step: 7d / 30d / no expiry
- Chain checklist with arrow keys + Space to toggle
- Esc to go back to previous step from any sub-menu
- Shared policy-picker.js used by both `wallet create` and `agent create-token`
- Add `config unset <key>` command to remove config values
- Add passphrase warning before wallet creation confirmation
- Remove --key-file import option
- Custom policy display now shows deny-transfers, expiry countdown, chain names
- Update README, zerion skill, and wallet-trading skill

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- findMatchingPolicy checks for existing policies with same scope
  (scripts, chains, non-expired expiry with >50% time remaining)
- Shows "Reusing policy" vs "Policy created" in output
- Also includes unstaged changes from prior PR review round

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Rename --key to --evm-key for clarity
- Add --sol-key for Solana private key import (base58, hex, byte array)
- normalizeKeyToHex auto-detects key format and converts for OWS
- Track wallet origin (evm-key/sol-key/mnemonic) in config
- Suppress non-imported chain addresses in list, fund, and import output
- Hide walletOrigins from config list (internal-only)
- Strip null values from JSON output
- Remove misleading import-only security warning, add passphrase warning
- Update README, router help, and skills

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…kens

- Add `agent use-token --wallet <name>` to switch active wallet and token
- Store tokens as agentTokens map keyed by wallet name
- Active token derived from agentTokens[defaultWallet] — no duplicate fields
- Remove redundant agentToken and activeTokenWallet config fields
- list-tokens shows wallet name and active flag
- revoke-token cleans up agentTokens config
- wallet delete revokes tokens, cleans config, promotes next wallet
- Hide internal config keys (agentTokens, walletOrigins) from config list

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Bugs:
- Remove stale mnemonic-file flag check (would crash at runtime)
- Block config get from leaking internal keys (agentTokens, walletOrigins)
- Remove dead imports (node:fs, node:path) from import.js
- Remove unused listSavedAgentTokens import from delete.js

Code reuse:
- Extract PASSPHRASE_WARNING and WALLET_ORIGIN constants to constants.js
- Add getWalletAddresses() helper — replaces 3x duplicated origin filtering
- Add getWalletNameById() to keystore.js — replaces 2x walletIdToName map
- Add removeWalletOrigin() to config.js — replaces inline cleanup in delete.js
- Validate key in config get (same as set/unset)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…n, send usage

- Replace unreliable default RPC with llamarpc + publicnode fallbacks and retry logic
- Support ETH_RPC_URL env var (matches existing SOLANA_RPC_URL pattern)
- Only mark newest token per wallet as active (config only saves one per wallet)
- Eliminate duplicate getWalletNameById lookups in list-tokens
- Validate address input — reject invalid strings with clear error
- Tighten Solana address regex to 43-44 chars (actual public key length)
- Add --chain to send usage message

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Trading commands (swap, bridge, send) now execute immediately.
The agent token requirement already provides authorization control.
Updated all docs, skills, and usage messages.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Fixes API-key signing for wallets imported via private key
(open-wallet-standard/core#151). The old version (1.1.2) tried to
parse the decrypted key material as a mnemonic, causing "invalid
mnemonic phrase" errors for non-mnemonic wallets.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
OWS enforces native rules (allowed_chains, expires_at) but does not
run executable policy scripts. This adds CLI-side enforcement before
signing for all trading commands (swap, bridge, send).

Closes the gap where deny-transfers policies were stored but never
actually checked during transaction execution.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…licy

- Active token lookup now filters by defaultWallet instead of taking
  the newest token across all wallets (security: prevents policy bypass)
- Policy lookup failures now block the transaction (fail-closed) instead
  of silently continuing

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add --from-chain to bridge examples (README, zerion SKILL, trading SKILL)
- Add agent use-token command to docs
- Add ETH_RPC_URL env var to README and zerion SKILL
- Add missing flags (--from-chain, --to, --to-token, --timeout) to README
- Remove commands.md from repo

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Both commands now resolve and display policy names so users can see
which policies guard each wallet's active agent token at a glance.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Display chains, expiry, and executable guards inline so users can see
what each wallet's active policy allows at a glance.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Trading and analysis commands are agent operations (no passphrase,
fully automated). Wallet setup, token/policy creation require
interactive input and are marked as manual/human-only.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
analyze command exits 1 when no API key exists (CI has no config).
Test now accepts both exit 0 (key in config) and exit 1 (no key,
graceful error with stderr output).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Tests were silently skipping by checking only ZERION_API_KEY env var,
ignoring the config file where the key actually lives. Also updates
stale response shape assertions (chains, analyze, ENS label).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Wire analyze to correct handler (overview.js), add Solana positional
address support, fix slippage 0 treated as falsy, include gas cost in
native balance check, validate --chain in trading commands, resolve
actual token decimals for send, validate policy script paths, use
atomic config writes with corruption protection, fix stale chain IDs
in skills, align MCP swap schema with always-execute behavior, remove
ambiguous 'm' expiry unit, remove dead activity.js.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@graysonhyc graysonhyc merged commit 426ad00 into main Apr 16, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants