feat(wallet-cli): add wallet factory and daemon entry point#9226
Merged
Conversation
39eb299 to
a8f34af
Compare
4 tasks
5f93797 to
6fbe77a
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 6fbe77a. Configure here.
1 task
grypez
requested changes
Jun 24, 2026
Add `createWallet`, which constructs a `@metamask/wallet` `Wallet` backed by the SQLite `KeyValueStore`, hydrates it from persisted state, imports the secret recovery phrase on first run, and returns a resilient `dispose` teardown handle (unsubscribe → wallet.destroy → store.close). Add the daemon process entry point (`daemon-entry.ts`) that wires `createWallet` into the JSON-RPC socket server with PID-slot claiming and graceful shutdown. Adapted from Erik Marks's #8446 modules with these deviations: - Use the current `@metamask/wallet` `instanceOptions` API, populating the now-required `storageService` (in-memory adapter), `connectivityController` (`AlwaysOnlineAdapter`), and `remoteFeatureFlagController` slots. Drop the old flat `Wallet({...})` options. - Drop `infuraProjectId` from the `Wallet` call (NetworkController is not yet wired on `main`); keep the env guard with a TODO(#9001). - Return `{ wallet, dispose }` instead of `{ wallet, store }`. - Construct a short-lived metadata probe so `loadState` can filter persisted rows against the live controller metadata. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…solved logger Address PR review feedback: - Extract the daemon data-dir mkdir + 0o700 chmod into a dedicated `ensureOwnerOnlyDirectory` in `data-dir.ts`, moving the access-control rationale into its JSDoc. This gives the owner-only guarantee a named import boundary the daemon-entry test can assert against directly. - Forward the resolved `logFn` (not the raw optional `log`) to `subscribeToChanges` in `createWallet`, so persistence-write failures always use the daemon's logger instead of falling through to the console fallback inconsistently. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…let factory `@metamask/wallet` 5.0.0 (#9001) makes `instanceOptions.networkController.infuraProjectId` a required option. Thread the daemon's already-validated `INFURA_PROJECT_ID` through `createWallet` and `buildInstanceOptions` (and the metadata probe) into the new `networkController` slot. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
7854c96 to
340a7b4
Compare
`@metamask/wallet` 5.0.0 (#9001) added `Wallet.init()`, which runs each instance's `init()` — notably `NetworkController.init()`, which applies the selected network so a provider proxy is available. Without it the daemon would serve RPC against a wallet that has no usable provider. Call `wallet.init()` after subscribing persistence (so init-driven state changes are written through) and before first-run SRP import. `init()` settles every step independently, so surface any rejected step via the logger rather than letting it pass silently. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Apply the fixes surfaced by the PR review: - Abort startup when a `wallet.init()` step rejects instead of logging and continuing. A failed `NetworkController.init` leaves no usable provider, and `getStatus`/`pingDaemon` would still report the daemon healthy — so serving RPC against it hid the failure. Now the rejected steps are logged and `createWallet` throws, letting the existing teardown + first-run DB cleanup run. - Narrow the RPC `call` dispatch from `messenger as any` to a contained `ArbitraryDispatch` structural cast through `unknown`, restoring arity checks and removing the `no-explicit-any` disable. - Widen `subscribeToChanges`/`subscribeToStateChanged` to accept a `Readonly` messenger so the factory no longer casts `wallet.messenger`. - Extract `CreateWalletConfig` and a shared `Logger` type. - Comment cleanups: note the intentionally-omitted `keyringController` slot, flag the `ClientType.Extension` CLI semantics, document the crash-mid-import vault gap, give the signal-handler istanbul-ignores reasons, and trim comments that restated the code. Add tests for the subscribe→init→import ordering and both init-failure paths (settled-rejected results and a rejected `init()` promise); coverage stays at 100%. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
`MM_WALLET_PASSWORD` and `MM_WALLET_SRP` were read into locals but left in `process.env` for the daemon's whole lifetime. Since the daemon is long-lived and dispatches arbitrary messenger actions over its socket, delete both from the environment once captured to limit their exposure to in-process code. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
grypez
approved these changes
Jun 25, 2026
pull Bot
pushed a commit
to dmrazzy/core
that referenced
this pull request
Jun 30, 2026
…oke test (MetaMask#9255) ## Explanation The final landing slice for `@metamask/wallet-cli`, after the scaffold (MetaMask#9065), persistence (MetaMask#9067), transport (MetaMask#9108), and factory (MetaMask#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 MetaMask#9226 review. Offline/CI-safe (neither construction nor `wallet.init()` reaches the network). - **`src/test/run-command.ts`** + a README **Usage** section. ## Checklist - [x] `build`, `test` (100% coverage), `lint`, and `changelog:validate` pass. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > New CLI paths accept wallet secrets and can invoke arbitrary messenger actions over a local socket; `purge` deletes persisted wallet state, though safeguards limit deletion when the daemon is still responsive. > > **Overview** > Adds the runnable **`mm daemon`** command 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), and **`call`** (JSON-RPC `call` with optional JSON-array args and timeout, TTY vs piped JSON output). > > Also ships **`bin/dev.mjs`/`dev.cmd`** (oclif dev mode via **`tsx`**), a **`runCommand`** oclif test harness, a **real `Wallet` `:memory:` e2e** for `createWallet`, Jest **Web Crypto polyfill** environment for KeyringController tests, README **Usage**, and knip/tsconfig tweaks for **`wallet-cli`**. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 15fbe92. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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
Adds the wallet factory and daemon entry point for
@metamask/wallet-cli, after the scaffold (#9065), persistence (#9067), and transport (#9108) slices:wallet-factory.ts—createWallet()builds a@metamask/walletWalletover the SQLiteKeyValueStore, hydrates it from persisted state, imports the SRP on first run, and returns{ wallet, dispose }.dispose(idempotent, resilient) runs unsubscribe →wallet.destroy()→store.close().daemon-entry.ts— the daemon process: claims the PID slot, builds the wallet, serves the messenger over the owner-only Unix socket, and tears down viadisposeon shutdown.It uses the current
@metamask/walletinstanceOptionsAPI —storageService(in-memory adapter),connectivityController(AlwaysOnlineAdapter), andremoteFeatureFlagController.NetworkControllerandTransactionControllerslots are left as TODOs until they're wired onmain.Supersedes draft #8847 (the
{ wallet, dispose }refactor), folded in here on top ofmain.Closes #8779.
Checklist
build,test(100% coverage on both new files),lint,changelog:validate, andconstraintspass.Note
High Risk
Touches keyring/SRP import, encrypted vault persistence, and exposes the full wallet messenger over a Unix socket with only filesystem permissions as the access boundary.
Overview
Adds
createWalletanddaemon-entryso the CLI can run a long-lived background wallet process backed by SQLite persistence.createWalletopens aKeyValueStore, hydrates controller state (via a short-lived metadata probeWallet), wires headlessinstanceOptions(in-memorystorageService,AlwaysOnlineAdapter, Infura + remote feature flags), subscribes persistence, runswallet.init()(startup aborts if any step fails), imports the SRP on first run when no vault exists, and returns{ wallet, dispose }with ordered, idempotent teardown. First-run failures can delete on-disk DB files.daemon-entryvalidates required env vars, removes password/SRP fromprocess.envafter capture, locks the data dir to0o700, runsclaimDaemonSlot(stale socket/PID cleanup, refuse live siblings), claims the daemon with an exclusive PID write before opening the DB, builds the wallet, and servesgetStatusplus acallRPC that forwards arbitrary messenger actions. Shutdown/SIG handlers coalesce teardown and only remove PID files the process owns.Also adds
remote-feature-flag-controllerandstorage-servicedependencies and a smallReadonlytyping tweak in persistence subscriptions.Reviewed by Cursor Bugbot for commit 180ffc1. Bugbot is set up for automated code reviews on this repo. Configure here.