@metamask-previews/wallet-cli 0.0.0-preview-a2621f612 → 0.0.0-preview-2a8139a3e

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.
Files changed (59) hide show
  1. package/CHANGELOG.md +3 -0
  2. package/README.md +13 -1
  3. package/dist/commands/daemon/start.cjs +5 -3
  4. package/dist/commands/daemon/start.cjs.map +1 -1
  5. package/dist/commands/daemon/start.d.cts +1 -1
  6. package/dist/commands/daemon/start.d.cts.map +1 -1
  7. package/dist/commands/daemon/start.d.mts +1 -1
  8. package/dist/commands/daemon/start.d.mts.map +1 -1
  9. package/dist/commands/daemon/start.mjs +5 -3
  10. package/dist/commands/daemon/start.mjs.map +1 -1
  11. package/dist/commands/wallet/unlock.cjs +75 -0
  12. package/dist/commands/wallet/unlock.cjs.map +1 -0
  13. package/dist/commands/wallet/unlock.d.cts +11 -0
  14. package/dist/commands/wallet/unlock.d.cts.map +1 -0
  15. package/dist/commands/wallet/unlock.d.mts +11 -0
  16. package/dist/commands/wallet/unlock.d.mts.map +1 -0
  17. package/dist/commands/wallet/unlock.mjs +73 -0
  18. package/dist/commands/wallet/unlock.mjs.map +1 -0
  19. package/dist/daemon/daemon-entry.cjs +6 -4
  20. package/dist/daemon/daemon-entry.cjs.map +1 -1
  21. package/dist/daemon/daemon-entry.mjs +6 -4
  22. package/dist/daemon/daemon-entry.mjs.map +1 -1
  23. package/dist/daemon/daemon-spawn.cjs +19 -8
  24. package/dist/daemon/daemon-spawn.cjs.map +1 -1
  25. package/dist/daemon/daemon-spawn.d.cts.map +1 -1
  26. package/dist/daemon/daemon-spawn.d.mts.map +1 -1
  27. package/dist/daemon/daemon-spawn.mjs +19 -8
  28. package/dist/daemon/daemon-spawn.mjs.map +1 -1
  29. package/dist/daemon/prompts.cjs +17 -1
  30. package/dist/daemon/prompts.cjs.map +1 -1
  31. package/dist/daemon/prompts.d.cts +9 -0
  32. package/dist/daemon/prompts.d.cts.map +1 -1
  33. package/dist/daemon/prompts.d.mts +9 -0
  34. package/dist/daemon/prompts.d.mts.map +1 -1
  35. package/dist/daemon/prompts.mjs +15 -0
  36. package/dist/daemon/prompts.mjs.map +1 -1
  37. package/dist/daemon/types.cjs.map +1 -1
  38. package/dist/daemon/types.d.cts +8 -1
  39. package/dist/daemon/types.d.cts.map +1 -1
  40. package/dist/daemon/types.d.mts +8 -1
  41. package/dist/daemon/types.d.mts.map +1 -1
  42. package/dist/daemon/types.mjs.map +1 -1
  43. package/dist/daemon/utils.cjs +14 -2
  44. package/dist/daemon/utils.cjs.map +1 -1
  45. package/dist/daemon/utils.d.cts +9 -0
  46. package/dist/daemon/utils.d.cts.map +1 -1
  47. package/dist/daemon/utils.d.mts +9 -0
  48. package/dist/daemon/utils.d.mts.map +1 -1
  49. package/dist/daemon/utils.mjs +12 -1
  50. package/dist/daemon/utils.mjs.map +1 -1
  51. package/dist/daemon/wallet-factory.cjs +49 -7
  52. package/dist/daemon/wallet-factory.cjs.map +1 -1
  53. package/dist/daemon/wallet-factory.d.cts +15 -7
  54. package/dist/daemon/wallet-factory.d.cts.map +1 -1
  55. package/dist/daemon/wallet-factory.d.mts +15 -7
  56. package/dist/daemon/wallet-factory.d.mts.map +1 -1
  57. package/dist/daemon/wallet-factory.mjs +49 -7
  58. package/dist/daemon/wallet-factory.mjs.map +1 -1
  59. package/package.json +2 -1
package/CHANGELOG.md CHANGED
@@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
9
9
 
10
10
  ### Added
11
11
 
