@metamask-previews/wallet-cli 0.0.0-preview-4621eb3 → 0.0.0-preview-8f1765558

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 (143) hide show
  1. package/CHANGELOG.md +2 -7
  2. package/README.md +0 -27
  3. package/dist/daemon/types.cjs.map +1 -1
  4. package/dist/daemon/types.d.cts +0 -33
  5. package/dist/daemon/types.d.cts.map +1 -1
  6. package/dist/daemon/types.d.mts +0 -33
  7. package/dist/daemon/types.d.mts.map +1 -1
  8. package/dist/daemon/types.mjs.map +1 -1
  9. package/dist/persistence/persistence.cjs.map +1 -1
  10. package/dist/persistence/persistence.d.cts +1 -1
  11. package/dist/persistence/persistence.d.cts.map +1 -1
  12. package/dist/persistence/persistence.d.mts +1 -1
  13. package/dist/persistence/persistence.d.mts.map +1 -1
  14. package/dist/persistence/persistence.mjs.map +1 -1
  15. package/package.json +2 -8
  16. package/dist/commands/daemon/call.cjs +0 -83
  17. package/dist/commands/daemon/call.cjs.map +0 -1
  18. package/dist/commands/daemon/call.d.cts +0 -14
  19. package/dist/commands/daemon/call.d.cts.map +0 -1
  20. package/dist/commands/daemon/call.d.mts +0 -14
  21. package/dist/commands/daemon/call.d.mts.map +0 -1
  22. package/dist/commands/daemon/call.mjs +0 -81
  23. package/dist/commands/daemon/call.mjs.map +0 -1
  24. package/dist/commands/daemon/purge.cjs +0 -58
  25. package/dist/commands/daemon/purge.cjs.map +0 -1
  26. package/dist/commands/daemon/purge.d.cts +0 -10
  27. package/dist/commands/daemon/purge.d.cts.map +0 -1
  28. package/dist/commands/daemon/purge.d.mts +0 -10
  29. package/dist/commands/daemon/purge.d.mts.map +0 -1
  30. package/dist/commands/daemon/purge.mjs +0 -56
  31. package/dist/commands/daemon/purge.mjs.map +0 -1
  32. package/dist/commands/daemon/start.cjs +0 -48
  33. package/dist/commands/daemon/start.cjs.map +0 -1
  34. package/dist/commands/daemon/start.d.cts +0 -12
  35. package/dist/commands/daemon/start.d.cts.map +0 -1
  36. package/dist/commands/daemon/start.d.mts +0 -12
  37. package/dist/commands/daemon/start.d.mts.map +0 -1
  38. package/dist/commands/daemon/start.mjs +0 -46
  39. package/dist/commands/daemon/start.mjs.map +0 -1
  40. package/dist/commands/daemon/status.cjs +0 -59
  41. package/dist/commands/daemon/status.cjs.map +0 -1
  42. package/dist/commands/daemon/status.d.cts +0 -7
  43. package/dist/commands/daemon/status.d.cts.map +0 -1
  44. package/dist/commands/daemon/status.d.mts +0 -7
  45. package/dist/commands/daemon/status.d.mts.map +0 -1
  46. package/dist/commands/daemon/status.mjs +0 -57
  47. package/dist/commands/daemon/status.mjs.map +0 -1
  48. package/dist/commands/daemon/stop.cjs +0 -26
  49. package/dist/commands/daemon/stop.cjs.map +0 -1
  50. package/dist/commands/daemon/stop.d.cts +0 -7
  51. package/dist/commands/daemon/stop.d.cts.map +0 -1
  52. package/dist/commands/daemon/stop.d.mts +0 -7
  53. package/dist/commands/daemon/stop.d.mts.map +0 -1
  54. package/dist/commands/daemon/stop.mjs +0 -24
  55. package/dist/commands/daemon/stop.mjs.map +0 -1
  56. package/dist/daemon/daemon-client.cjs +0 -140
  57. package/dist/daemon/daemon-client.cjs.map +0 -1
  58. package/dist/daemon/daemon-client.d.cts +0 -79
  59. package/dist/daemon/daemon-client.d.cts.map +0 -1
  60. package/dist/daemon/daemon-client.d.mts +0 -79
  61. package/dist/daemon/daemon-client.d.mts.map +0 -1
  62. package/dist/daemon/daemon-client.mjs +0 -135
  63. package/dist/daemon/daemon-client.mjs.map +0 -1
  64. package/dist/daemon/daemon-entry.cjs +0 -245
  65. package/dist/daemon/daemon-entry.cjs.map +0 -1
  66. package/dist/daemon/daemon-entry.d.cts +0 -2
  67. package/dist/daemon/daemon-entry.d.cts.map +0 -1
  68. package/dist/daemon/daemon-entry.d.mts +0 -2
  69. package/dist/daemon/daemon-entry.d.mts.map +0 -1
  70. package/dist/daemon/daemon-entry.mjs +0 -243
  71. package/dist/daemon/daemon-entry.mjs.map +0 -1
  72. package/dist/daemon/daemon-spawn.cjs +0 -106
  73. package/dist/daemon/daemon-spawn.cjs.map +0 -1
  74. package/dist/daemon/daemon-spawn.d.cts +0 -28
  75. package/dist/daemon/daemon-spawn.d.cts.map +0 -1
  76. package/dist/daemon/daemon-spawn.d.mts +0 -28
  77. package/dist/daemon/daemon-spawn.d.mts.map +0 -1
  78. package/dist/daemon/daemon-spawn.mjs +0 -102
  79. package/dist/daemon/daemon-spawn.mjs.map +0 -1
  80. package/dist/daemon/data-dir.cjs +0 -23
  81. package/dist/daemon/data-dir.cjs.map +0 -1
  82. package/dist/daemon/data-dir.d.cts +0 -14
  83. package/dist/daemon/data-dir.d.cts.map +0 -1
  84. package/dist/daemon/data-dir.d.mts +0 -14
  85. package/dist/daemon/data-dir.d.mts.map +0 -1
  86. package/dist/daemon/data-dir.mjs +0 -19
  87. package/dist/daemon/data-dir.mjs.map +0 -1
  88. package/dist/daemon/prompts.cjs +0 -21
  89. package/dist/daemon/prompts.cjs.map +0 -1
  90. package/dist/daemon/prompts.d.cts +0 -11
  91. package/dist/daemon/prompts.d.cts.map +0 -1
  92. package/dist/daemon/prompts.d.mts +0 -11
  93. package/dist/daemon/prompts.d.mts.map +0 -1
  94. package/dist/daemon/prompts.mjs +0 -17
  95. package/dist/daemon/prompts.mjs.map +0 -1
  96. package/dist/daemon/rpc-socket-server.cjs +0 -271
  97. package/dist/daemon/rpc-socket-server.cjs.map +0 -1
  98. package/dist/daemon/rpc-socket-server.d.cts +0 -44
  99. package/dist/daemon/rpc-socket-server.d.cts.map +0 -1
  100. package/dist/daemon/rpc-socket-server.d.mts +0 -44
  101. package/dist/daemon/rpc-socket-server.d.mts.map +0 -1
  102. package/dist/daemon/rpc-socket-server.mjs +0 -267
  103. package/dist/daemon/rpc-socket-server.mjs.map +0 -1
  104. package/dist/daemon/socket-line.cjs +0 -89
  105. package/dist/daemon/socket-line.cjs.map +0 -1
  106. package/dist/daemon/socket-line.d.cts +0 -19
  107. package/dist/daemon/socket-line.d.cts.map +0 -1
  108. package/dist/daemon/socket-line.d.mts +0 -19
  109. package/dist/daemon/socket-line.d.mts.map +0 -1
  110. package/dist/daemon/socket-line.mjs +0 -84
  111. package/dist/daemon/socket-line.mjs.map +0 -1
  112. package/dist/daemon/stop-daemon.cjs +0 -103
  113. package/dist/daemon/stop-daemon.cjs.map +0 -1
  114. package/dist/daemon/stop-daemon.d.cts +0 -18
  115. package/dist/daemon/stop-daemon.d.cts.map +0 -1
  116. package/dist/daemon/stop-daemon.d.mts +0 -18
  117. package/dist/daemon/stop-daemon.d.mts.map +0 -1
  118. package/dist/daemon/stop-daemon.mjs +0 -99
  119. package/dist/daemon/stop-daemon.mjs.map +0 -1
  120. package/dist/daemon/utils.cjs +0 -109
  121. package/dist/daemon/utils.cjs.map +0 -1
  122. package/dist/daemon/utils.d.cts +0 -50
  123. package/dist/daemon/utils.d.cts.map +0 -1
  124. package/dist/daemon/utils.d.mts +0 -50
  125. package/dist/daemon/utils.d.mts.map +0 -1
  126. package/dist/daemon/utils.mjs +0 -101
  127. package/dist/daemon/utils.mjs.map +0 -1
  128. package/dist/daemon/wallet-factory.cjs +0 -234
  129. package/dist/daemon/wallet-factory.cjs.map +0 -1
  130. package/dist/daemon/wallet-factory.d.cts +0 -55
  131. package/dist/daemon/wallet-factory.d.cts.map +0 -1
  132. package/dist/daemon/wallet-factory.d.mts +0 -55
  133. package/dist/daemon/wallet-factory.d.mts.map +0 -1
  134. package/dist/daemon/wallet-factory.mjs +0 -230
  135. package/dist/daemon/wallet-factory.mjs.map +0 -1
  136. package/dist/test/run-command.cjs +0 -73
  137. package/dist/test/run-command.cjs.map +0 -1
  138. package/dist/test/run-command.d.cts +0 -27
  139. package/dist/test/run-command.d.cts.map +0 -1
  140. package/dist/test/run-command.d.mts +0 -27
  141. package/dist/test/run-command.d.mts.map +0 -1
  142. package/dist/test/run-command.mjs +0 -69
  143. package/dist/test/run-command.mjs.map +0 -1
package/CHANGELOG.md CHANGED
@@ -9,14 +9,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
9
9
 
10
10
  ### Added
11
11
 
