@metamask-previews/wallet-cli 0.0.0-preview-5da60b6 → 0.0.0-preview-d67bfdbad

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -23,7 +23,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
23
23
  - Add a daemon transport layer: a JSON-RPC client and server over a Unix socket, plus daemon spawn/stop lifecycle helpers ([#9108](https://github.com/MetaMask/core/pull/9108))
24
24
  - Add SQLite-backed persistence for wallet controller state ([#9067](https://github.com/MetaMask/core/pull/9067))
25
25
  - Initial package scaffold for `@metamask/wallet-cli`, an [oclif](https://oclif.io)-based `mm` CLI for `@metamask/wallet` ([#9065](https://github.com/MetaMask/core/pull/9065)).
26
- - Register no-op `AnalyticsController` handlers on the daemon wallet so `NetworkController`'s RPC service analytics calls resolve; the daemon does not report analytics ([#9270](https://github.com/MetaMask/core/pull/9270))
27
26
 
28
27
  ### Changed
29
28
 
@@ -1,8 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.createWallet = void 0;
4
- const analytics_controller_1 = require("@metamask/analytics-controller");
5
- const messenger_1 = require("@metamask/messenger");
6
4
  const remote_feature_flag_controller_1 = require("@metamask/remote-feature-flag-controller");
7
5
  const storage_service_1 = require("@metamask/storage-service");
8
6
  const wallet_1 = require("@metamask/wallet");
@@ -99,29 +97,6 @@ function buildInstanceOptions(infuraProjectId) {
99
97
  },
100
98
  };
101
99
  }
102
- /**
103
- * Register handlers for the `AnalyticsController` actions that
104
- * `NetworkController` calls to emit RPC service analytics. The daemon does not
105
- * report analytics, so these do nothing: `getState` returns an empty analytics
106
- * ID, which stops the controller before it tracks anything, and `trackEvent` is
107
- * a no-op. Registering them keeps those messenger calls from throwing.
108
- *
109
- * @param messenger - The wallet's root messenger.
110
- */
111
- function registerAnalyticsStubHandlers(messenger) {
112
- // Register on an `AnalyticsController`-namespaced messenger, not the root: a
113
- // messenger can only register handlers for its own namespace, and doing so
114
- // delegates them up to the root, where `NetworkController` can reach them.
115
- const analyticsMessenger = new messenger_1.Messenger({
116
- namespace: 'AnalyticsController',
117
- parent: messenger,
118
- });
119
- analyticsMessenger.registerActionHandler('AnalyticsController:getState', () => ({
120
- ...(0, analytics_controller_1.getDefaultAnalyticsControllerState)(),
121
- analyticsId: '',
122
- }));
123
- analyticsMessenger.registerActionHandler('AnalyticsController:trackEvent', () => undefined);
124
- }
125
100
  /**
126
101
  * Create a configured `Wallet` for daemon use, backed by a SQLite key-value
127
102
  * store for controller-state persistence.
@@ -183,7 +158,6 @@ async function createWallet({ databasePath, password, srp, infuraProjectId, log,
183
158
  state,
184
159
  instanceOptions: buildInstanceOptions(infuraProjectId),
185
160
  });
186
- registerAnalyticsStubHandlers(wallet.messenger);
187
161
  persistenceUnsubscribe = (0, persistence_js_1.subscribeToChanges)(wallet.messenger, wallet.controllerMetadata, store, logFn);
188
162
  // Installed before `init` so any approval raised during initialization is covered.
189
163
  autoApprovalUnsubscribe = (0, auto_approval_js_1.subscribeToAutoApproval)(wallet.messenger, logFn);
@@ -261,7 +235,6 @@ async function loadPersistedState(store, infuraProjectId, logFn) {
261
235
  const probe = new wallet_1.Wallet({
262
236
  instanceOptions: buildInstanceOptions(infuraProjectId),
263
237
  });
264
- registerAnalyticsStubHandlers(probe.messenger);
265
238
  try {
266
239
  return (0, persistence_js_1.loadState)(store, probe.controllerMetadata);
267
240
  }
@@ -1 +1 @@
1
- {"version":3,"file":"wallet-factory.cjs","sourceRoot":"","sources":["../../src/daemon/wallet-factory.ts"],"names":[],"mappings":"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","sourcesContent":["import { getDefaultAnalyticsControllerState } from '@metamask/analytics-controller';\nimport type {\n AnalyticsControllerGetStateAction,\n AnalyticsControllerTrackEventAction,\n} from '@metamask/analytics-controller';\nimport { Messenger } from '@metamask/messenger';\nimport {\n ClientConfigApiService,\n ClientType,\n DistributionType,\n EnvironmentType,\n} from '@metamask/remote-feature-flag-controller';\nimport { InMemoryStorageAdapter } from '@metamask/storage-service';\nimport type { Json } from '@metamask/utils';\nimport {\n AlwaysOnlineAdapter,\n importSecretRecoveryPhrase,\n Wallet,\n} from '@metamask/wallet';\nimport type {\n DefaultActions,\n DefaultEvents,\n RootMessenger,\n WalletOptions,\n} from '@metamask/wallet';\nimport { rm } from 'node:fs/promises';\n\nimport { KeyValueStore } from '../persistence/KeyValueStore.js';\nimport { loadState, subscribeToChanges } from '../persistence/persistence.js';\nimport { subscribeToAutoApproval } from './auto-approval.js';\nimport type { Password, Srp } from './secrets.js';\nimport type { Logger } from './types.js';\n\nconst IN_MEMORY_DATABASE_PATH = ':memory:';\n\nexport type CreateWalletConfig = {\n databasePath: string;\n password?: Password;\n srp: Srp;\n infuraProjectId: string;\n log?: Logger;\n};\n\nexport type CreateWalletResult = {\n wallet: Wallet;\n /**\n * Tear down everything `createWallet` set up, in the order that keeps\n * in-flight persistence writes valid: stop the state-change subscription,\n * destroy the wallet, then close the store (closing first would cause a\n * teardown-time persistence write to fail). Resilient — a failure in any\n * step is logged and the remaining steps still run — and idempotent (repeat\n * calls coalesce onto the same teardown).\n */\n dispose: () => Promise<void>;\n};\n\n/**\n * Build the per-instance options the daemon's `Wallet` is constructed with.\n *\n * Returns a fresh set on every call so the metadata probe and the real wallet\n * never share adapter/service instances (the probe is destroyed, which may\n * tear those instances down).\n *\n * Only the slots wired on `@metamask/wallet` today are populated:\n * - `storageService` — backed by an in-memory adapter. This is the wallet's\n * large-blob store (snap source, caches), distinct from the SQLite\n * `KeyValueStore` that persists controller state; no wired controller\n * offloads durable data to it yet, so in-memory suffices.\n * - `connectivityController` — the `AlwaysOnlineAdapter` exported for\n * node-like hosts that have no platform connectivity signal.\n * - `networkController` — the Infura project ID used to reach Infura RPC\n * endpoints.\n * - `remoteFeatureFlagController` — a `ClientConfigApiService` fetching real\n * flags over the network.\n * - `approvalController` — a no-op `showApprovalRequest` (the daemon is\n * headless); pending requests are accepted separately by the auto-approval\n * subscription `createWallet` installs, not by this hook.\n * - `gasFeeController` — only `clientId: 'cli'` (sent as `X-Client-Id` to the\n * gas API); the API endpoints, poll interval, and compatibility callbacks are\n * left at the wallet package's platform-agnostic defaults (the default\n * `EIP1559APIEndpoint` is already the production URL, so the daemon does not\n * re-specify it).\n * - `transactionController` — swaps processing disabled and no client hooks;\n * see the slot's inline comment for why the daemon relies on the\n * controller's defaults for everything else.\n *\n * The optional `keyringController` slot is intentionally omitted so the\n * controller's built-in defaults (e.g. the PBKDF2 encryptor) apply.\n *\n * @param infuraProjectId - The Infura project ID for the `NetworkController`.\n * @returns The `instanceOptions` for the `Wallet` constructor.\n */\nfunction buildInstanceOptions(\n infuraProjectId: string,\n): WalletOptions['instanceOptions'] {\n return {\n approvalController: {\n // The daemon is headless, so there is no UI to open: requests are\n // resolved by the auto-approval subscription (see `subscribeToAutoApproval`)\n // rather than by this hook, which stays a no-op.\n // TODO: surface approval requests over the daemon transport.\n showApprovalRequest: (): undefined => undefined,\n },\n connectivityController: {\n connectivityAdapter: new AlwaysOnlineAdapter(),\n },\n gasFeeController: {\n // Identifies the CLI to the gas estimation API via the `X-Client-Id`\n // header.\n clientId: 'cli',\n },\n networkController: {\n infuraProjectId,\n },\n remoteFeatureFlagController: {\n clientConfigApiService: new ClientConfigApiService({\n fetch: globalThis.fetch,\n config: {\n // TODO: switch to a CLI-specific `ClientType` once one exists; until\n // then `Extension` buckets feature flags as an extension client.\n client: ClientType.Extension,\n distribution: DistributionType.Main,\n environment: EnvironmentType.Production,\n },\n }),\n getMetaMetricsId: (): string => 'cli',\n clientVersion: '0.0.0',\n },\n storageService: {\n storage: new InMemoryStorageAdapter(),\n },\n transactionController: {\n // The CLI exposes no swaps surface, so skip the swaps-specific\n // post-processing a full wallet client runs (mobile makes the same\n // choice; the extension keeps swaps enabled).\n disableSwaps: true,\n // No CLI-specific transaction hooks: the controller's built-in publish\n // path broadcasts through the wired `NetworkController` provider, and the\n // remaining hooks (metrics, notifications, gas-fee tokens) are client-UI\n // concerns the headless daemon has no equivalent for. Every other option\n // (`getPermittedAccounts`, `isSimulationEnabled`, `trace`, …) is left at\n // the controller's default.\n hooks: {},\n },\n };\n}\n\n/**\n * Register handlers for the `AnalyticsController` actions that\n * `NetworkController` calls to emit RPC service analytics. The daemon does not\n * report analytics, so these do nothing: `getState` returns an empty analytics\n * ID, which stops the controller before it tracks anything, and `trackEvent` is\n * a no-op. Registering them keeps those messenger calls from throwing.\n *\n * @param messenger - The wallet's root messenger.\n */\nfunction registerAnalyticsStubHandlers(messenger: Wallet['messenger']): void {\n // Register on an `AnalyticsController`-namespaced messenger, not the root: a\n // messenger can only register handlers for its own namespace, and doing so\n // delegates them up to the root, where `NetworkController` can reach them.\n const analyticsMessenger = new Messenger<\n 'AnalyticsController',\n AnalyticsControllerGetStateAction | AnalyticsControllerTrackEventAction,\n never,\n RootMessenger<DefaultActions, DefaultEvents>\n >({\n namespace: 'AnalyticsController',\n parent: messenger as RootMessenger<DefaultActions, DefaultEvents>,\n });\n analyticsMessenger.registerActionHandler(\n 'AnalyticsController:getState',\n () => ({\n ...getDefaultAnalyticsControllerState(),\n analyticsId: '',\n }),\n );\n analyticsMessenger.registerActionHandler(\n 'AnalyticsController:trackEvent',\n () => undefined,\n );\n}\n\n/**\n * Create a configured `Wallet` for daemon use, backed by a SQLite key-value\n * store for controller-state persistence.\n *\n * Loads any previously-persisted controller state from the store, seeds the\n * wallet with it, then subscribes the store to subsequent state changes so all\n * persist-flagged properties are written through.\n *\n * If the store does not yet contain a keyring vault (first run), the supplied\n * secret recovery phrase is imported using the supplied password. On\n * subsequent runs, the persisted vault is reused: when a password is\n * supplied, the wallet is unlocked via `KeyringController:submitPassword` so\n * keyring-bound messenger actions work immediately; when no password is\n * supplied, the wallet starts locked and the caller is expected to invoke\n * `mm wallet unlock` before any keyring-bound operation. First-run startup\n * without a password is rejected (the SRP cannot be imported without one).\n * On a subsequent run, a wrong password rejects startup with a `Failed to\n * unlock the persisted vault` error that wraps the `submitPassword` rejection.\n *\n * On any failure after the store is opened, the store is closed (and the wallet\n * destroyed, if constructed). On a first-run failure, the on-disk database is\n * also removed so a retry does not latch onto an orphaned partial vault — this\n * covers in-process failures only; a hard crash (SIGKILL/power loss) mid-import\n * can still leave a vault on disk.\n *\n * @param config - Wallet configuration.\n * @param config.databasePath - Path to the SQLite database file (or\n * `':memory:'` for ephemeral use).\n * @param config.password - The wallet password. Optional on subsequent runs;\n * when omitted, the daemon starts with a locked keyring. Required on first\n * run (to import the SRP).\n * @param config.srp - The secret recovery phrase (BIP-39 mnemonic). Used\n * only on first run.\n * @param config.infuraProjectId - The Infura project ID for the\n * `NetworkController`.\n * @param config.log - Optional logger for persistence-write and teardown\n * failures. Without it, failures fall back to `console.error` (which a detached\n * daemon's `stdio: 'ignore'` discards).\n * @returns The `Wallet` and a `dispose` handle that tears it down.\n */\nexport async function createWallet({\n databasePath,\n password,\n srp,\n infuraProjectId,\n log,\n}: CreateWalletConfig): Promise<CreateWalletResult> {\n const logFn = log ?? ((message: string): void => console.error(message));\n const store = new KeyValueStore(databasePath);\n let wallet: Wallet | undefined;\n let persistenceUnsubscribe: (() => void) | undefined;\n let autoApprovalUnsubscribe: (() => void) | undefined;\n let wasFirstRun = false;\n\n try {\n const state = await loadPersistedState(store, infuraProjectId, logFn);\n wasFirstRun = !hasPersistedKeyring(state);\n\n // Validate the first-run precondition BEFORE constructing the wallet,\n // so a doomed startup doesn't build a Wallet (and wire persistence\n // handlers) just to tear it down.\n if (wasFirstRun && password === undefined) {\n throw new Error(\n 'A password is required on first run to import the secret recovery phrase. ' +\n 'Pass `--password` (or `MM_WALLET_PASSWORD`) on `mm daemon start`.',\n );\n }\n\n wallet = new Wallet({\n state,\n instanceOptions: buildInstanceOptions(infuraProjectId),\n });\n registerAnalyticsStubHandlers(wallet.messenger);\n persistenceUnsubscribe = subscribeToChanges(\n wallet.messenger,\n wallet.controllerMetadata,\n store,\n logFn,\n );\n\n // Installed before `init` so any approval raised during initialization is covered.\n autoApprovalUnsubscribe = subscribeToAutoApproval(wallet.messenger, logFn);\n\n // Complete post-construction controller setup before serving requests —\n // e.g. `NetworkController.init` applies the selected network so a provider\n // is available. `init` settles every step independently; a rejected step\n // leaves the wallet only partially usable, so abort startup (the catch\n // below tears down and, on first run, removes the partial database) rather\n // than serving a degraded daemon.\n const initResults = await wallet.init();\n const initFailures = initResults.filter(\n (result): result is PromiseRejectedResult => result.status === 'rejected',\n );\n for (const failure of initFailures) {\n logFn(`Wallet init step failed: ${String(failure.reason)}`);\n }\n if (initFailures.length > 0) {\n const firstReason = String(initFailures[0].reason);\n throw new Error(\n `Wallet initialization failed (${initFailures.length} step(s)); refusing to serve a partially initialized wallet. First failure: ${firstReason}`,\n );\n }\n\n if (wasFirstRun) {\n // The precondition check above throws when `wasFirstRun && password ===\n // undefined`, so `password` is defined here. TS does not correlate the\n // two separate variables, so it cannot narrow `password` from\n // `wasFirstRun` alone — hence the assertion.\n await importSecretRecoveryPhrase(\n wallet,\n (password as Password).unwrap(),\n srp.unwrap(),\n );\n } else if (password !== undefined) {\n try {\n await wallet.messenger.call(\n 'KeyringController:submitPassword',\n password.unwrap(),\n );\n } catch (error) {\n throw new Error(\n `Failed to unlock the persisted vault: ${String(error)}`,\n );\n }\n }\n\n let disposePromise: Promise<void> | undefined;\n return {\n wallet,\n dispose: async () =>\n (disposePromise ??= teardown(\n persistenceUnsubscribe,\n autoApprovalUnsubscribe,\n wallet,\n store,\n logFn,\n )),\n };\n } catch (error) {\n await teardown(\n persistenceUnsubscribe,\n autoApprovalUnsubscribe,\n wallet,\n store,\n logFn,\n );\n\n if (wasFirstRun && databasePath !== IN_MEMORY_DATABASE_PATH) {\n // Best-effort cleanup of the on-disk SQLite files (main, WAL, SHM) so a\n // partially-persisted KeyringController vault cannot mislead the next run\n // into skipping SRP import. Covers in-process failures only — a crash\n // (SIGKILL/power loss) mid-import leaves the vault on disk.\n await Promise.all(\n [databasePath, `${databasePath}-wal`, `${databasePath}-shm`].map(\n (path) =>\n rm(path, { force: true }).catch((rmError: unknown) => {\n logFn(\n `Failed to remove ${path} during first-run cleanup: ${String(rmError)}`,\n );\n }),\n ),\n );\n }\n\n throw error;\n }\n}\n\n/**\n * Load persisted controller state, filtered to currently persist-flagged\n * properties.\n *\n * `loadState` filters against the live controller metadata, but that metadata\n * is only knowable from a constructed `Wallet` — and its output is what seeds\n * the real wallet. So this constructs a short-lived probe purely to read the\n * metadata, then tears it down.\n *\n * TODO: drop the probe once `@metamask/wallet` exposes controller metadata\n * without constructing a `Wallet`.\n *\n * @param store - The key-value store to read from.\n * @param infuraProjectId - The Infura project ID for the probe's\n * `NetworkController`.\n * @param logFn - Logger for a probe-teardown failure.\n * @returns The filtered persisted state, suitable for the `Wallet` `state`\n * option.\n */\nasync function loadPersistedState(\n store: KeyValueStore,\n infuraProjectId: string,\n logFn: Logger,\n): Promise<Record<string, Record<string, Json>>> {\n const probe = new Wallet({\n instanceOptions: buildInstanceOptions(infuraProjectId),\n });\n registerAnalyticsStubHandlers(probe.messenger);\n try {\n return loadState(store, probe.controllerMetadata);\n } finally {\n await probe.destroy().catch((error: unknown) => {\n logFn(`Metadata probe destroy failed: ${String(error)}`);\n });\n }\n}\n\n/**\n * Best-effort unsubscribe: run the function if present, logging (and\n * swallowing) any error so a later teardown step still runs.\n *\n * @param unsubscribe - The unsubscribe function, if one was registered.\n * @param label - Human-readable subscription name for the failure log.\n * @param logFn - Logger for a failure.\n */\nfunction runUnsubscribe(\n unsubscribe: (() => void) | undefined,\n label: string,\n logFn: Logger,\n): void {\n if (!unsubscribe) {\n return;\n }\n try {\n unsubscribe();\n } catch (error) {\n logFn(\n `${label} unsubscribe failed during teardown — subscription may remain ` +\n `live until the process exits: ${String(error)}`,\n );\n }\n}\n\n/**\n * Persistence-safe teardown of a wallet and its store; see {@link\n * CreateWalletResult.dispose} for the ordering rationale. Each step is\n * best-effort, so a failure is logged and the remaining steps still run.\n *\n * @param persistenceUnsubscribe - The persistence-subscription unsubscribe\n * function, if one was registered.\n * @param autoApprovalUnsubscribe - The auto-approval-subscription unsubscribe\n * function, if one was registered.\n * @param wallet - The wallet to destroy, if one was constructed.\n * @param store - The store to close.\n * @param logFn - Logger for step failures.\n */\nasync function teardown(\n persistenceUnsubscribe: (() => void) | undefined,\n autoApprovalUnsubscribe: (() => void) | undefined,\n wallet: Wallet | undefined,\n store: KeyValueStore,\n logFn: Logger,\n): Promise<void> {\n runUnsubscribe(persistenceUnsubscribe, 'Persistence', logFn);\n runUnsubscribe(autoApprovalUnsubscribe, 'Auto-approval', logFn);\n if (wallet) {\n try {\n await wallet.destroy();\n } catch (error) {\n logFn(`wallet.destroy() failed during teardown: ${String(error)}`);\n }\n }\n try {\n store.close();\n } catch (error) {\n logFn(`store.close() failed during teardown: ${String(error)}`);\n }\n}\n\n/**\n * Determine whether the loaded state already contains a keyring vault.\n *\n * The KeyringController persists its `vault` once an SRP has been imported, so\n * its presence indicates that first-run setup completed before.\n *\n * @param state - The state loaded from the key-value store.\n * @returns True if a KeyringController vault string is present.\n */\nfunction hasPersistedKeyring(\n state: Record<string, Record<string, unknown>>,\n): boolean {\n return typeof state.KeyringController?.vault === 'string';\n}\n"]}
1
+ {"version":3,"file":"wallet-factory.cjs","sourceRoot":"","sources":["../../src/daemon/wallet-factory.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n ClientConfigApiService,\n ClientType,\n DistributionType,\n EnvironmentType,\n} from '@metamask/remote-feature-flag-controller';\nimport { InMemoryStorageAdapter } from '@metamask/storage-service';\nimport type { Json } from '@metamask/utils';\nimport {\n AlwaysOnlineAdapter,\n importSecretRecoveryPhrase,\n Wallet,\n} from '@metamask/wallet';\nimport type { WalletOptions } from '@metamask/wallet';\nimport { rm } from 'node:fs/promises';\n\nimport { KeyValueStore } from '../persistence/KeyValueStore.js';\nimport { loadState, subscribeToChanges } from '../persistence/persistence.js';\nimport { subscribeToAutoApproval } from './auto-approval.js';\nimport type { Password, Srp } from './secrets.js';\nimport type { Logger } from './types.js';\n\nconst IN_MEMORY_DATABASE_PATH = ':memory:';\n\nexport type CreateWalletConfig = {\n databasePath: string;\n password?: Password;\n srp: Srp;\n infuraProjectId: string;\n log?: Logger;\n};\n\nexport type CreateWalletResult = {\n wallet: Wallet;\n /**\n * Tear down everything `createWallet` set up, in the order that keeps\n * in-flight persistence writes valid: stop the state-change subscription,\n * destroy the wallet, then close the store (closing first would cause a\n * teardown-time persistence write to fail). Resilient — a failure in any\n * step is logged and the remaining steps still run — and idempotent (repeat\n * calls coalesce onto the same teardown).\n */\n dispose: () => Promise<void>;\n};\n\n/**\n * Build the per-instance options the daemon's `Wallet` is constructed with.\n *\n * Returns a fresh set on every call so the metadata probe and the real wallet\n * never share adapter/service instances (the probe is destroyed, which may\n * tear those instances down).\n *\n * Only the slots wired on `@metamask/wallet` today are populated:\n * - `storageService` — backed by an in-memory adapter. This is the wallet's\n * large-blob store (snap source, caches), distinct from the SQLite\n * `KeyValueStore` that persists controller state; no wired controller\n * offloads durable data to it yet, so in-memory suffices.\n * - `connectivityController` — the `AlwaysOnlineAdapter` exported for\n * node-like hosts that have no platform connectivity signal.\n * - `networkController` — the Infura project ID used to reach Infura RPC\n * endpoints.\n * - `remoteFeatureFlagController` — a `ClientConfigApiService` fetching real\n * flags over the network.\n * - `approvalController` — a no-op `showApprovalRequest` (the daemon is\n * headless); pending requests are accepted separately by the auto-approval\n * subscription `createWallet` installs, not by this hook.\n * - `gasFeeController` — only `clientId: 'cli'` (sent as `X-Client-Id` to the\n * gas API); the API endpoints, poll interval, and compatibility callbacks are\n * left at the wallet package's platform-agnostic defaults (the default\n * `EIP1559APIEndpoint` is already the production URL, so the daemon does not\n * re-specify it).\n * - `transactionController` — swaps processing disabled and no client hooks;\n * see the slot's inline comment for why the daemon relies on the\n * controller's defaults for everything else.\n *\n * The optional `keyringController` slot is intentionally omitted so the\n * controller's built-in defaults (e.g. the PBKDF2 encryptor) apply.\n *\n * @param infuraProjectId - The Infura project ID for the `NetworkController`.\n * @returns The `instanceOptions` for the `Wallet` constructor.\n */\nfunction buildInstanceOptions(\n infuraProjectId: string,\n): WalletOptions['instanceOptions'] {\n return {\n approvalController: {\n // The daemon is headless, so there is no UI to open: requests are\n // resolved by the auto-approval subscription (see `subscribeToAutoApproval`)\n // rather than by this hook, which stays a no-op.\n // TODO: surface approval requests over the daemon transport.\n showApprovalRequest: (): undefined => undefined,\n },\n connectivityController: {\n connectivityAdapter: new AlwaysOnlineAdapter(),\n },\n gasFeeController: {\n // Identifies the CLI to the gas estimation API via the `X-Client-Id`\n // header.\n clientId: 'cli',\n },\n networkController: {\n infuraProjectId,\n },\n remoteFeatureFlagController: {\n clientConfigApiService: new ClientConfigApiService({\n fetch: globalThis.fetch,\n config: {\n // TODO: switch to a CLI-specific `ClientType` once one exists; until\n // then `Extension` buckets feature flags as an extension client.\n client: ClientType.Extension,\n distribution: DistributionType.Main,\n environment: EnvironmentType.Production,\n },\n }),\n getMetaMetricsId: (): string => 'cli',\n clientVersion: '0.0.0',\n },\n storageService: {\n storage: new InMemoryStorageAdapter(),\n },\n transactionController: {\n // The CLI exposes no swaps surface, so skip the swaps-specific\n // post-processing a full wallet client runs (mobile makes the same\n // choice; the extension keeps swaps enabled).\n disableSwaps: true,\n // No CLI-specific transaction hooks: the controller's built-in publish\n // path broadcasts through the wired `NetworkController` provider, and the\n // remaining hooks (metrics, notifications, gas-fee tokens) are client-UI\n // concerns the headless daemon has no equivalent for. Every other option\n // (`getPermittedAccounts`, `isSimulationEnabled`, `trace`, …) is left at\n // the controller's default.\n hooks: {},\n },\n };\n}\n\n/**\n * Create a configured `Wallet` for daemon use, backed by a SQLite key-value\n * store for controller-state persistence.\n *\n * Loads any previously-persisted controller state from the store, seeds the\n * wallet with it, then subscribes the store to subsequent state changes so all\n * persist-flagged properties are written through.\n *\n * If the store does not yet contain a keyring vault (first run), the supplied\n * secret recovery phrase is imported using the supplied password. On\n * subsequent runs, the persisted vault is reused: when a password is\n * supplied, the wallet is unlocked via `KeyringController:submitPassword` so\n * keyring-bound messenger actions work immediately; when no password is\n * supplied, the wallet starts locked and the caller is expected to invoke\n * `mm wallet unlock` before any keyring-bound operation. First-run startup\n * without a password is rejected (the SRP cannot be imported without one).\n * On a subsequent run, a wrong password rejects startup with a `Failed to\n * unlock the persisted vault` error that wraps the `submitPassword` rejection.\n *\n * On any failure after the store is opened, the store is closed (and the wallet\n * destroyed, if constructed). On a first-run failure, the on-disk database is\n * also removed so a retry does not latch onto an orphaned partial vault — this\n * covers in-process failures only; a hard crash (SIGKILL/power loss) mid-import\n * can still leave a vault on disk.\n *\n * @param config - Wallet configuration.\n * @param config.databasePath - Path to the SQLite database file (or\n * `':memory:'` for ephemeral use).\n * @param config.password - The wallet password. Optional on subsequent runs;\n * when omitted, the daemon starts with a locked keyring. Required on first\n * run (to import the SRP).\n * @param config.srp - The secret recovery phrase (BIP-39 mnemonic). Used\n * only on first run.\n * @param config.infuraProjectId - The Infura project ID for the\n * `NetworkController`.\n * @param config.log - Optional logger for persistence-write and teardown\n * failures. Without it, failures fall back to `console.error` (which a detached\n * daemon's `stdio: 'ignore'` discards).\n * @returns The `Wallet` and a `dispose` handle that tears it down.\n */\nexport async function createWallet({\n databasePath,\n password,\n srp,\n infuraProjectId,\n log,\n}: CreateWalletConfig): Promise<CreateWalletResult> {\n const logFn = log ?? ((message: string): void => console.error(message));\n const store = new KeyValueStore(databasePath);\n let wallet: Wallet | undefined;\n let persistenceUnsubscribe: (() => void) | undefined;\n let autoApprovalUnsubscribe: (() => void) | undefined;\n let wasFirstRun = false;\n\n try {\n const state = await loadPersistedState(store, infuraProjectId, logFn);\n wasFirstRun = !hasPersistedKeyring(state);\n\n // Validate the first-run precondition BEFORE constructing the wallet,\n // so a doomed startup doesn't build a Wallet (and wire persistence\n // handlers) just to tear it down.\n if (wasFirstRun && password === undefined) {\n throw new Error(\n 'A password is required on first run to import the secret recovery phrase. ' +\n 'Pass `--password` (or `MM_WALLET_PASSWORD`) on `mm daemon start`.',\n );\n }\n\n wallet = new Wallet({\n state,\n instanceOptions: buildInstanceOptions(infuraProjectId),\n });\n persistenceUnsubscribe = subscribeToChanges(\n wallet.messenger,\n wallet.controllerMetadata,\n store,\n logFn,\n );\n\n // Installed before `init` so any approval raised during initialization is covered.\n autoApprovalUnsubscribe = subscribeToAutoApproval(wallet.messenger, logFn);\n\n // Complete post-construction controller setup before serving requests —\n // e.g. `NetworkController.init` applies the selected network so a provider\n // is available. `init` settles every step independently; a rejected step\n // leaves the wallet only partially usable, so abort startup (the catch\n // below tears down and, on first run, removes the partial database) rather\n // than serving a degraded daemon.\n const initResults = await wallet.init();\n const initFailures = initResults.filter(\n (result): result is PromiseRejectedResult => result.status === 'rejected',\n );\n for (const failure of initFailures) {\n logFn(`Wallet init step failed: ${String(failure.reason)}`);\n }\n if (initFailures.length > 0) {\n const firstReason = String(initFailures[0].reason);\n throw new Error(\n `Wallet initialization failed (${initFailures.length} step(s)); refusing to serve a partially initialized wallet. First failure: ${firstReason}`,\n );\n }\n\n if (wasFirstRun) {\n // The precondition check above throws when `wasFirstRun && password ===\n // undefined`, so `password` is defined here. TS does not correlate the\n // two separate variables, so it cannot narrow `password` from\n // `wasFirstRun` alone — hence the assertion.\n await importSecretRecoveryPhrase(\n wallet,\n (password as Password).unwrap(),\n srp.unwrap(),\n );\n } else if (password !== undefined) {\n try {\n await wallet.messenger.call(\n 'KeyringController:submitPassword',\n password.unwrap(),\n );\n } catch (error) {\n throw new Error(\n `Failed to unlock the persisted vault: ${String(error)}`,\n );\n }\n }\n\n let disposePromise: Promise<void> | undefined;\n return {\n wallet,\n dispose: async () =>\n (disposePromise ??= teardown(\n persistenceUnsubscribe,\n autoApprovalUnsubscribe,\n wallet,\n store,\n logFn,\n )),\n };\n } catch (error) {\n await teardown(\n persistenceUnsubscribe,\n autoApprovalUnsubscribe,\n wallet,\n store,\n logFn,\n );\n\n if (wasFirstRun && databasePath !== IN_MEMORY_DATABASE_PATH) {\n // Best-effort cleanup of the on-disk SQLite files (main, WAL, SHM) so a\n // partially-persisted KeyringController vault cannot mislead the next run\n // into skipping SRP import. Covers in-process failures only — a crash\n // (SIGKILL/power loss) mid-import leaves the vault on disk.\n await Promise.all(\n [databasePath, `${databasePath}-wal`, `${databasePath}-shm`].map(\n (path) =>\n rm(path, { force: true }).catch((rmError: unknown) => {\n logFn(\n `Failed to remove ${path} during first-run cleanup: ${String(rmError)}`,\n );\n }),\n ),\n );\n }\n\n throw error;\n }\n}\n\n/**\n * Load persisted controller state, filtered to currently persist-flagged\n * properties.\n *\n * `loadState` filters against the live controller metadata, but that metadata\n * is only knowable from a constructed `Wallet` — and its output is what seeds\n * the real wallet. So this constructs a short-lived probe purely to read the\n * metadata, then tears it down.\n *\n * TODO: drop the probe once `@metamask/wallet` exposes controller metadata\n * without constructing a `Wallet`.\n *\n * @param store - The key-value store to read from.\n * @param infuraProjectId - The Infura project ID for the probe's\n * `NetworkController`.\n * @param logFn - Logger for a probe-teardown failure.\n * @returns The filtered persisted state, suitable for the `Wallet` `state`\n * option.\n */\nasync function loadPersistedState(\n store: KeyValueStore,\n infuraProjectId: string,\n logFn: Logger,\n): Promise<Record<string, Record<string, Json>>> {\n const probe = new Wallet({\n instanceOptions: buildInstanceOptions(infuraProjectId),\n });\n try {\n return loadState(store, probe.controllerMetadata);\n } finally {\n await probe.destroy().catch((error: unknown) => {\n logFn(`Metadata probe destroy failed: ${String(error)}`);\n });\n }\n}\n\n/**\n * Best-effort unsubscribe: run the function if present, logging (and\n * swallowing) any error so a later teardown step still runs.\n *\n * @param unsubscribe - The unsubscribe function, if one was registered.\n * @param label - Human-readable subscription name for the failure log.\n * @param logFn - Logger for a failure.\n */\nfunction runUnsubscribe(\n unsubscribe: (() => void) | undefined,\n label: string,\n logFn: Logger,\n): void {\n if (!unsubscribe) {\n return;\n }\n try {\n unsubscribe();\n } catch (error) {\n logFn(\n `${label} unsubscribe failed during teardown — subscription may remain ` +\n `live until the process exits: ${String(error)}`,\n );\n }\n}\n\n/**\n * Persistence-safe teardown of a wallet and its store; see {@link\n * CreateWalletResult.dispose} for the ordering rationale. Each step is\n * best-effort, so a failure is logged and the remaining steps still run.\n *\n * @param persistenceUnsubscribe - The persistence-subscription unsubscribe\n * function, if one was registered.\n * @param autoApprovalUnsubscribe - The auto-approval-subscription unsubscribe\n * function, if one was registered.\n * @param wallet - The wallet to destroy, if one was constructed.\n * @param store - The store to close.\n * @param logFn - Logger for step failures.\n */\nasync function teardown(\n persistenceUnsubscribe: (() => void) | undefined,\n autoApprovalUnsubscribe: (() => void) | undefined,\n wallet: Wallet | undefined,\n store: KeyValueStore,\n logFn: Logger,\n): Promise<void> {\n runUnsubscribe(persistenceUnsubscribe, 'Persistence', logFn);\n runUnsubscribe(autoApprovalUnsubscribe, 'Auto-approval', logFn);\n if (wallet) {\n try {\n await wallet.destroy();\n } catch (error) {\n logFn(`wallet.destroy() failed during teardown: ${String(error)}`);\n }\n }\n try {\n store.close();\n } catch (error) {\n logFn(`store.close() failed during teardown: ${String(error)}`);\n }\n}\n\n/**\n * Determine whether the loaded state already contains a keyring vault.\n *\n * The KeyringController persists its `vault` once an SRP has been imported, so\n * its presence indicates that first-run setup completed before.\n *\n * @param state - The state loaded from the key-value store.\n * @returns True if a KeyringController vault string is present.\n */\nfunction hasPersistedKeyring(\n state: Record<string, Record<string, unknown>>,\n): boolean {\n return typeof state.KeyringController?.vault === 'string';\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"wallet-factory.d.cts","sourceRoot":"","sources":["../../src/daemon/wallet-factory.ts"],"names":[],"mappings":"AAcA,OAAO,EAGL,MAAM,EACP,yBAAyB;AAY1B,OAAO,KAAK,EAAE,QAAQ,EAAE,GAAG,EAAE,sBAAqB;AAClD,OAAO,KAAK,EAAE,MAAM,EAAE,oBAAmB;AAIzC,MAAM,MAAM,kBAAkB,GAAG;IAC/B,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,GAAG,EAAE,GAAG,CAAC;IACT,eAAe,EAAE,MAAM,CAAC;IACxB,GAAG,CAAC,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf;;;;;;;OAOG;IACH,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9B,CAAC;AAgIF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AACH,wBAAsB,YAAY,CAAC,EACjC,YAAY,EACZ,QAAQ,EACR,GAAG,EACH,eAAe,EACf,GAAG,GACJ,EAAE,kBAAkB,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAwHlD"}
1
+ {"version":3,"file":"wallet-factory.d.cts","sourceRoot":"","sources":["../../src/daemon/wallet-factory.ts"],"names":[],"mappings":"AAQA,OAAO,EAGL,MAAM,EACP,yBAAyB;AAO1B,OAAO,KAAK,EAAE,QAAQ,EAAE,GAAG,EAAE,sBAAqB;AAClD,OAAO,KAAK,EAAE,MAAM,EAAE,oBAAmB;AAIzC,MAAM,MAAM,kBAAkB,GAAG;IAC/B,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,GAAG,EAAE,GAAG,CAAC;IACT,eAAe,EAAE,MAAM,CAAC;IACxB,GAAG,CAAC,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf;;;;;;;OAOG;IACH,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9B,CAAC;AA6FF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AACH,wBAAsB,YAAY,CAAC,EACjC,YAAY,EACZ,QAAQ,EACR,GAAG,EACH,eAAe,EACf,GAAG,GACJ,EAAE,kBAAkB,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAuHlD"}
@@ -1 +1 @@
1
- {"version":3,"file":"wallet-factory.d.mts","sourceRoot":"","sources":["../../src/daemon/wallet-factory.ts"],"names":[],"mappings":"AAcA,OAAO,EAGL,MAAM,EACP,yBAAyB;AAY1B,OAAO,KAAK,EAAE,QAAQ,EAAE,GAAG,EAAE,sBAAqB;AAClD,OAAO,KAAK,EAAE,MAAM,EAAE,oBAAmB;AAIzC,MAAM,MAAM,kBAAkB,GAAG;IAC/B,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,GAAG,EAAE,GAAG,CAAC;IACT,eAAe,EAAE,MAAM,CAAC;IACxB,GAAG,CAAC,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf;;;;;;;OAOG;IACH,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9B,CAAC;AAgIF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AACH,wBAAsB,YAAY,CAAC,EACjC,YAAY,EACZ,QAAQ,EACR,GAAG,EACH,eAAe,EACf,GAAG,GACJ,EAAE,kBAAkB,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAwHlD"}
1
+ {"version":3,"file":"wallet-factory.d.mts","sourceRoot":"","sources":["../../src/daemon/wallet-factory.ts"],"names":[],"mappings":"AAQA,OAAO,EAGL,MAAM,EACP,yBAAyB;AAO1B,OAAO,KAAK,EAAE,QAAQ,EAAE,GAAG,EAAE,sBAAqB;AAClD,OAAO,KAAK,EAAE,MAAM,EAAE,oBAAmB;AAIzC,MAAM,MAAM,kBAAkB,GAAG;IAC/B,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,GAAG,EAAE,GAAG,CAAC;IACT,eAAe,EAAE,MAAM,CAAC;IACxB,GAAG,CAAC,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf;;;;;;;OAOG;IACH,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9B,CAAC;AA6FF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AACH,wBAAsB,YAAY,CAAC,EACjC,YAAY,EACZ,QAAQ,EACR,GAAG,EACH,eAAe,EACf,GAAG,GACJ,EAAE,kBAAkB,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAuHlD"}
@@ -1,5 +1,3 @@
1
- import { getDefaultAnalyticsControllerState } from "@metamask/analytics-controller";
2
- import { Messenger } from "@metamask/messenger";
3
1
  import { ClientConfigApiService, ClientType, DistributionType, EnvironmentType } from "@metamask/remote-feature-flag-controller";
4
2
  import { InMemoryStorageAdapter } from "@metamask/storage-service";
5
3
  import { AlwaysOnlineAdapter, importSecretRecoveryPhrase, Wallet } from "@metamask/wallet";
@@ -96,29 +94,6 @@ function buildInstanceOptions(infuraProjectId) {
96
94
  },
97
95
  };
98
96
  }
99
- /**
100
- * Register handlers for the `AnalyticsController` actions that
101
- * `NetworkController` calls to emit RPC service analytics. The daemon does not
102
- * report analytics, so these do nothing: `getState` returns an empty analytics
103
- * ID, which stops the controller before it tracks anything, and `trackEvent` is
104
- * a no-op. Registering them keeps those messenger calls from throwing.
105
- *
106
- * @param messenger - The wallet's root messenger.
107
- */
108
- function registerAnalyticsStubHandlers(messenger) {
109
- // Register on an `AnalyticsController`-namespaced messenger, not the root: a
110
- // messenger can only register handlers for its own namespace, and doing so
111
- // delegates them up to the root, where `NetworkController` can reach them.
112
- const analyticsMessenger = new Messenger({
113
- namespace: 'AnalyticsController',
114
- parent: messenger,
115
- });
116
- analyticsMessenger.registerActionHandler('AnalyticsController:getState', () => ({
117
- ...getDefaultAnalyticsControllerState(),
118
- analyticsId: '',
119
- }));
120
- analyticsMessenger.registerActionHandler('AnalyticsController:trackEvent', () => undefined);
121
- }
122
97
  /**
123
98
  * Create a configured `Wallet` for daemon use, backed by a SQLite key-value
124
99
  * store for controller-state persistence.
@@ -180,7 +155,6 @@ export async function createWallet({ databasePath, password, srp, infuraProjectI
180
155
  state,
181
156
  instanceOptions: buildInstanceOptions(infuraProjectId),
182
157
  });
183
- registerAnalyticsStubHandlers(wallet.messenger);
184
158
  persistenceUnsubscribe = subscribeToChanges(wallet.messenger, wallet.controllerMetadata, store, logFn);
185
159
  // Installed before `init` so any approval raised during initialization is covered.
186
160
  autoApprovalUnsubscribe = subscribeToAutoApproval(wallet.messenger, logFn);
@@ -257,7 +231,6 @@ async function loadPersistedState(store, infuraProjectId, logFn) {
257
231
  const probe = new Wallet({
258
232
  instanceOptions: buildInstanceOptions(infuraProjectId),
259
233
  });
260
- registerAnalyticsStubHandlers(probe.messenger);
261
234
  try {
262
235
  return loadState(store, probe.controllerMetadata);
263
236
  }
@@ -1 +1 @@
1
- {"version":3,"file":"wallet-factory.mjs","sourceRoot":"","sources":["../../src/daemon/wallet-factory.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kCAAkC,EAAE,uCAAuC;AAKpF,OAAO,EAAE,SAAS,EAAE,4BAA4B;AAChD,OAAO,EACL,sBAAsB,EACtB,UAAU,EACV,gBAAgB,EAChB,eAAe,EAChB,iDAAiD;AAClD,OAAO,EAAE,sBAAsB,EAAE,kCAAkC;AAEnE,OAAO,EACL,mBAAmB,EACnB,0BAA0B,EAC1B,MAAM,EACP,yBAAyB;AAO1B,OAAO,EAAE,EAAE,EAAE,yBAAyB;AAEtC,OAAO,EAAE,aAAa,EAAE,yCAAwC;AAChE,OAAO,EAAE,SAAS,EAAE,kBAAkB,EAAE,uCAAsC;AAC9E,OAAO,EAAE,uBAAuB,EAAE,4BAA2B;AAI7D,MAAM,uBAAuB,GAAG,UAAU,CAAC;AAuB3C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,SAAS,oBAAoB,CAC3B,eAAuB;IAEvB,OAAO;QACL,kBAAkB,EAAE;YAClB,kEAAkE;YAClE,6EAA6E;YAC7E,iDAAiD;YACjD,6DAA6D;YAC7D,mBAAmB,EAAE,GAAc,EAAE,CAAC,SAAS;SAChD;QACD,sBAAsB,EAAE;YACtB,mBAAmB,EAAE,IAAI,mBAAmB,EAAE;SAC/C;QACD,gBAAgB,EAAE;YAChB,qEAAqE;YACrE,UAAU;YACV,QAAQ,EAAE,KAAK;SAChB;QACD,iBAAiB,EAAE;YACjB,eAAe;SAChB;QACD,2BAA2B,EAAE;YAC3B,sBAAsB,EAAE,IAAI,sBAAsB,CAAC;gBACjD,KAAK,EAAE,UAAU,CAAC,KAAK;gBACvB,MAAM,EAAE;oBACN,qEAAqE;oBACrE,iEAAiE;oBACjE,MAAM,EAAE,UAAU,CAAC,SAAS;oBAC5B,YAAY,EAAE,gBAAgB,CAAC,IAAI;oBACnC,WAAW,EAAE,eAAe,CAAC,UAAU;iBACxC;aACF,CAAC;YACF,gBAAgB,EAAE,GAAW,EAAE,CAAC,KAAK;YACrC,aAAa,EAAE,OAAO;SACvB;QACD,cAAc,EAAE;YACd,OAAO,EAAE,IAAI,sBAAsB,EAAE;SACtC;QACD,qBAAqB,EAAE;YACrB,+DAA+D;YAC/D,mEAAmE;YACnE,8CAA8C;YAC9C,YAAY,EAAE,IAAI;YAClB,uEAAuE;YACvE,0EAA0E;YAC1E,yEAAyE;YACzE,yEAAyE;YACzE,yEAAyE;YACzE,4BAA4B;YAC5B,KAAK,EAAE,EAAE;SACV;KACF,CAAC;AACJ,CAAC;AAED;;;;;;;;GAQG;AACH,SAAS,6BAA6B,CAAC,SAA8B;IACnE,6EAA6E;IAC7E,2EAA2E;IAC3E,2EAA2E;IAC3E,MAAM,kBAAkB,GAAG,IAAI,SAAS,CAKtC;QACA,SAAS,EAAE,qBAAqB;QAChC,MAAM,EAAE,SAAyD;KAClE,CAAC,CAAC;IACH,kBAAkB,CAAC,qBAAqB,CACtC,8BAA8B,EAC9B,GAAG,EAAE,CAAC,CAAC;QACL,GAAG,kCAAkC,EAAE;QACvC,WAAW,EAAE,EAAE;KAChB,CAAC,CACH,CAAC;IACF,kBAAkB,CAAC,qBAAqB,CACtC,gCAAgC,EAChC,GAAG,EAAE,CAAC,SAAS,CAChB,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,EACjC,YAAY,EACZ,QAAQ,EACR,GAAG,EACH,eAAe,EACf,GAAG,GACgB;IACnB,MAAM,KAAK,GAAG,GAAG,IAAI,CAAC,CAAC,OAAe,EAAQ,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC;IACzE,MAAM,KAAK,GAAG,IAAI,aAAa,CAAC,YAAY,CAAC,CAAC;IAC9C,IAAI,MAA0B,CAAC;IAC/B,IAAI,sBAAgD,CAAC;IACrD,IAAI,uBAAiD,CAAC;IACtD,IAAI,WAAW,GAAG,KAAK,CAAC;IAExB,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,MAAM,kBAAkB,CAAC,KAAK,EAAE,eAAe,EAAE,KAAK,CAAC,CAAC;QACtE,WAAW,GAAG,CAAC,mBAAmB,CAAC,KAAK,CAAC,CAAC;QAE1C,sEAAsE;QACtE,mEAAmE;QACnE,kCAAkC;QAClC,IAAI,WAAW,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;YAC1C,MAAM,IAAI,KAAK,CACb,4EAA4E;gBAC1E,mEAAmE,CACtE,CAAC;QACJ,CAAC;QAED,MAAM,GAAG,IAAI,MAAM,CAAC;YAClB,KAAK;YACL,eAAe,EAAE,oBAAoB,CAAC,eAAe,CAAC;SACvD,CAAC,CAAC;QACH,6BAA6B,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;QAChD,sBAAsB,GAAG,kBAAkB,CACzC,MAAM,CAAC,SAAS,EAChB,MAAM,CAAC,kBAAkB,EACzB,KAAK,EACL,KAAK,CACN,CAAC;QAEF,mFAAmF;QACnF,uBAAuB,GAAG,uBAAuB,CAAC,MAAM,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;QAE3E,wEAAwE;QACxE,2EAA2E;QAC3E,yEAAyE;QACzE,uEAAuE;QACvE,2EAA2E;QAC3E,kCAAkC;QAClC,MAAM,WAAW,GAAG,MAAM,MAAM,CAAC,IAAI,EAAE,CAAC;QACxC,MAAM,YAAY,GAAG,WAAW,CAAC,MAAM,CACrC,CAAC,MAAM,EAAmC,EAAE,CAAC,MAAM,CAAC,MAAM,KAAK,UAAU,CAC1E,CAAC;QACF,KAAK,MAAM,OAAO,IAAI,YAAY,EAAE,CAAC;YACnC,KAAK,CAAC,4BAA4B,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QAC9D,CAAC;QACD,IAAI,YAAY,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC5B,MAAM,WAAW,GAAG,MAAM,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;YACnD,MAAM,IAAI,KAAK,CACb,iCAAiC,YAAY,CAAC,MAAM,+EAA+E,WAAW,EAAE,CACjJ,CAAC;QACJ,CAAC;QAED,IAAI,WAAW,EAAE,CAAC;YAChB,wEAAwE;YACxE,uEAAuE;YACvE,8DAA8D;YAC9D,6CAA6C;YAC7C,MAAM,0BAA0B,CAC9B,MAAM,EACL,QAAqB,CAAC,MAAM,EAAE,EAC/B,GAAG,CAAC,MAAM,EAAE,CACb,CAAC;QACJ,CAAC;aAAM,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;YAClC,IAAI,CAAC;gBACH,MAAM,MAAM,CAAC,SAAS,CAAC,IAAI,CACzB,kCAAkC,EAClC,QAAQ,CAAC,MAAM,EAAE,CAClB,CAAC;YACJ,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,MAAM,IAAI,KAAK,CACb,yCAAyC,MAAM,CAAC,KAAK,CAAC,EAAE,CACzD,CAAC;YACJ,CAAC;QACH,CAAC;QAED,IAAI,cAAyC,CAAC;QAC9C,OAAO;YACL,MAAM;YACN,OAAO,EAAE,KAAK,IAAI,EAAE,CAClB,CAAC,cAAc,KAAd,cAAc,GAAK,QAAQ,CAC1B,sBAAsB,EACtB,uBAAuB,EACvB,MAAM,EACN,KAAK,EACL,KAAK,CACN,EAAC;SACL,CAAC;IACJ,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,QAAQ,CACZ,sBAAsB,EACtB,uBAAuB,EACvB,MAAM,EACN,KAAK,EACL,KAAK,CACN,CAAC;QAEF,IAAI,WAAW,IAAI,YAAY,KAAK,uBAAuB,EAAE,CAAC;YAC5D,wEAAwE;YACxE,0EAA0E;YAC1E,sEAAsE;YACtE,4DAA4D;YAC5D,MAAM,OAAO,CAAC,GAAG,CACf,CAAC,YAAY,EAAE,GAAG,YAAY,MAAM,EAAE,GAAG,YAAY,MAAM,CAAC,CAAC,GAAG,CAC9D,CAAC,IAAI,EAAE,EAAE,CACP,EAAE,CAAC,IAAI,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,OAAgB,EAAE,EAAE;gBACnD,KAAK,CACH,oBAAoB,IAAI,8BAA8B,MAAM,CAAC,OAAO,CAAC,EAAE,CACxE,CAAC;YACJ,CAAC,CAAC,CACL,CACF,CAAC;QACJ,CAAC;QAED,MAAM,KAAK,CAAC;IACd,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,KAAK,UAAU,kBAAkB,CAC/B,KAAoB,EACpB,eAAuB,EACvB,KAAa;IAEb,MAAM,KAAK,GAAG,IAAI,MAAM,CAAC;QACvB,eAAe,EAAE,oBAAoB,CAAC,eAAe,CAAC;KACvD,CAAC,CAAC;IACH,6BAA6B,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;IAC/C,IAAI,CAAC;QACH,OAAO,SAAS,CAAC,KAAK,EAAE,KAAK,CAAC,kBAAkB,CAAC,CAAC;IACpD,CAAC;YAAS,CAAC;QACT,MAAM,KAAK,CAAC,OAAO,EAAE,CAAC,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE;YAC7C,KAAK,CAAC,kCAAkC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QAC3D,CAAC,CAAC,CAAC;IACL,CAAC;AACH,CAAC;AAED;;;;;;;GAOG;AACH,SAAS,cAAc,CACrB,WAAqC,EACrC,KAAa,EACb,KAAa;IAEb,IAAI,CAAC,WAAW,EAAE,CAAC;QACjB,OAAO;IACT,CAAC;IACD,IAAI,CAAC;QACH,WAAW,EAAE,CAAC;IAChB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,KAAK,CACH,GAAG,KAAK,gEAAgE;YACtE,iCAAiC,MAAM,CAAC,KAAK,CAAC,EAAE,CACnD,CAAC;IACJ,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,KAAK,UAAU,QAAQ,CACrB,sBAAgD,EAChD,uBAAiD,EACjD,MAA0B,EAC1B,KAAoB,EACpB,KAAa;IAEb,cAAc,CAAC,sBAAsB,EAAE,aAAa,EAAE,KAAK,CAAC,CAAC;IAC7D,cAAc,CAAC,uBAAuB,EAAE,eAAe,EAAE,KAAK,CAAC,CAAC;IAChE,IAAI,MAAM,EAAE,CAAC;QACX,IAAI,CAAC;YACH,MAAM,MAAM,CAAC,OAAO,EAAE,CAAC;QACzB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,KAAK,CAAC,4CAA4C,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QACrE,CAAC;IACH,CAAC;IACD,IAAI,CAAC;QACH,KAAK,CAAC,KAAK,EAAE,CAAC;IAChB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,KAAK,CAAC,yCAAyC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IAClE,CAAC;AACH,CAAC;AAED;;;;;;;;GAQG;AACH,SAAS,mBAAmB,CAC1B,KAA8C;IAE9C,OAAO,OAAO,KAAK,CAAC,iBAAiB,EAAE,KAAK,KAAK,QAAQ,CAAC;AAC5D,CAAC","sourcesContent":["import { getDefaultAnalyticsControllerState } from '@metamask/analytics-controller';\nimport type {\n AnalyticsControllerGetStateAction,\n AnalyticsControllerTrackEventAction,\n} from '@metamask/analytics-controller';\nimport { Messenger } from '@metamask/messenger';\nimport {\n ClientConfigApiService,\n ClientType,\n DistributionType,\n EnvironmentType,\n} from '@metamask/remote-feature-flag-controller';\nimport { InMemoryStorageAdapter } from '@metamask/storage-service';\nimport type { Json } from '@metamask/utils';\nimport {\n AlwaysOnlineAdapter,\n importSecretRecoveryPhrase,\n Wallet,\n} from '@metamask/wallet';\nimport type {\n DefaultActions,\n DefaultEvents,\n RootMessenger,\n WalletOptions,\n} from '@metamask/wallet';\nimport { rm } from 'node:fs/promises';\n\nimport { KeyValueStore } from '../persistence/KeyValueStore.js';\nimport { loadState, subscribeToChanges } from '../persistence/persistence.js';\nimport { subscribeToAutoApproval } from './auto-approval.js';\nimport type { Password, Srp } from './secrets.js';\nimport type { Logger } from './types.js';\n\nconst IN_MEMORY_DATABASE_PATH = ':memory:';\n\nexport type CreateWalletConfig = {\n databasePath: string;\n password?: Password;\n srp: Srp;\n infuraProjectId: string;\n log?: Logger;\n};\n\nexport type CreateWalletResult = {\n wallet: Wallet;\n /**\n * Tear down everything `createWallet` set up, in the order that keeps\n * in-flight persistence writes valid: stop the state-change subscription,\n * destroy the wallet, then close the store (closing first would cause a\n * teardown-time persistence write to fail). Resilient — a failure in any\n * step is logged and the remaining steps still run — and idempotent (repeat\n * calls coalesce onto the same teardown).\n */\n dispose: () => Promise<void>;\n};\n\n/**\n * Build the per-instance options the daemon's `Wallet` is constructed with.\n *\n * Returns a fresh set on every call so the metadata probe and the real wallet\n * never share adapter/service instances (the probe is destroyed, which may\n * tear those instances down).\n *\n * Only the slots wired on `@metamask/wallet` today are populated:\n * - `storageService` — backed by an in-memory adapter. This is the wallet's\n * large-blob store (snap source, caches), distinct from the SQLite\n * `KeyValueStore` that persists controller state; no wired controller\n * offloads durable data to it yet, so in-memory suffices.\n * - `connectivityController` — the `AlwaysOnlineAdapter` exported for\n * node-like hosts that have no platform connectivity signal.\n * - `networkController` — the Infura project ID used to reach Infura RPC\n * endpoints.\n * - `remoteFeatureFlagController` — a `ClientConfigApiService` fetching real\n * flags over the network.\n * - `approvalController` — a no-op `showApprovalRequest` (the daemon is\n * headless); pending requests are accepted separately by the auto-approval\n * subscription `createWallet` installs, not by this hook.\n * - `gasFeeController` — only `clientId: 'cli'` (sent as `X-Client-Id` to the\n * gas API); the API endpoints, poll interval, and compatibility callbacks are\n * left at the wallet package's platform-agnostic defaults (the default\n * `EIP1559APIEndpoint` is already the production URL, so the daemon does not\n * re-specify it).\n * - `transactionController` — swaps processing disabled and no client hooks;\n * see the slot's inline comment for why the daemon relies on the\n * controller's defaults for everything else.\n *\n * The optional `keyringController` slot is intentionally omitted so the\n * controller's built-in defaults (e.g. the PBKDF2 encryptor) apply.\n *\n * @param infuraProjectId - The Infura project ID for the `NetworkController`.\n * @returns The `instanceOptions` for the `Wallet` constructor.\n */\nfunction buildInstanceOptions(\n infuraProjectId: string,\n): WalletOptions['instanceOptions'] {\n return {\n approvalController: {\n // The daemon is headless, so there is no UI to open: requests are\n // resolved by the auto-approval subscription (see `subscribeToAutoApproval`)\n // rather than by this hook, which stays a no-op.\n // TODO: surface approval requests over the daemon transport.\n showApprovalRequest: (): undefined => undefined,\n },\n connectivityController: {\n connectivityAdapter: new AlwaysOnlineAdapter(),\n },\n gasFeeController: {\n // Identifies the CLI to the gas estimation API via the `X-Client-Id`\n // header.\n clientId: 'cli',\n },\n networkController: {\n infuraProjectId,\n },\n remoteFeatureFlagController: {\n clientConfigApiService: new ClientConfigApiService({\n fetch: globalThis.fetch,\n config: {\n // TODO: switch to a CLI-specific `ClientType` once one exists; until\n // then `Extension` buckets feature flags as an extension client.\n client: ClientType.Extension,\n distribution: DistributionType.Main,\n environment: EnvironmentType.Production,\n },\n }),\n getMetaMetricsId: (): string => 'cli',\n clientVersion: '0.0.0',\n },\n storageService: {\n storage: new InMemoryStorageAdapter(),\n },\n transactionController: {\n // The CLI exposes no swaps surface, so skip the swaps-specific\n // post-processing a full wallet client runs (mobile makes the same\n // choice; the extension keeps swaps enabled).\n disableSwaps: true,\n // No CLI-specific transaction hooks: the controller's built-in publish\n // path broadcasts through the wired `NetworkController` provider, and the\n // remaining hooks (metrics, notifications, gas-fee tokens) are client-UI\n // concerns the headless daemon has no equivalent for. Every other option\n // (`getPermittedAccounts`, `isSimulationEnabled`, `trace`, …) is left at\n // the controller's default.\n hooks: {},\n },\n };\n}\n\n/**\n * Register handlers for the `AnalyticsController` actions that\n * `NetworkController` calls to emit RPC service analytics. The daemon does not\n * report analytics, so these do nothing: `getState` returns an empty analytics\n * ID, which stops the controller before it tracks anything, and `trackEvent` is\n * a no-op. Registering them keeps those messenger calls from throwing.\n *\n * @param messenger - The wallet's root messenger.\n */\nfunction registerAnalyticsStubHandlers(messenger: Wallet['messenger']): void {\n // Register on an `AnalyticsController`-namespaced messenger, not the root: a\n // messenger can only register handlers for its own namespace, and doing so\n // delegates them up to the root, where `NetworkController` can reach them.\n const analyticsMessenger = new Messenger<\n 'AnalyticsController',\n AnalyticsControllerGetStateAction | AnalyticsControllerTrackEventAction,\n never,\n RootMessenger<DefaultActions, DefaultEvents>\n >({\n namespace: 'AnalyticsController',\n parent: messenger as RootMessenger<DefaultActions, DefaultEvents>,\n });\n analyticsMessenger.registerActionHandler(\n 'AnalyticsController:getState',\n () => ({\n ...getDefaultAnalyticsControllerState(),\n analyticsId: '',\n }),\n );\n analyticsMessenger.registerActionHandler(\n 'AnalyticsController:trackEvent',\n () => undefined,\n );\n}\n\n/**\n * Create a configured `Wallet` for daemon use, backed by a SQLite key-value\n * store for controller-state persistence.\n *\n * Loads any previously-persisted controller state from the store, seeds the\n * wallet with it, then subscribes the store to subsequent state changes so all\n * persist-flagged properties are written through.\n *\n * If the store does not yet contain a keyring vault (first run), the supplied\n * secret recovery phrase is imported using the supplied password. On\n * subsequent runs, the persisted vault is reused: when a password is\n * supplied, the wallet is unlocked via `KeyringController:submitPassword` so\n * keyring-bound messenger actions work immediately; when no password is\n * supplied, the wallet starts locked and the caller is expected to invoke\n * `mm wallet unlock` before any keyring-bound operation. First-run startup\n * without a password is rejected (the SRP cannot be imported without one).\n * On a subsequent run, a wrong password rejects startup with a `Failed to\n * unlock the persisted vault` error that wraps the `submitPassword` rejection.\n *\n * On any failure after the store is opened, the store is closed (and the wallet\n * destroyed, if constructed). On a first-run failure, the on-disk database is\n * also removed so a retry does not latch onto an orphaned partial vault — this\n * covers in-process failures only; a hard crash (SIGKILL/power loss) mid-import\n * can still leave a vault on disk.\n *\n * @param config - Wallet configuration.\n * @param config.databasePath - Path to the SQLite database file (or\n * `':memory:'` for ephemeral use).\n * @param config.password - The wallet password. Optional on subsequent runs;\n * when omitted, the daemon starts with a locked keyring. Required on first\n * run (to import the SRP).\n * @param config.srp - The secret recovery phrase (BIP-39 mnemonic). Used\n * only on first run.\n * @param config.infuraProjectId - The Infura project ID for the\n * `NetworkController`.\n * @param config.log - Optional logger for persistence-write and teardown\n * failures. Without it, failures fall back to `console.error` (which a detached\n * daemon's `stdio: 'ignore'` discards).\n * @returns The `Wallet` and a `dispose` handle that tears it down.\n */\nexport async function createWallet({\n databasePath,\n password,\n srp,\n infuraProjectId,\n log,\n}: CreateWalletConfig): Promise<CreateWalletResult> {\n const logFn = log ?? ((message: string): void => console.error(message));\n const store = new KeyValueStore(databasePath);\n let wallet: Wallet | undefined;\n let persistenceUnsubscribe: (() => void) | undefined;\n let autoApprovalUnsubscribe: (() => void) | undefined;\n let wasFirstRun = false;\n\n try {\n const state = await loadPersistedState(store, infuraProjectId, logFn);\n wasFirstRun = !hasPersistedKeyring(state);\n\n // Validate the first-run precondition BEFORE constructing the wallet,\n // so a doomed startup doesn't build a Wallet (and wire persistence\n // handlers) just to tear it down.\n if (wasFirstRun && password === undefined) {\n throw new Error(\n 'A password is required on first run to import the secret recovery phrase. ' +\n 'Pass `--password` (or `MM_WALLET_PASSWORD`) on `mm daemon start`.',\n );\n }\n\n wallet = new Wallet({\n state,\n instanceOptions: buildInstanceOptions(infuraProjectId),\n });\n registerAnalyticsStubHandlers(wallet.messenger);\n persistenceUnsubscribe = subscribeToChanges(\n wallet.messenger,\n wallet.controllerMetadata,\n store,\n logFn,\n );\n\n // Installed before `init` so any approval raised during initialization is covered.\n autoApprovalUnsubscribe = subscribeToAutoApproval(wallet.messenger, logFn);\n\n // Complete post-construction controller setup before serving requests —\n // e.g. `NetworkController.init` applies the selected network so a provider\n // is available. `init` settles every step independently; a rejected step\n // leaves the wallet only partially usable, so abort startup (the catch\n // below tears down and, on first run, removes the partial database) rather\n // than serving a degraded daemon.\n const initResults = await wallet.init();\n const initFailures = initResults.filter(\n (result): result is PromiseRejectedResult => result.status === 'rejected',\n );\n for (const failure of initFailures) {\n logFn(`Wallet init step failed: ${String(failure.reason)}`);\n }\n if (initFailures.length > 0) {\n const firstReason = String(initFailures[0].reason);\n throw new Error(\n `Wallet initialization failed (${initFailures.length} step(s)); refusing to serve a partially initialized wallet. First failure: ${firstReason}`,\n );\n }\n\n if (wasFirstRun) {\n // The precondition check above throws when `wasFirstRun && password ===\n // undefined`, so `password` is defined here. TS does not correlate the\n // two separate variables, so it cannot narrow `password` from\n // `wasFirstRun` alone — hence the assertion.\n await importSecretRecoveryPhrase(\n wallet,\n (password as Password).unwrap(),\n srp.unwrap(),\n );\n } else if (password !== undefined) {\n try {\n await wallet.messenger.call(\n 'KeyringController:submitPassword',\n password.unwrap(),\n );\n } catch (error) {\n throw new Error(\n `Failed to unlock the persisted vault: ${String(error)}`,\n );\n }\n }\n\n let disposePromise: Promise<void> | undefined;\n return {\n wallet,\n dispose: async () =>\n (disposePromise ??= teardown(\n persistenceUnsubscribe,\n autoApprovalUnsubscribe,\n wallet,\n store,\n logFn,\n )),\n };\n } catch (error) {\n await teardown(\n persistenceUnsubscribe,\n autoApprovalUnsubscribe,\n wallet,\n store,\n logFn,\n );\n\n if (wasFirstRun && databasePath !== IN_MEMORY_DATABASE_PATH) {\n // Best-effort cleanup of the on-disk SQLite files (main, WAL, SHM) so a\n // partially-persisted KeyringController vault cannot mislead the next run\n // into skipping SRP import. Covers in-process failures only — a crash\n // (SIGKILL/power loss) mid-import leaves the vault on disk.\n await Promise.all(\n [databasePath, `${databasePath}-wal`, `${databasePath}-shm`].map(\n (path) =>\n rm(path, { force: true }).catch((rmError: unknown) => {\n logFn(\n `Failed to remove ${path} during first-run cleanup: ${String(rmError)}`,\n );\n }),\n ),\n );\n }\n\n throw error;\n }\n}\n\n/**\n * Load persisted controller state, filtered to currently persist-flagged\n * properties.\n *\n * `loadState` filters against the live controller metadata, but that metadata\n * is only knowable from a constructed `Wallet` — and its output is what seeds\n * the real wallet. So this constructs a short-lived probe purely to read the\n * metadata, then tears it down.\n *\n * TODO: drop the probe once `@metamask/wallet` exposes controller metadata\n * without constructing a `Wallet`.\n *\n * @param store - The key-value store to read from.\n * @param infuraProjectId - The Infura project ID for the probe's\n * `NetworkController`.\n * @param logFn - Logger for a probe-teardown failure.\n * @returns The filtered persisted state, suitable for the `Wallet` `state`\n * option.\n */\nasync function loadPersistedState(\n store: KeyValueStore,\n infuraProjectId: string,\n logFn: Logger,\n): Promise<Record<string, Record<string, Json>>> {\n const probe = new Wallet({\n instanceOptions: buildInstanceOptions(infuraProjectId),\n });\n registerAnalyticsStubHandlers(probe.messenger);\n try {\n return loadState(store, probe.controllerMetadata);\n } finally {\n await probe.destroy().catch((error: unknown) => {\n logFn(`Metadata probe destroy failed: ${String(error)}`);\n });\n }\n}\n\n/**\n * Best-effort unsubscribe: run the function if present, logging (and\n * swallowing) any error so a later teardown step still runs.\n *\n * @param unsubscribe - The unsubscribe function, if one was registered.\n * @param label - Human-readable subscription name for the failure log.\n * @param logFn - Logger for a failure.\n */\nfunction runUnsubscribe(\n unsubscribe: (() => void) | undefined,\n label: string,\n logFn: Logger,\n): void {\n if (!unsubscribe) {\n return;\n }\n try {\n unsubscribe();\n } catch (error) {\n logFn(\n `${label} unsubscribe failed during teardown — subscription may remain ` +\n `live until the process exits: ${String(error)}`,\n );\n }\n}\n\n/**\n * Persistence-safe teardown of a wallet and its store; see {@link\n * CreateWalletResult.dispose} for the ordering rationale. Each step is\n * best-effort, so a failure is logged and the remaining steps still run.\n *\n * @param persistenceUnsubscribe - The persistence-subscription unsubscribe\n * function, if one was registered.\n * @param autoApprovalUnsubscribe - The auto-approval-subscription unsubscribe\n * function, if one was registered.\n * @param wallet - The wallet to destroy, if one was constructed.\n * @param store - The store to close.\n * @param logFn - Logger for step failures.\n */\nasync function teardown(\n persistenceUnsubscribe: (() => void) | undefined,\n autoApprovalUnsubscribe: (() => void) | undefined,\n wallet: Wallet | undefined,\n store: KeyValueStore,\n logFn: Logger,\n): Promise<void> {\n runUnsubscribe(persistenceUnsubscribe, 'Persistence', logFn);\n runUnsubscribe(autoApprovalUnsubscribe, 'Auto-approval', logFn);\n if (wallet) {\n try {\n await wallet.destroy();\n } catch (error) {\n logFn(`wallet.destroy() failed during teardown: ${String(error)}`);\n }\n }\n try {\n store.close();\n } catch (error) {\n logFn(`store.close() failed during teardown: ${String(error)}`);\n }\n}\n\n/**\n * Determine whether the loaded state already contains a keyring vault.\n *\n * The KeyringController persists its `vault` once an SRP has been imported, so\n * its presence indicates that first-run setup completed before.\n *\n * @param state - The state loaded from the key-value store.\n * @returns True if a KeyringController vault string is present.\n */\nfunction hasPersistedKeyring(\n state: Record<string, Record<string, unknown>>,\n): boolean {\n return typeof state.KeyringController?.vault === 'string';\n}\n"]}
1
+ {"version":3,"file":"wallet-factory.mjs","sourceRoot":"","sources":["../../src/daemon/wallet-factory.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n ClientConfigApiService,\n ClientType,\n DistributionType,\n EnvironmentType,\n} from '@metamask/remote-feature-flag-controller';\nimport { InMemoryStorageAdapter } from '@metamask/storage-service';\nimport type { Json } from '@metamask/utils';\nimport {\n AlwaysOnlineAdapter,\n importSecretRecoveryPhrase,\n Wallet,\n} from '@metamask/wallet';\nimport type { WalletOptions } from '@metamask/wallet';\nimport { rm } from 'node:fs/promises';\n\nimport { KeyValueStore } from '../persistence/KeyValueStore.js';\nimport { loadState, subscribeToChanges } from '../persistence/persistence.js';\nimport { subscribeToAutoApproval } from './auto-approval.js';\nimport type { Password, Srp } from './secrets.js';\nimport type { Logger } from './types.js';\n\nconst IN_MEMORY_DATABASE_PATH = ':memory:';\n\nexport type CreateWalletConfig = {\n databasePath: string;\n password?: Password;\n srp: Srp;\n infuraProjectId: string;\n log?: Logger;\n};\n\nexport type CreateWalletResult = {\n wallet: Wallet;\n /**\n * Tear down everything `createWallet` set up, in the order that keeps\n * in-flight persistence writes valid: stop the state-change subscription,\n * destroy the wallet, then close the store (closing first would cause a\n * teardown-time persistence write to fail). Resilient — a failure in any\n * step is logged and the remaining steps still run — and idempotent (repeat\n * calls coalesce onto the same teardown).\n */\n dispose: () => Promise<void>;\n};\n\n/**\n * Build the per-instance options the daemon's `Wallet` is constructed with.\n *\n * Returns a fresh set on every call so the metadata probe and the real wallet\n * never share adapter/service instances (the probe is destroyed, which may\n * tear those instances down).\n *\n * Only the slots wired on `@metamask/wallet` today are populated:\n * - `storageService` — backed by an in-memory adapter. This is the wallet's\n * large-blob store (snap source, caches), distinct from the SQLite\n * `KeyValueStore` that persists controller state; no wired controller\n * offloads durable data to it yet, so in-memory suffices.\n * - `connectivityController` — the `AlwaysOnlineAdapter` exported for\n * node-like hosts that have no platform connectivity signal.\n * - `networkController` — the Infura project ID used to reach Infura RPC\n * endpoints.\n * - `remoteFeatureFlagController` — a `ClientConfigApiService` fetching real\n * flags over the network.\n * - `approvalController` — a no-op `showApprovalRequest` (the daemon is\n * headless); pending requests are accepted separately by the auto-approval\n * subscription `createWallet` installs, not by this hook.\n * - `gasFeeController` — only `clientId: 'cli'` (sent as `X-Client-Id` to the\n * gas API); the API endpoints, poll interval, and compatibility callbacks are\n * left at the wallet package's platform-agnostic defaults (the default\n * `EIP1559APIEndpoint` is already the production URL, so the daemon does not\n * re-specify it).\n * - `transactionController` — swaps processing disabled and no client hooks;\n * see the slot's inline comment for why the daemon relies on the\n * controller's defaults for everything else.\n *\n * The optional `keyringController` slot is intentionally omitted so the\n * controller's built-in defaults (e.g. the PBKDF2 encryptor) apply.\n *\n * @param infuraProjectId - The Infura project ID for the `NetworkController`.\n * @returns The `instanceOptions` for the `Wallet` constructor.\n */\nfunction buildInstanceOptions(\n infuraProjectId: string,\n): WalletOptions['instanceOptions'] {\n return {\n approvalController: {\n // The daemon is headless, so there is no UI to open: requests are\n // resolved by the auto-approval subscription (see `subscribeToAutoApproval`)\n // rather than by this hook, which stays a no-op.\n // TODO: surface approval requests over the daemon transport.\n showApprovalRequest: (): undefined => undefined,\n },\n connectivityController: {\n connectivityAdapter: new AlwaysOnlineAdapter(),\n },\n gasFeeController: {\n // Identifies the CLI to the gas estimation API via the `X-Client-Id`\n // header.\n clientId: 'cli',\n },\n networkController: {\n infuraProjectId,\n },\n remoteFeatureFlagController: {\n clientConfigApiService: new ClientConfigApiService({\n fetch: globalThis.fetch,\n config: {\n // TODO: switch to a CLI-specific `ClientType` once one exists; until\n // then `Extension` buckets feature flags as an extension client.\n client: ClientType.Extension,\n distribution: DistributionType.Main,\n environment: EnvironmentType.Production,\n },\n }),\n getMetaMetricsId: (): string => 'cli',\n clientVersion: '0.0.0',\n },\n storageService: {\n storage: new InMemoryStorageAdapter(),\n },\n transactionController: {\n // The CLI exposes no swaps surface, so skip the swaps-specific\n // post-processing a full wallet client runs (mobile makes the same\n // choice; the extension keeps swaps enabled).\n disableSwaps: true,\n // No CLI-specific transaction hooks: the controller's built-in publish\n // path broadcasts through the wired `NetworkController` provider, and the\n // remaining hooks (metrics, notifications, gas-fee tokens) are client-UI\n // concerns the headless daemon has no equivalent for. Every other option\n // (`getPermittedAccounts`, `isSimulationEnabled`, `trace`, …) is left at\n // the controller's default.\n hooks: {},\n },\n };\n}\n\n/**\n * Create a configured `Wallet` for daemon use, backed by a SQLite key-value\n * store for controller-state persistence.\n *\n * Loads any previously-persisted controller state from the store, seeds the\n * wallet with it, then subscribes the store to subsequent state changes so all\n * persist-flagged properties are written through.\n *\n * If the store does not yet contain a keyring vault (first run), the supplied\n * secret recovery phrase is imported using the supplied password. On\n * subsequent runs, the persisted vault is reused: when a password is\n * supplied, the wallet is unlocked via `KeyringController:submitPassword` so\n * keyring-bound messenger actions work immediately; when no password is\n * supplied, the wallet starts locked and the caller is expected to invoke\n * `mm wallet unlock` before any keyring-bound operation. First-run startup\n * without a password is rejected (the SRP cannot be imported without one).\n * On a subsequent run, a wrong password rejects startup with a `Failed to\n * unlock the persisted vault` error that wraps the `submitPassword` rejection.\n *\n * On any failure after the store is opened, the store is closed (and the wallet\n * destroyed, if constructed). On a first-run failure, the on-disk database is\n * also removed so a retry does not latch onto an orphaned partial vault — this\n * covers in-process failures only; a hard crash (SIGKILL/power loss) mid-import\n * can still leave a vault on disk.\n *\n * @param config - Wallet configuration.\n * @param config.databasePath - Path to the SQLite database file (or\n * `':memory:'` for ephemeral use).\n * @param config.password - The wallet password. Optional on subsequent runs;\n * when omitted, the daemon starts with a locked keyring. Required on first\n * run (to import the SRP).\n * @param config.srp - The secret recovery phrase (BIP-39 mnemonic). Used\n * only on first run.\n * @param config.infuraProjectId - The Infura project ID for the\n * `NetworkController`.\n * @param config.log - Optional logger for persistence-write and teardown\n * failures. Without it, failures fall back to `console.error` (which a detached\n * daemon's `stdio: 'ignore'` discards).\n * @returns The `Wallet` and a `dispose` handle that tears it down.\n */\nexport async function createWallet({\n databasePath,\n password,\n srp,\n infuraProjectId,\n log,\n}: CreateWalletConfig): Promise<CreateWalletResult> {\n const logFn = log ?? ((message: string): void => console.error(message));\n const store = new KeyValueStore(databasePath);\n let wallet: Wallet | undefined;\n let persistenceUnsubscribe: (() => void) | undefined;\n let autoApprovalUnsubscribe: (() => void) | undefined;\n let wasFirstRun = false;\n\n try {\n const state = await loadPersistedState(store, infuraProjectId, logFn);\n wasFirstRun = !hasPersistedKeyring(state);\n\n // Validate the first-run precondition BEFORE constructing the wallet,\n // so a doomed startup doesn't build a Wallet (and wire persistence\n // handlers) just to tear it down.\n if (wasFirstRun && password === undefined) {\n throw new Error(\n 'A password is required on first run to import the secret recovery phrase. ' +\n 'Pass `--password` (or `MM_WALLET_PASSWORD`) on `mm daemon start`.',\n );\n }\n\n wallet = new Wallet({\n state,\n instanceOptions: buildInstanceOptions(infuraProjectId),\n });\n persistenceUnsubscribe = subscribeToChanges(\n wallet.messenger,\n wallet.controllerMetadata,\n store,\n logFn,\n );\n\n // Installed before `init` so any approval raised during initialization is covered.\n autoApprovalUnsubscribe = subscribeToAutoApproval(wallet.messenger, logFn);\n\n // Complete post-construction controller setup before serving requests —\n // e.g. `NetworkController.init` applies the selected network so a provider\n // is available. `init` settles every step independently; a rejected step\n // leaves the wallet only partially usable, so abort startup (the catch\n // below tears down and, on first run, removes the partial database) rather\n // than serving a degraded daemon.\n const initResults = await wallet.init();\n const initFailures = initResults.filter(\n (result): result is PromiseRejectedResult => result.status === 'rejected',\n );\n for (const failure of initFailures) {\n logFn(`Wallet init step failed: ${String(failure.reason)}`);\n }\n if (initFailures.length > 0) {\n const firstReason = String(initFailures[0].reason);\n throw new Error(\n `Wallet initialization failed (${initFailures.length} step(s)); refusing to serve a partially initialized wallet. First failure: ${firstReason}`,\n );\n }\n\n if (wasFirstRun) {\n // The precondition check above throws when `wasFirstRun && password ===\n // undefined`, so `password` is defined here. TS does not correlate the\n // two separate variables, so it cannot narrow `password` from\n // `wasFirstRun` alone — hence the assertion.\n await importSecretRecoveryPhrase(\n wallet,\n (password as Password).unwrap(),\n srp.unwrap(),\n );\n } else if (password !== undefined) {\n try {\n await wallet.messenger.call(\n 'KeyringController:submitPassword',\n password.unwrap(),\n );\n } catch (error) {\n throw new Error(\n `Failed to unlock the persisted vault: ${String(error)}`,\n );\n }\n }\n\n let disposePromise: Promise<void> | undefined;\n return {\n wallet,\n dispose: async () =>\n (disposePromise ??= teardown(\n persistenceUnsubscribe,\n autoApprovalUnsubscribe,\n wallet,\n store,\n logFn,\n )),\n };\n } catch (error) {\n await teardown(\n persistenceUnsubscribe,\n autoApprovalUnsubscribe,\n wallet,\n store,\n logFn,\n );\n\n if (wasFirstRun && databasePath !== IN_MEMORY_DATABASE_PATH) {\n // Best-effort cleanup of the on-disk SQLite files (main, WAL, SHM) so a\n // partially-persisted KeyringController vault cannot mislead the next run\n // into skipping SRP import. Covers in-process failures only — a crash\n // (SIGKILL/power loss) mid-import leaves the vault on disk.\n await Promise.all(\n [databasePath, `${databasePath}-wal`, `${databasePath}-shm`].map(\n (path) =>\n rm(path, { force: true }).catch((rmError: unknown) => {\n logFn(\n `Failed to remove ${path} during first-run cleanup: ${String(rmError)}`,\n );\n }),\n ),\n );\n }\n\n throw error;\n }\n}\n\n/**\n * Load persisted controller state, filtered to currently persist-flagged\n * properties.\n *\n * `loadState` filters against the live controller metadata, but that metadata\n * is only knowable from a constructed `Wallet` — and its output is what seeds\n * the real wallet. So this constructs a short-lived probe purely to read the\n * metadata, then tears it down.\n *\n * TODO: drop the probe once `@metamask/wallet` exposes controller metadata\n * without constructing a `Wallet`.\n *\n * @param store - The key-value store to read from.\n * @param infuraProjectId - The Infura project ID for the probe's\n * `NetworkController`.\n * @param logFn - Logger for a probe-teardown failure.\n * @returns The filtered persisted state, suitable for the `Wallet` `state`\n * option.\n */\nasync function loadPersistedState(\n store: KeyValueStore,\n infuraProjectId: string,\n logFn: Logger,\n): Promise<Record<string, Record<string, Json>>> {\n const probe = new Wallet({\n instanceOptions: buildInstanceOptions(infuraProjectId),\n });\n try {\n return loadState(store, probe.controllerMetadata);\n } finally {\n await probe.destroy().catch((error: unknown) => {\n logFn(`Metadata probe destroy failed: ${String(error)}`);\n });\n }\n}\n\n/**\n * Best-effort unsubscribe: run the function if present, logging (and\n * swallowing) any error so a later teardown step still runs.\n *\n * @param unsubscribe - The unsubscribe function, if one was registered.\n * @param label - Human-readable subscription name for the failure log.\n * @param logFn - Logger for a failure.\n */\nfunction runUnsubscribe(\n unsubscribe: (() => void) | undefined,\n label: string,\n logFn: Logger,\n): void {\n if (!unsubscribe) {\n return;\n }\n try {\n unsubscribe();\n } catch (error) {\n logFn(\n `${label} unsubscribe failed during teardown — subscription may remain ` +\n `live until the process exits: ${String(error)}`,\n );\n }\n}\n\n/**\n * Persistence-safe teardown of a wallet and its store; see {@link\n * CreateWalletResult.dispose} for the ordering rationale. Each step is\n * best-effort, so a failure is logged and the remaining steps still run.\n *\n * @param persistenceUnsubscribe - The persistence-subscription unsubscribe\n * function, if one was registered.\n * @param autoApprovalUnsubscribe - The auto-approval-subscription unsubscribe\n * function, if one was registered.\n * @param wallet - The wallet to destroy, if one was constructed.\n * @param store - The store to close.\n * @param logFn - Logger for step failures.\n */\nasync function teardown(\n persistenceUnsubscribe: (() => void) | undefined,\n autoApprovalUnsubscribe: (() => void) | undefined,\n wallet: Wallet | undefined,\n store: KeyValueStore,\n logFn: Logger,\n): Promise<void> {\n runUnsubscribe(persistenceUnsubscribe, 'Persistence', logFn);\n runUnsubscribe(autoApprovalUnsubscribe, 'Auto-approval', logFn);\n if (wallet) {\n try {\n await wallet.destroy();\n } catch (error) {\n logFn(`wallet.destroy() failed during teardown: ${String(error)}`);\n }\n }\n try {\n store.close();\n } catch (error) {\n logFn(`store.close() failed during teardown: ${String(error)}`);\n }\n}\n\n/**\n * Determine whether the loaded state already contains a keyring vault.\n *\n * The KeyringController persists its `vault` once an SRP has been imported, so\n * its presence indicates that first-run setup completed before.\n *\n * @param state - The state loaded from the key-value store.\n * @returns True if a KeyringController vault string is present.\n */\nfunction hasPersistedKeyring(\n state: Record<string, Record<string, unknown>>,\n): boolean {\n return typeof state.KeyringController?.vault === 'string';\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@metamask-previews/wallet-cli",
3
- "version": "0.0.0-preview-5da60b6",
3
+ "version": "0.0.0-preview-d67bfdbad",
4
4
  "description": "The CLI of @metamask/wallet",
5
5
  "keywords": [
6
6
  "Ethereum",
@@ -49,9 +49,7 @@
49
49
  "dependencies": {
50
50
  "@inquirer/confirm": "^6.0.11",
51
51
  "@inquirer/password": "^5.1.1",
52
- "@metamask/analytics-controller": "^1.2.1",
53
52
  "@metamask/base-controller": "^9.1.0",
54
- "@metamask/messenger": "^2.0.0",
55
53
  "@metamask/remote-feature-flag-controller": "^4.2.2",
56
54
  "@metamask/rpc-errors": "^7.0.2",
57
55
  "@metamask/scure-bip39": "^2.1.1",