Skip to content

feat(wallet-cli): add daemon commands, dev-mode launchers, and e2e smoke test#9255

Open
sirtimid wants to merge 10 commits into
mainfrom
sirtimid/wallet-cli-commands
Open

feat(wallet-cli): add daemon commands, dev-mode launchers, and e2e smoke test#9255
sirtimid wants to merge 10 commits into
mainfrom
sirtimid/wallet-cli-commands

Conversation

@sirtimid

@sirtimid sirtimid commented Jun 24, 2026

Copy link
Copy Markdown
Member

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) — the mm daemon command suite over the daemon layers already on main: start spawns the daemon and reports its socket, status/stop ping and tear it down, purge deletes the daemon state files, and call dispatches an arbitrary Controller:method messenger action.
  • bin/dev.mjs + bin/dev.cmd — the dev-mode launchers deferred from the scaffold slice, run via the tsx loader (knip ignoreDependencies: ['tsx']).
  • wallet-factory.e2e.test.ts — a smoke test that feeds the wired instanceOptions into a real Wallet (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 nor wallet.init() reaches the network).
  • src/test/run-command.ts + a README Usage section.

Checklist

  • build, test (100% coverage), lint, and changelog:validate pass.

Note

Medium Risk
Commands start an unlocked wallet with secrets and daemon call can invoke arbitrary messenger actions; purge deletes SQLite wallet state but refuses when the daemon is still responsive.

Overview
Adds the user-facing mm daemon command suite so @metamask/wallet-cli is runnable on top of the existing spawn/transport/factory layers: start (credentials via flags or INFURA_PROJECT_ID / MM_WALLET_PASSWORD / MM_WALLET_SRP), status, stop, purge (confirm or --force, whitelist-only file deletion with a guard if the daemon is still responsive), and call (JSON-RPC call to any Controller:method with optional JSON-array args and --timeout).

Also ships bin/dev.mjs / bin/dev.cmd for oclif dev mode via tsx, a run-command test harness for oclif commands, wallet-factory.e2e.test.ts against a real in-memory Wallet, Jest Web Crypto polyfill for KeyringController tests, README Usage, changelog entry, knip ignore for tsx, and tsconfig outDir/rootDir so commands compile to dist.

Reviewed by Cursor Bugbot for commit 1fe01e7. Bugbot is set up for automated code reviews on this repo. Configure here.

sirtimid added a commit that referenced this pull request Jun 24, 2026
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@sirtimid sirtimid force-pushed the sirtimid/wallet-cli-wallet-factory branch from 7854c96 to 340a7b4 Compare June 25, 2026 13:18
Base automatically changed from sirtimid/wallet-cli-wallet-factory to main June 25, 2026 16:24
sirtimid and others added 6 commits June 25, 2026 19:26
…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>
@sirtimid sirtimid force-pushed the sirtimid/wallet-cli-commands branch from 4736dda to 12029ec Compare June 25, 2026 17:32
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>
@sirtimid sirtimid force-pushed the sirtimid/wallet-cli-commands branch from 0eb5147 to c29e73e Compare June 25, 2026 18:52
@sirtimid sirtimid marked this pull request as ready for review June 25, 2026 18:57
@sirtimid sirtimid requested review from a team as code owners June 25, 2026 18:57
@sirtimid sirtimid temporarily deployed to default-branch June 25, 2026 18:57 — with GitHub Actions Inactive

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ 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 c29e73e. Configure here.

Comment thread packages/wallet-cli/bin/dev.cmd Outdated
sirtimid and others added 3 commits June 25, 2026 21:12
… 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>
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.

1 participant