12
+ - Wire the `transactionController` slot in the daemon wallet's instance options, so the daemon runs the `TransactionController` with an explicit CLI-appropriate configuration (swaps processing disabled, no client hooks) rather than relying on the controller's implicit defaults ([#9509](https://github.com/MetaMask/core/pull/9509))
13
+ - Add the `mm wallet unlock` command, which dispatches `KeyringController:submitPassword` over the daemon socket, allowing the keyring to be unlocked after a daemon start with no password or after a `mm daemon call KeyringController:setLocked` ([#8821](https://github.com/MetaMask/core/pull/8821))
12
14
  - Add the `mm daemon list` command, which prints the messenger actions the running daemon can dispatch via `daemon call`, enumerated from the live messenger so the list cannot drift from what `call` accepts ([#9339](https://github.com/MetaMask/core/pull/9339))
13
15
  - Add the `mm daemon` command suite (`start`, `stop`, `status`, `purge`, and `call`) for running the wallet daemon and dispatching messenger actions over its socket ([#9255](https://github.com/MetaMask/core/pull/9255))
14
16
  - Add a wallet factory and daemon entry point that construct a `@metamask/wallet` `Wallet` backed by the SQLite key-value store, hydrate it from persisted state, run controller initialization (aborting startup if any step fails), import the secret recovery phrase on first run, and expose a `dispose` teardown handle ([#9226](https://github.com/MetaMask/core/pull/9226))
@@ -18,6 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
18
20
 
19
21
  ### Changed
20
22
 
23
+ - `--password` / `MM_WALLET_PASSWORD` is now optional on `mm daemon start`; on subsequent runs, omitting it starts the daemon with a locked keyring, and the persisted vault is auto-unlocked when a password is supplied ([#8821](https://github.com/MetaMask/core/pull/8821))
21
24
  - The daemon RPC server now validates `params` against each handler's superstruct before dispatch, returning a `-32602 invalidParams` error on mismatch instead of passing raw params to the handler ([#8846](https://github.com/MetaMask/core/pull/8846))
22
25
  - Report daemon socket connection errors consistently across `mm daemon call` and `mm daemon list` ([#9339](https://github.com/MetaMask/core/pull/9339))
23
26
  - Bump `@metamask/wallet` from `^3.0.0` to `^7.0.1` ([#9218](https://github.com/MetaMask/core/pull/9218), [#9263](https://github.com/MetaMask/core/pull/9263), [#9349](https://github.com/MetaMask/core/pull/9349), [#9396](https://github.com/MetaMask/core/pull/9396), [#9470](https://github.com/MetaMask/core/pull/9470))
package/README.md CHANGED
@@ -12,12 +12,24 @@ or
12
12
 
13
13
  ## Usage
14
14
 
15
- The CLI drives a long-lived background **daemon** that holds an unlocked `@metamask/wallet` in memory and exposes its messenger over a per-user Unix socket. All commands live under the `mm daemon` topic; run `mm --help` (or `mm daemon <command> --help`) for the full reference.
15
+ The CLI drives a long-lived background daemon that holds a `@metamask/wallet` in memory and exposes its messenger over a per-user Unix socket. All commands live under the `mm daemon` and `mm wallet` topics; run `mm --help` (or `mm <topic> <command> --help`) for the full reference.
16
16
 
17
17
  Start the daemon (flags may also be supplied as the `INFURA_PROJECT_ID`, `MM_WALLET_PASSWORD`, and `MM_WALLET_SRP` environment variables — preferred for secrets):
18
18
 
19
19
  ```sh
20
+ # First run — password required to import the secret recovery phrase:
20
21
  mm daemon start --infura-project-id <key> --password <pw> --srp "<phrase>"
22
+
23
+ # Subsequent runs — password optional. Omit to start with a locked keyring
24
+ # and unlock later with `mm wallet unlock`:
25
+ mm daemon start --infura-project-id <key> --srp "<phrase>"
26
+ ```
27
+
28
+ Unlock the keyring after a password-less start (or after `KeyringController:setLocked`):
29
+
30
+ ```sh
31
+ mm wallet unlock --password <pw> # or: MM_WALLET_PASSWORD=<pw> mm wallet unlock
32
+ mm wallet unlock # prompts interactively (input masked)
21
33
  ```
22
34
 
23
35
  Discover what the running wallet can do — `list` prints every messenger action currently dispatchable via `call`. This surface grows as more controllers are wired, so treat it as evolving rather than a stability contract:
@@ -7,7 +7,7 @@ class DaemonStart extends core_1.Command {
7
7
  async run() {
8
8
  const { flags } = await this.parse(DaemonStart);
9
9
  const infuraProjectId = flags['infura-project-id'];
10
- const password = secrets_1.Password.from(flags.password);
10
+ const password = flags.password ? secrets_1.Password.from(flags.password) : undefined;
11
11
  const srp = secrets_1.Srp.from(flags.srp);
12
12
  const { state, socketPath } = await (0, daemon_spawn_1.ensureDaemon)({
13
13
  dataDir: this.config.dataDir,
@@ -28,6 +28,7 @@ DaemonStart.description = 'Start the wallet daemon';
28
28
  DaemonStart.examples = [
29
29
  '<%= config.bin %> daemon start --infura-project-id <key> --password <pw> --srp <phrase>',
30
30
  'INFURA_PROJECT_ID=<key> MM_WALLET_PASSWORD=<pw> MM_WALLET_SRP=<phrase> <%= config.bin %> daemon start',
31
+ '<%= config.bin %> daemon start --infura-project-id <key> --srp <phrase> # then `mm wallet unlock` later',
31
32
  ];
32
33
  DaemonStart.flags = {
33
34
  'infura-project-id': core_1.Flags.string({
@@ -36,9 +37,10 @@ DaemonStart.flags = {
36
37
  required: true,
37
38
  }),
38
39
  password: core_1.Flags.string({
39
- description: 'Wallet password (testing only — use MM_WALLET_PASSWORD env var in production)',
40
+ description: 'Wallet password (testing only — use MM_WALLET_PASSWORD env var in production). ' +
41
+ 'Required on first run; on subsequent runs, omit (and leave MM_WALLET_PASSWORD unset) to start with a locked keyring and use `mm wallet unlock`.',
40
42
  env: 'MM_WALLET_PASSWORD',
41
- required: true,
43
+ required: false,
42
44
  }),
43
45
  srp: core_1.Flags.string({
44
46
  description: 'Secret recovery phrase (testing only — use MM_WALLET_SRP env var in production)',
@@ -1 +1 @@
1
- {"version":3,"file":"start.cjs","sourceRoot":"","sources":["../../../src/commands/daemon/start.ts"],"names":[],"mappings":";;AAAA,sCAA6C;AAE7C,gEAAyD;AACzD,sDAAqD;AAErD,MAAqB,WAAY,SAAQ,cAAO;IA4BvC,KAAK,CAAC,GAAG;QACd,MAAM,EAAE,KAAK,EAAE,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;QAChD,MAAM,eAAe,GAAG,KAAK,CAAC,mBAAmB,CAAC,CAAC;QACnD,MAAM,QAAQ,GAAG,kBAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;QAC/C,MAAM,GAAG,GAAG,aAAG,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAEhC,MAAM,EAAE,KAAK,EAAE,UAAU,EAAE,GAAG,MAAM,IAAA,2BAAY,EAAC;YAC/C,OAAO,EAAE,IAAI,CAAC,MAAM,CAAC,OAAO;YAC5B,eAAe;YACf,QAAQ;YACR,GAAG;YACH,WAAW,EAAE,IAAI,CAAC,MAAM,CAAC,IAAI;SAC9B,CAAC,CAAC;QAEH,IAAI,KAAK,KAAK,iBAAiB,EAAE,CAAC;YAChC,IAAI,CAAC,GAAG,CACN,mCAAmC,UAAU,IAAI;gBAC/C,6FAA6F,CAChG,CAAC;YACF,OAAO;QACT,CAAC;QAED,IAAI,CAAC,GAAG,CAAC,2BAA2B,UAAU,EAAE,CAAC,CAAC;IACpD,CAAC;;AAlDe,uBAAW,GAAG,yBAAyB,CAAC;AAExC,oBAAQ,GAAG;IACzB,yFAAyF;IACzF,uGAAuG;CACxG,CAAC;AAEc,iBAAK,GAAG;IACtB,mBAAmB,EAAE,YAAK,CAAC,MAAM,CAAC;QAChC,WAAW,EAAE,sCAAsC;QACnD,GAAG,EAAE,mBAAmB;QACxB,QAAQ,EAAE,IAAI;KACf,CAAC;IACF,QAAQ,EAAE,YAAK,CAAC,MAAM,CAAC;QACrB,WAAW,EACT,+EAA+E;QACjF,GAAG,EAAE,oBAAoB;QACzB,QAAQ,EAAE,IAAI;KACf,CAAC;IACF,GAAG,EAAE,YAAK,CAAC,MAAM,CAAC;QAChB,WAAW,EACT,iFAAiF;QACnF,GAAG,EAAE,eAAe;QACpB,QAAQ,EAAE,IAAI;KACf,CAAC;CACH,CAAC;kBA1BiB,WAAW","sourcesContent":["import { Command, Flags } from '@oclif/core';\n\nimport { ensureDaemon } from '../../daemon/daemon-spawn';\nimport { Password, Srp } from '../../daemon/secrets';\n\nexport default class DaemonStart extends Command {\n static override description = 'Start the wallet daemon';\n\n static override examples = [\n '<%= config.bin %> daemon start --infura-project-id <key> --password <pw> --srp <phrase>',\n 'INFURA_PROJECT_ID=<key> MM_WALLET_PASSWORD=<pw> MM_WALLET_SRP=<phrase> <%= config.bin %> daemon start',\n ];\n\n static override flags = {\n 'infura-project-id': Flags.string({\n description: 'Infura project ID for network access',\n env: 'INFURA_PROJECT_ID',\n required: true,\n }),\n password: Flags.string({\n description:\n 'Wallet password (testing only — use MM_WALLET_PASSWORD env var in production)',\n env: 'MM_WALLET_PASSWORD',\n required: true,\n }),\n srp: Flags.string({\n description:\n 'Secret recovery phrase (testing only — use MM_WALLET_SRP env var in production)',\n env: 'MM_WALLET_SRP',\n required: true,\n }),\n };\n\n public async run(): Promise<void> {\n const { flags } = await this.parse(DaemonStart);\n const infuraProjectId = flags['infura-project-id'];\n const password = Password.from(flags.password);\n const srp = Srp.from(flags.srp);\n\n const { state, socketPath } = await ensureDaemon({\n dataDir: this.config.dataDir,\n infuraProjectId,\n password,\n srp,\n packageRoot: this.config.root,\n });\n\n if (state === 'already-running') {\n this.log(\n `Daemon already running. Socket: ${socketPath}. ` +\n `The provided flags were not applied; run \\`mm daemon stop\\` and start again to change them.`,\n );\n return;\n }\n\n this.log(`Daemon running. Socket: ${socketPath}`);\n }\n}\n"]}
1
+ {"version":3,"file":"start.cjs","sourceRoot":"","sources":["../../../src/commands/daemon/start.ts"],"names":[],"mappings":";;AAAA,sCAA6C;AAE7C,gEAAyD;AACzD,sDAAqD;AAErD,MAAqB,WAAY,SAAQ,cAAO;IA8BvC,KAAK,CAAC,GAAG;QACd,MAAM,EAAE,KAAK,EAAE,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;QAChD,MAAM,eAAe,GAAG,KAAK,CAAC,mBAAmB,CAAC,CAAC;QACnD,MAAM,QAAQ,GAAG,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,kBAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QAC5E,MAAM,GAAG,GAAG,aAAG,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAEhC,MAAM,EAAE,KAAK,EAAE,UAAU,EAAE,GAAG,MAAM,IAAA,2BAAY,EAAC;YAC/C,OAAO,EAAE,IAAI,CAAC,MAAM,CAAC,OAAO;YAC5B,eAAe;YACf,QAAQ;YACR,GAAG;YACH,WAAW,EAAE,IAAI,CAAC,MAAM,CAAC,IAAI;SAC9B,CAAC,CAAC;QAEH,IAAI,KAAK,KAAK,iBAAiB,EAAE,CAAC;YAChC,IAAI,CAAC,GAAG,CACN,mCAAmC,UAAU,IAAI;gBAC/C,6FAA6F,CAChG,CAAC;YACF,OAAO;QACT,CAAC;QAED,IAAI,CAAC,GAAG,CAAC,2BAA2B,UAAU,EAAE,CAAC,CAAC;IACpD,CAAC;;AApDe,uBAAW,GAAG,yBAAyB,CAAC;AAExC,oBAAQ,GAAG;IACzB,yFAAyF;IACzF,uGAAuG;IACvG,2GAA2G;CAC5G,CAAC;AAEc,iBAAK,GAAG;IACtB,mBAAmB,EAAE,YAAK,CAAC,MAAM,CAAC;QAChC,WAAW,EAAE,sCAAsC;QACnD,GAAG,EAAE,mBAAmB;QACxB,QAAQ,EAAE,IAAI;KACf,CAAC;IACF,QAAQ,EAAE,YAAK,CAAC,MAAM,CAAC;QACrB,WAAW,EACT,iFAAiF;YACjF,iJAAiJ;QACnJ,GAAG,EAAE,oBAAoB;QACzB,QAAQ,EAAE,KAAK;KAChB,CAAC;IACF,GAAG,EAAE,YAAK,CAAC,MAAM,CAAC;QAChB,WAAW,EACT,iFAAiF;QACnF,GAAG,EAAE,eAAe;QACpB,QAAQ,EAAE,IAAI;KACf,CAAC;CACH,CAAC;kBA5BiB,WAAW","sourcesContent":["import { Command, Flags } from '@oclif/core';\n\nimport { ensureDaemon } from '../../daemon/daemon-spawn';\nimport { Password, Srp } from '../../daemon/secrets';\n\nexport default class DaemonStart extends Command {\n static override description = 'Start the wallet daemon';\n\n static override examples = [\n '<%= config.bin %> daemon start --infura-project-id <key> --password <pw> --srp <phrase>',\n 'INFURA_PROJECT_ID=<key> MM_WALLET_PASSWORD=<pw> MM_WALLET_SRP=<phrase> <%= config.bin %> daemon start',\n '<%= config.bin %> daemon start --infura-project-id <key> --srp <phrase> # then `mm wallet unlock` later',\n ];\n\n static override flags = {\n 'infura-project-id': Flags.string({\n description: 'Infura project ID for network access',\n env: 'INFURA_PROJECT_ID',\n required: true,\n }),\n password: Flags.string({\n description:\n 'Wallet password (testing only — use MM_WALLET_PASSWORD env var in production). ' +\n 'Required on first run; on subsequent runs, omit (and leave MM_WALLET_PASSWORD unset) to start with a locked keyring and use `mm wallet unlock`.',\n env: 'MM_WALLET_PASSWORD',\n required: false,\n }),\n srp: Flags.string({\n description:\n 'Secret recovery phrase (testing only — use MM_WALLET_SRP env var in production)',\n env: 'MM_WALLET_SRP',\n required: true,\n }),\n };\n\n public async run(): Promise<void> {\n const { flags } = await this.parse(DaemonStart);\n const infuraProjectId = flags['infura-project-id'];\n const password = flags.password ? Password.from(flags.password) : undefined;\n const srp = Srp.from(flags.srp);\n\n const { state, socketPath } = await ensureDaemon({\n dataDir: this.config.dataDir,\n infuraProjectId,\n password,\n srp,\n packageRoot: this.config.root,\n });\n\n if (state === 'already-running') {\n this.log(\n `Daemon already running. Socket: ${socketPath}. ` +\n `The provided flags were not applied; run \\`mm daemon stop\\` and start again to change them.`,\n );\n return;\n }\n\n this.log(`Daemon running. Socket: ${socketPath}`);\n }\n}\n"]}
@@ -4,7 +4,7 @@ export default class DaemonStart extends Command {
4
4
  static examples: string[];
5
5
  static flags: {
6
6
  'infura-project-id': import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
7
- password: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
7
+ password: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
8
8
  srp: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
9
9
  };
10
10
  run(): Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"start.d.cts","sourceRoot":"","sources":["../../../src/commands/daemon/start.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAS,oBAAoB;AAK7C,MAAM,CAAC,OAAO,OAAO,WAAY,SAAQ,OAAO;IAC9C,OAAgB,WAAW,SAA6B;IAExD,OAAgB,QAAQ,WAGtB;IAEF,OAAgB,KAAK;;;;MAkBnB;IAEW,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC;CAwBlC"}
1
+ {"version":3,"file":"start.d.cts","sourceRoot":"","sources":["../../../src/commands/daemon/start.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAS,oBAAoB;AAK7C,MAAM,CAAC,OAAO,OAAO,WAAY,SAAQ,OAAO;IAC9C,OAAgB,WAAW,SAA6B;IAExD,OAAgB,QAAQ,WAItB;IAEF,OAAgB,KAAK;;;;MAmBnB;IAEW,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC;CAwBlC"}
@@ -4,7 +4,7 @@ export default class DaemonStart extends Command {
4
4
  static examples: string[];
5
5
  static flags: {
6
6
  'infura-project-id': import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
7
- password: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
7
+ password: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
8
8
  srp: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
9
9
  };
10
10
  run(): Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"start.d.mts","sourceRoot":"","sources":["../../../src/commands/daemon/start.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAS,oBAAoB;AAK7C,MAAM,CAAC,OAAO,OAAO,WAAY,SAAQ,OAAO;IAC9C,OAAgB,WAAW,SAA6B;IAExD,OAAgB,QAAQ,WAGtB;IAEF,OAAgB,KAAK;;;;MAkBnB;IAEW,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC;CAwBlC"}
1
+ {"version":3,"file":"start.d.mts","sourceRoot":"","sources":["../../../src/commands/daemon/start.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAS,oBAAoB;AAK7C,MAAM,CAAC,OAAO,OAAO,WAAY,SAAQ,OAAO;IAC9C,OAAgB,WAAW,SAA6B;IAExD,OAAgB,QAAQ,WAItB;IAEF,OAAgB,KAAK;;;;MAmBnB;IAEW,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC;CAwBlC"}
@@ -5,7 +5,7 @@ class DaemonStart extends Command {
5
5
  async run() {
6
6
  const { flags } = await this.parse(DaemonStart);
7
7
  const infuraProjectId = flags['infura-project-id'];
8
- const password = Password.from(flags.password);
8
+ const password = flags.password ? Password.from(flags.password) : undefined;
9
9
  const srp = Srp.from(flags.srp);
10
10
  const { state, socketPath } = await ensureDaemon({
11
11
  dataDir: this.config.dataDir,
@@ -26,6 +26,7 @@ DaemonStart.description = 'Start the wallet daemon';
26
26
  DaemonStart.examples = [
27
27
  '<%= config.bin %> daemon start --infura-project-id <key> --password <pw> --srp <phrase>',
28
28
  'INFURA_PROJECT_ID=<key> MM_WALLET_PASSWORD=<pw> MM_WALLET_SRP=<phrase> <%= config.bin %> daemon start',
29
+ '<%= config.bin %> daemon start --infura-project-id <key> --srp <phrase> # then `mm wallet unlock` later',
29
30
  ];
30
31
  DaemonStart.flags = {
31
32
  'infura-project-id': Flags.string({
@@ -34,9 +35,10 @@ DaemonStart.flags = {
34
35
  required: true,
35
36
  }),
36
37
  password: Flags.string({
37
- description: 'Wallet password (testing only — use MM_WALLET_PASSWORD env var in production)',
38
+ description: 'Wallet password (testing only — use MM_WALLET_PASSWORD env var in production). ' +
39
+ 'Required on first run; on subsequent runs, omit (and leave MM_WALLET_PASSWORD unset) to start with a locked keyring and use `mm wallet unlock`.',
38
40
  env: 'MM_WALLET_PASSWORD',
39
- required: true,
41
+ required: false,
40
42
  }),
41
43
  srp: Flags.string({
42
44
  description: 'Secret recovery phrase (testing only — use MM_WALLET_SRP env var in production)',
@@ -1 +1 @@
1
- {"version":3,"file":"start.mjs","sourceRoot":"","sources":["../../../src/commands/daemon/start.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,oBAAoB;AAE7C,OAAO,EAAE,YAAY,EAAE,sCAAkC;AACzD,OAAO,EAAE,QAAQ,EAAE,GAAG,EAAE,iCAA6B;AAErD,MAAqB,WAAY,SAAQ,OAAO;IA4BvC,KAAK,CAAC,GAAG;QACd,MAAM,EAAE,KAAK,EAAE,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;QAChD,MAAM,eAAe,GAAG,KAAK,CAAC,mBAAmB,CAAC,CAAC;QACnD,MAAM,QAAQ,GAAG,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;QAC/C,MAAM,GAAG,GAAG,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAEhC,MAAM,EAAE,KAAK,EAAE,UAAU,EAAE,GAAG,MAAM,YAAY,CAAC;YAC/C,OAAO,EAAE,IAAI,CAAC,MAAM,CAAC,OAAO;YAC5B,eAAe;YACf,QAAQ;YACR,GAAG;YACH,WAAW,EAAE,IAAI,CAAC,MAAM,CAAC,IAAI;SAC9B,CAAC,CAAC;QAEH,IAAI,KAAK,KAAK,iBAAiB,EAAE,CAAC;YAChC,IAAI,CAAC,GAAG,CACN,mCAAmC,UAAU,IAAI;gBAC/C,6FAA6F,CAChG,CAAC;YACF,OAAO;QACT,CAAC;QAED,IAAI,CAAC,GAAG,CAAC,2BAA2B,UAAU,EAAE,CAAC,CAAC;IACpD,CAAC;;AAlDe,uBAAW,GAAG,yBAAyB,CAAC;AAExC,oBAAQ,GAAG;IACzB,yFAAyF;IACzF,uGAAuG;CACxG,CAAC;AAEc,iBAAK,GAAG;IACtB,mBAAmB,EAAE,KAAK,CAAC,MAAM,CAAC;QAChC,WAAW,EAAE,sCAAsC;QACnD,GAAG,EAAE,mBAAmB;QACxB,QAAQ,EAAE,IAAI;KACf,CAAC;IACF,QAAQ,EAAE,KAAK,CAAC,MAAM,CAAC;QACrB,WAAW,EACT,+EAA+E;QACjF,GAAG,EAAE,oBAAoB;QACzB,QAAQ,EAAE,IAAI;KACf,CAAC;IACF,GAAG,EAAE,KAAK,CAAC,MAAM,CAAC;QAChB,WAAW,EACT,iFAAiF;QACnF,GAAG,EAAE,eAAe;QACpB,QAAQ,EAAE,IAAI;KACf,CAAC;CACH,CAAC;eA1BiB,WAAW","sourcesContent":["import { Command, Flags } from '@oclif/core';\n\nimport { ensureDaemon } from '../../daemon/daemon-spawn';\nimport { Password, Srp } from '../../daemon/secrets';\n\nexport default class DaemonStart extends Command {\n static override description = 'Start the wallet daemon';\n\n static override examples = [\n '<%= config.bin %> daemon start --infura-project-id <key> --password <pw> --srp <phrase>',\n 'INFURA_PROJECT_ID=<key> MM_WALLET_PASSWORD=<pw> MM_WALLET_SRP=<phrase> <%= config.bin %> daemon start',\n ];\n\n static override flags = {\n 'infura-project-id': Flags.string({\n description: 'Infura project ID for network access',\n env: 'INFURA_PROJECT_ID',\n required: true,\n }),\n password: Flags.string({\n description:\n 'Wallet password (testing only — use MM_WALLET_PASSWORD env var in production)',\n env: 'MM_WALLET_PASSWORD',\n required: true,\n }),\n srp: Flags.string({\n description:\n 'Secret recovery phrase (testing only — use MM_WALLET_SRP env var in production)',\n env: 'MM_WALLET_SRP',\n required: true,\n }),\n };\n\n public async run(): Promise<void> {\n const { flags } = await this.parse(DaemonStart);\n const infuraProjectId = flags['infura-project-id'];\n const password = Password.from(flags.password);\n const srp = Srp.from(flags.srp);\n\n const { state, socketPath } = await ensureDaemon({\n dataDir: this.config.dataDir,\n infuraProjectId,\n password,\n srp,\n packageRoot: this.config.root,\n });\n\n if (state === 'already-running') {\n this.log(\n `Daemon already running. Socket: ${socketPath}. ` +\n `The provided flags were not applied; run \\`mm daemon stop\\` and start again to change them.`,\n );\n return;\n }\n\n this.log(`Daemon running. Socket: ${socketPath}`);\n }\n}\n"]}
1
+ {"version":3,"file":"start.mjs","sourceRoot":"","sources":["../../../src/commands/daemon/start.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,oBAAoB;AAE7C,OAAO,EAAE,YAAY,EAAE,sCAAkC;AACzD,OAAO,EAAE,QAAQ,EAAE,GAAG,EAAE,iCAA6B;AAErD,MAAqB,WAAY,SAAQ,OAAO;IA8BvC,KAAK,CAAC,GAAG;QACd,MAAM,EAAE,KAAK,EAAE,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;QAChD,MAAM,eAAe,GAAG,KAAK,CAAC,mBAAmB,CAAC,CAAC;QACnD,MAAM,QAAQ,GAAG,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QAC5E,MAAM,GAAG,GAAG,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAEhC,MAAM,EAAE,KAAK,EAAE,UAAU,EAAE,GAAG,MAAM,YAAY,CAAC;YAC/C,OAAO,EAAE,IAAI,CAAC,MAAM,CAAC,OAAO;YAC5B,eAAe;YACf,QAAQ;YACR,GAAG;YACH,WAAW,EAAE,IAAI,CAAC,MAAM,CAAC,IAAI;SAC9B,CAAC,CAAC;QAEH,IAAI,KAAK,KAAK,iBAAiB,EAAE,CAAC;YAChC,IAAI,CAAC,GAAG,CACN,mCAAmC,UAAU,IAAI;gBAC/C,6FAA6F,CAChG,CAAC;YACF,OAAO;QACT,CAAC;QAED,IAAI,CAAC,GAAG,CAAC,2BAA2B,UAAU,EAAE,CAAC,CAAC;IACpD,CAAC;;AApDe,uBAAW,GAAG,yBAAyB,CAAC;AAExC,oBAAQ,GAAG;IACzB,yFAAyF;IACzF,uGAAuG;IACvG,2GAA2G;CAC5G,CAAC;AAEc,iBAAK,GAAG;IACtB,mBAAmB,EAAE,KAAK,CAAC,MAAM,CAAC;QAChC,WAAW,EAAE,sCAAsC;QACnD,GAAG,EAAE,mBAAmB;QACxB,QAAQ,EAAE,IAAI;KACf,CAAC;IACF,QAAQ,EAAE,KAAK,CAAC,MAAM,CAAC;QACrB,WAAW,EACT,iFAAiF;YACjF,iJAAiJ;QACnJ,GAAG,EAAE,oBAAoB;QACzB,QAAQ,EAAE,KAAK;KAChB,CAAC;IACF,GAAG,EAAE,KAAK,CAAC,MAAM,CAAC;QAChB,WAAW,EACT,iFAAiF;QACnF,GAAG,EAAE,eAAe;QACpB,QAAQ,EAAE,IAAI;KACf,CAAC;CACH,CAAC;eA5BiB,WAAW","sourcesContent":["import { Command, Flags } from '@oclif/core';\n\nimport { ensureDaemon } from '../../daemon/daemon-spawn';\nimport { Password, Srp } from '../../daemon/secrets';\n\nexport default class DaemonStart extends Command {\n static override description = 'Start the wallet daemon';\n\n static override examples = [\n '<%= config.bin %> daemon start --infura-project-id <key> --password <pw> --srp <phrase>',\n 'INFURA_PROJECT_ID=<key> MM_WALLET_PASSWORD=<pw> MM_WALLET_SRP=<phrase> <%= config.bin %> daemon start',\n '<%= config.bin %> daemon start --infura-project-id <key> --srp <phrase> # then `mm wallet unlock` later',\n ];\n\n static override flags = {\n 'infura-project-id': Flags.string({\n description: 'Infura project ID for network access',\n env: 'INFURA_PROJECT_ID',\n required: true,\n }),\n password: Flags.string({\n description:\n 'Wallet password (testing only — use MM_WALLET_PASSWORD env var in production). ' +\n 'Required on first run; on subsequent runs, omit (and leave MM_WALLET_PASSWORD unset) to start with a locked keyring and use `mm wallet unlock`.',\n env: 'MM_WALLET_PASSWORD',\n required: false,\n }),\n srp: Flags.string({\n description:\n 'Secret recovery phrase (testing only — use MM_WALLET_SRP env var in production)',\n env: 'MM_WALLET_SRP',\n required: true,\n }),\n };\n\n public async run(): Promise<void> {\n const { flags } = await this.parse(DaemonStart);\n const infuraProjectId = flags['infura-project-id'];\n const password = flags.password ? Password.from(flags.password) : undefined;\n const srp = Srp.from(flags.srp);\n\n const { state, socketPath } = await ensureDaemon({\n dataDir: this.config.dataDir,\n infuraProjectId,\n password,\n srp,\n packageRoot: this.config.root,\n });\n\n if (state === 'already-running') {\n this.log(\n `Daemon already running. Socket: ${socketPath}. ` +\n `The provided flags were not applied; run \\`mm daemon stop\\` and start again to change them.`,\n );\n return;\n }\n\n this.log(`Daemon running. Socket: ${socketPath}`);\n }\n}\n"]}
@@ -0,0 +1,75 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const utils_1 = require("@metamask/utils");
4
+ const core_1 = require("@oclif/core");
5
+ const daemon_client_1 = require("../../daemon/daemon-client.cjs");
6
+ const paths_1 = require("../../daemon/paths.cjs");
7
+ const prompts_1 = require("../../daemon/prompts.cjs");
8
+ const utils_2 = require("../../daemon/utils.cjs");
9
+ class WalletUnlock extends core_1.Command {
10
+ async run() {
11
+ const { flags } = await this.parse(WalletUnlock);
12
+ const { timeout: timeoutMs } = flags;
13
+ // `emptyToUndefined` collapses `--password ''` and `MM_WALLET_PASSWORD=''`
14
+ // to undefined so the prompt fires instead of sending an empty string.
15
+ const flagPassword = (0, utils_2.emptyToUndefined)(flags.password);
16
+ let password;
17
+ try {
18
+ password = flagPassword ?? (await (0, prompts_1.promptPassword)());
19
+ }
20
+ catch (error) {
21
+ // Only swallow ExitPromptError (@inquirer/core's Ctrl+C signal); any
22
+ // other rejection (import failure, internal prompt crash) should surface.
23
+ if (error instanceof Error && error.name === 'ExitPromptError') {
24
+ return;
25
+ }
26
+ throw error;
27
+ }
28
+ const { socketPath } = (0, paths_1.getDaemonPaths)(this.config.dataDir);
29
+ let response;
30
+ try {
31
+ response = await (0, daemon_client_1.sendCommand)({
32
+ socketPath,
33
+ method: 'call',
34
+ params: ['KeyringController:submitPassword', password],
35
+ ...(timeoutMs === undefined ? {} : { timeoutMs }),
36
+ });
37
+ }
38
+ catch (error) {
39
+ this.error((0, utils_2.makeDaemonConnectionError)(error));
40
+ }
41
+ if ((0, utils_1.isJsonRpcFailure)(response)) {
42
+ const { code, message, data } = response.error;
43
+ // `isJsonRpcFailure` already validates that `data` is JSON, so
44
+ // `JSON.stringify` cannot throw here.
45
+ const dataSuffix = data === undefined || data === null
46
+ ? ''
47
+ : ` data=${JSON.stringify(data)}`;
48
+ this.error(`Failed to unlock: ${message} (code ${String(code)})${dataSuffix}`);
49
+ }
50
+ this.log('Wallet unlocked.');
51
+ }
52
+ }
53
+ WalletUnlock.description = 'Unlock the wallet (submits the password to KeyringController). ' +
54
+ 'Use this after `mm daemon start` was run without `--password`, or ' +
55
+ 'after the keyring was locked via `KeyringController:setLocked`.';
56
+ WalletUnlock.examples = [
57
+ '<%= config.bin %> wallet unlock --password <pw>',
58
+ 'MM_WALLET_PASSWORD=<pw> <%= config.bin %> wallet unlock',
59
+ '<%= config.bin %> wallet unlock # prompts interactively',
60
+ ];
61
+ WalletUnlock.flags = {
62
+ password: core_1.Flags.string({
63
+ description: 'Wallet password (testing only — use MM_WALLET_PASSWORD env var in production). ' +
64
+ 'When omitted, the command prompts interactively.',
65
+ env: 'MM_WALLET_PASSWORD',
66
+ required: false,
67
+ }),
68
+ timeout: core_1.Flags.integer({
69
+ char: 't',
70
+ description: 'Response timeout in milliseconds',
71
+ required: false,
72
+ }),
73
+ };
74
+ exports.default = WalletUnlock;
75
+ //# sourceMappingURL=unlock.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"unlock.cjs","sourceRoot":"","sources":["../../../src/commands/wallet/unlock.ts"],"names":[],"mappings":";;AAAA,2CAAmD;AACnD,sCAA6C;AAE7C,kEAAyD;AACzD,kDAAoD;AACpD,sDAAsD;AACtD,kDAG4B;AAE5B,MAAqB,YAAa,SAAQ,cAAO;IA2BxC,KAAK,CAAC,GAAG;QACd,MAAM,EAAE,KAAK,EAAE,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;QACjD,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,GAAG,KAAK,CAAC;QAErC,2EAA2E;QAC3E,uEAAuE;QACvE,MAAM,YAAY,GAAG,IAAA,wBAAgB,EAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;QACtD,IAAI,QAAgB,CAAC;QACrB,IAAI,CAAC;YACH,QAAQ,GAAG,YAAY,IAAI,CAAC,MAAM,IAAA,wBAAc,GAAE,CAAC,CAAC;QACtD,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,qEAAqE;YACrE,0EAA0E;YAC1E,IAAI,KAAK,YAAY,KAAK,IAAI,KAAK,CAAC,IAAI,KAAK,iBAAiB,EAAE,CAAC;gBAC/D,OAAO;YACT,CAAC;YACD,MAAM,KAAK,CAAC;QACd,CAAC;QAED,MAAM,EAAE,UAAU,EAAE,GAAG,IAAA,sBAAc,EAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QAE3D,IAAI,QAAQ,CAAC;QACb,IAAI,CAAC;YACH,QAAQ,GAAG,MAAM,IAAA,2BAAW,EAAC;gBAC3B,UAAU;gBACV,MAAM,EAAE,MAAM;gBACd,MAAM,EAAE,CAAC,kCAAkC,EAAE,QAAQ,CAAC;gBACtD,GAAG,CAAC,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,CAAC;aAClD,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,KAAK,CAAC,IAAA,iCAAyB,EAAC,KAAK,CAAC,CAAC,CAAC;QAC/C,CAAC;QAED,IAAI,IAAA,wBAAgB,EAAC,QAAQ,CAAC,EAAE,CAAC;YAC/B,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,GAAG,QAAQ,CAAC,KAAK,CAAC;YAC/C,+DAA+D;YAC/D,sCAAsC;YACtC,MAAM,UAAU,GACd,IAAI,KAAK,SAAS,IAAI,IAAI,KAAK,IAAI;gBACjC,CAAC,CAAC,EAAE;gBACJ,CAAC,CAAC,SAAS,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC;YACtC,IAAI,CAAC,KAAK,CACR,qBAAqB,OAAO,UAAU,MAAM,CAAC,IAAI,CAAC,IAAI,UAAU,EAAE,CACnE,CAAC;QACJ,CAAC;QAED,IAAI,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC;IAC/B,CAAC;;AAzEe,wBAAW,GACzB,iEAAiE;IACjE,oEAAoE;IACpE,iEAAiE,CAAC;AAEpD,qBAAQ,GAAG;IACzB,iDAAiD;IACjD,yDAAyD;IACzD,2DAA2D;CAC5D,CAAC;AAEc,kBAAK,GAAG;IACtB,QAAQ,EAAE,YAAK,CAAC,MAAM,CAAC;QACrB,WAAW,EACT,iFAAiF;YACjF,kDAAkD;QACpD,GAAG,EAAE,oBAAoB;QACzB,QAAQ,EAAE,KAAK;KAChB,CAAC;IACF,OAAO,EAAE,YAAK,CAAC,OAAO,CAAC;QACrB,IAAI,EAAE,GAAG;QACT,WAAW,EAAE,kCAAkC;QAC/C,QAAQ,EAAE,KAAK;KAChB,CAAC;CACH,CAAC;kBAzBiB,YAAY","sourcesContent":["import { isJsonRpcFailure } from '@metamask/utils';\nimport { Command, Flags } from '@oclif/core';\n\nimport { sendCommand } from '../../daemon/daemon-client';\nimport { getDaemonPaths } from '../../daemon/paths';\nimport { promptPassword } from '../../daemon/prompts';\nimport {\n emptyToUndefined,\n makeDaemonConnectionError,\n} from '../../daemon/utils';\n\nexport default class WalletUnlock extends Command {\n static override description =\n 'Unlock the wallet (submits the password to KeyringController). ' +\n 'Use this after `mm daemon start` was run without `--password`, or ' +\n 'after the keyring was locked via `KeyringController:setLocked`.';\n\n static override examples = [\n '<%= config.bin %> wallet unlock --password <pw>',\n 'MM_WALLET_PASSWORD=<pw> <%= config.bin %> wallet unlock',\n '<%= config.bin %> wallet unlock # prompts interactively',\n ];\n\n static override flags = {\n password: Flags.string({\n description:\n 'Wallet password (testing only — use MM_WALLET_PASSWORD env var in production). ' +\n 'When omitted, the command prompts interactively.',\n env: 'MM_WALLET_PASSWORD',\n required: false,\n }),\n timeout: Flags.integer({\n char: 't',\n description: 'Response timeout in milliseconds',\n required: false,\n }),\n };\n\n public async run(): Promise<void> {\n const { flags } = await this.parse(WalletUnlock);\n const { timeout: timeoutMs } = flags;\n\n // `emptyToUndefined` collapses `--password ''` and `MM_WALLET_PASSWORD=''`\n // to undefined so the prompt fires instead of sending an empty string.\n const flagPassword = emptyToUndefined(flags.password);\n let password: string;\n try {\n password = flagPassword ?? (await promptPassword());\n } catch (error) {\n // Only swallow ExitPromptError (@inquirer/core's Ctrl+C signal); any\n // other rejection (import failure, internal prompt crash) should surface.\n if (error instanceof Error && error.name === 'ExitPromptError') {\n return;\n }\n throw error;\n }\n\n const { socketPath } = getDaemonPaths(this.config.dataDir);\n\n let response;\n try {\n response = await sendCommand({\n socketPath,\n method: 'call',\n params: ['KeyringController:submitPassword', password],\n ...(timeoutMs === undefined ? {} : { timeoutMs }),\n });\n } catch (error) {\n this.error(makeDaemonConnectionError(error));\n }\n\n if (isJsonRpcFailure(response)) {\n const { code, message, data } = response.error;\n // `isJsonRpcFailure` already validates that `data` is JSON, so\n // `JSON.stringify` cannot throw here.\n const dataSuffix =\n data === undefined || data === null\n ? ''\n : ` data=${JSON.stringify(data)}`;\n this.error(\n `Failed to unlock: ${message} (code ${String(code)})${dataSuffix}`,\n );\n }\n\n this.log('Wallet unlocked.');\n }\n}\n"]}
@@ -0,0 +1,11 @@
1
+ import { Command } from "@oclif/core";
2
+ export default class WalletUnlock extends Command {
3
+ static description: string;
4
+ static examples: string[];
5
+ static flags: {
6
+ password: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
7
+ timeout: import("@oclif/core/interfaces").OptionFlag<number | undefined, import("@oclif/core/interfaces").CustomOptions>;
8
+ };
9
+ run(): Promise<void>;
10
+ }
11
+ //# sourceMappingURL=unlock.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"unlock.d.cts","sourceRoot":"","sources":["../../../src/commands/wallet/unlock.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAS,oBAAoB;AAU7C,MAAM,CAAC,OAAO,OAAO,YAAa,SAAQ,OAAO;IAC/C,OAAgB,WAAW,SAGyC;IAEpE,OAAgB,QAAQ,WAItB;IAEF,OAAgB,KAAK;;;MAanB;IAEW,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC;CAgDlC"}
@@ -0,0 +1,11 @@
1
+ import { Command } from "@oclif/core";
2
+ export default class WalletUnlock extends Command {
3
+ static description: string;
4
+ static examples: string[];
5
+ static flags: {
6
+ password: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
7
+ timeout: import("@oclif/core/interfaces").OptionFlag<number | undefined, import("@oclif/core/interfaces").CustomOptions>;
8
+ };
9
+ run(): Promise<void>;
10
+ }
11
+ //# sourceMappingURL=unlock.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"unlock.d.mts","sourceRoot":"","sources":["../../../src/commands/wallet/unlock.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAS,oBAAoB;AAU7C,MAAM,CAAC,OAAO,OAAO,YAAa,SAAQ,OAAO;IAC/C,OAAgB,WAAW,SAGyC;IAEpE,OAAgB,QAAQ,WAItB;IAEF,OAAgB,KAAK;;;MAanB;IAEW,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC;CAgDlC"}
@@ -0,0 +1,73 @@
1
+ import { isJsonRpcFailure } from "@metamask/utils";
2
+ import { Command, Flags } from "@oclif/core";
3
+ import { sendCommand } from "../../daemon/daemon-client.mjs";
4
+ import { getDaemonPaths } from "../../daemon/paths.mjs";
5
+ import { promptPassword } from "../../daemon/prompts.mjs";
6
+ import { emptyToUndefined, makeDaemonConnectionError } from "../../daemon/utils.mjs";
7
+ class WalletUnlock extends Command {
8
+ async run() {
9
+ const { flags } = await this.parse(WalletUnlock);
10
+ const { timeout: timeoutMs } = flags;
11
+ // `emptyToUndefined` collapses `--password ''` and `MM_WALLET_PASSWORD=''`
12
+ // to undefined so the prompt fires instead of sending an empty string.
13
+ const flagPassword = emptyToUndefined(flags.password);
14
+ let password;
15
+ try {
16
+ password = flagPassword ?? (await promptPassword());
17
+ }
18
+ catch (error) {
19
+ // Only swallow ExitPromptError (@inquirer/core's Ctrl+C signal); any
20
+ // other rejection (import failure, internal prompt crash) should surface.
21
+ if (error instanceof Error && error.name === 'ExitPromptError') {
22
+ return;
23
+ }
24
+ throw error;
25
+ }
26
+ const { socketPath } = getDaemonPaths(this.config.dataDir);
27
+ let response;
28
+ try {
29
+ response = await sendCommand({
30
+ socketPath,
31
+ method: 'call',
32
+ params: ['KeyringController:submitPassword', password],
33
+ ...(timeoutMs === undefined ? {} : { timeoutMs }),
34
+ });
35
+ }
36
+ catch (error) {
37
+ this.error(makeDaemonConnectionError(error));
38
+ }
39
+ if (isJsonRpcFailure(response)) {
40
+ const { code, message, data } = response.error;
41
+ // `isJsonRpcFailure` already validates that `data` is JSON, so
42
+ // `JSON.stringify` cannot throw here.
43
+ const dataSuffix = data === undefined || data === null
44
+ ? ''
45
+ : ` data=${JSON.stringify(data)}`;
46
+ this.error(`Failed to unlock: ${message} (code ${String(code)})${dataSuffix}`);
47
+ }
48
+ this.log('Wallet unlocked.');
49
+ }
50
+ }
51
+ WalletUnlock.description = 'Unlock the wallet (submits the password to KeyringController). ' +
52
+ 'Use this after `mm daemon start` was run without `--password`, or ' +
53
+ 'after the keyring was locked via `KeyringController:setLocked`.';
54
+ WalletUnlock.examples = [
55
+ '<%= config.bin %> wallet unlock --password <pw>',
56
+ 'MM_WALLET_PASSWORD=<pw> <%= config.bin %> wallet unlock',
57
+ '<%= config.bin %> wallet unlock # prompts interactively',
58
+ ];
59
+ WalletUnlock.flags = {
60
+ password: Flags.string({
61
+ description: 'Wallet password (testing only — use MM_WALLET_PASSWORD env var in production). ' +
62
+ 'When omitted, the command prompts interactively.',
63
+ env: 'MM_WALLET_PASSWORD',
64
+ required: false,
65
+ }),
66
+ timeout: Flags.integer({
67
+ char: 't',
68
+ description: 'Response timeout in milliseconds',
69
+ required: false,
70
+ }),
71
+ };
72
+ export default WalletUnlock;
73
+ //# sourceMappingURL=unlock.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"unlock.mjs","sourceRoot":"","sources":["../../../src/commands/wallet/unlock.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,wBAAwB;AACnD,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,oBAAoB;AAE7C,OAAO,EAAE,WAAW,EAAE,uCAAmC;AACzD,OAAO,EAAE,cAAc,EAAE,+BAA2B;AACpD,OAAO,EAAE,cAAc,EAAE,iCAA6B;AACtD,OAAO,EACL,gBAAgB,EAChB,yBAAyB,EAC1B,+BAA2B;AAE5B,MAAqB,YAAa,SAAQ,OAAO;IA2BxC,KAAK,CAAC,GAAG;QACd,MAAM,EAAE,KAAK,EAAE,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;QACjD,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,GAAG,KAAK,CAAC;QAErC,2EAA2E;QAC3E,uEAAuE;QACvE,MAAM,YAAY,GAAG,gBAAgB,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;QACtD,IAAI,QAAgB,CAAC;QACrB,IAAI,CAAC;YACH,QAAQ,GAAG,YAAY,IAAI,CAAC,MAAM,cAAc,EAAE,CAAC,CAAC;QACtD,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,qEAAqE;YACrE,0EAA0E;YAC1E,IAAI,KAAK,YAAY,KAAK,IAAI,KAAK,CAAC,IAAI,KAAK,iBAAiB,EAAE,CAAC;gBAC/D,OAAO;YACT,CAAC;YACD,MAAM,KAAK,CAAC;QACd,CAAC;QAED,MAAM,EAAE,UAAU,EAAE,GAAG,cAAc,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QAE3D,IAAI,QAAQ,CAAC;QACb,IAAI,CAAC;YACH,QAAQ,GAAG,MAAM,WAAW,CAAC;gBAC3B,UAAU;gBACV,MAAM,EAAE,MAAM;gBACd,MAAM,EAAE,CAAC,kCAAkC,EAAE,QAAQ,CAAC;gBACtD,GAAG,CAAC,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,CAAC;aAClD,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,KAAK,CAAC,yBAAyB,CAAC,KAAK,CAAC,CAAC,CAAC;QAC/C,CAAC;QAED,IAAI,gBAAgB,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC/B,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,GAAG,QAAQ,CAAC,KAAK,CAAC;YAC/C,+DAA+D;YAC/D,sCAAsC;YACtC,MAAM,UAAU,GACd,IAAI,KAAK,SAAS,IAAI,IAAI,KAAK,IAAI;gBACjC,CAAC,CAAC,EAAE;gBACJ,CAAC,CAAC,SAAS,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC;YACtC,IAAI,CAAC,KAAK,CACR,qBAAqB,OAAO,UAAU,MAAM,CAAC,IAAI,CAAC,IAAI,UAAU,EAAE,CACnE,CAAC;QACJ,CAAC;QAED,IAAI,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC;IAC/B,CAAC;;AAzEe,wBAAW,GACzB,iEAAiE;IACjE,oEAAoE;IACpE,iEAAiE,CAAC;AAEpD,qBAAQ,GAAG;IACzB,iDAAiD;IACjD,yDAAyD;IACzD,2DAA2D;CAC5D,CAAC;AAEc,kBAAK,GAAG;IACtB,QAAQ,EAAE,KAAK,CAAC,MAAM,CAAC;QACrB,WAAW,EACT,iFAAiF;YACjF,kDAAkD;QACpD,GAAG,EAAE,oBAAoB;QACzB,QAAQ,EAAE,KAAK;KAChB,CAAC;IACF,OAAO,EAAE,KAAK,CAAC,OAAO,CAAC;QACrB,IAAI,EAAE,GAAG;QACT,WAAW,EAAE,kCAAkC;QAC/C,QAAQ,EAAE,KAAK;KAChB,CAAC;CACH,CAAC;eAzBiB,YAAY","sourcesContent":["import { isJsonRpcFailure } from '@metamask/utils';\nimport { Command, Flags } from '@oclif/core';\n\nimport { sendCommand } from '../../daemon/daemon-client';\nimport { getDaemonPaths } from '../../daemon/paths';\nimport { promptPassword } from '../../daemon/prompts';\nimport {\n emptyToUndefined,\n makeDaemonConnectionError,\n} from '../../daemon/utils';\n\nexport default class WalletUnlock extends Command {\n static override description =\n 'Unlock the wallet (submits the password to KeyringController). ' +\n 'Use this after `mm daemon start` was run without `--password`, or ' +\n 'after the keyring was locked via `KeyringController:setLocked`.';\n\n static override examples = [\n '<%= config.bin %> wallet unlock --password <pw>',\n 'MM_WALLET_PASSWORD=<pw> <%= config.bin %> wallet unlock',\n '<%= config.bin %> wallet unlock # prompts interactively',\n ];\n\n static override flags = {\n password: Flags.string({\n description:\n 'Wallet password (testing only — use MM_WALLET_PASSWORD env var in production). ' +\n 'When omitted, the command prompts interactively.',\n env: 'MM_WALLET_PASSWORD',\n required: false,\n }),\n timeout: Flags.integer({\n char: 't',\n description: 'Response timeout in milliseconds',\n required: false,\n }),\n };\n\n public async run(): Promise<void> {\n const { flags } = await this.parse(WalletUnlock);\n const { timeout: timeoutMs } = flags;\n\n // `emptyToUndefined` collapses `--password ''` and `MM_WALLET_PASSWORD=''`\n // to undefined so the prompt fires instead of sending an empty string.\n const flagPassword = emptyToUndefined(flags.password);\n let password: string;\n try {\n password = flagPassword ?? (await promptPassword());\n } catch (error) {\n // Only swallow ExitPromptError (@inquirer/core's Ctrl+C signal); any\n // other rejection (import failure, internal prompt crash) should surface.\n if (error instanceof Error && error.name === 'ExitPromptError') {\n return;\n }\n throw error;\n }\n\n const { socketPath } = getDaemonPaths(this.config.dataDir);\n\n let response;\n try {\n response = await sendCommand({\n socketPath,\n method: 'call',\n params: ['KeyringController:submitPassword', password],\n ...(timeoutMs === undefined ? {} : { timeoutMs }),\n });\n } catch (error) {\n this.error(makeDaemonConnectionError(error));\n }\n\n if (isJsonRpcFailure(response)) {\n const { code, message, data } = response.error;\n // `isJsonRpcFailure` already validates that `data` is JSON, so\n // `JSON.stringify` cannot throw here.\n const dataSuffix =\n data === undefined || data === null\n ? ''\n : ` data=${JSON.stringify(data)}`;\n this.error(\n `Failed to unlock: ${message} (code ${String(code)})${dataSuffix}`,\n );\n }\n\n this.log('Wallet unlocked.');\n }\n}\n"]}
@@ -41,10 +41,12 @@ async function main() {
41
41
  if (!infuraProjectId) {
42
42
  throw new Error('INFURA_PROJECT_ID environment variable is required');
43
43
  }
44
+ // Password is optional: when absent, the daemon starts without unlocking
45
+ // the keyring (e.g. when the user prefers to call `mm wallet unlock`
46
+ // interactively rather than embed the password in their environment).
47
+ // First-run startup still requires a password; wallet-factory enforces
48
+ // that and surfaces a clear error.
44
49
  const passwordRaw = process.env.MM_WALLET_PASSWORD;
45
- if (!passwordRaw) {
46
- throw new Error('MM_WALLET_PASSWORD environment variable is required');
47
- }
48
50
  const srpRaw = process.env.MM_WALLET_SRP;
49
51
  if (!srpRaw) {
50
52
  throw new Error('MM_WALLET_SRP environment variable is required');
@@ -53,7 +55,7 @@ async function main() {
53
55
  // value) does not leave the raw secrets in the long-lived daemon's env.
54
56
  delete process.env.MM_WALLET_PASSWORD;
55
57
  delete process.env.MM_WALLET_SRP;
56
- const password = secrets_1.Password.from(passwordRaw);
58
+ const password = passwordRaw ? secrets_1.Password.from(passwordRaw) : undefined;
57
59
  const srp = secrets_1.Srp.from(srpRaw);
58
60
  await (0, data_dir_1.ensureOwnerOnlyDirectory)(dataDir);
59
61
  const { socketPath: defaultSocketPath, pidPath, logPath, dbPath, } = (0, paths_1.getDaemonPaths)(dataDir);
@@ -1 +1 @@
1
- {"version":3,"file":"daemon-entry.cjs","sourceRoot":"","sources":["../../src/daemon/daemon-entry.ts"],"names":[],"mappings":";;AAAA,uDAAwD;AAGxD,+CAAuE;AAEvE,uDAA6C;AAC7C,6CAAsD;AACtD,uCAAyC;AACzC,+DAA2D;AAE3D,2CAA0C;AAC1C,uCAAwC;AAOxC,uCAAuE;AACvE,yDAAgD;AAEhD;;;;GAIG;AACH,MAAM,gBAAgB,GAAG,IAAA,oBAAM,EAC7B,YAAY,EACZ,CAAC,KAAK,EAAE,EAAE;IACR,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QAC1B,OAAO,mBAAmB,CAAC;IAC7B,CAAC;IACD,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACvB,OAAO,4BAA4B,CAAC;IACtC,CAAC;IACD,IAAI,OAAO,KAAK,CAAC,CAAC,CAAC,KAAK,QAAQ,EAAE,CAAC;QACjC,OAAO,uDAAuD,CAAC;IACjE,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC,CACF,CAAC;AAEF,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;AAE7B,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE;IAC9B,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,iBAAiB,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IACzD,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;AACvB,CAAC,CAAC,CAAC;AAEH,KAAK,UAAU,IAAI;IACjB,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC;IAC/C,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,MAAM,IAAI,KAAK,CAAC,qDAAqD,CAAC,CAAC;IACzE,CAAC;IAED,MAAM,eAAe,GAAG,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC;IACtD,IAAI,CAAC,eAAe,EAAE,CAAC;QACrB,MAAM,IAAI,KAAK,CAAC,oDAAoD,CAAC,CAAC;IACxE,CAAC;IAED,MAAM,WAAW,GAAG,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC;IACnD,IAAI,CAAC,WAAW,EAAE,CAAC;QACjB,MAAM,IAAI,KAAK,CAAC,qDAAqD,CAAC,CAAC;IACzE,CAAC;IAED,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC;IACzC,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,MAAM,IAAI,KAAK,CAAC,gDAAgD,CAAC,CAAC;IACpE,CAAC;IAED,wEAAwE;IACxE,wEAAwE;IACxE,OAAO,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC;IACtC,OAAO,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC;IAEjC,MAAM,QAAQ,GAAG,kBAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;IAC5C,MAAM,GAAG,GAAG,aAAG,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAE7B,MAAM,IAAA,mCAAwB,EAAC,OAAO,CAAC,CAAC;IAExC,MAAM,EACJ,UAAU,EAAE,iBAAiB,EAC7B,OAAO,EACP,OAAO,EACP,MAAM,GACP,GAAG,IAAA,sBAAc,EAAC,OAAO,CAAC,CAAC;IAC5B,MAAM,UAAU,GAAG,OAAO,CAAC,GAAG,CAAC,qBAAqB,IAAI,iBAAiB,CAAC;IAE1E,MAAM,GAAG,GAAG,UAAU,CAAC,OAAO,CAAC,CAAC;IAChC,GAAG,CAAC,oBAAoB,CAAC,CAAC;IAE1B,2EAA2E;IAC3E,2EAA2E;IAC3E,uEAAuE;IACvE,MAAM,eAAe,CAAC,OAAO,EAAE,UAAU,EAAE,GAAG,CAAC,CAAC;IAEhD,MAAM,eAAe,GAAG,GAAG,OAAO,CAAC,GAAG,KAAK,SAAS,IAAI,CAAC;IAEzD,kEAAkE;IAClE,yEAAyE;IACzE,0EAA0E;IAC1E,yEAAyE;IACzE,oEAAoE;IACpE,IAAI,CAAC;QACH,MAAM,IAAA,oBAAS,EAAC,OAAO,EAAE,eAAe,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;IAC5D,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,KAAK,YAAY,KAAK;YAC1B,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE;gBACnB,OAAO,EAAE,kCAAkC,OAAO,KAAK,KAAK,CAAC,OAAO,EAAE;aACvE,CAAC;YACJ,CAAC,CAAC,2EAA2E;gBAC3E,IAAI,KAAK,CACP,kCAAkC,OAAO,KAAK,MAAM,CAAC,KAAK,CAAC,EAAE,CAC9D,CAAC;IACR,CAAC;IAED,IAAI,MAA0B,CAAC;IAC/B,IAAI,OAA0C,CAAC;IAC/C,IAAI,MAAyC,CAAC;IAE9C,IAAI,CAAC;QACH,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,MAAM,IAAA,6BAAY,EAAC;YACxC,YAAY,EAAE,MAAM;YACpB,QAAQ;YACR,GAAG;YACH,eAAe;YACf,GAAG;SACJ,CAAC,CAAC,CAAC;QAEJ,MAAM,iBAAiB,GAAG,MAAM,CAAC;QACjC,wEAAwE;QACxE,sEAAsE;QACtE,sEAAsE;QACtE,0EAA0E;QAC1E,0EAA0E;QAC1E,wEAAwE;QACxE,iEAAiE;QACjE,MAAM,QAAQ,GAAG,iBAAiB,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CACpD,iBAAiB,CAAC,SAAS,CACA,CAAC;QAE9B,MAAM,QAAQ,GAAkB;YAC9B,SAAS,EAAE,IAAA,qBAAa,EACtB,IAAA,qBAAO,EAAC,IAAI,CAAC,EACb,KAAK,IAA+B,EAAE,CAAC,CAAC;gBACtC,GAAG,EAAE,OAAO,CAAC,GAAG;gBAChB,MAAM,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC,GAAG,IAAI,CAAC;aACpD,CAAC,CACH;YACD,IAAI,EAAE,IAAA,qBAAa,EAAC,gBAAgB,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE;gBACrD,MAAM,CAAC,MAAM,EAAE,GAAG,IAAI,CAAC,GAAG,MAAM,CAAC;gBACjC,OAAO,MAAM,QAAQ,CAAC,MAAM,EAAE,GAAI,IAAe,CAAC,CAAC;YACrD,CAAC,CAAC;YACF,mEAAmE;YACnE,qEAAqE;YACrE,oCAAoC;YACpC,WAAW,EAAE,IAAA,qBAAa,EACxB,IAAA,qBAAO,EAAC,IAAI,CAAC,EACb,KAAK,IAAmB,EAAE,CACxB,iBAAiB,CAAC,SAAS,CAAC,wBAAwB,EAAE,CACzD;SACF,CAAC;QAEF,yEAAyE;QACzE,yEAAyE;QACzE,mDAAmD;QACnD,MAAM,GAAG,MAAM,IAAA,wCAAoB,EAAC;YAClC,UAAU;YACV,QAAQ;YACR,UAAU,EAAE,KAAK,IAAI,EAAE,CAAC,QAAQ,CAAC,cAAc,CAAC;YAChD,GAAG;SACJ,CAAC,CAAC;IACL,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,wEAAwE;QACxE,gDAAgD;QAChD,IAAI,OAAO,EAAE,CAAC;YACZ,MAAM,OAAO,EAAE,CAAC;QAClB,CAAC;QACD,yEAAyE;QACzE,+CAA+C;QAC/C,MAAM,kBAAkB,CAAC,OAAO,EAAE,eAAe,CAAC,CAAC,KAAK,CACtD,CAAC,OAAgB,EAAE,EAAE;YACnB,GAAG,CAAC,6CAA6C,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QACtE,CAAC,CACF,CAAC;QACF,MAAM,KAAK,CAAC;IACd,CAAC;IAED,2EAA2E;IAC3E,uCAAuC;IACvC,MAAM,YAAY,GAAG,MAAM,CAAC;IAC5B,MAAM,aAAa,GAAG,OAAO,CAAC;IAE9B,GAAG,CAAC,2BAA2B,UAAU,EAAE,CAAC,CAAC;IAE7C,IAAI,eAA0C,CAAC;IAE/C;;;;;OAKG;IACH,KAAK,UAAU,QAAQ,CAAC,MAAc;QACpC,IAAI,eAAe,KAAK,SAAS,EAAE,CAAC;YAClC,GAAG,CAAC,kBAAkB,MAAM,MAAM,CAAC,CAAC;YACpC,eAAe,GAAG,CAAC,KAAK,IAAmB,EAAE;gBAC3C,IAAI,CAAC;oBACH,MAAM,YAAY,CAAC,KAAK,EAAE,CAAC;gBAC7B,CAAC;gBAAC,OAAO,UAAU,EAAE,CAAC;oBACpB,GAAG,CAAC,0BAA0B,MAAM,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC;gBACtD,CAAC;gBACD,IAAI,CAAC;oBACH,MAAM,aAAa,EAAE,CAAC;gBACxB,CAAC;gBAAC,OAAO,YAAY,EAAE,CAAC;oBACtB,GAAG,CAAC,qCAAqC,MAAM,CAAC,YAAY,CAAC,EAAE,CAAC,CAAC;gBACnE,CAAC;gBACD,MAAM,OAAO,CAAC,GAAG,CAAC;oBAChB,kBAAkB,CAAC,OAAO,EAAE,eAAe,CAAC,CAAC,KAAK,CAChD,CAAC,OAAgB,EAAE,EAAE;wBACnB,GAAG,CAAC,8BAA8B,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;oBACvD,CAAC,CACF;oBACD,IAAA,aAAE,EAAC,UAAU,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,OAAgB,EAAE,EAAE;wBACzD,GAAG,CAAC,iCAAiC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;oBAC1D,CAAC,CAAC;iBACH,CAAC,CAAC;YACL,CAAC,CAAC,EAAE,CAAC;QACP,CAAC;QACD,OAAO,eAAe,CAAC;IACzB,CAAC;IAED,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,GAAG,EAAE;QACzB,iJAAiJ;QACjJ,QAAQ,CAAC,SAAS,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;IAC7C,CAAC,CAAC,CAAC;IACH,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,GAAG,EAAE;QACxB,iJAAiJ;QACjJ,QAAQ,CAAC,QAAQ,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;IAC5C,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;;;;;;;GAQG;AACH,KAAK,UAAU,eAAe,CAC5B,OAAe,EACf,UAAkB,EAClB,GAAW;IAEX,MAAM,WAAW,GAAG,MAAM,IAAA,mBAAW,EAAC,OAAO,CAAC,CAAC;IAC/C,MAAM,IAAI,GAAG,MAAM,IAAA,0BAAU,EAAC,UAAU,CAAC,CAAC;IAE1C,IAAI,IAAI,CAAC,MAAM,KAAK,YAAY,EAAE,CAAC;QACjC,MAAM,OAAO,GACX,WAAW,KAAK,SAAS;YACvB,CAAC,CAAC,uBAAuB;YACzB,CAAC,CAAC,QAAQ,WAAW,GAAG,CAAC;QAC7B,MAAM,IAAI,KAAK,CAAC,kCAAkC,UAAU,IAAI,OAAO,EAAE,CAAC,CAAC;IAC7E,CAAC;IAED,wEAAwE;IACxE,iDAAiD;IACjD,wEAAwE;IACxE,qCAAqC;IACrC,wEAAwE;IACxE,wEAAwE;IACxE,6CAA6C;IAC7C,IAAI,WAAW,KAAK,SAAS,IAAI,IAAA,sBAAc,EAAC,WAAW,CAAC,EAAE,CAAC;QAC7D,MAAM,MAAM,GACV,IAAI,CAAC,MAAM,KAAK,aAAa;YAC3B,CAAC,CAAC,aAAa,UAAU,qBAAqB,IAAI,CAAC,KAAK,CAAC,OAAO,GAAG;YACnE,CAAC,CAAC,gBAAgB,UAAU,0BAA0B,CAAC;QAC3D,MAAM,IAAI,KAAK,CACb,oCAAoC,WAAW,MAAM,MAAM,IAAI;YAC7D,+EAA+E,CAClF,CAAC;IACJ,CAAC;IAED,IAAI,IAAI,CAAC,MAAM,KAAK,aAAa,EAAE,CAAC;QAClC,GAAG,CAAC,4BAA4B,UAAU,KAAK,IAAI,CAAC,KAAK,CAAC,OAAO,IAAI,CAAC,CAAC;IACzE,CAAC;IACD,wEAAwE;IACxE,2EAA2E;IAC3E,yEAAyE;IACzE,qDAAqD;IACrD,MAAM,OAAO,CAAC,GAAG,CAAC;QAChB,IAAA,aAAE,EAAC,OAAO,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;QAC5B,IAAA,aAAE,EAAC,UAAU,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;KAChC,CAAC,CAAC;AACL,CAAC;AAED;;;;;;;GAOG;AACH,KAAK,UAAU,kBAAkB,CAC/B,OAAe,EACf,gBAAwB;IAExB,IAAI,MAAc,CAAC;IACnB,IAAI,CAAC;QACH,MAAM,GAAG,MAAM,IAAA,mBAAQ,EAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IAC5C,CAAC;IAAC,OAAO,KAAc,EAAE,CAAC;QACxB,IAAI,IAAA,uBAAe,EAAC,KAAK,EAAE,QAAQ,CAAC,EAAE,CAAC;YACrC,OAAO;QACT,CAAC;QACD,MAAM,KAAK,CAAC;IACd,CAAC;IACD,IAAI,MAAM,KAAK,gBAAgB,EAAE,CAAC;QAChC,MAAM,IAAA,aAAE,EAAC,OAAO,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IACrC,CAAC;AACH,CAAC;AAED;;;;;;GAMG;AACH,SAAS,UAAU,CAAC,OAAe;IACjC,OAAO,CAAC,OAAe,EAAQ,EAAE;QAC/B,MAAM,IAAI,GAAG,IAAI,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,KAAK,OAAO,IAAI,CAAC;QAC1D,IAAA,qBAAU,EAAC,OAAO,EAAE,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE;YACjD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,sBAAsB,MAAM,CAAC,KAAK,CAAC,KAAK,OAAO,IAAI,CAAC,CAAC;QAC5E,CAAC,CAAC,CAAC;IACL,CAAC,CAAC;AACJ,CAAC","sourcesContent":["import { define, literal } from '@metamask/superstruct';\nimport type { Json } from '@metamask/utils';\nimport type { Wallet } from '@metamask/wallet';\nimport { appendFile, readFile, rm, writeFile } from 'node:fs/promises';\n\nimport { pingDaemon } from './daemon-client';\nimport { ensureOwnerOnlyDirectory } from './data-dir';\nimport { getDaemonPaths } from './paths';\nimport { startRpcSocketServer } from './rpc-socket-server';\nimport type { RpcSocketServerHandle } from './rpc-socket-server';\nimport { Password, Srp } from './secrets';\nimport { defineHandler } from './types';\nimport type {\n DaemonStatusInfo,\n Logger,\n RpcDispatcher,\n RpcHandlerMap,\n} from './types';\nimport { isErrorWithCode, isProcessAlive, readPidFile } from './utils';\nimport { createWallet } from './wallet-factory';\n\n/**\n * Params struct for the `call` RPC method. `params` must be a non-empty array\n * whose first element is the messenger action name; remaining elements are\n * positional action arguments forwarded as-is to `messenger.call`.\n */\nconst callParamsStruct = define<[string, ...unknown[]]>(\n 'CallParams',\n (value) => {\n if (!Array.isArray(value)) {\n return 'Expected an array';\n }\n if (value.length === 0) {\n return 'Expected a non-empty array';\n }\n if (typeof value[0] !== 'string') {\n return 'Expected the first element to be a string action name';\n }\n return true;\n },\n);\n\nconst startTime = Date.now();\n\nmain().catch((error: unknown) => {\n process.stderr.write(`Daemon fatal: ${String(error)}\\n`);\n process.exitCode = 1;\n});\n\nasync function main(): Promise<void> {\n const dataDir = process.env.MM_DAEMON_DATA_DIR;\n if (!dataDir) {\n throw new Error('MM_DAEMON_DATA_DIR environment variable is required');\n }\n\n const infuraProjectId = process.env.INFURA_PROJECT_ID;\n if (!infuraProjectId) {\n throw new Error('INFURA_PROJECT_ID environment variable is required');\n }\n\n const passwordRaw = process.env.MM_WALLET_PASSWORD;\n if (!passwordRaw) {\n throw new Error('MM_WALLET_PASSWORD environment variable is required');\n }\n\n const srpRaw = process.env.MM_WALLET_SRP;\n if (!srpRaw) {\n throw new Error('MM_WALLET_SRP environment variable is required');\n }\n\n // Scrub before validation so a throw from Password.from / Srp.from (bad\n // value) does not leave the raw secrets in the long-lived daemon's env.\n delete process.env.MM_WALLET_PASSWORD;\n delete process.env.MM_WALLET_SRP;\n\n const password = Password.from(passwordRaw);\n const srp = Srp.from(srpRaw);\n\n await ensureOwnerOnlyDirectory(dataDir);\n\n const {\n socketPath: defaultSocketPath,\n pidPath,\n logPath,\n dbPath,\n } = getDaemonPaths(dataDir);\n const socketPath = process.env.MM_DAEMON_SOCKET_PATH ?? defaultSocketPath;\n\n const log = makeLogger(logPath);\n log('Starting daemon...');\n\n // Pre-flight: refuse to take over if a responsive daemon already owns this\n // socket. If the existing PID file is stale (or the socket is dead), clean\n // it up so the exclusive PID-file write below has a chance to succeed.\n await claimDaemonSlot(pidPath, socketPath, log);\n\n const pidFileContents = `${process.pid}\\n${startTime}\\n`;\n\n // Claim the slot atomically BEFORE opening the SQLite database or\n // constructing the Wallet. Two concurrent `daemon start` invocations can\n // both pass `claimDaemonSlot` (the gap between its preflight and the slot\n // write is racy); without this ordering, both would open `wallet.db` and\n // both would run first-run SRP import before one loses the wx race.\n try {\n await writeFile(pidPath, pidFileContents, { flag: 'wx' });\n } catch (error) {\n throw error instanceof Error\n ? Object.assign(error, {\n message: `Failed to claim daemon slot at ${pidPath}: ${error.message}`,\n })\n : /* istanbul ignore next -- node:fs/promises always rejects with an Error */\n new Error(\n `Failed to claim daemon slot at ${pidPath}: ${String(error)}`,\n );\n }\n\n let wallet: Wallet | undefined;\n let dispose: (() => Promise<void>) | undefined;\n let handle: RpcSocketServerHandle | undefined;\n\n try {\n ({ wallet, dispose } = await createWallet({\n databasePath: dbPath,\n password,\n srp,\n infuraProjectId,\n log,\n }));\n\n const constructedWallet = wallet;\n // Arbitrary messenger dispatch is intentional: the CLI exposes the full\n // messenger surface over a Unix socket inside the per-user oclif data\n // directory. The dataDir is chmodded to 0o700 above and the socket to\n // 0o600 by the RPC server on bind, so only the owning user can open them,\n // but there is no in-process auth check beyond that filesystem-permission\n // barrier. The messenger is strongly typed by action name; we narrow it\n // once here to the RpcDispatcher shape the `call` handler needs.\n const dispatch = constructedWallet.messenger.call.bind(\n constructedWallet.messenger,\n ) as unknown as RpcDispatcher;\n\n const handlers: RpcHandlerMap = {\n getStatus: defineHandler(\n literal(null),\n async (): Promise<DaemonStatusInfo> => ({\n pid: process.pid,\n uptime: Math.floor((Date.now() - startTime) / 1000),\n }),\n ),\n call: defineHandler(callParamsStruct, async (params) => {\n const [action, ...args] = params;\n return await dispatch(action, ...(args as Json[]));\n }),\n // Exposes the callable surface for discovery: it grows silently as\n // controllers are wired, so consumers need a way to see it without a\n // hand-kept catalog that would rot.\n listActions: defineHandler(\n literal(null),\n async (): Promise<Json> =>\n constructedWallet.messenger.getRegisteredActionTypes(),\n ),\n };\n\n // `startRpcSocketServer` restricts the socket to the owner (chmod 0o600)\n // on bind and never leaves a live server/socket behind if it rejects, so\n // the catch below has nothing of its own to close.\n handle = await startRpcSocketServer({\n socketPath,\n handlers,\n onShutdown: async () => shutdown('RPC shutdown'),\n log,\n });\n } catch (error) {\n // `dispose` is undefined only when `createWallet` itself threw — it has\n // already torn down its own store in that case.\n if (dispose) {\n await dispose();\n }\n // Only remove the PID file if it's still ours (we may have lost the race\n // and the file now belongs to another daemon).\n await removeOwnedPidFile(pidPath, pidFileContents).catch(\n (rmError: unknown) => {\n log(`Failed to remove PID file during cleanup: ${String(rmError)}`);\n },\n );\n throw error;\n }\n\n // Stable non-undefined refs for the shutdown closures (TS won't narrow the\n // outer `let`s across closure escape).\n const activeHandle = handle;\n const activeDispose = dispose;\n\n log(`Daemon started. Socket: ${socketPath}`);\n\n let shutdownPromise: Promise<void> | undefined;\n\n /**\n * Shut down the daemon idempotently. Concurrent calls coalesce.\n *\n * @param reason - A label describing why shutdown was triggered.\n * @returns A promise that resolves when shutdown completes.\n */\n async function shutdown(reason: string): Promise<void> {\n if (shutdownPromise === undefined) {\n log(`Shutting down (${reason})...`);\n shutdownPromise = (async (): Promise<void> => {\n try {\n await activeHandle.close();\n } catch (closeError) {\n log(`handle.close() failed: ${String(closeError)}`);\n }\n try {\n await activeDispose();\n } catch (disposeError) {\n log(`dispose() failed during shutdown: ${String(disposeError)}`);\n }\n await Promise.all([\n removeOwnedPidFile(pidPath, pidFileContents).catch(\n (rmError: unknown) => {\n log(`Failed to remove PID file: ${String(rmError)}`);\n },\n ),\n rm(socketPath, { force: true }).catch((rmError: unknown) => {\n log(`Failed to remove socket file: ${String(rmError)}`);\n }),\n ]);\n })();\n }\n return shutdownPromise;\n }\n\n process.on('SIGTERM', () => {\n /* istanbul ignore next -- `shutdown` logs each step internally; the catch only guards against an unhandled rejection from the signal handler. */\n shutdown('SIGTERM').catch(() => undefined);\n });\n process.on('SIGINT', () => {\n /* istanbul ignore next -- `shutdown` logs each step internally; the catch only guards against an unhandled rejection from the signal handler. */\n shutdown('SIGINT').catch(() => undefined);\n });\n}\n\n/**\n * Refuse to start if a responsive daemon already owns the socket. Otherwise\n * clear any stale PID/socket files so the exclusive PID-file write can\n * proceed.\n *\n * @param pidPath - The PID file path.\n * @param socketPath - The socket path.\n * @param log - Logger for diagnostic messages.\n */\nasync function claimDaemonSlot(\n pidPath: string,\n socketPath: string,\n log: Logger,\n): Promise<void> {\n const existingPid = await readPidFile(pidPath);\n const ping = await pingDaemon(socketPath);\n\n if (ping.status === 'responsive') {\n const pidPart =\n existingPid === undefined\n ? '(no PID file present)'\n : `(pid ${existingPid})`;\n throw new Error(`A daemon is already running on ${socketPath} ${pidPart}`);\n }\n\n // Refuse to clobber when the recorded PID is still alive, regardless of\n // whether the socket exists. Possible scenarios:\n // - `unreachable`: wedged or mid-startup sibling daemon (socket present\n // but not responding to JSON-RPC).\n // - `absent`: a sibling daemon that hasn't yet bound its socket, or one\n // whose socket was manually removed. In either case, removing its PID\n // file would orphan it from `daemon stop`.\n if (existingPid !== undefined && isProcessAlive(existingPid)) {\n const detail =\n ping.status === 'unreachable'\n ? `socket at ${socketPath} is unresponsive (${ping.error.message})`\n : `no socket at ${socketPath}, but pid is still alive`;\n throw new Error(\n `A daemon is already running (pid ${existingPid}): ${detail}. ` +\n `Run \\`mm daemon stop\\` (or \\`mm daemon purge\\`) before starting a new daemon.`,\n );\n }\n\n if (ping.status === 'unreachable') {\n log(`Removing stale socket at ${socketPath} (${ping.error.message}).`);\n }\n // Always clear both files before claiming the slot. The PID file may be\n // corrupt (truncated, partial write from a crashed run); without this, the\n // exclusive `wx` write below would fail with EEXIST and the daemon could\n // not start until a human manually deleted the file.\n await Promise.all([\n rm(pidPath, { force: true }),\n rm(socketPath, { force: true }),\n ]);\n}\n\n/**\n * Remove the PID file only if it still contains our exact contents. Guards\n * against a racing daemon's PID file being removed by this daemon during\n * cleanup.\n *\n * @param pidPath - Path to the PID file.\n * @param expectedContents - The contents we wrote when claiming the slot.\n */\nasync function removeOwnedPidFile(\n pidPath: string,\n expectedContents: string,\n): Promise<void> {\n let actual: string;\n try {\n actual = await readFile(pidPath, 'utf-8');\n } catch (error: unknown) {\n if (isErrorWithCode(error, 'ENOENT')) {\n return;\n }\n throw error;\n }\n if (actual === expectedContents) {\n await rm(pidPath, { force: true });\n }\n}\n\n/**\n * Create a file logger that appends timestamped lines to `logPath`, falling\n * back to stderr if the append fails.\n *\n * @param logPath - The log file path.\n * @returns A logging function.\n */\nfunction makeLogger(logPath: string): Logger {\n return (message: string): void => {\n const line = `[${new Date().toISOString()}] ${message}\\n`;\n appendFile(logPath, line).catch((error: unknown) => {\n process.stderr.write(`[log write failed: ${String(error)}] ${message}\\n`);\n });\n };\n}\n"]}
1
+ {"version":3,"file":"daemon-entry.cjs","sourceRoot":"","sources":["../../src/daemon/daemon-entry.ts"],"names":[],"mappings":"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","sourcesContent":["import { define, literal } from '@metamask/superstruct';\nimport type { Json } from '@metamask/utils';\nimport type { Wallet } from '@metamask/wallet';\nimport { appendFile, readFile, rm, writeFile } from 'node:fs/promises';\n\nimport { pingDaemon } from './daemon-client';\nimport { ensureOwnerOnlyDirectory } from './data-dir';\nimport { getDaemonPaths } from './paths';\nimport { startRpcSocketServer } from './rpc-socket-server';\nimport type { RpcSocketServerHandle } from './rpc-socket-server';\nimport { Password, Srp } from './secrets';\nimport { defineHandler } from './types';\nimport type {\n DaemonStatusInfo,\n Logger,\n RpcDispatcher,\n RpcHandlerMap,\n} from './types';\nimport { isErrorWithCode, isProcessAlive, readPidFile } from './utils';\nimport { createWallet } from './wallet-factory';\n\n/**\n * Params struct for the `call` RPC method. `params` must be a non-empty array\n * whose first element is the messenger action name; remaining elements are\n * positional action arguments forwarded as-is to `messenger.call`.\n */\nconst callParamsStruct = define<[string, ...unknown[]]>(\n 'CallParams',\n (value) => {\n if (!Array.isArray(value)) {\n return 'Expected an array';\n }\n if (value.length === 0) {\n return 'Expected a non-empty array';\n }\n if (typeof value[0] !== 'string') {\n return 'Expected the first element to be a string action name';\n }\n return true;\n },\n);\n\nconst startTime = Date.now();\n\nmain().catch((error: unknown) => {\n process.stderr.write(`Daemon fatal: ${String(error)}\\n`);\n process.exitCode = 1;\n});\n\nasync function main(): Promise<void> {\n const dataDir = process.env.MM_DAEMON_DATA_DIR;\n if (!dataDir) {\n throw new Error('MM_DAEMON_DATA_DIR environment variable is required');\n }\n\n const infuraProjectId = process.env.INFURA_PROJECT_ID;\n if (!infuraProjectId) {\n throw new Error('INFURA_PROJECT_ID environment variable is required');\n }\n\n // Password is optional: when absent, the daemon starts without unlocking\n // the keyring (e.g. when the user prefers to call `mm wallet unlock`\n // interactively rather than embed the password in their environment).\n // First-run startup still requires a password; wallet-factory enforces\n // that and surfaces a clear error.\n const passwordRaw = process.env.MM_WALLET_PASSWORD;\n\n const srpRaw = process.env.MM_WALLET_SRP;\n if (!srpRaw) {\n throw new Error('MM_WALLET_SRP environment variable is required');\n }\n\n // Scrub before validation so a throw from Password.from / Srp.from (bad\n // value) does not leave the raw secrets in the long-lived daemon's env.\n delete process.env.MM_WALLET_PASSWORD;\n delete process.env.MM_WALLET_SRP;\n\n const password = passwordRaw ? Password.from(passwordRaw) : undefined;\n const srp = Srp.from(srpRaw);\n\n await ensureOwnerOnlyDirectory(dataDir);\n\n const {\n socketPath: defaultSocketPath,\n pidPath,\n logPath,\n dbPath,\n } = getDaemonPaths(dataDir);\n const socketPath = process.env.MM_DAEMON_SOCKET_PATH ?? defaultSocketPath;\n\n const log = makeLogger(logPath);\n log('Starting daemon...');\n\n // Pre-flight: refuse to take over if a responsive daemon already owns this\n // socket. If the existing PID file is stale (or the socket is dead), clean\n // it up so the exclusive PID-file write below has a chance to succeed.\n await claimDaemonSlot(pidPath, socketPath, log);\n\n const pidFileContents = `${process.pid}\\n${startTime}\\n`;\n\n // Claim the slot atomically BEFORE opening the SQLite database or\n // constructing the Wallet. Two concurrent `daemon start` invocations can\n // both pass `claimDaemonSlot` (the gap between its preflight and the slot\n // write is racy); without this ordering, both would open `wallet.db` and\n // both would run first-run SRP import before one loses the wx race.\n try {\n await writeFile(pidPath, pidFileContents, { flag: 'wx' });\n } catch (error) {\n throw error instanceof Error\n ? Object.assign(error, {\n message: `Failed to claim daemon slot at ${pidPath}: ${error.message}`,\n })\n : /* istanbul ignore next -- node:fs/promises always rejects with an Error */\n new Error(\n `Failed to claim daemon slot at ${pidPath}: ${String(error)}`,\n );\n }\n\n let wallet: Wallet | undefined;\n let dispose: (() => Promise<void>) | undefined;\n let handle: RpcSocketServerHandle | undefined;\n\n try {\n ({ wallet, dispose } = await createWallet({\n databasePath: dbPath,\n password,\n srp,\n infuraProjectId,\n log,\n }));\n\n const constructedWallet = wallet;\n // Arbitrary messenger dispatch is intentional: the CLI exposes the full\n // messenger surface over a Unix socket inside the per-user oclif data\n // directory. The dataDir is chmodded to 0o700 above and the socket to\n // 0o600 by the RPC server on bind, so only the owning user can open them,\n // but there is no in-process auth check beyond that filesystem-permission\n // barrier. The messenger is strongly typed by action name; we narrow it\n // once here to the RpcDispatcher shape the `call` handler needs.\n const dispatch = constructedWallet.messenger.call.bind(\n constructedWallet.messenger,\n ) as unknown as RpcDispatcher;\n\n const handlers: RpcHandlerMap = {\n getStatus: defineHandler(\n literal(null),\n async (): Promise<DaemonStatusInfo> => ({\n pid: process.pid,\n uptime: Math.floor((Date.now() - startTime) / 1000),\n }),\n ),\n call: defineHandler(callParamsStruct, async (params) => {\n const [action, ...args] = params;\n return await dispatch(action, ...(args as Json[]));\n }),\n // Exposes the callable surface for discovery: it grows silently as\n // controllers are wired, so consumers need a way to see it without a\n // hand-kept catalog that would rot.\n listActions: defineHandler(\n literal(null),\n async (): Promise<Json> =>\n constructedWallet.messenger.getRegisteredActionTypes(),\n ),\n };\n\n // `startRpcSocketServer` restricts the socket to the owner (chmod 0o600)\n // on bind and never leaves a live server/socket behind if it rejects, so\n // the catch below has nothing of its own to close.\n handle = await startRpcSocketServer({\n socketPath,\n handlers,\n onShutdown: async () => shutdown('RPC shutdown'),\n log,\n });\n } catch (error) {\n // `dispose` is undefined only when `createWallet` itself threw — it has\n // already torn down its own store in that case.\n if (dispose) {\n await dispose();\n }\n // Only remove the PID file if it's still ours (we may have lost the race\n // and the file now belongs to another daemon).\n await removeOwnedPidFile(pidPath, pidFileContents).catch(\n (rmError: unknown) => {\n log(`Failed to remove PID file during cleanup: ${String(rmError)}`);\n },\n );\n throw error;\n }\n\n // Stable non-undefined refs for the shutdown closures (TS won't narrow the\n // outer `let`s across closure escape).\n const activeHandle = handle;\n const activeDispose = dispose;\n\n log(`Daemon started. Socket: ${socketPath}`);\n\n let shutdownPromise: Promise<void> | undefined;\n\n /**\n * Shut down the daemon idempotently. Concurrent calls coalesce.\n *\n * @param reason - A label describing why shutdown was triggered.\n * @returns A promise that resolves when shutdown completes.\n */\n async function shutdown(reason: string): Promise<void> {\n if (shutdownPromise === undefined) {\n log(`Shutting down (${reason})...`);\n shutdownPromise = (async (): Promise<void> => {\n try {\n await activeHandle.close();\n } catch (closeError) {\n log(`handle.close() failed: ${String(closeError)}`);\n }\n try {\n await activeDispose();\n } catch (disposeError) {\n log(`dispose() failed during shutdown: ${String(disposeError)}`);\n }\n await Promise.all([\n removeOwnedPidFile(pidPath, pidFileContents).catch(\n (rmError: unknown) => {\n log(`Failed to remove PID file: ${String(rmError)}`);\n },\n ),\n rm(socketPath, { force: true }).catch((rmError: unknown) => {\n log(`Failed to remove socket file: ${String(rmError)}`);\n }),\n ]);\n })();\n }\n return shutdownPromise;\n }\n\n process.on('SIGTERM', () => {\n /* istanbul ignore next -- `shutdown` logs each step internally; the catch only guards against an unhandled rejection from the signal handler. */\n shutdown('SIGTERM').catch(() => undefined);\n });\n process.on('SIGINT', () => {\n /* istanbul ignore next -- `shutdown` logs each step internally; the catch only guards against an unhandled rejection from the signal handler. */\n shutdown('SIGINT').catch(() => undefined);\n });\n}\n\n/**\n * Refuse to start if a responsive daemon already owns the socket. Otherwise\n * clear any stale PID/socket files so the exclusive PID-file write can\n * proceed.\n *\n * @param pidPath - The PID file path.\n * @param socketPath - The socket path.\n * @param log - Logger for diagnostic messages.\n */\nasync function claimDaemonSlot(\n pidPath: string,\n socketPath: string,\n log: Logger,\n): Promise<void> {\n const existingPid = await readPidFile(pidPath);\n const ping = await pingDaemon(socketPath);\n\n if (ping.status === 'responsive') {\n const pidPart =\n existingPid === undefined\n ? '(no PID file present)'\n : `(pid ${existingPid})`;\n throw new Error(`A daemon is already running on ${socketPath} ${pidPart}`);\n }\n\n // Refuse to clobber when the recorded PID is still alive, regardless of\n // whether the socket exists. Possible scenarios:\n // - `unreachable`: wedged or mid-startup sibling daemon (socket present\n // but not responding to JSON-RPC).\n // - `absent`: a sibling daemon that hasn't yet bound its socket, or one\n // whose socket was manually removed. In either case, removing its PID\n // file would orphan it from `daemon stop`.\n if (existingPid !== undefined && isProcessAlive(existingPid)) {\n const detail =\n ping.status === 'unreachable'\n ? `socket at ${socketPath} is unresponsive (${ping.error.message})`\n : `no socket at ${socketPath}, but pid is still alive`;\n throw new Error(\n `A daemon is already running (pid ${existingPid}): ${detail}. ` +\n `Run \\`mm daemon stop\\` (or \\`mm daemon purge\\`) before starting a new daemon.`,\n );\n }\n\n if (ping.status === 'unreachable') {\n log(`Removing stale socket at ${socketPath} (${ping.error.message}).`);\n }\n // Always clear both files before claiming the slot. The PID file may be\n // corrupt (truncated, partial write from a crashed run); without this, the\n // exclusive `wx` write below would fail with EEXIST and the daemon could\n // not start until a human manually deleted the file.\n await Promise.all([\n rm(pidPath, { force: true }),\n rm(socketPath, { force: true }),\n ]);\n}\n\n/**\n * Remove the PID file only if it still contains our exact contents. Guards\n * against a racing daemon's PID file being removed by this daemon during\n * cleanup.\n *\n * @param pidPath - Path to the PID file.\n * @param expectedContents - The contents we wrote when claiming the slot.\n */\nasync function removeOwnedPidFile(\n pidPath: string,\n expectedContents: string,\n): Promise<void> {\n let actual: string;\n try {\n actual = await readFile(pidPath, 'utf-8');\n } catch (error: unknown) {\n if (isErrorWithCode(error, 'ENOENT')) {\n return;\n }\n throw error;\n }\n if (actual === expectedContents) {\n await rm(pidPath, { force: true });\n }\n}\n\n/**\n * Create a file logger that appends timestamped lines to `logPath`, falling\n * back to stderr if the append fails.\n *\n * @param logPath - The log file path.\n * @returns A logging function.\n */\nfunction makeLogger(logPath: string): Logger {\n return (message: string): void => {\n const line = `[${new Date().toISOString()}] ${message}\\n`;\n appendFile(logPath, line).catch((error: unknown) => {\n process.stderr.write(`[log write failed: ${String(error)}] ${message}\\n`);\n });\n };\n}\n"]}
@@ -39,10 +39,12 @@ async function main() {
39
39
  if (!infuraProjectId) {
40
40
  throw new Error('INFURA_PROJECT_ID environment variable is required');
41
41
  }
42
+ // Password is optional: when absent, the daemon starts without unlocking
43
+ // the keyring (e.g. when the user prefers to call `mm wallet unlock`
44
+ // interactively rather than embed the password in their environment).
45
+ // First-run startup still requires a password; wallet-factory enforces
46
+ // that and surfaces a clear error.
42
47
  const passwordRaw = process.env.MM_WALLET_PASSWORD;
43
- if (!passwordRaw) {
44
- throw new Error('MM_WALLET_PASSWORD environment variable is required');
45
- }
46
48
  const srpRaw = process.env.MM_WALLET_SRP;
47
49
  if (!srpRaw) {
48
50
  throw new Error('MM_WALLET_SRP environment variable is required');
@@ -51,7 +53,7 @@ async function main() {
51
53
  // value) does not leave the raw secrets in the long-lived daemon's env.
52
54
  delete process.env.MM_WALLET_PASSWORD;
53
55
  delete process.env.MM_WALLET_SRP;
54
- const password = Password.from(passwordRaw);
56
+ const password = passwordRaw ? Password.from(passwordRaw) : undefined;
55
57
  const srp = Srp.from(srpRaw);
56
58
  await ensureOwnerOnlyDirectory(dataDir);
57
59
  const { socketPath: defaultSocketPath, pidPath, logPath, dbPath, } = getDaemonPaths(dataDir);