12
- - 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))
13
- - 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))
14
- - 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))
15
12
  - Add SQLite-backed persistence for wallet controller state ([#9067](https://github.com/MetaMask/core/pull/9067))
13
+ - A `KeyValueStore` backed by `better-sqlite3` for synchronous reads and writes.
14
+ - `loadState` to rehydrate persist-flagged controller state from the store and `subscribeToChanges` to write persist-flagged controller state through to disk on every `stateChanged` event.
16
15
  - 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)).
17
16
 
18
- ### Changed
19
-
20
- - Bump `@metamask/wallet` from `^3.0.0` to `^5.0.0` ([#9218](https://github.com/MetaMask/core/pull/9218), [#9263](https://github.com/MetaMask/core/pull/9263))
21
-
22
17
  [Unreleased]: https://github.com/MetaMask/core/
package/README.md CHANGED
@@ -10,33 +10,6 @@ or
10
10
 
11
11
  `npm install @metamask/wallet-cli`
12
12
 
13
- ## Usage
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.
16
-
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
-
19
- ```sh
20
- mm daemon start --infura-project-id <key> --password <pw> --srp "<phrase>"
21
- ```
22
-
23
- Call any messenger action on the running wallet (positional JSON array for arguments, optional `--timeout`):
24
-
25
- ```sh
26
- mm daemon call AccountsController:listAccounts
27
- mm daemon call KeyringController:getState --timeout 10000
28
- ```
29
-
30
- Inspect or tear it down:
31
-
32
- ```sh
33
- mm daemon status # PID + uptime, or why the socket is unreachable
34
- mm daemon stop # graceful shutdown (falls back to SIGTERM/SIGKILL)
35
- mm daemon purge # stop, then delete all daemon state files (--force to skip the prompt)
36
- ```
37
-
38
- State (socket, PID file, log, and the SQLite database) lives in the per-user oclif data directory; override it with `MM_DATA_DIR`.
39
-
40
13
  ## Troubleshooting
41
14
 
42
15
  ### Rebuilding `better-sqlite3`
@@ -1 +1 @@
1
- {"version":3,"file":"types.cjs","sourceRoot":"","sources":["../../src/daemon/types.ts"],"names":[],"mappings":"","sourcesContent":["import type { Json } from '@metamask/utils';\n\n/**\n * Sink for daemon diagnostic messages. A backgrounded daemon's stdio may be\n * discarded, so hosts supply a logger that writes somewhere durable.\n */\nexport type Logger = (message: string) => void;\n\n/**\n * A function that handles a JSON-RPC method call.\n *\n * The `params` argument will be `null` if the client did not provide params.\n */\nexport type RpcHandler = (params: Json) => Promise<Json | void>;\n\n/**\n * A map of RPC method names to their handler functions.\n */\nexport type RpcHandlerMap = Record<string, RpcHandler>;\n\n/**\n * Resolved paths for daemon state files.\n */\nexport type DaemonPaths = {\n socketPath: string;\n pidPath: string;\n logPath: string;\n dbPath: string;\n};\n\n/**\n * Status information returned by the daemon's `getStatus` RPC method.\n */\nexport type DaemonStatusInfo = {\n pid: number;\n uptime: number;\n};\n\n/**\n * Configuration passed to the daemon spawner.\n */\nexport type DaemonSpawnConfig = {\n dataDir: string;\n infuraProjectId: string;\n password: string;\n srp: string;\n packageRoot: string;\n};\n"]}
1
+ {"version":3,"file":"types.cjs","sourceRoot":"","sources":["../../src/daemon/types.ts"],"names":[],"mappings":"","sourcesContent":["/**\n * Resolved paths for daemon state files.\n */\nexport type DaemonPaths = {\n socketPath: string;\n pidPath: string;\n logPath: string;\n dbPath: string;\n};\n"]}
@@ -1,19 +1,3 @@
1
- import type { Json } from "@metamask/utils";
2
- /**
3
- * Sink for daemon diagnostic messages. A backgrounded daemon's stdio may be
4
- * discarded, so hosts supply a logger that writes somewhere durable.
5
- */
6
- export type Logger = (message: string) => void;
7
- /**
8
- * A function that handles a JSON-RPC method call.
9
- *
10
- * The `params` argument will be `null` if the client did not provide params.
11
- */
12
- export type RpcHandler = (params: Json) => Promise<Json | void>;
13
- /**
14
- * A map of RPC method names to their handler functions.
15
- */
16
- export type RpcHandlerMap = Record<string, RpcHandler>;
17
1
  /**
18
2
  * Resolved paths for daemon state files.
19
3
  */
@@ -23,21 +7,4 @@ export type DaemonPaths = {
23
7
  logPath: string;
24
8
  dbPath: string;
25
9
  };
26
- /**
27
- * Status information returned by the daemon's `getStatus` RPC method.
28
- */
29
- export type DaemonStatusInfo = {
30
- pid: number;
31
- uptime: number;
32
- };
33
- /**
34
- * Configuration passed to the daemon spawner.
35
- */
36
- export type DaemonSpawnConfig = {
37
- dataDir: string;
38
- infuraProjectId: string;
39
- password: string;
40
- srp: string;
41
- packageRoot: string;
42
- };
43
10
  //# sourceMappingURL=types.d.cts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.cts","sourceRoot":"","sources":["../../src/daemon/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,wBAAwB;AAE5C;;;GAGG;AACH,MAAM,MAAM,MAAM,GAAG,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;AAE/C;;;;GAIG;AACH,MAAM,MAAM,UAAU,GAAG,CAAC,MAAM,EAAE,IAAI,KAAK,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC;AAEhE;;GAEG;AACH,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;AAEvD;;GAEG;AACH,MAAM,MAAM,WAAW,GAAG;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,gBAAgB,GAAG;IAC7B,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,iBAAiB,GAAG;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,eAAe,EAAE,MAAM,CAAC;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;IACZ,WAAW,EAAE,MAAM,CAAC;CACrB,CAAC"}
1
+ {"version":3,"file":"types.d.cts","sourceRoot":"","sources":["../../src/daemon/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,MAAM,WAAW,GAAG;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC"}
@@ -1,19 +1,3 @@
1
- import type { Json } from "@metamask/utils";
2
- /**
3
- * Sink for daemon diagnostic messages. A backgrounded daemon's stdio may be
4
- * discarded, so hosts supply a logger that writes somewhere durable.
5
- */
6
- export type Logger = (message: string) => void;
7
- /**
8
- * A function that handles a JSON-RPC method call.
9
- *
10
- * The `params` argument will be `null` if the client did not provide params.
11
- */
12
- export type RpcHandler = (params: Json) => Promise<Json | void>;
13
- /**
14
- * A map of RPC method names to their handler functions.
15
- */
16
- export type RpcHandlerMap = Record<string, RpcHandler>;
17
1
  /**
18
2
  * Resolved paths for daemon state files.
19
3
  */
@@ -23,21 +7,4 @@ export type DaemonPaths = {
23
7
  logPath: string;
24
8
  dbPath: string;
25
9
  };
26
- /**
27
- * Status information returned by the daemon's `getStatus` RPC method.
28
- */
29
- export type DaemonStatusInfo = {
30
- pid: number;
31
- uptime: number;
32
- };
33
- /**
34
- * Configuration passed to the daemon spawner.
35
- */
36
- export type DaemonSpawnConfig = {
37
- dataDir: string;
38
- infuraProjectId: string;
39
- password: string;
40
- srp: string;
41
- packageRoot: string;
42
- };
43
10
  //# sourceMappingURL=types.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.mts","sourceRoot":"","sources":["../../src/daemon/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,wBAAwB;AAE5C;;;GAGG;AACH,MAAM,MAAM,MAAM,GAAG,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;AAE/C;;;;GAIG;AACH,MAAM,MAAM,UAAU,GAAG,CAAC,MAAM,EAAE,IAAI,KAAK,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC;AAEhE;;GAEG;AACH,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;AAEvD;;GAEG;AACH,MAAM,MAAM,WAAW,GAAG;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,gBAAgB,GAAG;IAC7B,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,iBAAiB,GAAG;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,eAAe,EAAE,MAAM,CAAC;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;IACZ,WAAW,EAAE,MAAM,CAAC;CACrB,CAAC"}
1
+ {"version":3,"file":"types.d.mts","sourceRoot":"","sources":["../../src/daemon/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,MAAM,WAAW,GAAG;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"types.mjs","sourceRoot":"","sources":["../../src/daemon/types.ts"],"names":[],"mappings":"","sourcesContent":["import type { Json } from '@metamask/utils';\n\n/**\n * Sink for daemon diagnostic messages. A backgrounded daemon's stdio may be\n * discarded, so hosts supply a logger that writes somewhere durable.\n */\nexport type Logger = (message: string) => void;\n\n/**\n * A function that handles a JSON-RPC method call.\n *\n * The `params` argument will be `null` if the client did not provide params.\n */\nexport type RpcHandler = (params: Json) => Promise<Json | void>;\n\n/**\n * A map of RPC method names to their handler functions.\n */\nexport type RpcHandlerMap = Record<string, RpcHandler>;\n\n/**\n * Resolved paths for daemon state files.\n */\nexport type DaemonPaths = {\n socketPath: string;\n pidPath: string;\n logPath: string;\n dbPath: string;\n};\n\n/**\n * Status information returned by the daemon's `getStatus` RPC method.\n */\nexport type DaemonStatusInfo = {\n pid: number;\n uptime: number;\n};\n\n/**\n * Configuration passed to the daemon spawner.\n */\nexport type DaemonSpawnConfig = {\n dataDir: string;\n infuraProjectId: string;\n password: string;\n srp: string;\n packageRoot: string;\n};\n"]}
1
+ {"version":3,"file":"types.mjs","sourceRoot":"","sources":["../../src/daemon/types.ts"],"names":[],"mappings":"","sourcesContent":["/**\n * Resolved paths for daemon state files.\n */\nexport type DaemonPaths = {\n socketPath: string;\n pidPath: string;\n logPath: string;\n dbPath: string;\n};\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"persistence.cjs","sourceRoot":"","sources":["../../src/persistence/persistence.ts"],"names":[],"mappings":"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","sourcesContent":["import type { StateMetadataConstraint } from '@metamask/base-controller';\nimport { hasProperty } from '@metamask/utils';\nimport type { Json } from '@metamask/utils';\nimport type {\n DefaultActions,\n DefaultEvents,\n RootMessenger,\n} from '@metamask/wallet';\nimport type { Patch } from 'immer';\n\nimport type { KeyValueStore } from './KeyValueStore';\n\n/**\n * Handler for a controller's `stateChanged` event: the new controller state and\n * the Immer patches describing what changed.\n */\ntype StateChangedHandler = (\n state: Record<string, Json>,\n patches: Patch[],\n) => void;\n\n/**\n * Construct a store key from a controller name and property name.\n *\n * @param controllerName - The controller name.\n * @param propertyName - The property name.\n * @returns The store key in the format `ControllerName.propertyName`.\n */\nfunction storeKey(controllerName: string, propertyName: string): string {\n return `${controllerName}.${propertyName}`;\n}\n\n/**\n * Load persisted state from the key-value store and reconstruct it as\n * a record keyed by controller name.\n *\n * Keys in the store follow the format `ControllerName.propertyName`.\n * This function groups them into `{ [controllerName]: { [propertyName]: value } }`.\n *\n * Only properties that are currently persist-flagged in `controllerMetadata`\n * are rehydrated. Rows for controllers or properties that no longer exist — or\n * whose `persist` flag has since been disabled — are ignored. This keeps\n * loading symmetric with {@link subscribeToChanges}, which only ever writes\n * persist-flagged properties: without the filter, a migration that stops\n * persisting a property would leave its stale row on disk to be resurrected\n * into the `Wallet` constructor state on the next restart.\n *\n * @param store - The key-value store to read from.\n * @param controllerMetadata - A map from controller name to its state metadata,\n * used to filter out keys that are no longer persist-flagged.\n * @returns A record of controller states, keyed by controller name, suitable\n * for the `state` option of the `Wallet` constructor.\n */\nexport function loadState(\n store: KeyValueStore,\n controllerMetadata: Readonly<\n Record<string, Readonly<StateMetadataConstraint>>\n >,\n): Record<string, Record<string, Json>> {\n const allPairs = store.getAll();\n const state: Record<string, Record<string, Json>> = {};\n\n for (const [key, value] of Object.entries(allPairs)) {\n const dotIndex = key.indexOf('.');\n if (dotIndex === -1) {\n throw new Error(\n `Invalid key in store: '${key}'. Expected format 'ControllerName.propertyName'.`,\n );\n }\n const controllerName = key.slice(0, dotIndex);\n const propertyName = key.slice(dotIndex + 1);\n\n if (!controllerName || !propertyName) {\n throw new Error(\n `Invalid key in store: '${key}'. Both controller name and property name must be non-empty.`,\n );\n }\n\n if (!isPersisted(controllerMetadata[controllerName], propertyName)) {\n continue;\n }\n\n if (!state[controllerName]) {\n state[controllerName] = {};\n }\n state[controllerName][propertyName] = value;\n }\n return state;\n}\n\n/**\n * Subscribe to all controller `stateChanged` events and persist changes\n * to the key-value store.\n *\n * For each controller's metadata, this function determines which state\n * properties are persist-flagged. When a `stateChanged` event fires, it uses\n * the Immer patches to identify which top-level properties changed, filters\n * to only persist-flagged properties, and writes them to the store.\n *\n * @param messenger - The root messenger to subscribe on.\n * @param controllerMetadata - A map from controller name to its state metadata.\n * @param store - The key-value store to write to.\n * @param log - Optional logger for persistence-write failures. Defaults to\n * `console.error` when omitted. A daemon host should supply its own logger,\n * since a backgrounded daemon's stdio may be discarded.\n * @returns A function that unsubscribes all persistence handlers.\n */\nexport function subscribeToChanges(\n messenger: Readonly<RootMessenger<DefaultActions, DefaultEvents>>,\n controllerMetadata: Readonly<\n Record<string, Readonly<StateMetadataConstraint>>\n >,\n store: KeyValueStore,\n log?: (message: string) => void,\n): () => void {\n const unsubscribers: (() => void)[] = [];\n const logFn =\n log ??\n ((message: string): void => {\n console.error(message);\n });\n\n for (const [controllerName, metadata] of Object.entries(controllerMetadata)) {\n const persistedProperties = getPersistPropertyNames(metadata);\n if (persistedProperties.size === 0) {\n continue;\n }\n\n const eventType = `${controllerName}:stateChanged`;\n\n const handler: StateChangedHandler = (state, patches) => {\n const changed = getChangedProperties(patches, persistedProperties);\n\n for (const prop of changed) {\n const key = storeKey(controllerName, prop);\n const removed = !hasProperty(state, prop);\n\n // Derive the value before the try/catch so a throwing `StateDeriver`\n // surfaces as its own error instead of a misreported write failure.\n const persistFlag = metadata[prop]?.persist;\n const value =\n !removed && typeof persistFlag === 'function'\n ? persistFlag(state[prop] as never)\n : state[prop];\n\n try {\n if (removed) {\n store.delete(key);\n } else {\n store.set(key, value);\n }\n } catch (error) {\n // TODO: Surface persistence-write failures up the stack so callers\n // can decide to halt rather than continue with diverging in-memory\n // and on-disk state. For now, log and continue.\n logFn(`Failed to persist state for ${key}: ${String(error)}`);\n }\n }\n };\n\n unsubscribers.push(subscribeToStateChanged(messenger, eventType, handler));\n }\n\n const unsubscribeAll = (): void => {\n while (unsubscribers.length > 0) {\n unsubscribers.pop()?.();\n }\n };\n\n return unsubscribeAll;\n}\n\n/**\n * Subscribe a handler to a controller's `stateChanged` event.\n *\n * The event name is built from a runtime controller name, so it widens to\n * `string` and cannot be proven to be a literal member of the messenger's event\n * union at compile time. This helper localizes that single unavoidable cast\n * behind a typed {@link StateChangedHandler}, so the `(state, patches)` payload\n * shape stays compile-checked at every call site instead of being erased by a\n * statement-level `@ts-expect-error`.\n *\n * @param messenger - The root messenger to subscribe on.\n * @param eventType - The `${controllerName}:stateChanged` event name.\n * @param handler - The state-change handler to register.\n * @returns A function that unsubscribes the handler.\n */\nfunction subscribeToStateChanged(\n messenger: Readonly<RootMessenger<DefaultActions, DefaultEvents>>,\n eventType: string,\n handler: StateChangedHandler,\n): () => void {\n const subscriber = messenger as unknown as {\n subscribe: (eventType: string, handler: StateChangedHandler) => void;\n unsubscribe: (eventType: string, handler: StateChangedHandler) => void;\n };\n subscriber.subscribe(eventType, handler);\n return () => {\n subscriber.unsubscribe(eventType, handler);\n };\n}\n\n/**\n * Determine whether a property is currently persist-flagged.\n *\n * The `persist` flag is truthy when it is `true` or a `StateDeriver` function,\n * and falsy when it is `false` or when the controller or property is absent\n * from the metadata. `loadState` and `subscribeToChanges` share this predicate\n * so the read and write paths can never disagree on what counts as persisted.\n *\n * @param metadata - The controller's state metadata, or `undefined` when the\n * controller is absent from the metadata map.\n * @param propertyName - The property name to check.\n * @returns `true` if the property should be persisted.\n */\nfunction isPersisted(\n metadata: Readonly<StateMetadataConstraint> | undefined,\n propertyName: string,\n): boolean {\n return Boolean(metadata?.[propertyName]?.persist);\n}\n\n/**\n * Get the set of property names whose `persist` metadata is truthy\n * (either `true` or a `StateDeriver` function).\n *\n * @param metadata - The controller's state metadata.\n * @returns A set of property names that should be persisted.\n */\nfunction getPersistPropertyNames(\n metadata: StateMetadataConstraint,\n): ReadonlySet<string> {\n const names = new Set<string>();\n for (const key of Object.keys(metadata)) {\n if (isPersisted(metadata, key)) {\n names.add(key);\n }\n }\n return names;\n}\n\n/**\n * Extracts the set of persist-flagged top-level property names that changed\n * from an array of Immer patches.\n *\n * If any patch has an empty path (indicating a root state replacement),\n * all persist-flagged properties are returned.\n *\n * @param patches - Immer patches from a state update.\n * @param persistedProperties - The set of persist-flagged property names.\n * @returns A set of top-level property names that were modified.\n */\nfunction getChangedProperties(\n patches: Patch[],\n persistedProperties: ReadonlySet<string>,\n): ReadonlySet<string> {\n const changed = new Set<string>();\n for (const patch of patches) {\n if (patch.path.length === 0) {\n return persistedProperties;\n }\n\n const prop = String(patch.path[0]);\n if (persistedProperties.has(prop)) {\n changed.add(prop);\n }\n }\n return changed;\n}\n"]}
1
+ {"version":3,"file":"persistence.cjs","sourceRoot":"","sources":["../../src/persistence/persistence.ts"],"names":[],"mappings":"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","sourcesContent":["import type { StateMetadataConstraint } from '@metamask/base-controller';\nimport { hasProperty } from '@metamask/utils';\nimport type { Json } from '@metamask/utils';\nimport type {\n DefaultActions,\n DefaultEvents,\n RootMessenger,\n} from '@metamask/wallet';\nimport type { Patch } from 'immer';\n\nimport type { KeyValueStore } from './KeyValueStore';\n\n/**\n * Handler for a controller's `stateChanged` event: the new controller state and\n * the Immer patches describing what changed.\n */\ntype StateChangedHandler = (\n state: Record<string, Json>,\n patches: Patch[],\n) => void;\n\n/**\n * Construct a store key from a controller name and property name.\n *\n * @param controllerName - The controller name.\n * @param propertyName - The property name.\n * @returns The store key in the format `ControllerName.propertyName`.\n */\nfunction storeKey(controllerName: string, propertyName: string): string {\n return `${controllerName}.${propertyName}`;\n}\n\n/**\n * Load persisted state from the key-value store and reconstruct it as\n * a record keyed by controller name.\n *\n * Keys in the store follow the format `ControllerName.propertyName`.\n * This function groups them into `{ [controllerName]: { [propertyName]: value } }`.\n *\n * Only properties that are currently persist-flagged in `controllerMetadata`\n * are rehydrated. Rows for controllers or properties that no longer exist — or\n * whose `persist` flag has since been disabled — are ignored. This keeps\n * loading symmetric with {@link subscribeToChanges}, which only ever writes\n * persist-flagged properties: without the filter, a migration that stops\n * persisting a property would leave its stale row on disk to be resurrected\n * into the `Wallet` constructor state on the next restart.\n *\n * @param store - The key-value store to read from.\n * @param controllerMetadata - A map from controller name to its state metadata,\n * used to filter out keys that are no longer persist-flagged.\n * @returns A record of controller states, keyed by controller name, suitable\n * for the `state` option of the `Wallet` constructor.\n */\nexport function loadState(\n store: KeyValueStore,\n controllerMetadata: Readonly<\n Record<string, Readonly<StateMetadataConstraint>>\n >,\n): Record<string, Record<string, Json>> {\n const allPairs = store.getAll();\n const state: Record<string, Record<string, Json>> = {};\n\n for (const [key, value] of Object.entries(allPairs)) {\n const dotIndex = key.indexOf('.');\n if (dotIndex === -1) {\n throw new Error(\n `Invalid key in store: '${key}'. Expected format 'ControllerName.propertyName'.`,\n );\n }\n const controllerName = key.slice(0, dotIndex);\n const propertyName = key.slice(dotIndex + 1);\n\n if (!controllerName || !propertyName) {\n throw new Error(\n `Invalid key in store: '${key}'. Both controller name and property name must be non-empty.`,\n );\n }\n\n if (!isPersisted(controllerMetadata[controllerName], propertyName)) {\n continue;\n }\n\n if (!state[controllerName]) {\n state[controllerName] = {};\n }\n state[controllerName][propertyName] = value;\n }\n return state;\n}\n\n/**\n * Subscribe to all controller `stateChanged` events and persist changes\n * to the key-value store.\n *\n * For each controller's metadata, this function determines which state\n * properties are persist-flagged. When a `stateChanged` event fires, it uses\n * the Immer patches to identify which top-level properties changed, filters\n * to only persist-flagged properties, and writes them to the store.\n *\n * @param messenger - The root messenger to subscribe on.\n * @param controllerMetadata - A map from controller name to its state metadata.\n * @param store - The key-value store to write to.\n * @param log - Optional logger for persistence-write failures. Defaults to\n * `console.error` when omitted. A daemon host should supply its own logger,\n * since a backgrounded daemon's stdio may be discarded.\n * @returns A function that unsubscribes all persistence handlers.\n */\nexport function subscribeToChanges(\n messenger: RootMessenger<DefaultActions, DefaultEvents>,\n controllerMetadata: Readonly<\n Record<string, Readonly<StateMetadataConstraint>>\n >,\n store: KeyValueStore,\n log?: (message: string) => void,\n): () => void {\n const unsubscribers: (() => void)[] = [];\n const logFn =\n log ??\n ((message: string): void => {\n console.error(message);\n });\n\n for (const [controllerName, metadata] of Object.entries(controllerMetadata)) {\n const persistedProperties = getPersistPropertyNames(metadata);\n if (persistedProperties.size === 0) {\n continue;\n }\n\n const eventType = `${controllerName}:stateChanged`;\n\n const handler: StateChangedHandler = (state, patches) => {\n const changed = getChangedProperties(patches, persistedProperties);\n\n for (const prop of changed) {\n const key = storeKey(controllerName, prop);\n const removed = !hasProperty(state, prop);\n\n // Derive the value before the try/catch so a throwing `StateDeriver`\n // surfaces as its own error instead of a misreported write failure.\n const persistFlag = metadata[prop]?.persist;\n const value =\n !removed && typeof persistFlag === 'function'\n ? persistFlag(state[prop] as never)\n : state[prop];\n\n try {\n if (removed) {\n store.delete(key);\n } else {\n store.set(key, value);\n }\n } catch (error) {\n // TODO: Surface persistence-write failures up the stack so callers\n // can decide to halt rather than continue with diverging in-memory\n // and on-disk state. For now, log and continue.\n logFn(`Failed to persist state for ${key}: ${String(error)}`);\n }\n }\n };\n\n unsubscribers.push(subscribeToStateChanged(messenger, eventType, handler));\n }\n\n const unsubscribeAll = (): void => {\n while (unsubscribers.length > 0) {\n unsubscribers.pop()?.();\n }\n };\n\n return unsubscribeAll;\n}\n\n/**\n * Subscribe a handler to a controller's `stateChanged` event.\n *\n * The event name is built from a runtime controller name, so it widens to\n * `string` and cannot be proven to be a literal member of the messenger's event\n * union at compile time. This helper localizes that single unavoidable cast\n * behind a typed {@link StateChangedHandler}, so the `(state, patches)` payload\n * shape stays compile-checked at every call site instead of being erased by a\n * statement-level `@ts-expect-error`.\n *\n * @param messenger - The root messenger to subscribe on.\n * @param eventType - The `${controllerName}:stateChanged` event name.\n * @param handler - The state-change handler to register.\n * @returns A function that unsubscribes the handler.\n */\nfunction subscribeToStateChanged(\n messenger: RootMessenger<DefaultActions, DefaultEvents>,\n eventType: string,\n handler: StateChangedHandler,\n): () => void {\n const subscriber = messenger as unknown as {\n subscribe: (eventType: string, handler: StateChangedHandler) => void;\n unsubscribe: (eventType: string, handler: StateChangedHandler) => void;\n };\n subscriber.subscribe(eventType, handler);\n return () => {\n subscriber.unsubscribe(eventType, handler);\n };\n}\n\n/**\n * Determine whether a property is currently persist-flagged.\n *\n * The `persist` flag is truthy when it is `true` or a `StateDeriver` function,\n * and falsy when it is `false` or when the controller or property is absent\n * from the metadata. `loadState` and `subscribeToChanges` share this predicate\n * so the read and write paths can never disagree on what counts as persisted.\n *\n * @param metadata - The controller's state metadata, or `undefined` when the\n * controller is absent from the metadata map.\n * @param propertyName - The property name to check.\n * @returns `true` if the property should be persisted.\n */\nfunction isPersisted(\n metadata: Readonly<StateMetadataConstraint> | undefined,\n propertyName: string,\n): boolean {\n return Boolean(metadata?.[propertyName]?.persist);\n}\n\n/**\n * Get the set of property names whose `persist` metadata is truthy\n * (either `true` or a `StateDeriver` function).\n *\n * @param metadata - The controller's state metadata.\n * @returns A set of property names that should be persisted.\n */\nfunction getPersistPropertyNames(\n metadata: StateMetadataConstraint,\n): ReadonlySet<string> {\n const names = new Set<string>();\n for (const key of Object.keys(metadata)) {\n if (isPersisted(metadata, key)) {\n names.add(key);\n }\n }\n return names;\n}\n\n/**\n * Extracts the set of persist-flagged top-level property names that changed\n * from an array of Immer patches.\n *\n * If any patch has an empty path (indicating a root state replacement),\n * all persist-flagged properties are returned.\n *\n * @param patches - Immer patches from a state update.\n * @param persistedProperties - The set of persist-flagged property names.\n * @returns A set of top-level property names that were modified.\n */\nfunction getChangedProperties(\n patches: Patch[],\n persistedProperties: ReadonlySet<string>,\n): ReadonlySet<string> {\n const changed = new Set<string>();\n for (const patch of patches) {\n if (patch.path.length === 0) {\n return persistedProperties;\n }\n\n const prop = String(patch.path[0]);\n if (persistedProperties.has(prop)) {\n changed.add(prop);\n }\n }\n return changed;\n}\n"]}
@@ -41,5 +41,5 @@ export declare function loadState(store: KeyValueStore, controllerMetadata: Read
41
41
  * since a backgrounded daemon's stdio may be discarded.
42
42
  * @returns A function that unsubscribes all persistence handlers.
43
43
  */
44
- export declare function subscribeToChanges(messenger: Readonly<RootMessenger<DefaultActions, DefaultEvents>>, controllerMetadata: Readonly<Record<string, Readonly<StateMetadataConstraint>>>, store: KeyValueStore, log?: (message: string) => void): () => void;
44
+ export declare function subscribeToChanges(messenger: RootMessenger<DefaultActions, DefaultEvents>, controllerMetadata: Readonly<Record<string, Readonly<StateMetadataConstraint>>>, store: KeyValueStore, log?: (message: string) => void): () => void;
45
45
  //# sourceMappingURL=persistence.d.cts.map
@@ -1 +1 @@
1
- {"version":3,"file":"persistence.d.cts","sourceRoot":"","sources":["../../src/persistence/persistence.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,uBAAuB,EAAE,kCAAkC;AAEzE,OAAO,KAAK,EAAE,IAAI,EAAE,wBAAwB;AAC5C,OAAO,KAAK,EACV,cAAc,EACd,aAAa,EACb,aAAa,EACd,yBAAyB;AAG1B,OAAO,KAAK,EAAE,aAAa,EAAE,4BAAwB;AAsBrD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,SAAS,CACvB,KAAK,EAAE,aAAa,EACpB,kBAAkB,EAAE,QAAQ,CAC1B,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,uBAAuB,CAAC,CAAC,CAClD,GACA,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,CA8BtC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,kBAAkB,CAChC,SAAS,EAAE,QAAQ,CAAC,aAAa,CAAC,cAAc,EAAE,aAAa,CAAC,CAAC,EACjE,kBAAkB,EAAE,QAAQ,CAC1B,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,uBAAuB,CAAC,CAAC,CAClD,EACD,KAAK,EAAE,aAAa,EACpB,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,GAC9B,MAAM,IAAI,CAwDZ"}
1
+ {"version":3,"file":"persistence.d.cts","sourceRoot":"","sources":["../../src/persistence/persistence.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,uBAAuB,EAAE,kCAAkC;AAEzE,OAAO,KAAK,EAAE,IAAI,EAAE,wBAAwB;AAC5C,OAAO,KAAK,EACV,cAAc,EACd,aAAa,EACb,aAAa,EACd,yBAAyB;AAG1B,OAAO,KAAK,EAAE,aAAa,EAAE,4BAAwB;AAsBrD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,SAAS,CACvB,KAAK,EAAE,aAAa,EACpB,kBAAkB,EAAE,QAAQ,CAC1B,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,uBAAuB,CAAC,CAAC,CAClD,GACA,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,CA8BtC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,kBAAkB,CAChC,SAAS,EAAE,aAAa,CAAC,cAAc,EAAE,aAAa,CAAC,EACvD,kBAAkB,EAAE,QAAQ,CAC1B,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,uBAAuB,CAAC,CAAC,CAClD,EACD,KAAK,EAAE,aAAa,EACpB,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,GAC9B,MAAM,IAAI,CAwDZ"}
@@ -41,5 +41,5 @@ export declare function loadState(store: KeyValueStore, controllerMetadata: Read
41
41
  * since a backgrounded daemon's stdio may be discarded.
42
42
  * @returns A function that unsubscribes all persistence handlers.
43
43
  */
44
- export declare function subscribeToChanges(messenger: Readonly<RootMessenger<DefaultActions, DefaultEvents>>, controllerMetadata: Readonly<Record<string, Readonly<StateMetadataConstraint>>>, store: KeyValueStore, log?: (message: string) => void): () => void;
44
+ export declare function subscribeToChanges(messenger: RootMessenger<DefaultActions, DefaultEvents>, controllerMetadata: Readonly<Record<string, Readonly<StateMetadataConstraint>>>, store: KeyValueStore, log?: (message: string) => void): () => void;
45
45
  //# sourceMappingURL=persistence.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"persistence.d.mts","sourceRoot":"","sources":["../../src/persistence/persistence.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,uBAAuB,EAAE,kCAAkC;AAEzE,OAAO,KAAK,EAAE,IAAI,EAAE,wBAAwB;AAC5C,OAAO,KAAK,EACV,cAAc,EACd,aAAa,EACb,aAAa,EACd,yBAAyB;AAG1B,OAAO,KAAK,EAAE,aAAa,EAAE,4BAAwB;AAsBrD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,SAAS,CACvB,KAAK,EAAE,aAAa,EACpB,kBAAkB,EAAE,QAAQ,CAC1B,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,uBAAuB,CAAC,CAAC,CAClD,GACA,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,CA8BtC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,kBAAkB,CAChC,SAAS,EAAE,QAAQ,CAAC,aAAa,CAAC,cAAc,EAAE,aAAa,CAAC,CAAC,EACjE,kBAAkB,EAAE,QAAQ,CAC1B,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,uBAAuB,CAAC,CAAC,CAClD,EACD,KAAK,EAAE,aAAa,EACpB,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,GAC9B,MAAM,IAAI,CAwDZ"}
1
+ {"version":3,"file":"persistence.d.mts","sourceRoot":"","sources":["../../src/persistence/persistence.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,uBAAuB,EAAE,kCAAkC;AAEzE,OAAO,KAAK,EAAE,IAAI,EAAE,wBAAwB;AAC5C,OAAO,KAAK,EACV,cAAc,EACd,aAAa,EACb,aAAa,EACd,yBAAyB;AAG1B,OAAO,KAAK,EAAE,aAAa,EAAE,4BAAwB;AAsBrD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,SAAS,CACvB,KAAK,EAAE,aAAa,EACpB,kBAAkB,EAAE,QAAQ,CAC1B,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,uBAAuB,CAAC,CAAC,CAClD,GACA,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,CA8BtC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,kBAAkB,CAChC,SAAS,EAAE,aAAa,CAAC,cAAc,EAAE,aAAa,CAAC,EACvD,kBAAkB,EAAE,QAAQ,CAC1B,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,uBAAuB,CAAC,CAAC,CAClD,EACD,KAAK,EAAE,aAAa,EACpB,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,GAC9B,MAAM,IAAI,CAwDZ"}
@@ -1 +1 @@
1
- {"version":3,"file":"persistence.mjs","sourceRoot":"","sources":["../../src/persistence/persistence.ts"],"names":[],"mappings":"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","sourcesContent":["import type { StateMetadataConstraint } from '@metamask/base-controller';\nimport { hasProperty } from '@metamask/utils';\nimport type { Json } from '@metamask/utils';\nimport type {\n DefaultActions,\n DefaultEvents,\n RootMessenger,\n} from '@metamask/wallet';\nimport type { Patch } from 'immer';\n\nimport type { KeyValueStore } from './KeyValueStore';\n\n/**\n * Handler for a controller's `stateChanged` event: the new controller state and\n * the Immer patches describing what changed.\n */\ntype StateChangedHandler = (\n state: Record<string, Json>,\n patches: Patch[],\n) => void;\n\n/**\n * Construct a store key from a controller name and property name.\n *\n * @param controllerName - The controller name.\n * @param propertyName - The property name.\n * @returns The store key in the format `ControllerName.propertyName`.\n */\nfunction storeKey(controllerName: string, propertyName: string): string {\n return `${controllerName}.${propertyName}`;\n}\n\n/**\n * Load persisted state from the key-value store and reconstruct it as\n * a record keyed by controller name.\n *\n * Keys in the store follow the format `ControllerName.propertyName`.\n * This function groups them into `{ [controllerName]: { [propertyName]: value } }`.\n *\n * Only properties that are currently persist-flagged in `controllerMetadata`\n * are rehydrated. Rows for controllers or properties that no longer exist — or\n * whose `persist` flag has since been disabled — are ignored. This keeps\n * loading symmetric with {@link subscribeToChanges}, which only ever writes\n * persist-flagged properties: without the filter, a migration that stops\n * persisting a property would leave its stale row on disk to be resurrected\n * into the `Wallet` constructor state on the next restart.\n *\n * @param store - The key-value store to read from.\n * @param controllerMetadata - A map from controller name to its state metadata,\n * used to filter out keys that are no longer persist-flagged.\n * @returns A record of controller states, keyed by controller name, suitable\n * for the `state` option of the `Wallet` constructor.\n */\nexport function loadState(\n store: KeyValueStore,\n controllerMetadata: Readonly<\n Record<string, Readonly<StateMetadataConstraint>>\n >,\n): Record<string, Record<string, Json>> {\n const allPairs = store.getAll();\n const state: Record<string, Record<string, Json>> = {};\n\n for (const [key, value] of Object.entries(allPairs)) {\n const dotIndex = key.indexOf('.');\n if (dotIndex === -1) {\n throw new Error(\n `Invalid key in store: '${key}'. Expected format 'ControllerName.propertyName'.`,\n );\n }\n const controllerName = key.slice(0, dotIndex);\n const propertyName = key.slice(dotIndex + 1);\n\n if (!controllerName || !propertyName) {\n throw new Error(\n `Invalid key in store: '${key}'. Both controller name and property name must be non-empty.`,\n );\n }\n\n if (!isPersisted(controllerMetadata[controllerName], propertyName)) {\n continue;\n }\n\n if (!state[controllerName]) {\n state[controllerName] = {};\n }\n state[controllerName][propertyName] = value;\n }\n return state;\n}\n\n/**\n * Subscribe to all controller `stateChanged` events and persist changes\n * to the key-value store.\n *\n * For each controller's metadata, this function determines which state\n * properties are persist-flagged. When a `stateChanged` event fires, it uses\n * the Immer patches to identify which top-level properties changed, filters\n * to only persist-flagged properties, and writes them to the store.\n *\n * @param messenger - The root messenger to subscribe on.\n * @param controllerMetadata - A map from controller name to its state metadata.\n * @param store - The key-value store to write to.\n * @param log - Optional logger for persistence-write failures. Defaults to\n * `console.error` when omitted. A daemon host should supply its own logger,\n * since a backgrounded daemon's stdio may be discarded.\n * @returns A function that unsubscribes all persistence handlers.\n */\nexport function subscribeToChanges(\n messenger: Readonly<RootMessenger<DefaultActions, DefaultEvents>>,\n controllerMetadata: Readonly<\n Record<string, Readonly<StateMetadataConstraint>>\n >,\n store: KeyValueStore,\n log?: (message: string) => void,\n): () => void {\n const unsubscribers: (() => void)[] = [];\n const logFn =\n log ??\n ((message: string): void => {\n console.error(message);\n });\n\n for (const [controllerName, metadata] of Object.entries(controllerMetadata)) {\n const persistedProperties = getPersistPropertyNames(metadata);\n if (persistedProperties.size === 0) {\n continue;\n }\n\n const eventType = `${controllerName}:stateChanged`;\n\n const handler: StateChangedHandler = (state, patches) => {\n const changed = getChangedProperties(patches, persistedProperties);\n\n for (const prop of changed) {\n const key = storeKey(controllerName, prop);\n const removed = !hasProperty(state, prop);\n\n // Derive the value before the try/catch so a throwing `StateDeriver`\n // surfaces as its own error instead of a misreported write failure.\n const persistFlag = metadata[prop]?.persist;\n const value =\n !removed && typeof persistFlag === 'function'\n ? persistFlag(state[prop] as never)\n : state[prop];\n\n try {\n if (removed) {\n store.delete(key);\n } else {\n store.set(key, value);\n }\n } catch (error) {\n // TODO: Surface persistence-write failures up the stack so callers\n // can decide to halt rather than continue with diverging in-memory\n // and on-disk state. For now, log and continue.\n logFn(`Failed to persist state for ${key}: ${String(error)}`);\n }\n }\n };\n\n unsubscribers.push(subscribeToStateChanged(messenger, eventType, handler));\n }\n\n const unsubscribeAll = (): void => {\n while (unsubscribers.length > 0) {\n unsubscribers.pop()?.();\n }\n };\n\n return unsubscribeAll;\n}\n\n/**\n * Subscribe a handler to a controller's `stateChanged` event.\n *\n * The event name is built from a runtime controller name, so it widens to\n * `string` and cannot be proven to be a literal member of the messenger's event\n * union at compile time. This helper localizes that single unavoidable cast\n * behind a typed {@link StateChangedHandler}, so the `(state, patches)` payload\n * shape stays compile-checked at every call site instead of being erased by a\n * statement-level `@ts-expect-error`.\n *\n * @param messenger - The root messenger to subscribe on.\n * @param eventType - The `${controllerName}:stateChanged` event name.\n * @param handler - The state-change handler to register.\n * @returns A function that unsubscribes the handler.\n */\nfunction subscribeToStateChanged(\n messenger: Readonly<RootMessenger<DefaultActions, DefaultEvents>>,\n eventType: string,\n handler: StateChangedHandler,\n): () => void {\n const subscriber = messenger as unknown as {\n subscribe: (eventType: string, handler: StateChangedHandler) => void;\n unsubscribe: (eventType: string, handler: StateChangedHandler) => void;\n };\n subscriber.subscribe(eventType, handler);\n return () => {\n subscriber.unsubscribe(eventType, handler);\n };\n}\n\n/**\n * Determine whether a property is currently persist-flagged.\n *\n * The `persist` flag is truthy when it is `true` or a `StateDeriver` function,\n * and falsy when it is `false` or when the controller or property is absent\n * from the metadata. `loadState` and `subscribeToChanges` share this predicate\n * so the read and write paths can never disagree on what counts as persisted.\n *\n * @param metadata - The controller's state metadata, or `undefined` when the\n * controller is absent from the metadata map.\n * @param propertyName - The property name to check.\n * @returns `true` if the property should be persisted.\n */\nfunction isPersisted(\n metadata: Readonly<StateMetadataConstraint> | undefined,\n propertyName: string,\n): boolean {\n return Boolean(metadata?.[propertyName]?.persist);\n}\n\n/**\n * Get the set of property names whose `persist` metadata is truthy\n * (either `true` or a `StateDeriver` function).\n *\n * @param metadata - The controller's state metadata.\n * @returns A set of property names that should be persisted.\n */\nfunction getPersistPropertyNames(\n metadata: StateMetadataConstraint,\n): ReadonlySet<string> {\n const names = new Set<string>();\n for (const key of Object.keys(metadata)) {\n if (isPersisted(metadata, key)) {\n names.add(key);\n }\n }\n return names;\n}\n\n/**\n * Extracts the set of persist-flagged top-level property names that changed\n * from an array of Immer patches.\n *\n * If any patch has an empty path (indicating a root state replacement),\n * all persist-flagged properties are returned.\n *\n * @param patches - Immer patches from a state update.\n * @param persistedProperties - The set of persist-flagged property names.\n * @returns A set of top-level property names that were modified.\n */\nfunction getChangedProperties(\n patches: Patch[],\n persistedProperties: ReadonlySet<string>,\n): ReadonlySet<string> {\n const changed = new Set<string>();\n for (const patch of patches) {\n if (patch.path.length === 0) {\n return persistedProperties;\n }\n\n const prop = String(patch.path[0]);\n if (persistedProperties.has(prop)) {\n changed.add(prop);\n }\n }\n return changed;\n}\n"]}
1
+ {"version":3,"file":"persistence.mjs","sourceRoot":"","sources":["../../src/persistence/persistence.ts"],"names":[],"mappings":"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","sourcesContent":["import type { StateMetadataConstraint } from '@metamask/base-controller';\nimport { hasProperty } from '@metamask/utils';\nimport type { Json } from '@metamask/utils';\nimport type {\n DefaultActions,\n DefaultEvents,\n RootMessenger,\n} from '@metamask/wallet';\nimport type { Patch } from 'immer';\n\nimport type { KeyValueStore } from './KeyValueStore';\n\n/**\n * Handler for a controller's `stateChanged` event: the new controller state and\n * the Immer patches describing what changed.\n */\ntype StateChangedHandler = (\n state: Record<string, Json>,\n patches: Patch[],\n) => void;\n\n/**\n * Construct a store key from a controller name and property name.\n *\n * @param controllerName - The controller name.\n * @param propertyName - The property name.\n * @returns The store key in the format `ControllerName.propertyName`.\n */\nfunction storeKey(controllerName: string, propertyName: string): string {\n return `${controllerName}.${propertyName}`;\n}\n\n/**\n * Load persisted state from the key-value store and reconstruct it as\n * a record keyed by controller name.\n *\n * Keys in the store follow the format `ControllerName.propertyName`.\n * This function groups them into `{ [controllerName]: { [propertyName]: value } }`.\n *\n * Only properties that are currently persist-flagged in `controllerMetadata`\n * are rehydrated. Rows for controllers or properties that no longer exist — or\n * whose `persist` flag has since been disabled — are ignored. This keeps\n * loading symmetric with {@link subscribeToChanges}, which only ever writes\n * persist-flagged properties: without the filter, a migration that stops\n * persisting a property would leave its stale row on disk to be resurrected\n * into the `Wallet` constructor state on the next restart.\n *\n * @param store - The key-value store to read from.\n * @param controllerMetadata - A map from controller name to its state metadata,\n * used to filter out keys that are no longer persist-flagged.\n * @returns A record of controller states, keyed by controller name, suitable\n * for the `state` option of the `Wallet` constructor.\n */\nexport function loadState(\n store: KeyValueStore,\n controllerMetadata: Readonly<\n Record<string, Readonly<StateMetadataConstraint>>\n >,\n): Record<string, Record<string, Json>> {\n const allPairs = store.getAll();\n const state: Record<string, Record<string, Json>> = {};\n\n for (const [key, value] of Object.entries(allPairs)) {\n const dotIndex = key.indexOf('.');\n if (dotIndex === -1) {\n throw new Error(\n `Invalid key in store: '${key}'. Expected format 'ControllerName.propertyName'.`,\n );\n }\n const controllerName = key.slice(0, dotIndex);\n const propertyName = key.slice(dotIndex + 1);\n\n if (!controllerName || !propertyName) {\n throw new Error(\n `Invalid key in store: '${key}'. Both controller name and property name must be non-empty.`,\n );\n }\n\n if (!isPersisted(controllerMetadata[controllerName], propertyName)) {\n continue;\n }\n\n if (!state[controllerName]) {\n state[controllerName] = {};\n }\n state[controllerName][propertyName] = value;\n }\n return state;\n}\n\n/**\n * Subscribe to all controller `stateChanged` events and persist changes\n * to the key-value store.\n *\n * For each controller's metadata, this function determines which state\n * properties are persist-flagged. When a `stateChanged` event fires, it uses\n * the Immer patches to identify which top-level properties changed, filters\n * to only persist-flagged properties, and writes them to the store.\n *\n * @param messenger - The root messenger to subscribe on.\n * @param controllerMetadata - A map from controller name to its state metadata.\n * @param store - The key-value store to write to.\n * @param log - Optional logger for persistence-write failures. Defaults to\n * `console.error` when omitted. A daemon host should supply its own logger,\n * since a backgrounded daemon's stdio may be discarded.\n * @returns A function that unsubscribes all persistence handlers.\n */\nexport function subscribeToChanges(\n messenger: RootMessenger<DefaultActions, DefaultEvents>,\n controllerMetadata: Readonly<\n Record<string, Readonly<StateMetadataConstraint>>\n >,\n store: KeyValueStore,\n log?: (message: string) => void,\n): () => void {\n const unsubscribers: (() => void)[] = [];\n const logFn =\n log ??\n ((message: string): void => {\n console.error(message);\n });\n\n for (const [controllerName, metadata] of Object.entries(controllerMetadata)) {\n const persistedProperties = getPersistPropertyNames(metadata);\n if (persistedProperties.size === 0) {\n continue;\n }\n\n const eventType = `${controllerName}:stateChanged`;\n\n const handler: StateChangedHandler = (state, patches) => {\n const changed = getChangedProperties(patches, persistedProperties);\n\n for (const prop of changed) {\n const key = storeKey(controllerName, prop);\n const removed = !hasProperty(state, prop);\n\n // Derive the value before the try/catch so a throwing `StateDeriver`\n // surfaces as its own error instead of a misreported write failure.\n const persistFlag = metadata[prop]?.persist;\n const value =\n !removed && typeof persistFlag === 'function'\n ? persistFlag(state[prop] as never)\n : state[prop];\n\n try {\n if (removed) {\n store.delete(key);\n } else {\n store.set(key, value);\n }\n } catch (error) {\n // TODO: Surface persistence-write failures up the stack so callers\n // can decide to halt rather than continue with diverging in-memory\n // and on-disk state. For now, log and continue.\n logFn(`Failed to persist state for ${key}: ${String(error)}`);\n }\n }\n };\n\n unsubscribers.push(subscribeToStateChanged(messenger, eventType, handler));\n }\n\n const unsubscribeAll = (): void => {\n while (unsubscribers.length > 0) {\n unsubscribers.pop()?.();\n }\n };\n\n return unsubscribeAll;\n}\n\n/**\n * Subscribe a handler to a controller's `stateChanged` event.\n *\n * The event name is built from a runtime controller name, so it widens to\n * `string` and cannot be proven to be a literal member of the messenger's event\n * union at compile time. This helper localizes that single unavoidable cast\n * behind a typed {@link StateChangedHandler}, so the `(state, patches)` payload\n * shape stays compile-checked at every call site instead of being erased by a\n * statement-level `@ts-expect-error`.\n *\n * @param messenger - The root messenger to subscribe on.\n * @param eventType - The `${controllerName}:stateChanged` event name.\n * @param handler - The state-change handler to register.\n * @returns A function that unsubscribes the handler.\n */\nfunction subscribeToStateChanged(\n messenger: RootMessenger<DefaultActions, DefaultEvents>,\n eventType: string,\n handler: StateChangedHandler,\n): () => void {\n const subscriber = messenger as unknown as {\n subscribe: (eventType: string, handler: StateChangedHandler) => void;\n unsubscribe: (eventType: string, handler: StateChangedHandler) => void;\n };\n subscriber.subscribe(eventType, handler);\n return () => {\n subscriber.unsubscribe(eventType, handler);\n };\n}\n\n/**\n * Determine whether a property is currently persist-flagged.\n *\n * The `persist` flag is truthy when it is `true` or a `StateDeriver` function,\n * and falsy when it is `false` or when the controller or property is absent\n * from the metadata. `loadState` and `subscribeToChanges` share this predicate\n * so the read and write paths can never disagree on what counts as persisted.\n *\n * @param metadata - The controller's state metadata, or `undefined` when the\n * controller is absent from the metadata map.\n * @param propertyName - The property name to check.\n * @returns `true` if the property should be persisted.\n */\nfunction isPersisted(\n metadata: Readonly<StateMetadataConstraint> | undefined,\n propertyName: string,\n): boolean {\n return Boolean(metadata?.[propertyName]?.persist);\n}\n\n/**\n * Get the set of property names whose `persist` metadata is truthy\n * (either `true` or a `StateDeriver` function).\n *\n * @param metadata - The controller's state metadata.\n * @returns A set of property names that should be persisted.\n */\nfunction getPersistPropertyNames(\n metadata: StateMetadataConstraint,\n): ReadonlySet<string> {\n const names = new Set<string>();\n for (const key of Object.keys(metadata)) {\n if (isPersisted(metadata, key)) {\n names.add(key);\n }\n }\n return names;\n}\n\n/**\n * Extracts the set of persist-flagged top-level property names that changed\n * from an array of Immer patches.\n *\n * If any patch has an empty path (indicating a root state replacement),\n * all persist-flagged properties are returned.\n *\n * @param patches - Immer patches from a state update.\n * @param persistedProperties - The set of persist-flagged property names.\n * @returns A set of top-level property names that were modified.\n */\nfunction getChangedProperties(\n patches: Patch[],\n persistedProperties: ReadonlySet<string>,\n): ReadonlySet<string> {\n const changed = new Set<string>();\n for (const patch of patches) {\n if (patch.path.length === 0) {\n return persistedProperties;\n }\n\n const prop = String(patch.path[0]);\n if (persistedProperties.has(prop)) {\n changed.add(prop);\n }\n }\n return changed;\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-4621eb3",
3
+ "version": "0.0.0-preview-8f1765558",
4
4
  "description": "The CLI of @metamask/wallet",
5
5
  "keywords": [
6
6
  "Ethereum",
@@ -43,13 +43,9 @@
43
43
  "test:watch": "NODE_OPTIONS=--experimental-vm-modules jest --watch"
44
44
  },
45
45
  "dependencies": {
46
- "@inquirer/confirm": "^6.0.11",
47
46
  "@metamask/base-controller": "^9.1.0",
48
- "@metamask/remote-feature-flag-controller": "^4.2.2",
49
- "@metamask/rpc-errors": "^7.0.2",
50
- "@metamask/storage-service": "^1.0.2",
51
47
  "@metamask/utils": "^11.11.0",
52
- "@metamask/wallet": "^5.0.0",
48
+ "@metamask/wallet": "^3.0.0",
53
49
  "@oclif/core": "^4.10.5",
54
50
  "better-sqlite3": "^12.9.0",
55
51
  "immer": "^9.0.6"
@@ -61,9 +57,7 @@
61
57
  "@types/jest": "^29.5.14",
62
58
  "deepmerge": "^4.2.2",
63
59
  "jest": "^29.7.0",
64
- "jest-environment-node": "^29.7.0",
65
60
  "ts-jest": "^29.2.5",
66
- "tsx": "^4.20.5",
67
61
  "typescript": "~5.3.3"
68
62
  },
69
63
  "oclif": {
@@ -1,83 +0,0 @@
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 utils_2 = require("../../daemon/utils.cjs");
8
- class DaemonCall extends core_1.Command {
9
- async run() {
10
- const { args, flags } = await this.parse(DaemonCall);
11
- const { action } = args;
12
- const timeoutMs = flags.timeout;
13
- // The daemon's `call` RPC expects `[action, ...args]`. `JSON.parse` returns
14
- // `unknown`, but anything it produces is structurally `Json`, so we cast to
15
- // `Json[]` once we've confirmed the parsed payload is an array.
16
- const rpcParams = [action];
17
- if (args.params !== undefined) {
18
- let parsed;
19
- try {
20
- parsed = JSON.parse(args.params);
21
- }
22
- catch {
23
- this.error('params must be valid JSON');
24
- }
25
- if (!Array.isArray(parsed)) {
26
- this.error('params must be a JSON array');
27
- }
28
- rpcParams.push(...parsed);
29
- }
30
- const { socketPath } = (0, paths_1.getDaemonPaths)(this.config.dataDir);
31
- let response;
32
- try {
33
- response = await (0, daemon_client_1.sendCommand)({
34
- socketPath,
35
- method: 'call',
36
- params: rpcParams,
37
- ...(timeoutMs === undefined ? {} : { timeoutMs }),
38
- });
39
- }
40
- catch (error) {
41
- if ((0, utils_2.isErrorWithCode)(error, 'ENOENT') ||
42
- (0, utils_2.isErrorWithCode)(error, 'ECONNREFUSED')) {
43
- this.error('Daemon is not running. Start it with `mm daemon start`.');
44
- }
45
- this.error(error instanceof Error ? error.message : String(error));
46
- }
47
- if ((0, utils_1.isJsonRpcFailure)(response)) {
48
- this.error(`${response.error.message} (code ${String(response.error.code)})`);
49
- }
50
- const isTTY = process.stdout.isTTY ?? false;
51
- if (isTTY) {
52
- this.log(JSON.stringify(response.result, null, 2));
53
- }
54
- else {
55
- process.stdout.write(`${JSON.stringify(response.result)}\n`);
56
- }
57
- }
58
- }
59
- DaemonCall.description = 'Call a messenger action on the wallet daemon';
60
- DaemonCall.examples = [
61
- '<%= config.bin %> daemon call KeyringController:getState',
62
- '<%= config.bin %> daemon call NetworkController:getState',
63
- '<%= config.bin %> daemon call ApprovalController:getState --timeout 10000',
64
- ];
65
- DaemonCall.args = {
66
- action: core_1.Args.string({
67
- description: 'The messenger action name (e.g. AccountsController:listAccounts)',
68
- required: true,
69
- }),
70
- params: core_1.Args.string({
71
- description: 'JSON-encoded arguments array (e.g. \'["arg1", "arg2"]\')',
72
- required: false,
73
- }),
74
- };
75
- DaemonCall.flags = {
76
- timeout: core_1.Flags.integer({
77
- char: 't',
78
- description: 'Response timeout in milliseconds',
79
- required: false,
80
- }),
81
- };
82
- exports.default = DaemonCall;
83
- //# sourceMappingURL=call.cjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"call.cjs","sourceRoot":"","sources":["../../../src/commands/daemon/call.ts"],"names":[],"mappings":";;AACA,2CAAmD;AACnD,sCAAmD;AAEnD,kEAAyD;AACzD,kDAAoD;AACpD,kDAAqD;AAErD,MAAqB,UAAW,SAAQ,cAAO;IA6BtC,KAAK,CAAC,GAAG;QACd,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;QACrD,MAAM,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;QACxB,MAAM,SAAS,GAAG,KAAK,CAAC,OAAO,CAAC;QAEhC,4EAA4E;QAC5E,4EAA4E;QAC5E,gEAAgE;QAChE,MAAM,SAAS,GAAW,CAAC,MAAM,CAAC,CAAC;QACnC,IAAI,IAAI,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;YAC9B,IAAI,MAAe,CAAC;YACpB,IAAI,CAAC;gBACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;YACnC,CAAC;YAAC,MAAM,CAAC;gBACP,IAAI,CAAC,KAAK,CAAC,2BAA2B,CAAC,CAAC;YAC1C,CAAC;YAED,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;gBAC3B,IAAI,CAAC,KAAK,CAAC,6BAA6B,CAAC,CAAC;YAC5C,CAAC;YAED,SAAS,CAAC,IAAI,CAAC,GAAI,MAAiB,CAAC,CAAC;QACxC,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,SAAS;gBACjB,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,IACE,IAAA,uBAAe,EAAC,KAAK,EAAE,QAAQ,CAAC;gBAChC,IAAA,uBAAe,EAAC,KAAK,EAAE,cAAc,CAAC,EACtC,CAAC;gBACD,IAAI,CAAC,KAAK,CAAC,yDAAyD,CAAC,CAAC;YACxE,CAAC;YACD,IAAI,CAAC,KAAK,CAAC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;QACrE,CAAC;QAED,IAAI,IAAA,wBAAgB,EAAC,QAAQ,CAAC,EAAE,CAAC;YAC/B,IAAI,CAAC,KAAK,CACR,GAAG,QAAQ,CAAC,KAAK,CAAC,OAAO,UAAU,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAClE,CAAC;QACJ,CAAC;QAED,MAAM,KAAK,GAAG,OAAO,CAAC,MAAM,CAAC,KAAK,IAAI,KAAK,CAAC;QAC5C,IAAI,KAAK,EAAE,CAAC;YACV,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;QACrD,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QAC/D,CAAC;IACH,CAAC;;AApFe,sBAAW,GAAG,8CAA8C,CAAC;AAE7D,mBAAQ,GAAG;IACzB,0DAA0D;IAC1D,0DAA0D;IAC1D,2EAA2E;CAC5E,CAAC;AAEc,eAAI,GAAG;IACrB,MAAM,EAAE,WAAI,CAAC,MAAM,CAAC;QAClB,WAAW,EACT,kEAAkE;QACpE,QAAQ,EAAE,IAAI;KACf,CAAC;IACF,MAAM,EAAE,WAAI,CAAC,MAAM,CAAC;QAClB,WAAW,EAAE,0DAA0D;QACvE,QAAQ,EAAE,KAAK;KAChB,CAAC;CACH,CAAC;AAEc,gBAAK,GAAG;IACtB,OAAO,EAAE,YAAK,CAAC,OAAO,CAAC;QACrB,IAAI,EAAE,GAAG;QACT,WAAW,EAAE,kCAAkC;QAC/C,QAAQ,EAAE,KAAK;KAChB,CAAC;CACH,CAAC;kBA3BiB,UAAU","sourcesContent":["import type { Json } from '@metamask/utils';\nimport { isJsonRpcFailure } from '@metamask/utils';\nimport { Args, Command, Flags } from '@oclif/core';\n\nimport { sendCommand } from '../../daemon/daemon-client';\nimport { getDaemonPaths } from '../../daemon/paths';\nimport { isErrorWithCode } from '../../daemon/utils';\n\nexport default class DaemonCall extends Command {\n static override description = 'Call a messenger action on the wallet daemon';\n\n static override examples = [\n '<%= config.bin %> daemon call KeyringController:getState',\n '<%= config.bin %> daemon call NetworkController:getState',\n '<%= config.bin %> daemon call ApprovalController:getState --timeout 10000',\n ];\n\n static override args = {\n action: Args.string({\n description:\n 'The messenger action name (e.g. AccountsController:listAccounts)',\n required: true,\n }),\n params: Args.string({\n description: 'JSON-encoded arguments array (e.g. \\'[\"arg1\", \"arg2\"]\\')',\n required: false,\n }),\n };\n\n static override flags = {\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 { args, flags } = await this.parse(DaemonCall);\n const { action } = args;\n const timeoutMs = flags.timeout;\n\n // The daemon's `call` RPC expects `[action, ...args]`. `JSON.parse` returns\n // `unknown`, but anything it produces is structurally `Json`, so we cast to\n // `Json[]` once we've confirmed the parsed payload is an array.\n const rpcParams: Json[] = [action];\n if (args.params !== undefined) {\n let parsed: unknown;\n try {\n parsed = JSON.parse(args.params);\n } catch {\n this.error('params must be valid JSON');\n }\n\n if (!Array.isArray(parsed)) {\n this.error('params must be a JSON array');\n }\n\n rpcParams.push(...(parsed as Json[]));\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: rpcParams,\n ...(timeoutMs === undefined ? {} : { timeoutMs }),\n });\n } catch (error) {\n if (\n isErrorWithCode(error, 'ENOENT') ||\n isErrorWithCode(error, 'ECONNREFUSED')\n ) {\n this.error('Daemon is not running. Start it with `mm daemon start`.');\n }\n this.error(error instanceof Error ? error.message : String(error));\n }\n\n if (isJsonRpcFailure(response)) {\n this.error(\n `${response.error.message} (code ${String(response.error.code)})`,\n );\n }\n\n const isTTY = process.stdout.isTTY ?? false;\n if (isTTY) {\n this.log(JSON.stringify(response.result, null, 2));\n } else {\n process.stdout.write(`${JSON.stringify(response.result)}\\n`);\n }\n }\n}\n"]}
@@ -1,14 +0,0 @@
1
- import { Command } from "@oclif/core";
2
- export default class DaemonCall extends Command {
3
- static description: string;
4
- static examples: string[];
5
- static args: {
6
- action: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
7
- params: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
8
- };
9
- static flags: {
10
- timeout: import("@oclif/core/interfaces").OptionFlag<number | undefined, import("@oclif/core/interfaces").CustomOptions>;
11
- };
12
- run(): Promise<void>;
13
- }
14
- //# sourceMappingURL=call.d.cts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"call.d.cts","sourceRoot":"","sources":["../../../src/commands/daemon/call.ts"],"names":[],"mappings":"AAEA,OAAO,EAAQ,OAAO,EAAS,oBAAoB;AAMnD,MAAM,CAAC,OAAO,OAAO,UAAW,SAAQ,OAAO;IAC7C,OAAgB,WAAW,SAAkD;IAE7E,OAAgB,QAAQ,WAItB;IAEF,OAAgB,IAAI;;;MAUlB;IAEF,OAAgB,KAAK;;MAMnB;IAEW,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC;CAyDlC"}
@@ -1,14 +0,0 @@
1
- import { Command } from "@oclif/core";
2
- export default class DaemonCall extends Command {
3
- static description: string;
4
- static examples: string[];
5
- static args: {
6
- action: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
7
- params: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
8
- };
9
- static flags: {
10
- timeout: import("@oclif/core/interfaces").OptionFlag<number | undefined, import("@oclif/core/interfaces").CustomOptions>;
11
- };
12
- run(): Promise<void>;
13
- }
14
- //# sourceMappingURL=call.d.mts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"call.d.mts","sourceRoot":"","sources":["../../../src/commands/daemon/call.ts"],"names":[],"mappings":"AAEA,OAAO,EAAQ,OAAO,EAAS,oBAAoB;AAMnD,MAAM,CAAC,OAAO,OAAO,UAAW,SAAQ,OAAO;IAC7C,OAAgB,WAAW,SAAkD;IAE7E,OAAgB,QAAQ,WAItB;IAEF,OAAgB,IAAI;;;MAUlB;IAEF,OAAgB,KAAK;;MAMnB;IAEW,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC;CAyDlC"}
@@ -1,81 +0,0 @@
1
- import { isJsonRpcFailure } from "@metamask/utils";
2
- import { Args, Command, Flags } from "@oclif/core";
3
- import { sendCommand } from "../../daemon/daemon-client.mjs";
4
- import { getDaemonPaths } from "../../daemon/paths.mjs";
5
- import { isErrorWithCode } from "../../daemon/utils.mjs";
6
- class DaemonCall extends Command {
7
- async run() {
8
- const { args, flags } = await this.parse(DaemonCall);
9
- const { action } = args;
10
- const timeoutMs = flags.timeout;
11
- // The daemon's `call` RPC expects `[action, ...args]`. `JSON.parse` returns
12
- // `unknown`, but anything it produces is structurally `Json`, so we cast to
13
- // `Json[]` once we've confirmed the parsed payload is an array.
14
- const rpcParams = [action];
15
- if (args.params !== undefined) {
16
- let parsed;
17
- try {
18
- parsed = JSON.parse(args.params);
19
- }
20
- catch {
21
- this.error('params must be valid JSON');
22
- }
23
- if (!Array.isArray(parsed)) {
24
- this.error('params must be a JSON array');
25
- }
26
- rpcParams.push(...parsed);
27
- }
28
- const { socketPath } = getDaemonPaths(this.config.dataDir);
29
- let response;
30
- try {
31
- response = await sendCommand({
32
- socketPath,
33
- method: 'call',
34
- params: rpcParams,
35
- ...(timeoutMs === undefined ? {} : { timeoutMs }),
36
- });
37
- }
38
- catch (error) {
39
- if (isErrorWithCode(error, 'ENOENT') ||
40
- isErrorWithCode(error, 'ECONNREFUSED')) {
41
- this.error('Daemon is not running. Start it with `mm daemon start`.');
42
- }
43
- this.error(error instanceof Error ? error.message : String(error));
44
- }
45
- if (isJsonRpcFailure(response)) {
46
- this.error(`${response.error.message} (code ${String(response.error.code)})`);
47
- }
48
- const isTTY = process.stdout.isTTY ?? false;
49
- if (isTTY) {
50
- this.log(JSON.stringify(response.result, null, 2));
51
- }
52
- else {
53
- process.stdout.write(`${JSON.stringify(response.result)}\n`);
54
- }
55
- }
56
- }
57
- DaemonCall.description = 'Call a messenger action on the wallet daemon';
58
- DaemonCall.examples = [
59
- '<%= config.bin %> daemon call KeyringController:getState',
60
- '<%= config.bin %> daemon call NetworkController:getState',
61
- '<%= config.bin %> daemon call ApprovalController:getState --timeout 10000',
62
- ];
63
- DaemonCall.args = {
64
- action: Args.string({
65
- description: 'The messenger action name (e.g. AccountsController:listAccounts)',
66
- required: true,
67
- }),
68
- params: Args.string({
69
- description: 'JSON-encoded arguments array (e.g. \'["arg1", "arg2"]\')',
70
- required: false,
71
- }),
72
- };
73
- DaemonCall.flags = {
74
- timeout: Flags.integer({
75
- char: 't',
76
- description: 'Response timeout in milliseconds',
77
- required: false,
78
- }),
79
- };
80
- export default DaemonCall;
81
- //# sourceMappingURL=call.mjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"call.mjs","sourceRoot":"","sources":["../../../src/commands/daemon/call.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Json } from '@metamask/utils';\nimport { isJsonRpcFailure } from '@metamask/utils';\nimport { Args, Command, Flags } from '@oclif/core';\n\nimport { sendCommand } from '../../daemon/daemon-client';\nimport { getDaemonPaths } from '../../daemon/paths';\nimport { isErrorWithCode } from '../../daemon/utils';\n\nexport default class DaemonCall extends Command {\n static override description = 'Call a messenger action on the wallet daemon';\n\n static override examples = [\n '<%= config.bin %> daemon call KeyringController:getState',\n '<%= config.bin %> daemon call NetworkController:getState',\n '<%= config.bin %> daemon call ApprovalController:getState --timeout 10000',\n ];\n\n static override args = {\n action: Args.string({\n description:\n 'The messenger action name (e.g. AccountsController:listAccounts)',\n required: true,\n }),\n params: Args.string({\n description: 'JSON-encoded arguments array (e.g. \\'[\"arg1\", \"arg2\"]\\')',\n required: false,\n }),\n };\n\n static override flags = {\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 { args, flags } = await this.parse(DaemonCall);\n const { action } = args;\n const timeoutMs = flags.timeout;\n\n // The daemon's `call` RPC expects `[action, ...args]`. `JSON.parse` returns\n // `unknown`, but anything it produces is structurally `Json`, so we cast to\n // `Json[]` once we've confirmed the parsed payload is an array.\n const rpcParams: Json[] = [action];\n if (args.params !== undefined) {\n let parsed: unknown;\n try {\n parsed = JSON.parse(args.params);\n } catch {\n this.error('params must be valid JSON');\n }\n\n if (!Array.isArray(parsed)) {\n this.error('params must be a JSON array');\n }\n\n rpcParams.push(...(parsed as Json[]));\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: rpcParams,\n ...(timeoutMs === undefined ? {} : { timeoutMs }),\n });\n } catch (error) {\n if (\n isErrorWithCode(error, 'ENOENT') ||\n isErrorWithCode(error, 'ECONNREFUSED')\n ) {\n this.error('Daemon is not running. Start it with `mm daemon start`.');\n }\n this.error(error instanceof Error ? error.message : String(error));\n }\n\n if (isJsonRpcFailure(response)) {\n this.error(\n `${response.error.message} (code ${String(response.error.code)})`,\n );\n }\n\n const isTTY = process.stdout.isTTY ?? false;\n if (isTTY) {\n this.log(JSON.stringify(response.result, null, 2));\n } else {\n process.stdout.write(`${JSON.stringify(response.result)}\\n`);\n }\n }\n}\n"]}