feat(wallet-cli): add daemon commands, dev-mode launchers, and e2e smoke test#9255
Merged
Conversation
sirtimid
added a commit
that referenced
this pull request
Jun 24, 2026
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
7854c96 to
340a7b4
Compare
sirtimid
added a commit
that referenced
this pull request
Jun 25, 2026
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
0eb5147 to
c29e73e
Compare
…oke test Add the `mm daemon` command suite — `start`, `stop`, `status`, `purge`, and `call` — completing the CLI's user-facing surface, plus the oclif test harness (`src/test/run-command.ts`) and the dev-mode launchers (`bin/dev.mjs`, `bin/dev.cmd`) deferred from the scaffold slice. The commands are Erik's verbatim; their imported daemon interfaces all match the modules already on `main` from the persistence/transport/factory slices. Also add `tsx` as the dev-mode `--import` loader (with a knip `ignoreDependencies` entry, since it's referenced only as an argument string), exclude the test harness from coverage, and add a real-construction e2e smoke test that feeds `buildInstanceOptions` into a real `Wallet` against `:memory:` — closing the gap that the mocked unit test cannot reach. Documents the `mm daemon` usage in the README and adds an ARCHITECTURE.md describing the daemon → factory → persistence → transport layering. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…tion
README documented MM_DAEMON_DATA_DIR as the data-dir override, but that's only
the internal start->daemon spawn-contract var (overwritten with config.dataDir
and ignored by the other commands). The real user override is MM_DATA_DIR, the
oclif scopedEnvVar('DATA_DIR'). Also assert the e2e's first account is a real
SRP-derived EVM address, so it fails loudly on an empty/placeholder account.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
#9001 (NetworkController) landed and wired on main, making `infuraProjectId` a required field of `CreateWalletConfig` and adding `wallet.init()` to the factory's startup path. Pass a dummy project ID so the e2e type-checks against the merged factory, and note in the comment that `init()` (NetworkController) is offline-safe too — it is synchronous and never calls `lookupNetwork`. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Remove narration that restates what the code or test name already says (the stop-vs-not-running branch, the "simulate a non-Error throw" notes, the purge whitelist composition) and tighten the purge refuse-when- responsive comment to its non-obvious rationale. Keeps the genuine "why" comments (the call.ts Json cast safety, the purge whitelist rationale). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The e2e constructs a real KeyringController, whose default browser-passworder encryptor uses the Web Crypto API — `crypto` (`getRandomValues`/`subtle`) and the `CryptoKey` constructor (an `instanceof` check in `exportKey`). Under `--experimental-vm-modules` the test realm has neither global on Node < 21 (notably the CI `test-18` job), so the e2e failed first with "reading 'getRandomValues'" and then "CryptoKey is not defined". Add a custom jest environment that polyfills both from `node:crypto` when absent — the same two globals `@metamask/wallet`'s own `Wallet.test.ts` sets for this flow. Verified on Node 18.20.8 by driving the real browser-passworder `generateSalt -> keyFromPassword -> exportKey` sequence: it throws under the bare `node` environment and passes under this one. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… e2e `AccountsController:listAccounts` (and the rest of the AccountsController read surface) is `@deprecated` in favor of AccountTreeController / Keyring API v2 — neither wired in the CLI — so a CLI caller pointed at a deprecated action with no way to know. Switch the `call` examples and the e2e to `KeyringController:getState`, which is wired, non-deprecated, and exposes the SRP-derived account address directly. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
`bin/dev.cmd` invoked `node ... "%~dp0\dev"`, but the package only ships `dev.mjs` (run.cmd uses `run.mjs`), so the Windows dev launcher pointed at a nonexistent file. It also registered tsx via `--loader tsx`, which is removed in tsx 4.x; switch to `--import tsx`, matching `daemon-spawn` and the knip note. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The dev launchers (`bin/dev.mjs` / `bin/dev.cmd`, run with `--import tsx`) put oclif in development mode, but oclif still loaded the compiled `dist/commands` — so dev mode needed a prior build and never picked up source edits. oclif's `tsPath` only rewrites `dist/<x>` to `src/<x>` when the package's `tsconfig.json` declares both `outDir` and `rootDir`; ours had only `baseUrl` (they live in `tsconfig.build.json`, which oclif doesn't read). Add `outDir`/`rootDir` to `tsconfig.json` so dev mode loads `src/**/*.ts` via tsx, matching `@metamask/core-backend`. Verified: with `dist` removed, `node --import tsx bin/dev.mjs daemon call --help` loads the command from source. Build (tsconfig.build.json), eslint, constraints, and tests are unaffected. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1fe01e7 to
822000a
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 822000a. Configure here.
When the Unix socket is missing, `mm daemon status` reported the daemon as not running even when the recorded PID was still alive. That contradicted `stopDaemon`, which treats an absent socket plus an alive recorded PID as a daemon that outlived its socket. Consult the PID in the absent branch and surface the orphan, pointing at `mm daemon stop` / `mm daemon purge` for recovery. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
grypez
approved these changes
Jun 30, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Explanation
The final landing slice for
@metamask/wallet-cli, after the scaffold (#9065), persistence (#9067), transport (#9108), and factory (#9226) slices. It adds the user-facing command surface so the package is runnable:src/commands/daemon/{start,stop,status,purge,call}.ts(+ tests) — themm daemoncommand suite over the daemon layers already onmain:startspawns the daemon and reports its socket,status/stopping and tear it down,purgedeletes the daemon state files, andcalldispatches an arbitraryController:methodmessenger action.bin/dev.mjs+bin/dev.cmd— the dev-mode launchers deferred from the scaffold slice, run via thetsxloader (knipignoreDependencies: ['tsx']).wallet-factory.e2e.test.ts— a smoke test that feeds the wiredinstanceOptionsinto a realWallet(no mock) against:memory:, imports the SRP, and dispatches a messenger action — closing the gap flagged in feat(wallet-cli): add wallet factory and daemon entry point #9226 review. Offline/CI-safe (neither construction norwallet.init()reaches the network).src/test/run-command.ts+ a README Usage section.Checklist
build,test(100% coverage),lint, andchangelog:validatepass.Note
Medium Risk
New CLI paths accept wallet secrets and can invoke arbitrary messenger actions over a local socket;
purgedeletes persisted wallet state, though safeguards limit deletion when the daemon is still responsive.Overview
Adds the runnable
mm daemoncommand surface on top of the existing spawn/client/stop layers:start(Infura/password/SRP via flags or env),stop,status(orphan-PID and stale-PID warnings),purge(confirm/--force, whitelist-only file deletion, refuses purge while the daemon still responds), andcall(JSON-RPCcallwith optional JSON-array args and timeout, TTY vs piped JSON output).Also ships
bin/dev.mjs/dev.cmd(oclif dev mode viatsx), arunCommandoclif test harness, a realWallet:memory:e2e forcreateWallet, Jest Web Crypto polyfill environment for KeyringController tests, README Usage, and knip/tsconfig tweaks forwallet-cli.Reviewed by Cursor Bugbot for commit 15fbe92. Bugbot is set up for automated code reviews on this repo. Configure here.