feat(wallet-cli): Add wallet CLI with daemon support#8446
feat(wallet-cli): Add wallet CLI with daemon support#8446rekmarks wants to merge 21 commits intofeat/wallet-libraryfrom
Conversation
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
This comment was marked as resolved.
This comment was marked as resolved.
98a9392 to
1c1a4bf
Compare
|
@SocketSecurity ignore npm/jake@10.9.4 Transitive via |
623f9af to
3fb60ee
Compare
45d013e to
6ec54b9
Compare
f69d1cc to
6687519
Compare
…rocess Adds a daemon that runs a Wallet instance as a detached background process, communicating over JSON-RPC via Unix domain sockets. This mirrors the architecture of kernel-cli's daemon. Infrastructure (src/daemon/): - socket-line: newline-delimited socket I/O - rpc-socket-server: generic Unix socket JSON-RPC server - daemon-client: one-shot JSON-RPC client with retry - daemon-entry: standalone entry point for the spawned process - daemon-spawn: spawns daemon-entry as detached child - stop-daemon: shared stop logic with escalation - wallet-factory: creates configured Wallet from config - utils, paths, types: process utilities and path resolution Commands (src/commands/daemon/): - start: start daemon (--infura-project-id or INFURA_PROJECT_ID env) - stop: stop daemon (RPC shutdown -> SIGTERM -> SIGKILL) - status: check daemon status - purge: stop daemon and delete all state files Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
9 test files covering all daemon infrastructure: paths, socket-line, utils, rpc-socket-server, daemon-client, stop-daemon, wallet-factory, daemon-entry, and daemon-spawn. 96 tests total. Config changes: - jest.config.js: exclude commands/ from coverage (not yet tested) - eslint.config.mjs: disable n/no-process-env and n/no-sync for wallet-cli test files Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Wire sendSignal into stopDaemon so EPERM errors from process.kill are not silently treated as successful stops. Remove unused withTimeout utility. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Thread wallet password and secret recovery phrase through the daemon startup chain so the wallet is initialized with an imported SRP. - Export importSecretRecoveryPhrase from @metamask/wallet - Add --password and --srp required flags to `daemon start` - Pass MM_WALLET_PASSWORD / MM_WALLET_SRP env vars to spawned daemon - Make createWallet async; call importSecretRecoveryPhrase after init Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds a `daemon call` CLI command that forwards arbitrary messenger action calls to the running wallet daemon over JSON-RPC. The daemon registers a `call` RPC handler that invokes `wallet.messenger.call()` with the provided action name and arguments. Usage: wallet-cli daemon call AccountsController:listAccounts wallet-cli daemon call NetworkController:getState --timeout 10000 Also fixes lint errors in wallet-factory.ts (missing return types). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Remove placeholder index.ts and library exports (CLI-only package) - Add yarn constraints exception for wallet-cli exports - Document intentional `as any` messenger dispatch in daemon-entry - Remove duplicate socketPath param from ensureDaemon - Remove unused logPath from DaemonSpawnConfig - Add 30s server-side socket read timeout in rpc-socket-server - Handle sendCommand throwing in status command - Have purge remove entire data directory - Tighten isRpcError to require both code and message - Fix waitFor to return false on timeout instead of re-checking - Clarify multi-request rejection test assertions - Document password/srp CLI flags as testing-only Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Destructuring null params (when a request omits the params field) would throw a confusing TypeError. Add a runtime check that params is a non-empty array before destructuring. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
… typedoc and JSDoc - Await rm calls in shutdown finally block via Promise.all and in error cleanup path so callers know cleanup is complete - Use rpcErrors.parse() (-32700) for JSON.parse failures per JSON-RPC spec - Update typedoc.json entry points after index.ts removal - Move CONNECTION_TIMEOUT_MS above JSDoc so doc attaches to the function Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Reject params where the first element is not a string, preventing confusing downstream errors from messenger.call. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Log errors in catch blocks instead of silently swallowing them across
daemon-entry, stop-daemon, and rpc-socket-server
- Make shutdown cleanup independent: handle.close(), wallet.destroy(),
and file removal each run in their own try/catch so one failure
doesn't skip the others
- Add default 30s timeout to sendCommand to prevent indefinite hangs
- Use JsonRpcResponse from @metamask/utils as handleRequest return type
- Add shared DaemonStatusInfo type for the getStatus RPC contract
- Change RpcHandler to allow void return, document null params
- Filter expected socket errors (EPIPE/ECONNRESET), log unexpected ones
- Only suppress ENOENT in socket unlink, re-throw other errors
- Clean up socket file (not just PID file) when stopDaemon succeeds
- Add child.on('error') handler in daemon-spawn for spawn failures
- Switch makeLogger from sync appendFileSync to async appendFile
- Remove socketPath from DaemonSpawnConfig, derive from dataDir
- Include error details in status command catch block
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace the required --force flag with an interactive y/N confirmation prompt using @inquirer/confirm. The --force flag now skips the prompt instead of being mandatory. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
6687519 to
8e1bd9d
Compare
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 8e1bd9d. Configure here.
| } | ||
|
|
||
| const handler = handlers[method]; | ||
| if (!handler) { |
There was a problem hiding this comment.
Handler lookup vulnerable to prototype method invocation
Low Severity
The handlers[method] lookup on a plain object doesn't guard against Object.prototype properties. If a client sends a request with method set to "constructor", "toString", "hasOwnProperty", etc., the lookup returns a truthy built-in function instead of undefined, bypassing the "Method not found" check. The server then invokes that prototype function as if it were an RPC handler, returning an unexpected result instead of a -32601 error.
Using Object.hasOwn(handlers, method) (or creating handlers with Object.create(null)) before accessing the handler would ensure only explicitly registered methods are dispatched.
Reviewed by Cursor Bugbot for commit 8e1bd9d. Configure here.


Introduces an
oclifCLI for@metamask/wallet. It is more or less a port of the Ocap Kernel equivalent.Usage
First,
cd packages/wallet-cli.Then, to start the daemon:
Try calling an available controller action:
Actions params must be specified as a stringified JSON array:
When done, stop the daemon and clean up:
Summary
wallet-clipackage scaffolded with oclif (mmbinary)@metamask/walletinstance as a detached background process, communicating over JSON-RPC via Unix domain socketsstart,stop,status,purge,callcallcommand dispatches arbitrary messenger actions to the running daemon (e.g.mm daemon call AccountsController:listAccounts)Note
High Risk
Introduces a new local daemon that can dispatch arbitrary wallet messenger actions over a socket and requires handling secrets (SRP/password) and process lifecycle correctly, which is security- and reliability-sensitive despite being new code.
Overview
Introduces a new
@metamask/wallet-clipackage (Oclifmmbinary) that can start@metamask/walletas a detached background daemon and communicate with it over newline-delimited JSON-RPC on a Unix domain socket.Adds daemon management and interaction commands (
start,stop,status,purge,call), including acallpathway that forwards arbitrary messenger actions to the running wallet instance, plus supporting socket server/client/spawn/cleanup utilities with extensive unit tests.Monorepo wiring is updated to include the new workspace in
README.md,CODEOWNERS/teams.json, roottsconfigreferences, ESLint overrides, and Yarn workspace validation;@metamask/walletnow exportsimportSecretRecoveryPhrasefor CLI consumption.Reviewed by Cursor Bugbot for commit 8e1bd9d. Bugbot is set up for automated code reviews on this repo. Configure here.