@metamask-previews/wallet-cli 0.0.0-preview-b3db565c8 → 0.0.0-preview-8bfa290fb

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 (264) hide show
  1. package/CHANGELOG.md +4 -8
  2. package/dist/commands/daemon/call.cjs +79 -0
  3. package/dist/commands/daemon/call.cjs.map +1 -0
  4. package/dist/commands/daemon/{call.d.ts → call.d.cts} +2 -2
  5. package/dist/commands/daemon/call.d.cts.map +1 -0
  6. package/dist/commands/daemon/call.d.mts +14 -0
  7. package/dist/commands/daemon/call.d.mts.map +1 -0
  8. package/dist/commands/daemon/{call.js → call.mjs} +31 -30
  9. package/dist/commands/daemon/call.mjs.map +1 -0
  10. package/dist/commands/daemon/list.cjs +48 -0
  11. package/dist/commands/daemon/list.cjs.map +1 -0
  12. package/dist/commands/daemon/{list.d.ts → list.d.cts} +2 -2
  13. package/dist/commands/daemon/list.d.cts.map +1 -0
  14. package/dist/commands/daemon/list.d.mts +7 -0
  15. package/dist/commands/daemon/list.d.mts.map +1 -0
  16. package/dist/commands/daemon/{list.js → list.mjs} +10 -9
  17. package/dist/commands/daemon/list.mjs.map +1 -0
  18. package/dist/commands/daemon/purge.cjs +58 -0
  19. package/dist/commands/daemon/purge.cjs.map +1 -0
  20. package/dist/commands/daemon/{purge.d.ts → purge.d.cts} +2 -2
  21. package/dist/commands/daemon/purge.d.cts.map +1 -0
  22. package/dist/commands/daemon/purge.d.mts +10 -0
  23. package/dist/commands/daemon/purge.d.mts.map +1 -0
  24. package/dist/commands/daemon/{purge.js → purge.mjs} +20 -19
  25. package/dist/commands/daemon/purge.mjs.map +1 -0
  26. package/dist/commands/daemon/start.cjs +52 -0
  27. package/dist/commands/daemon/{start.js.map → start.cjs.map} +1 -1
  28. package/dist/commands/daemon/{start.d.ts → start.d.cts} +2 -2
  29. package/dist/commands/daemon/start.d.cts.map +1 -0
  30. package/dist/commands/daemon/start.d.mts +12 -0
  31. package/dist/commands/daemon/start.d.mts.map +1 -0
  32. package/dist/commands/daemon/start.mjs +50 -0
  33. package/dist/commands/daemon/start.mjs.map +1 -0
  34. package/dist/commands/daemon/status.cjs +59 -0
  35. package/dist/commands/daemon/status.cjs.map +1 -0
  36. package/dist/commands/daemon/{status.d.ts → status.d.cts} +2 -2
  37. package/dist/commands/daemon/status.d.cts.map +1 -0
  38. package/dist/commands/daemon/status.d.mts +7 -0
  39. package/dist/commands/daemon/status.d.mts.map +1 -0
  40. package/dist/commands/daemon/{status.js → status.mjs} +11 -10
  41. package/dist/commands/daemon/status.mjs.map +1 -0
  42. package/dist/commands/daemon/stop.cjs +26 -0
  43. package/dist/commands/daemon/stop.cjs.map +1 -0
  44. package/dist/commands/daemon/{stop.d.ts → stop.d.cts} +2 -2
  45. package/dist/commands/daemon/stop.d.cts.map +1 -0
  46. package/dist/commands/daemon/stop.d.mts +7 -0
  47. package/dist/commands/daemon/stop.d.mts.map +1 -0
  48. package/dist/commands/daemon/{stop.js → stop.mjs} +10 -9
  49. package/dist/commands/daemon/stop.mjs.map +1 -0
  50. package/dist/commands/wallet/send.cjs +332 -0
  51. package/dist/commands/wallet/send.cjs.map +1 -0
  52. package/dist/commands/wallet/{send.d.ts → send.d.cts} +3 -3
  53. package/dist/commands/wallet/send.d.cts.map +1 -0
  54. package/dist/commands/wallet/send.d.mts +42 -0
  55. package/dist/commands/wallet/send.d.mts.map +1 -0
  56. package/dist/commands/wallet/{send.js → send.mjs} +123 -111
  57. package/dist/commands/wallet/send.mjs.map +1 -0
  58. package/dist/commands/wallet/unlock.cjs +75 -0
  59. package/dist/commands/wallet/unlock.cjs.map +1 -0
  60. package/dist/commands/wallet/{unlock.d.ts → unlock.d.cts} +2 -2
  61. package/dist/commands/wallet/unlock.d.cts.map +1 -0
  62. package/dist/commands/wallet/unlock.d.mts +11 -0
  63. package/dist/commands/wallet/unlock.d.mts.map +1 -0
  64. package/dist/commands/wallet/{unlock.js → unlock.mjs} +30 -29
  65. package/dist/commands/wallet/unlock.mjs.map +1 -0
  66. package/dist/daemon/auto-approval.cjs +86 -0
  67. package/dist/daemon/auto-approval.cjs.map +1 -0
  68. package/dist/daemon/{auto-approval.d.ts → auto-approval.d.cts} +3 -3
  69. package/dist/daemon/auto-approval.d.cts.map +1 -0
  70. package/dist/daemon/auto-approval.d.mts +22 -0
  71. package/dist/daemon/auto-approval.d.mts.map +1 -0
  72. package/dist/daemon/{auto-approval.js → auto-approval.mjs} +1 -1
  73. package/dist/daemon/auto-approval.mjs.map +1 -0
  74. package/dist/daemon/daemon-client.cjs +149 -0
  75. package/dist/daemon/daemon-client.cjs.map +1 -0
  76. package/dist/daemon/{daemon-client.d.ts → daemon-client.d.cts} +2 -2
  77. package/dist/daemon/daemon-client.d.cts.map +1 -0
  78. package/dist/daemon/daemon-client.d.mts +84 -0
  79. package/dist/daemon/daemon-client.d.mts.map +1 -0
  80. package/dist/daemon/{daemon-client.js → daemon-client.mjs} +8 -8
  81. package/dist/daemon/daemon-client.mjs.map +1 -0
  82. package/dist/daemon/daemon-entry.cjs +278 -0
  83. package/dist/daemon/daemon-entry.cjs.map +1 -0
  84. package/dist/daemon/daemon-entry.d.cts +2 -0
  85. package/dist/daemon/daemon-entry.d.cts.map +1 -0
  86. package/dist/daemon/daemon-entry.d.mts +2 -0
  87. package/dist/daemon/daemon-entry.d.mts.map +1 -0
  88. package/dist/daemon/{daemon-entry.js → daemon-entry.mjs} +12 -12
  89. package/dist/daemon/daemon-entry.mjs.map +1 -0
  90. package/dist/daemon/daemon-spawn.cjs +138 -0
  91. package/dist/daemon/daemon-spawn.cjs.map +1 -0
  92. package/dist/daemon/{daemon-spawn.d.ts → daemon-spawn.d.cts} +2 -2
  93. package/dist/daemon/daemon-spawn.d.cts.map +1 -0
  94. package/dist/daemon/daemon-spawn.d.mts +28 -0
  95. package/dist/daemon/daemon-spawn.d.mts.map +1 -0
  96. package/dist/daemon/{daemon-spawn.js → daemon-spawn.mjs} +7 -7
  97. package/dist/daemon/daemon-spawn.mjs.map +1 -0
  98. package/dist/daemon/data-dir.cjs +23 -0
  99. package/dist/daemon/data-dir.cjs.map +1 -0
  100. package/dist/daemon/{data-dir.d.ts → data-dir.d.cts} +1 -1
  101. package/dist/daemon/data-dir.d.cts.map +1 -0
  102. package/dist/daemon/data-dir.d.mts +14 -0
  103. package/dist/daemon/data-dir.d.mts.map +1 -0
  104. package/dist/daemon/{data-dir.js → data-dir.mjs} +3 -3
  105. package/dist/daemon/data-dir.mjs.map +1 -0
  106. package/dist/daemon/paths.cjs +20 -0
  107. package/dist/daemon/paths.cjs.map +1 -0
  108. package/dist/daemon/{paths.d.ts → paths.d.cts} +2 -2
  109. package/dist/daemon/paths.d.cts.map +1 -0
  110. package/dist/daemon/paths.d.mts +9 -0
  111. package/dist/daemon/paths.d.mts.map +1 -0
  112. package/dist/daemon/{paths.js → paths.mjs} +2 -2
  113. package/dist/daemon/paths.mjs.map +1 -0
  114. package/dist/daemon/prompts.cjs +55 -0
  115. package/dist/daemon/prompts.cjs.map +1 -0
  116. package/dist/daemon/{prompts.d.ts → prompts.d.cts} +1 -1
  117. package/dist/daemon/prompts.d.cts.map +1 -0
  118. package/dist/daemon/prompts.d.mts +31 -0
  119. package/dist/daemon/prompts.d.mts.map +1 -0
  120. package/dist/daemon/{prompts.js → prompts.mjs} +4 -4
  121. package/dist/daemon/prompts.mjs.map +1 -0
  122. package/dist/daemon/rpc-socket-server.cjs +284 -0
  123. package/dist/daemon/rpc-socket-server.cjs.map +1 -0
  124. package/dist/daemon/{rpc-socket-server.d.ts → rpc-socket-server.d.cts} +2 -2
  125. package/dist/daemon/rpc-socket-server.d.cts.map +1 -0
  126. package/dist/daemon/rpc-socket-server.d.mts +44 -0
  127. package/dist/daemon/rpc-socket-server.d.mts.map +1 -0
  128. package/dist/daemon/{rpc-socket-server.js → rpc-socket-server.mjs} +8 -8
  129. package/dist/daemon/rpc-socket-server.mjs.map +1 -0
  130. package/dist/daemon/secrets.cjs +137 -0
  131. package/dist/daemon/secrets.cjs.map +1 -0
  132. package/dist/daemon/{secrets.d.ts → secrets.d.cts} +1 -1
  133. package/dist/daemon/secrets.d.cts.map +1 -0
  134. package/dist/daemon/secrets.d.mts +72 -0
  135. package/dist/daemon/secrets.d.mts.map +1 -0
  136. package/dist/daemon/{secrets.js → secrets.mjs} +23 -11
  137. package/dist/daemon/secrets.mjs.map +1 -0
  138. package/dist/daemon/send-transaction.cjs +147 -0
  139. package/dist/daemon/send-transaction.cjs.map +1 -0
  140. package/dist/daemon/{send-transaction.d.ts → send-transaction.d.cts} +9 -9
  141. package/dist/daemon/send-transaction.d.cts.map +1 -0
  142. package/dist/daemon/send-transaction.d.mts +102 -0
  143. package/dist/daemon/send-transaction.d.mts.map +1 -0
  144. package/dist/daemon/{send-transaction.js → send-transaction.mjs} +3 -3
  145. package/dist/daemon/send-transaction.mjs.map +1 -0
  146. package/dist/daemon/socket-line.cjs +93 -0
  147. package/dist/daemon/socket-line.cjs.map +1 -0
  148. package/dist/daemon/{socket-line.d.ts → socket-line.d.cts} +5 -2
  149. package/dist/daemon/socket-line.d.cts.map +1 -0
  150. package/dist/daemon/socket-line.d.mts +21 -0
  151. package/dist/daemon/socket-line.d.mts.map +1 -0
  152. package/dist/daemon/{socket-line.js → socket-line.mjs} +1 -1
  153. package/dist/daemon/socket-line.mjs.map +1 -0
  154. package/dist/daemon/stop-daemon.cjs +117 -0
  155. package/dist/daemon/stop-daemon.cjs.map +1 -0
  156. package/dist/daemon/{stop-daemon.d.ts → stop-daemon.d.cts} +1 -1
  157. package/dist/daemon/stop-daemon.d.cts.map +1 -0
  158. package/dist/daemon/stop-daemon.d.mts +28 -0
  159. package/dist/daemon/stop-daemon.d.mts.map +1 -0
  160. package/dist/daemon/{stop-daemon.js → stop-daemon.mjs} +7 -7
  161. package/dist/daemon/stop-daemon.mjs.map +1 -0
  162. package/dist/daemon/types.cjs +20 -0
  163. package/dist/daemon/types.cjs.map +1 -0
  164. package/dist/daemon/{types.d.ts → types.d.cts} +4 -4
  165. package/dist/daemon/types.d.cts.map +1 -0
  166. package/dist/daemon/types.d.mts +94 -0
  167. package/dist/daemon/types.d.mts.map +1 -0
  168. package/dist/daemon/{types.js → types.mjs} +1 -1
  169. package/dist/daemon/types.mjs.map +1 -0
  170. package/dist/daemon/utils.cjs +172 -0
  171. package/dist/daemon/utils.cjs.map +1 -0
  172. package/dist/daemon/{utils.d.ts → utils.d.cts} +3 -2
  173. package/dist/daemon/utils.d.cts.map +1 -0
  174. package/dist/daemon/utils.d.mts +87 -0
  175. package/dist/daemon/utils.d.mts.map +1 -0
  176. package/dist/daemon/{utils.js → utils.mjs} +3 -3
  177. package/dist/daemon/utils.mjs.map +1 -0
  178. package/dist/daemon/wallet-factory.cjs +341 -0
  179. package/dist/daemon/wallet-factory.cjs.map +1 -0
  180. package/dist/daemon/{wallet-factory.d.ts → wallet-factory.d.cts} +4 -4
  181. package/dist/daemon/wallet-factory.d.cts.map +1 -0
  182. package/dist/daemon/wallet-factory.d.mts +64 -0
  183. package/dist/daemon/wallet-factory.d.mts.map +1 -0
  184. package/dist/daemon/{wallet-factory.js → wallet-factory.mjs} +12 -12
  185. package/dist/daemon/wallet-factory.mjs.map +1 -0
  186. package/dist/persistence/KeyValueStore.cjs +79 -0
  187. package/dist/persistence/KeyValueStore.cjs.map +1 -0
  188. package/dist/persistence/{KeyValueStore.d.ts → KeyValueStore.d.cts} +2 -2
  189. package/dist/persistence/KeyValueStore.d.cts.map +1 -0
  190. package/dist/persistence/KeyValueStore.d.mts +18 -0
  191. package/dist/persistence/KeyValueStore.d.mts.map +1 -0
  192. package/dist/persistence/KeyValueStore.mjs +79 -0
  193. package/dist/persistence/KeyValueStore.mjs.map +1 -0
  194. package/dist/persistence/persistence.cjs +204 -0
  195. package/dist/persistence/persistence.cjs.map +1 -0
  196. package/dist/persistence/{persistence.d.ts → persistence.d.cts} +5 -5
  197. package/dist/persistence/persistence.d.cts.map +1 -0
  198. package/dist/persistence/persistence.d.mts +45 -0
  199. package/dist/persistence/persistence.d.mts.map +1 -0
  200. package/dist/persistence/{persistence.js → persistence.mjs} +2 -2
  201. package/dist/persistence/persistence.mjs.map +1 -0
  202. package/dist/test/run-command.cjs +73 -0
  203. package/dist/test/run-command.cjs.map +1 -0
  204. package/dist/test/{run-command.d.ts → run-command.d.cts} +4 -4
  205. package/dist/test/run-command.d.cts.map +1 -0
  206. package/dist/test/run-command.d.mts +27 -0
  207. package/dist/test/run-command.d.mts.map +1 -0
  208. package/dist/test/{run-command.js → run-command.mjs} +3 -3
  209. package/dist/test/run-command.mjs.map +1 -0
  210. package/package.json +15 -19
  211. package/dist/commands/daemon/call.d.ts.map +0 -1
  212. package/dist/commands/daemon/call.js.map +0 -1
  213. package/dist/commands/daemon/list.d.ts.map +0 -1
  214. package/dist/commands/daemon/list.js.map +0 -1
  215. package/dist/commands/daemon/purge.d.ts.map +0 -1
  216. package/dist/commands/daemon/purge.js.map +0 -1
  217. package/dist/commands/daemon/start.d.ts.map +0 -1
  218. package/dist/commands/daemon/start.js +0 -49
  219. package/dist/commands/daemon/status.d.ts.map +0 -1
  220. package/dist/commands/daemon/status.js.map +0 -1
  221. package/dist/commands/daemon/stop.d.ts.map +0 -1
  222. package/dist/commands/daemon/stop.js.map +0 -1
  223. package/dist/commands/wallet/send.d.ts.map +0 -1
  224. package/dist/commands/wallet/send.js.map +0 -1
  225. package/dist/commands/wallet/unlock.d.ts.map +0 -1
  226. package/dist/commands/wallet/unlock.js.map +0 -1
  227. package/dist/daemon/auto-approval.d.ts.map +0 -1
  228. package/dist/daemon/auto-approval.js.map +0 -1
  229. package/dist/daemon/daemon-client.d.ts.map +0 -1
  230. package/dist/daemon/daemon-client.js.map +0 -1
  231. package/dist/daemon/daemon-entry.d.ts +0 -2
  232. package/dist/daemon/daemon-entry.d.ts.map +0 -1
  233. package/dist/daemon/daemon-entry.js.map +0 -1
  234. package/dist/daemon/daemon-spawn.d.ts.map +0 -1
  235. package/dist/daemon/daemon-spawn.js.map +0 -1
  236. package/dist/daemon/data-dir.d.ts.map +0 -1
  237. package/dist/daemon/data-dir.js.map +0 -1
  238. package/dist/daemon/paths.d.ts.map +0 -1
  239. package/dist/daemon/paths.js.map +0 -1
  240. package/dist/daemon/prompts.d.ts.map +0 -1
  241. package/dist/daemon/prompts.js.map +0 -1
  242. package/dist/daemon/rpc-socket-server.d.ts.map +0 -1
  243. package/dist/daemon/rpc-socket-server.js.map +0 -1
  244. package/dist/daemon/secrets.d.ts.map +0 -1
  245. package/dist/daemon/secrets.js.map +0 -1
  246. package/dist/daemon/send-transaction.d.ts.map +0 -1
  247. package/dist/daemon/send-transaction.js.map +0 -1
  248. package/dist/daemon/socket-line.d.ts.map +0 -1
  249. package/dist/daemon/socket-line.js.map +0 -1
  250. package/dist/daemon/stop-daemon.d.ts.map +0 -1
  251. package/dist/daemon/stop-daemon.js.map +0 -1
  252. package/dist/daemon/types.d.ts.map +0 -1
  253. package/dist/daemon/types.js.map +0 -1
  254. package/dist/daemon/utils.d.ts.map +0 -1
  255. package/dist/daemon/utils.js.map +0 -1
  256. package/dist/daemon/wallet-factory.d.ts.map +0 -1
  257. package/dist/daemon/wallet-factory.js.map +0 -1
  258. package/dist/persistence/KeyValueStore.d.ts.map +0 -1
  259. package/dist/persistence/KeyValueStore.js +0 -59
  260. package/dist/persistence/KeyValueStore.js.map +0 -1
  261. package/dist/persistence/persistence.d.ts.map +0 -1
  262. package/dist/persistence/persistence.js.map +0 -1
  263. package/dist/test/run-command.d.ts.map +0 -1
  264. package/dist/test/run-command.js.map +0 -1
@@ -0,0 +1,87 @@
1
+ /// <reference types="node" />
2
+ import type { JsonRpcError } from "@metamask/utils";
3
+ /**
4
+ * Check whether an unknown error is a Node.js system error with the given code.
5
+ *
6
+ * @param error - The error to check.
7
+ * @param code - The expected error code (e.g. 'ENOENT', 'EPERM').
8
+ * @returns True if the error matches the code.
9
+ */
10
+ export declare function isErrorWithCode(error: unknown, code: string): boolean;
11
+ /**
12
+ * Turn an error thrown while contacting the daemon socket into a user-facing
13
+ * message for `Command.error`, so every command reports the same failure the
14
+ * same way. Distinguishes a stopped daemon (`ENOENT`/`ECONNREFUSED`), a
15
+ * connection dropped mid-request (`ECONNRESET`), and a permission problem
16
+ * (`EACCES`/`EPERM`), falling back to the raw error message.
17
+ *
18
+ * @param error - The value thrown while contacting the daemon.
19
+ * @returns A human-readable explanation of the failure.
20
+ */
21
+ export declare function makeDaemonConnectionError(error: unknown): string;
22
+ /**
23
+ * Format the error of a JSON-RPC failure response into a user-facing message
24
+ * for `Command.error`, so every command reports RPC failures the same way.
25
+ *
26
+ * @param error - The `error` field of a JSON-RPC failure response.
27
+ * @returns The error message annotated with its numeric code.
28
+ */
29
+ export declare function formatJsonRpcError(error: JsonRpcError): string;
30
+ /**
31
+ * Treat an empty string the same as `undefined` — "no value supplied". Used
32
+ * to normalise `--password ''` and `MM_WALLET_PASSWORD=''` so they trigger
33
+ * the interactive prompt rather than sending an empty string to the keyring.
34
+ *
35
+ * @param value - The value to normalise.
36
+ * @returns `undefined` if `value` is `undefined` or `''`; `value` otherwise.
37
+ */
38
+ export declare function emptyToUndefined(value: string | undefined): string | undefined;
39
+ /**
40
+ * Check whether a value is an array of strings. Used to validate untyped RPC
41
+ * results (e.g. the daemon's `listActions`) before treating them as `string[]`.
42
+ *
43
+ * @param value - The value to check.
44
+ * @returns True if the value is an array whose every element is a string.
45
+ */
46
+ export declare function isStringArray(value: unknown): value is string[];
47
+ /**
48
+ * Read a PID from a file. The file may contain just the PID, or the PID on
49
+ * the first line followed by additional metadata (e.g. start time written by
50
+ * the daemon).
51
+ *
52
+ * @param pidPath - The PID file path.
53
+ * @returns The PID, or undefined if the file is missing or its first line is
54
+ * not a positive integer.
55
+ */
56
+ export declare function readPidFile(pidPath: string): Promise<number | undefined>;
57
+ /**
58
+ * Check whether a process is alive by sending signal 0.
59
+ *
60
+ * Treats `ESRCH` as "process is gone", `EPERM` as "process exists but we
61
+ * cannot signal it" (still alive from our perspective), and rethrows
62
+ * anything else so the caller can surface unexpected failures rather than
63
+ * silently assuming the process is dead.
64
+ *
65
+ * @param pid - The process ID to check.
66
+ * @returns True if the process exists.
67
+ */
68
+ export declare function isProcessAlive(pid: number): boolean;
69
+ /**
70
+ * Send a signal to a process. Returns true if the signal was sent, false if
71
+ * the process does not exist (ESRCH). Re-throws on permission errors and
72
+ * other failures.
73
+ *
74
+ * @param pid - The process ID.
75
+ * @param signal - The signal to send.
76
+ * @returns True if the signal was delivered, false if the process is gone.
77
+ */
78
+ export declare function sendSignal(pid: number, signal: NodeJS.Signals): boolean;
79
+ /**
80
+ * Poll until a condition is met or the timeout elapses.
81
+ *
82
+ * @param check - A function that returns true when the condition is met.
83
+ * @param timeoutMs - Maximum time to wait in milliseconds.
84
+ * @returns True if the condition was met, false on timeout.
85
+ */
86
+ export declare function waitFor(check: () => boolean | Promise<boolean>, timeoutMs: number): Promise<boolean>;
87
+ //# sourceMappingURL=utils.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.d.mts","sourceRoot":"","sources":["../../src/daemon/utils.ts"],"names":[],"mappings":";AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,wBAAwB;AAIpD;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAErE;AAED;;;;;;;;;GASG;AACH,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAuBhE;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,YAAY,GAAG,MAAM,CAE9D;AAED;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,MAAM,GAAG,SAAS,GACxB,MAAM,GAAG,SAAS,CAEpB;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,EAAE,CAI/D;AAED;;;;;;;;GAQG;AACH,wBAAsB,WAAW,CAC/B,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAa7B;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAanD;AAED;;;;;;;;GAQG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,OAAO,GAAG,OAAO,CAUvE;AAED;;;;;;GAMG;AACH,wBAAsB,OAAO,CAC3B,KAAK,EAAE,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,EACvC,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,OAAO,CAAC,CASlB"}
@@ -1,5 +1,5 @@
1
- import { isErrorWithCode as hasErrorCode } from '@metamask/utils';
2
- import { readFile } from 'node:fs/promises';
1
+ import { isErrorWithCode as hasErrorCode } from "@metamask/utils";
2
+ import { readFile } from "node:fs/promises";
3
3
  /**
4
4
  * Check whether an unknown error is a Node.js system error with the given code.
5
5
  *
@@ -157,4 +157,4 @@ export async function waitFor(check, timeoutMs) {
157
157
  }
158
158
  return false;
159
159
  }
160
- //# sourceMappingURL=utils.js.map
160
+ //# sourceMappingURL=utils.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.mjs","sourceRoot":"","sources":["../../src/daemon/utils.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,IAAI,YAAY,EAAE,wBAAwB;AAClE,OAAO,EAAE,QAAQ,EAAE,yBAAyB;AAE5C;;;;;;GAMG;AACH,MAAM,UAAU,eAAe,CAAC,KAAc,EAAE,IAAY;IAC1D,OAAO,YAAY,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,IAAI,KAAK,IAAI,CAAC;AACpD,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,yBAAyB,CAAC,KAAc;IACtD,IACE,eAAe,CAAC,KAAK,EAAE,QAAQ,CAAC;QAChC,eAAe,CAAC,KAAK,EAAE,cAAc,CAAC,EACtC,CAAC;QACD,OAAO,yDAAyD,CAAC;IACnE,CAAC;IACD,6EAA6E;IAC7E,yEAAyE;IACzE,IAAI,eAAe,CAAC,KAAK,EAAE,YAAY,CAAC,EAAE,CAAC;QACzC,OAAO,CACL,+DAA+D;YAC/D,oEAAoE,CACrE,CAAC;IACJ,CAAC;IACD,IAAI,eAAe,CAAC,KAAK,EAAE,QAAQ,CAAC,IAAI,eAAe,CAAC,KAAK,EAAE,OAAO,CAAC,EAAE,CAAC;QACxE,OAAO,CACL,0DAA0D;YAC1D,iEAAiE;YACjE,6CAA6C,CAC9C,CAAC;IACJ,CAAC;IACD,OAAO,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAChE,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,kBAAkB,CAAC,KAAmB;IACpD,OAAO,GAAG,KAAK,CAAC,OAAO,UAAU,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC;AACzD,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,gBAAgB,CAC9B,KAAyB;IAEzB,OAAO,KAAK,KAAK,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC;AAC1C,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,aAAa,CAAC,KAAc;IAC1C,OAAO,CACL,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,IAAI,KAAK,QAAQ,CAAC,CACxE,CAAC;AACJ,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,OAAe;IAEf,IAAI,QAAgB,CAAC;IACrB,IAAI,CAAC;QACH,QAAQ,GAAG,MAAM,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IAC9C,CAAC;IAAC,OAAO,KAAc,EAAE,CAAC;QACxB,IAAI,eAAe,CAAC,KAAK,EAAE,QAAQ,CAAC,EAAE,CAAC;YACrC,OAAO,SAAS,CAAC;QACnB,CAAC;QACD,MAAM,KAAK,CAAC;IACd,CAAC;IACD,8EAA8E;IAC9E,MAAM,GAAG,GAAG,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;IACnD,OAAO,MAAM,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC;AAC5D,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,cAAc,CAAC,GAAW;IACxC,IAAI,CAAC;QACH,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;QACrB,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,OAAO,KAAc,EAAE,CAAC;QACxB,IAAI,eAAe,CAAC,KAAK,EAAE,OAAO,CAAC,EAAE,CAAC;YACpC,OAAO,KAAK,CAAC;QACf,CAAC;QACD,IAAI,eAAe,CAAC,KAAK,EAAE,OAAO,CAAC,EAAE,CAAC;YACpC,OAAO,IAAI,CAAC;QACd,CAAC;QACD,MAAM,KAAK,CAAC;IACd,CAAC;AACH,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,UAAU,CAAC,GAAW,EAAE,MAAsB;IAC5D,IAAI,CAAC;QACH,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;QAC1B,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,OAAO,KAAc,EAAE,CAAC;QACxB,IAAI,eAAe,CAAC,KAAK,EAAE,OAAO,CAAC,EAAE,CAAC;YACpC,OAAO,KAAK,CAAC;QACf,CAAC;QACD,MAAM,KAAK,CAAC;IACd,CAAC;AACH,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,OAAO,CAC3B,KAAuC,EACvC,SAAiB;IAEjB,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC;IACxC,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,QAAQ,EAAE,CAAC;QAC7B,IAAI,MAAM,KAAK,EAAE,EAAE,CAAC;YAClB,OAAO,IAAI,CAAC;QACd,CAAC;QACD,MAAM,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC,CAAC;IAC3D,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC","sourcesContent":["import type { JsonRpcError } from '@metamask/utils';\nimport { isErrorWithCode as hasErrorCode } from '@metamask/utils';\nimport { readFile } from 'node:fs/promises';\n\n/**\n * Check whether an unknown error is a Node.js system error with the given code.\n *\n * @param error - The error to check.\n * @param code - The expected error code (e.g. 'ENOENT', 'EPERM').\n * @returns True if the error matches the code.\n */\nexport function isErrorWithCode(error: unknown, code: string): boolean {\n return hasErrorCode(error) && error.code === code;\n}\n\n/**\n * Turn an error thrown while contacting the daemon socket into a user-facing\n * message for `Command.error`, so every command reports the same failure the\n * same way. Distinguishes a stopped daemon (`ENOENT`/`ECONNREFUSED`), a\n * connection dropped mid-request (`ECONNRESET`), and a permission problem\n * (`EACCES`/`EPERM`), falling back to the raw error message.\n *\n * @param error - The value thrown while contacting the daemon.\n * @returns A human-readable explanation of the failure.\n */\nexport function makeDaemonConnectionError(error: unknown): string {\n if (\n isErrorWithCode(error, 'ENOENT') ||\n isErrorWithCode(error, 'ECONNREFUSED')\n ) {\n return 'Daemon is not running. Start it with `mm daemon start`.';\n }\n // A reset drops an already-established connection, so the daemon was running\n // — most likely it crashed mid-request. Don't tell the user to start it.\n if (isErrorWithCode(error, 'ECONNRESET')) {\n return (\n 'Lost the connection to the daemon; it may have crashed while ' +\n 'handling the request. Check `mm daemon status` and the daemon log.'\n );\n }\n if (isErrorWithCode(error, 'EACCES') || isErrorWithCode(error, 'EPERM')) {\n return (\n 'Cannot connect to the daemon socket: permission denied. ' +\n 'The socket may be owned by another user, or MM_DAEMON_DATA_DIR ' +\n 'may point to a directory you cannot access.'\n );\n }\n return error instanceof Error ? error.message : String(error);\n}\n\n/**\n * Format the error of a JSON-RPC failure response into a user-facing message\n * for `Command.error`, so every command reports RPC failures the same way.\n *\n * @param error - The `error` field of a JSON-RPC failure response.\n * @returns The error message annotated with its numeric code.\n */\nexport function formatJsonRpcError(error: JsonRpcError): string {\n return `${error.message} (code ${String(error.code)})`;\n}\n\n/**\n * Treat an empty string the same as `undefined` — \"no value supplied\". Used\n * to normalise `--password ''` and `MM_WALLET_PASSWORD=''` so they trigger\n * the interactive prompt rather than sending an empty string to the keyring.\n *\n * @param value - The value to normalise.\n * @returns `undefined` if `value` is `undefined` or `''`; `value` otherwise.\n */\nexport function emptyToUndefined(\n value: string | undefined,\n): string | undefined {\n return value === '' ? undefined : value;\n}\n\n/**\n * Check whether a value is an array of strings. Used to validate untyped RPC\n * results (e.g. the daemon's `listActions`) before treating them as `string[]`.\n *\n * @param value - The value to check.\n * @returns True if the value is an array whose every element is a string.\n */\nexport function isStringArray(value: unknown): value is string[] {\n return (\n Array.isArray(value) && value.every((item) => typeof item === 'string')\n );\n}\n\n/**\n * Read a PID from a file. The file may contain just the PID, or the PID on\n * the first line followed by additional metadata (e.g. start time written by\n * the daemon).\n *\n * @param pidPath - The PID file path.\n * @returns The PID, or undefined if the file is missing or its first line is\n * not a positive integer.\n */\nexport async function readPidFile(\n pidPath: string,\n): Promise<number | undefined> {\n let contents: string;\n try {\n contents = await readFile(pidPath, 'utf-8');\n } catch (error: unknown) {\n if (isErrorWithCode(error, 'ENOENT')) {\n return undefined;\n }\n throw error;\n }\n // String.prototype.split always returns at least one element, so [0] is safe.\n const pid = Number(contents.split('\\n')[0].trim());\n return Number.isInteger(pid) && pid > 0 ? pid : undefined;\n}\n\n/**\n * Check whether a process is alive by sending signal 0.\n *\n * Treats `ESRCH` as \"process is gone\", `EPERM` as \"process exists but we\n * cannot signal it\" (still alive from our perspective), and rethrows\n * anything else so the caller can surface unexpected failures rather than\n * silently assuming the process is dead.\n *\n * @param pid - The process ID to check.\n * @returns True if the process exists.\n */\nexport function isProcessAlive(pid: number): boolean {\n try {\n process.kill(pid, 0);\n return true;\n } catch (error: unknown) {\n if (isErrorWithCode(error, 'ESRCH')) {\n return false;\n }\n if (isErrorWithCode(error, 'EPERM')) {\n return true;\n }\n throw error;\n }\n}\n\n/**\n * Send a signal to a process. Returns true if the signal was sent, false if\n * the process does not exist (ESRCH). Re-throws on permission errors and\n * other failures.\n *\n * @param pid - The process ID.\n * @param signal - The signal to send.\n * @returns True if the signal was delivered, false if the process is gone.\n */\nexport function sendSignal(pid: number, signal: NodeJS.Signals): boolean {\n try {\n process.kill(pid, signal);\n return true;\n } catch (error: unknown) {\n if (isErrorWithCode(error, 'ESRCH')) {\n return false;\n }\n throw error;\n }\n}\n\n/**\n * Poll until a condition is met or the timeout elapses.\n *\n * @param check - A function that returns true when the condition is met.\n * @param timeoutMs - Maximum time to wait in milliseconds.\n * @returns True if the condition was met, false on timeout.\n */\nexport async function waitFor(\n check: () => boolean | Promise<boolean>,\n timeoutMs: number,\n): Promise<boolean> {\n const deadline = Date.now() + timeoutMs;\n while (Date.now() < deadline) {\n if (await check()) {\n return true;\n }\n await new Promise((resolve) => setTimeout(resolve, 250));\n }\n return false;\n}\n"]}
@@ -0,0 +1,341 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createWallet = void 0;
4
+ const analytics_controller_1 = require("@metamask/analytics-controller");
5
+ const config_registry_controller_1 = require("@metamask/config-registry-controller");
6
+ const messenger_1 = require("@metamask/messenger");
7
+ const remote_feature_flag_controller_1 = require("@metamask/remote-feature-flag-controller");
8
+ const storage_service_1 = require("@metamask/storage-service");
9
+ const wallet_1 = require("@metamask/wallet");
10
+ const promises_1 = require("node:fs/promises");
11
+ const KeyValueStore_js_1 = require("../persistence/KeyValueStore.cjs");
12
+ const persistence_js_1 = require("../persistence/persistence.cjs");
13
+ const auto_approval_js_1 = require("./auto-approval.cjs");
14
+ const IN_MEMORY_DATABASE_PATH = ':memory:';
15
+ /**
16
+ * Build the per-instance options the daemon's `Wallet` is constructed with.
17
+ *
18
+ * Returns a fresh set on every call so the metadata probe and the real wallet
19
+ * never share adapter/service instances (the probe is destroyed, which may
20
+ * tear those instances down).
21
+ *
22
+ * Only the slots wired on `@metamask/wallet` today are populated:
23
+ * - `storageService` — backed by an in-memory adapter. This is the wallet's
24
+ * large-blob store (snap source, caches), distinct from the SQLite
25
+ * `KeyValueStore` that persists controller state; no wired controller
26
+ * offloads durable data to it yet, so in-memory suffices.
27
+ * - `connectivityController` — the `AlwaysOnlineAdapter` exported for
28
+ * node-like hosts that have no platform connectivity signal.
29
+ * - `networkController` — the Infura project ID used to reach Infura RPC
30
+ * endpoints.
31
+ * - `remoteFeatureFlagController` — a `ClientConfigApiService` fetching real
32
+ * flags over the network.
33
+ * - `approvalController` — a no-op `showApprovalRequest` (the daemon is
34
+ * headless); pending requests are accepted separately by the auto-approval
35
+ * subscription `createWallet` installs, not by this hook.
36
+ * - `gasFeeController` — only `clientId: 'cli'` (sent as `X-Client-Id` to the
37
+ * gas API); the API endpoints, poll interval, and compatibility callbacks are
38
+ * left at the wallet package's platform-agnostic defaults (the default
39
+ * `EIP1559APIEndpoint` is already the production URL, so the daemon does not
40
+ * re-specify it).
41
+ * - `transactionController` — swaps processing disabled and no client hooks;
42
+ * see the slot's inline comment for why the daemon relies on the
43
+ * controller's defaults for everything else.
44
+ *
45
+ * The optional `keyringController` slot is intentionally omitted so the
46
+ * controller's built-in defaults (e.g. the PBKDF2 encryptor) apply.
47
+ *
48
+ * @param infuraProjectId - The Infura project ID for the `NetworkController`.
49
+ * @returns The `instanceOptions` for the `Wallet` constructor.
50
+ */
51
+ function buildInstanceOptions(infuraProjectId) {
52
+ return {
53
+ configRegistryApiService: {
54
+ env: config_registry_controller_1.ConfigRegistryApiEnv.PRD,
55
+ },
56
+ approvalController: {
57
+ // The daemon is headless, so there is no UI to open: requests are
58
+ // resolved by the auto-approval subscription (see `subscribeToAutoApproval`)
59
+ // rather than by this hook, which stays a no-op.
60
+ // TODO: surface approval requests over the daemon transport.
61
+ showApprovalRequest: () => undefined,
62
+ },
63
+ connectivityController: {
64
+ connectivityAdapter: new wallet_1.AlwaysOnlineAdapter(),
65
+ },
66
+ gasFeeController: {
67
+ // Identifies the CLI to the gas estimation API via the `X-Client-Id`
68
+ // header.
69
+ clientId: 'cli',
70
+ },
71
+ networkController: {
72
+ infuraProjectId,
73
+ },
74
+ remoteFeatureFlagController: {
75
+ clientConfigApiService: new remote_feature_flag_controller_1.ClientConfigApiService({
76
+ fetch: globalThis.fetch,
77
+ config: {
78
+ // TODO: switch to a CLI-specific `ClientType` once one exists; until
79
+ // then `Extension` buckets feature flags as an extension client.
80
+ client: remote_feature_flag_controller_1.ClientType.Extension,
81
+ distribution: remote_feature_flag_controller_1.DistributionType.Main,
82
+ environment: remote_feature_flag_controller_1.EnvironmentType.Production,
83
+ },
84
+ }),
85
+ getMetaMetricsId: () => 'cli',
86
+ clientVersion: '0.0.0',
87
+ },
88
+ storageService: {
89
+ storage: new storage_service_1.InMemoryStorageAdapter(),
90
+ },
91
+ transactionController: {
92
+ // The CLI exposes no swaps surface, so skip the swaps-specific
93
+ // post-processing a full wallet client runs (mobile makes the same
94
+ // choice; the extension keeps swaps enabled).
95
+ disableSwaps: true,
96
+ // No CLI-specific transaction hooks: the controller's built-in publish
97
+ // path broadcasts through the wired `NetworkController` provider, and the
98
+ // remaining hooks (metrics, notifications, gas-fee tokens) are client-UI
99
+ // concerns the headless daemon has no equivalent for. Every other option
100
+ // (`getPermittedAccounts`, `isSimulationEnabled`, `trace`, …) is left at
101
+ // the controller's default.
102
+ hooks: {},
103
+ },
104
+ };
105
+ }
106
+ /**
107
+ * Register handlers for the `AnalyticsController` actions that
108
+ * `NetworkController` calls to emit RPC service analytics. The daemon does not
109
+ * report analytics, so these do nothing: `getState` returns an empty analytics
110
+ * ID, which stops the controller before it tracks anything, and `trackEvent` is
111
+ * a no-op. Registering them keeps those messenger calls from throwing.
112
+ *
113
+ * @param messenger - The wallet's root messenger.
114
+ */
115
+ function registerAnalyticsStubHandlers(messenger) {
116
+ // Register on an `AnalyticsController`-namespaced messenger, not the root: a
117
+ // messenger can only register handlers for its own namespace, and doing so
118
+ // delegates them up to the root, where `NetworkController` can reach them.
119
+ const analyticsMessenger = new messenger_1.Messenger({
120
+ namespace: 'AnalyticsController',
121
+ parent: messenger,
122
+ });
123
+ analyticsMessenger.registerActionHandler('AnalyticsController:getState', () => ({
124
+ ...(0, analytics_controller_1.getDefaultAnalyticsControllerState)(),
125
+ analyticsId: '',
126
+ }));
127
+ analyticsMessenger.registerActionHandler('AnalyticsController:trackEvent', () => undefined);
128
+ }
129
+ /**
130
+ * Create a configured `Wallet` for daemon use, backed by a SQLite key-value
131
+ * store for controller-state persistence.
132
+ *
133
+ * Loads any previously-persisted controller state from the store, seeds the
134
+ * wallet with it, then subscribes the store to subsequent state changes so all
135
+ * persist-flagged properties are written through.
136
+ *
137
+ * If the store does not yet contain a keyring vault (first run), the supplied
138
+ * secret recovery phrase is imported using the supplied password. On
139
+ * subsequent runs, the persisted vault is reused: when a password is
140
+ * supplied, the wallet is unlocked via `KeyringController:submitPassword` so
141
+ * keyring-bound messenger actions work immediately; when no password is
142
+ * supplied, the wallet starts locked and the caller is expected to invoke
143
+ * `mm wallet unlock` before any keyring-bound operation. First-run startup
144
+ * without a password is rejected (the SRP cannot be imported without one).
145
+ * On a subsequent run, a wrong password rejects startup with a `Failed to
146
+ * unlock the persisted vault` error that wraps the `submitPassword` rejection.
147
+ *
148
+ * On any failure after the store is opened, the store is closed (and the wallet
149
+ * destroyed, if constructed). On a first-run failure, the on-disk database is
150
+ * also removed so a retry does not latch onto an orphaned partial vault — this
151
+ * covers in-process failures only; a hard crash (SIGKILL/power loss) mid-import
152
+ * can still leave a vault on disk.
153
+ *
154
+ * @param config - Wallet configuration.
155
+ * @param config.databasePath - Path to the SQLite database file (or
156
+ * `':memory:'` for ephemeral use).
157
+ * @param config.password - The wallet password. Optional on subsequent runs;
158
+ * when omitted, the daemon starts with a locked keyring. Required on first
159
+ * run (to import the SRP).
160
+ * @param config.srp - The secret recovery phrase (BIP-39 mnemonic). Used
161
+ * only on first run.
162
+ * @param config.infuraProjectId - The Infura project ID for the
163
+ * `NetworkController`.
164
+ * @param config.log - Optional logger for persistence-write and teardown
165
+ * failures. Without it, failures fall back to `console.error` (which a detached
166
+ * daemon's `stdio: 'ignore'` discards).
167
+ * @returns The `Wallet` and a `dispose` handle that tears it down.
168
+ */
169
+ async function createWallet({ databasePath, password, srp, infuraProjectId, log, }) {
170
+ const logFn = log ?? ((message) => console.error(message));
171
+ const store = new KeyValueStore_js_1.KeyValueStore(databasePath);
172
+ let wallet;
173
+ let persistenceUnsubscribe;
174
+ let autoApprovalUnsubscribe;
175
+ let wasFirstRun = false;
176
+ try {
177
+ const state = await loadPersistedState(store, infuraProjectId, logFn);
178
+ wasFirstRun = !hasPersistedKeyring(state);
179
+ // Validate the first-run precondition BEFORE constructing the wallet,
180
+ // so a doomed startup doesn't build a Wallet (and wire persistence
181
+ // handlers) just to tear it down.
182
+ if (wasFirstRun && password === undefined) {
183
+ throw new Error('A password is required on first run to import the secret recovery phrase. ' +
184
+ 'Pass `--password` (or `MM_WALLET_PASSWORD`) on `mm daemon start`.');
185
+ }
186
+ wallet = new wallet_1.Wallet({
187
+ state,
188
+ instanceOptions: buildInstanceOptions(infuraProjectId),
189
+ });
190
+ registerAnalyticsStubHandlers(wallet.messenger);
191
+ persistenceUnsubscribe = (0, persistence_js_1.subscribeToChanges)(wallet.messenger, wallet.controllerMetadata, store, logFn);
192
+ // Installed before `init` so any approval raised during initialization is covered.
193
+ autoApprovalUnsubscribe = (0, auto_approval_js_1.subscribeToAutoApproval)(wallet.messenger, logFn);
194
+ // Complete post-construction controller setup before serving requests —
195
+ // e.g. `NetworkController.init` applies the selected network so a provider
196
+ // is available. `init` settles every step independently; a rejected step
197
+ // leaves the wallet only partially usable, so abort startup (the catch
198
+ // below tears down and, on first run, removes the partial database) rather
199
+ // than serving a degraded daemon.
200
+ const initResults = await wallet.init();
201
+ const initFailures = initResults.filter((result) => result.status === 'rejected');
202
+ for (const failure of initFailures) {
203
+ logFn(`Wallet init step failed: ${String(failure.reason)}`);
204
+ }
205
+ if (initFailures.length > 0) {
206
+ const firstReason = String(initFailures[0].reason);
207
+ throw new Error(`Wallet initialization failed (${initFailures.length} step(s)); refusing to serve a partially initialized wallet. First failure: ${firstReason}`);
208
+ }
209
+ if (wasFirstRun) {
210
+ // The precondition check above throws when `wasFirstRun && password ===
211
+ // undefined`, so `password` is defined here. TS does not correlate the
212
+ // two separate variables, so it cannot narrow `password` from
213
+ // `wasFirstRun` alone — hence the assertion.
214
+ await (0, wallet_1.importSecretRecoveryPhrase)(wallet, password.unwrap(), srp.unwrap());
215
+ }
216
+ else if (password !== undefined) {
217
+ try {
218
+ await wallet.messenger.call('KeyringController:submitPassword', password.unwrap());
219
+ }
220
+ catch (error) {
221
+ throw new Error(`Failed to unlock the persisted vault: ${String(error)}`);
222
+ }
223
+ }
224
+ let disposePromise;
225
+ return {
226
+ wallet,
227
+ dispose: async () => (disposePromise ?? (disposePromise = teardown(persistenceUnsubscribe, autoApprovalUnsubscribe, wallet, store, logFn))),
228
+ };
229
+ }
230
+ catch (error) {
231
+ await teardown(persistenceUnsubscribe, autoApprovalUnsubscribe, wallet, store, logFn);
232
+ if (wasFirstRun && databasePath !== IN_MEMORY_DATABASE_PATH) {
233
+ // Best-effort cleanup of the on-disk SQLite files (main, WAL, SHM) so a
234
+ // partially-persisted KeyringController vault cannot mislead the next run
235
+ // into skipping SRP import. Covers in-process failures only — a crash
236
+ // (SIGKILL/power loss) mid-import leaves the vault on disk.
237
+ await Promise.all([databasePath, `${databasePath}-wal`, `${databasePath}-shm`].map((path) => (0, promises_1.rm)(path, { force: true }).catch((rmError) => {
238
+ logFn(`Failed to remove ${path} during first-run cleanup: ${String(rmError)}`);
239
+ })));
240
+ }
241
+ throw error;
242
+ }
243
+ }
244
+ exports.createWallet = createWallet;
245
+ /**
246
+ * Load persisted controller state, filtered to currently persist-flagged
247
+ * properties.
248
+ *
249
+ * `loadState` filters against the live controller metadata, but that metadata
250
+ * is only knowable from a constructed `Wallet` — and its output is what seeds
251
+ * the real wallet. So this constructs a short-lived probe purely to read the
252
+ * metadata, then tears it down.
253
+ *
254
+ * TODO: drop the probe once `@metamask/wallet` exposes controller metadata
255
+ * without constructing a `Wallet`.
256
+ *
257
+ * @param store - The key-value store to read from.
258
+ * @param infuraProjectId - The Infura project ID for the probe's
259
+ * `NetworkController`.
260
+ * @param logFn - Logger for a probe-teardown failure.
261
+ * @returns The filtered persisted state, suitable for the `Wallet` `state`
262
+ * option.
263
+ */
264
+ async function loadPersistedState(store, infuraProjectId, logFn) {
265
+ const probe = new wallet_1.Wallet({
266
+ instanceOptions: buildInstanceOptions(infuraProjectId),
267
+ });
268
+ registerAnalyticsStubHandlers(probe.messenger);
269
+ try {
270
+ return (0, persistence_js_1.loadState)(store, probe.controllerMetadata);
271
+ }
272
+ finally {
273
+ await probe.destroy().catch((error) => {
274
+ logFn(`Metadata probe destroy failed: ${String(error)}`);
275
+ });
276
+ }
277
+ }
278
+ /**
279
+ * Best-effort unsubscribe: run the function if present, logging (and
280
+ * swallowing) any error so a later teardown step still runs.
281
+ *
282
+ * @param unsubscribe - The unsubscribe function, if one was registered.
283
+ * @param label - Human-readable subscription name for the failure log.
284
+ * @param logFn - Logger for a failure.
285
+ */
286
+ function runUnsubscribe(unsubscribe, label, logFn) {
287
+ if (!unsubscribe) {
288
+ return;
289
+ }
290
+ try {
291
+ unsubscribe();
292
+ }
293
+ catch (error) {
294
+ logFn(`${label} unsubscribe failed during teardown — subscription may remain ` +
295
+ `live until the process exits: ${String(error)}`);
296
+ }
297
+ }
298
+ /**
299
+ * Persistence-safe teardown of a wallet and its store; see {@link
300
+ * CreateWalletResult.dispose} for the ordering rationale. Each step is
301
+ * best-effort, so a failure is logged and the remaining steps still run.
302
+ *
303
+ * @param persistenceUnsubscribe - The persistence-subscription unsubscribe
304
+ * function, if one was registered.
305
+ * @param autoApprovalUnsubscribe - The auto-approval-subscription unsubscribe
306
+ * function, if one was registered.
307
+ * @param wallet - The wallet to destroy, if one was constructed.
308
+ * @param store - The store to close.
309
+ * @param logFn - Logger for step failures.
310
+ */
311
+ async function teardown(persistenceUnsubscribe, autoApprovalUnsubscribe, wallet, store, logFn) {
312
+ runUnsubscribe(persistenceUnsubscribe, 'Persistence', logFn);
313
+ runUnsubscribe(autoApprovalUnsubscribe, 'Auto-approval', logFn);
314
+ if (wallet) {
315
+ try {
316
+ await wallet.destroy();
317
+ }
318
+ catch (error) {
319
+ logFn(`wallet.destroy() failed during teardown: ${String(error)}`);
320
+ }
321
+ }
322
+ try {
323
+ store.close();
324
+ }
325
+ catch (error) {
326
+ logFn(`store.close() failed during teardown: ${String(error)}`);
327
+ }
328
+ }
329
+ /**
330
+ * Determine whether the loaded state already contains a keyring vault.
331
+ *
332
+ * The KeyringController persists its `vault` once an SRP has been imported, so
333
+ * its presence indicates that first-run setup completed before.
334
+ *
335
+ * @param state - The state loaded from the key-value store.
336
+ * @returns True if a KeyringController vault string is present.
337
+ */
338
+ function hasPersistedKeyring(state) {
339
+ return typeof state.KeyringController?.vault === 'string';
340
+ }
341
+ //# sourceMappingURL=wallet-factory.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wallet-factory.cjs","sourceRoot":"","sources":["../../src/daemon/wallet-factory.ts"],"names":[],"mappings":"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","sourcesContent":["import { getDefaultAnalyticsControllerState } from '@metamask/analytics-controller';\nimport type {\n AnalyticsControllerGetStateAction,\n AnalyticsControllerTrackEventAction,\n} from '@metamask/analytics-controller';\nimport { ConfigRegistryApiEnv } from '@metamask/config-registry-controller';\nimport { Messenger } from '@metamask/messenger';\nimport {\n ClientConfigApiService,\n ClientType,\n DistributionType,\n EnvironmentType,\n} from '@metamask/remote-feature-flag-controller';\nimport { InMemoryStorageAdapter } from '@metamask/storage-service';\nimport type { Json } from '@metamask/utils';\nimport {\n AlwaysOnlineAdapter,\n importSecretRecoveryPhrase,\n Wallet,\n} from '@metamask/wallet';\nimport type {\n DefaultActions,\n DefaultEvents,\n RootMessenger,\n WalletOptions,\n} from '@metamask/wallet';\nimport { rm } from 'node:fs/promises';\n\nimport { KeyValueStore } from '../persistence/KeyValueStore.js';\nimport { loadState, subscribeToChanges } from '../persistence/persistence.js';\nimport { subscribeToAutoApproval } from './auto-approval.js';\nimport type { Password, Srp } from './secrets.js';\nimport type { Logger } from './types.js';\n\nconst IN_MEMORY_DATABASE_PATH = ':memory:';\n\nexport type CreateWalletConfig = {\n databasePath: string;\n password?: Password;\n srp: Srp;\n infuraProjectId: string;\n log?: Logger;\n};\n\nexport type CreateWalletResult = {\n wallet: Wallet;\n /**\n * Tear down everything `createWallet` set up, in the order that keeps\n * in-flight persistence writes valid: stop the state-change subscription,\n * destroy the wallet, then close the store (closing first would cause a\n * teardown-time persistence write to fail). Resilient — a failure in any\n * step is logged and the remaining steps still run — and idempotent (repeat\n * calls coalesce onto the same teardown).\n */\n dispose: () => Promise<void>;\n};\n\n/**\n * Build the per-instance options the daemon's `Wallet` is constructed with.\n *\n * Returns a fresh set on every call so the metadata probe and the real wallet\n * never share adapter/service instances (the probe is destroyed, which may\n * tear those instances down).\n *\n * Only the slots wired on `@metamask/wallet` today are populated:\n * - `storageService` — backed by an in-memory adapter. This is the wallet's\n * large-blob store (snap source, caches), distinct from the SQLite\n * `KeyValueStore` that persists controller state; no wired controller\n * offloads durable data to it yet, so in-memory suffices.\n * - `connectivityController` — the `AlwaysOnlineAdapter` exported for\n * node-like hosts that have no platform connectivity signal.\n * - `networkController` — the Infura project ID used to reach Infura RPC\n * endpoints.\n * - `remoteFeatureFlagController` — a `ClientConfigApiService` fetching real\n * flags over the network.\n * - `approvalController` — a no-op `showApprovalRequest` (the daemon is\n * headless); pending requests are accepted separately by the auto-approval\n * subscription `createWallet` installs, not by this hook.\n * - `gasFeeController` — only `clientId: 'cli'` (sent as `X-Client-Id` to the\n * gas API); the API endpoints, poll interval, and compatibility callbacks are\n * left at the wallet package's platform-agnostic defaults (the default\n * `EIP1559APIEndpoint` is already the production URL, so the daemon does not\n * re-specify it).\n * - `transactionController` — swaps processing disabled and no client hooks;\n * see the slot's inline comment for why the daemon relies on the\n * controller's defaults for everything else.\n *\n * The optional `keyringController` slot is intentionally omitted so the\n * controller's built-in defaults (e.g. the PBKDF2 encryptor) apply.\n *\n * @param infuraProjectId - The Infura project ID for the `NetworkController`.\n * @returns The `instanceOptions` for the `Wallet` constructor.\n */\nfunction buildInstanceOptions(\n infuraProjectId: string,\n): WalletOptions['instanceOptions'] {\n return {\n configRegistryApiService: {\n env: ConfigRegistryApiEnv.PRD,\n },\n approvalController: {\n // The daemon is headless, so there is no UI to open: requests are\n // resolved by the auto-approval subscription (see `subscribeToAutoApproval`)\n // rather than by this hook, which stays a no-op.\n // TODO: surface approval requests over the daemon transport.\n showApprovalRequest: (): undefined => undefined,\n },\n connectivityController: {\n connectivityAdapter: new AlwaysOnlineAdapter(),\n },\n gasFeeController: {\n // Identifies the CLI to the gas estimation API via the `X-Client-Id`\n // header.\n clientId: 'cli',\n },\n networkController: {\n infuraProjectId,\n },\n remoteFeatureFlagController: {\n clientConfigApiService: new ClientConfigApiService({\n fetch: globalThis.fetch,\n config: {\n // TODO: switch to a CLI-specific `ClientType` once one exists; until\n // then `Extension` buckets feature flags as an extension client.\n client: ClientType.Extension,\n distribution: DistributionType.Main,\n environment: EnvironmentType.Production,\n },\n }),\n getMetaMetricsId: (): string => 'cli',\n clientVersion: '0.0.0',\n },\n storageService: {\n storage: new InMemoryStorageAdapter(),\n },\n transactionController: {\n // The CLI exposes no swaps surface, so skip the swaps-specific\n // post-processing a full wallet client runs (mobile makes the same\n // choice; the extension keeps swaps enabled).\n disableSwaps: true,\n // No CLI-specific transaction hooks: the controller's built-in publish\n // path broadcasts through the wired `NetworkController` provider, and the\n // remaining hooks (metrics, notifications, gas-fee tokens) are client-UI\n // concerns the headless daemon has no equivalent for. Every other option\n // (`getPermittedAccounts`, `isSimulationEnabled`, `trace`, …) is left at\n // the controller's default.\n hooks: {},\n },\n };\n}\n\n/**\n * Register handlers for the `AnalyticsController` actions that\n * `NetworkController` calls to emit RPC service analytics. The daemon does not\n * report analytics, so these do nothing: `getState` returns an empty analytics\n * ID, which stops the controller before it tracks anything, and `trackEvent` is\n * a no-op. Registering them keeps those messenger calls from throwing.\n *\n * @param messenger - The wallet's root messenger.\n */\nfunction registerAnalyticsStubHandlers(messenger: Wallet['messenger']): void {\n // Register on an `AnalyticsController`-namespaced messenger, not the root: a\n // messenger can only register handlers for its own namespace, and doing so\n // delegates them up to the root, where `NetworkController` can reach them.\n const analyticsMessenger = new Messenger<\n 'AnalyticsController',\n AnalyticsControllerGetStateAction | AnalyticsControllerTrackEventAction,\n never,\n RootMessenger<DefaultActions, DefaultEvents>\n >({\n namespace: 'AnalyticsController',\n parent: messenger as RootMessenger<DefaultActions, DefaultEvents>,\n });\n analyticsMessenger.registerActionHandler(\n 'AnalyticsController:getState',\n () => ({\n ...getDefaultAnalyticsControllerState(),\n analyticsId: '',\n }),\n );\n analyticsMessenger.registerActionHandler(\n 'AnalyticsController:trackEvent',\n () => undefined,\n );\n}\n\n/**\n * Create a configured `Wallet` for daemon use, backed by a SQLite key-value\n * store for controller-state persistence.\n *\n * Loads any previously-persisted controller state from the store, seeds the\n * wallet with it, then subscribes the store to subsequent state changes so all\n * persist-flagged properties are written through.\n *\n * If the store does not yet contain a keyring vault (first run), the supplied\n * secret recovery phrase is imported using the supplied password. On\n * subsequent runs, the persisted vault is reused: when a password is\n * supplied, the wallet is unlocked via `KeyringController:submitPassword` so\n * keyring-bound messenger actions work immediately; when no password is\n * supplied, the wallet starts locked and the caller is expected to invoke\n * `mm wallet unlock` before any keyring-bound operation. First-run startup\n * without a password is rejected (the SRP cannot be imported without one).\n * On a subsequent run, a wrong password rejects startup with a `Failed to\n * unlock the persisted vault` error that wraps the `submitPassword` rejection.\n *\n * On any failure after the store is opened, the store is closed (and the wallet\n * destroyed, if constructed). On a first-run failure, the on-disk database is\n * also removed so a retry does not latch onto an orphaned partial vault — this\n * covers in-process failures only; a hard crash (SIGKILL/power loss) mid-import\n * can still leave a vault on disk.\n *\n * @param config - Wallet configuration.\n * @param config.databasePath - Path to the SQLite database file (or\n * `':memory:'` for ephemeral use).\n * @param config.password - The wallet password. Optional on subsequent runs;\n * when omitted, the daemon starts with a locked keyring. Required on first\n * run (to import the SRP).\n * @param config.srp - The secret recovery phrase (BIP-39 mnemonic). Used\n * only on first run.\n * @param config.infuraProjectId - The Infura project ID for the\n * `NetworkController`.\n * @param config.log - Optional logger for persistence-write and teardown\n * failures. Without it, failures fall back to `console.error` (which a detached\n * daemon's `stdio: 'ignore'` discards).\n * @returns The `Wallet` and a `dispose` handle that tears it down.\n */\nexport async function createWallet({\n databasePath,\n password,\n srp,\n infuraProjectId,\n log,\n}: CreateWalletConfig): Promise<CreateWalletResult> {\n const logFn = log ?? ((message: string): void => console.error(message));\n const store = new KeyValueStore(databasePath);\n let wallet: Wallet | undefined;\n let persistenceUnsubscribe: (() => void) | undefined;\n let autoApprovalUnsubscribe: (() => void) | undefined;\n let wasFirstRun = false;\n\n try {\n const state = await loadPersistedState(store, infuraProjectId, logFn);\n wasFirstRun = !hasPersistedKeyring(state);\n\n // Validate the first-run precondition BEFORE constructing the wallet,\n // so a doomed startup doesn't build a Wallet (and wire persistence\n // handlers) just to tear it down.\n if (wasFirstRun && password === undefined) {\n throw new Error(\n 'A password is required on first run to import the secret recovery phrase. ' +\n 'Pass `--password` (or `MM_WALLET_PASSWORD`) on `mm daemon start`.',\n );\n }\n\n wallet = new Wallet({\n state,\n instanceOptions: buildInstanceOptions(infuraProjectId),\n });\n registerAnalyticsStubHandlers(wallet.messenger);\n persistenceUnsubscribe = subscribeToChanges(\n wallet.messenger,\n wallet.controllerMetadata,\n store,\n logFn,\n );\n\n // Installed before `init` so any approval raised during initialization is covered.\n autoApprovalUnsubscribe = subscribeToAutoApproval(wallet.messenger, logFn);\n\n // Complete post-construction controller setup before serving requests —\n // e.g. `NetworkController.init` applies the selected network so a provider\n // is available. `init` settles every step independently; a rejected step\n // leaves the wallet only partially usable, so abort startup (the catch\n // below tears down and, on first run, removes the partial database) rather\n // than serving a degraded daemon.\n const initResults = await wallet.init();\n const initFailures = initResults.filter(\n (result): result is PromiseRejectedResult => result.status === 'rejected',\n );\n for (const failure of initFailures) {\n logFn(`Wallet init step failed: ${String(failure.reason)}`);\n }\n if (initFailures.length > 0) {\n const firstReason = String(initFailures[0].reason);\n throw new Error(\n `Wallet initialization failed (${initFailures.length} step(s)); refusing to serve a partially initialized wallet. First failure: ${firstReason}`,\n );\n }\n\n if (wasFirstRun) {\n // The precondition check above throws when `wasFirstRun && password ===\n // undefined`, so `password` is defined here. TS does not correlate the\n // two separate variables, so it cannot narrow `password` from\n // `wasFirstRun` alone — hence the assertion.\n await importSecretRecoveryPhrase(\n wallet,\n (password as Password).unwrap(),\n srp.unwrap(),\n );\n } else if (password !== undefined) {\n try {\n await wallet.messenger.call(\n 'KeyringController:submitPassword',\n password.unwrap(),\n );\n } catch (error) {\n throw new Error(\n `Failed to unlock the persisted vault: ${String(error)}`,\n );\n }\n }\n\n let disposePromise: Promise<void> | undefined;\n return {\n wallet,\n dispose: async () =>\n (disposePromise ??= teardown(\n persistenceUnsubscribe,\n autoApprovalUnsubscribe,\n wallet,\n store,\n logFn,\n )),\n };\n } catch (error) {\n await teardown(\n persistenceUnsubscribe,\n autoApprovalUnsubscribe,\n wallet,\n store,\n logFn,\n );\n\n if (wasFirstRun && databasePath !== IN_MEMORY_DATABASE_PATH) {\n // Best-effort cleanup of the on-disk SQLite files (main, WAL, SHM) so a\n // partially-persisted KeyringController vault cannot mislead the next run\n // into skipping SRP import. Covers in-process failures only — a crash\n // (SIGKILL/power loss) mid-import leaves the vault on disk.\n await Promise.all(\n [databasePath, `${databasePath}-wal`, `${databasePath}-shm`].map(\n (path) =>\n rm(path, { force: true }).catch((rmError: unknown) => {\n logFn(\n `Failed to remove ${path} during first-run cleanup: ${String(rmError)}`,\n );\n }),\n ),\n );\n }\n\n throw error;\n }\n}\n\n/**\n * Load persisted controller state, filtered to currently persist-flagged\n * properties.\n *\n * `loadState` filters against the live controller metadata, but that metadata\n * is only knowable from a constructed `Wallet` — and its output is what seeds\n * the real wallet. So this constructs a short-lived probe purely to read the\n * metadata, then tears it down.\n *\n * TODO: drop the probe once `@metamask/wallet` exposes controller metadata\n * without constructing a `Wallet`.\n *\n * @param store - The key-value store to read from.\n * @param infuraProjectId - The Infura project ID for the probe's\n * `NetworkController`.\n * @param logFn - Logger for a probe-teardown failure.\n * @returns The filtered persisted state, suitable for the `Wallet` `state`\n * option.\n */\nasync function loadPersistedState(\n store: KeyValueStore,\n infuraProjectId: string,\n logFn: Logger,\n): Promise<Record<string, Record<string, Json>>> {\n const probe = new Wallet({\n instanceOptions: buildInstanceOptions(infuraProjectId),\n });\n registerAnalyticsStubHandlers(probe.messenger);\n try {\n return loadState(store, probe.controllerMetadata);\n } finally {\n await probe.destroy().catch((error: unknown) => {\n logFn(`Metadata probe destroy failed: ${String(error)}`);\n });\n }\n}\n\n/**\n * Best-effort unsubscribe: run the function if present, logging (and\n * swallowing) any error so a later teardown step still runs.\n *\n * @param unsubscribe - The unsubscribe function, if one was registered.\n * @param label - Human-readable subscription name for the failure log.\n * @param logFn - Logger for a failure.\n */\nfunction runUnsubscribe(\n unsubscribe: (() => void) | undefined,\n label: string,\n logFn: Logger,\n): void {\n if (!unsubscribe) {\n return;\n }\n try {\n unsubscribe();\n } catch (error) {\n logFn(\n `${label} unsubscribe failed during teardown — subscription may remain ` +\n `live until the process exits: ${String(error)}`,\n );\n }\n}\n\n/**\n * Persistence-safe teardown of a wallet and its store; see {@link\n * CreateWalletResult.dispose} for the ordering rationale. Each step is\n * best-effort, so a failure is logged and the remaining steps still run.\n *\n * @param persistenceUnsubscribe - The persistence-subscription unsubscribe\n * function, if one was registered.\n * @param autoApprovalUnsubscribe - The auto-approval-subscription unsubscribe\n * function, if one was registered.\n * @param wallet - The wallet to destroy, if one was constructed.\n * @param store - The store to close.\n * @param logFn - Logger for step failures.\n */\nasync function teardown(\n persistenceUnsubscribe: (() => void) | undefined,\n autoApprovalUnsubscribe: (() => void) | undefined,\n wallet: Wallet | undefined,\n store: KeyValueStore,\n logFn: Logger,\n): Promise<void> {\n runUnsubscribe(persistenceUnsubscribe, 'Persistence', logFn);\n runUnsubscribe(autoApprovalUnsubscribe, 'Auto-approval', logFn);\n if (wallet) {\n try {\n await wallet.destroy();\n } catch (error) {\n logFn(`wallet.destroy() failed during teardown: ${String(error)}`);\n }\n }\n try {\n store.close();\n } catch (error) {\n logFn(`store.close() failed during teardown: ${String(error)}`);\n }\n}\n\n/**\n * Determine whether the loaded state already contains a keyring vault.\n *\n * The KeyringController persists its `vault` once an SRP has been imported, so\n * its presence indicates that first-run setup completed before.\n *\n * @param state - The state loaded from the key-value store.\n * @returns True if a KeyringController vault string is present.\n */\nfunction hasPersistedKeyring(\n state: Record<string, Record<string, unknown>>,\n): boolean {\n return typeof state.KeyringController?.vault === 'string';\n}\n"]}
@@ -1,6 +1,6 @@
1
- import { Wallet } from '@metamask/wallet';
2
- import type { Password, Srp } from './secrets.js';
3
- import type { Logger } from './types.js';
1
+ import { Wallet } from "@metamask/wallet";
2
+ import type { Password, Srp } from "./secrets.cjs";
3
+ import type { Logger } from "./types.cjs";
4
4
  export type CreateWalletConfig = {
5
5
  databasePath: string;
6
6
  password?: Password;
@@ -61,4 +61,4 @@ export type CreateWalletResult = {
61
61
  * @returns The `Wallet` and a `dispose` handle that tears it down.
62
62
  */
63
63
  export declare function createWallet({ databasePath, password, srp, infuraProjectId, log, }: CreateWalletConfig): Promise<CreateWalletResult>;
64
- //# sourceMappingURL=wallet-factory.d.ts.map
64
+ //# sourceMappingURL=wallet-factory.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wallet-factory.d.cts","sourceRoot":"","sources":["../../src/daemon/wallet-factory.ts"],"names":[],"mappings":"AAeA,OAAO,EAGL,MAAM,EACP,yBAAyB;AAY1B,OAAO,KAAK,EAAE,QAAQ,EAAE,GAAG,EAAE,sBAAqB;AAClD,OAAO,KAAK,EAAE,MAAM,EAAE,oBAAmB;AAIzC,MAAM,MAAM,kBAAkB,GAAG;IAC/B,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,GAAG,EAAE,GAAG,CAAC;IACT,eAAe,EAAE,MAAM,CAAC;IACxB,GAAG,CAAC,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf;;;;;;;OAOG;IACH,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9B,CAAC;AAmIF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AACH,wBAAsB,YAAY,CAAC,EACjC,YAAY,EACZ,QAAQ,EACR,GAAG,EACH,eAAe,EACf,GAAG,GACJ,EAAE,kBAAkB,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAwHlD"}
@@ -0,0 +1,64 @@
1
+ import { Wallet } from "@metamask/wallet";
2
+ import type { Password, Srp } from "./secrets.mjs";
3
+ import type { Logger } from "./types.mjs";
4
+ export type CreateWalletConfig = {
5
+ databasePath: string;
6
+ password?: Password;
7
+ srp: Srp;
8
+ infuraProjectId: string;
9
+ log?: Logger;
10
+ };
11
+ export type CreateWalletResult = {
12
+ wallet: Wallet;
13
+ /**
14
+ * Tear down everything `createWallet` set up, in the order that keeps
15
+ * in-flight persistence writes valid: stop the state-change subscription,
16
+ * destroy the wallet, then close the store (closing first would cause a
17
+ * teardown-time persistence write to fail). Resilient — a failure in any
18
+ * step is logged and the remaining steps still run — and idempotent (repeat
19
+ * calls coalesce onto the same teardown).
20
+ */
21
+ dispose: () => Promise<void>;
22
+ };
23
+ /**
24
+ * Create a configured `Wallet` for daemon use, backed by a SQLite key-value
25
+ * store for controller-state persistence.
26
+ *
27
+ * Loads any previously-persisted controller state from the store, seeds the
28
+ * wallet with it, then subscribes the store to subsequent state changes so all
29
+ * persist-flagged properties are written through.
30
+ *
31
+ * If the store does not yet contain a keyring vault (first run), the supplied
32
+ * secret recovery phrase is imported using the supplied password. On
33
+ * subsequent runs, the persisted vault is reused: when a password is
34
+ * supplied, the wallet is unlocked via `KeyringController:submitPassword` so
35
+ * keyring-bound messenger actions work immediately; when no password is
36
+ * supplied, the wallet starts locked and the caller is expected to invoke
37
+ * `mm wallet unlock` before any keyring-bound operation. First-run startup
38
+ * without a password is rejected (the SRP cannot be imported without one).
39
+ * On a subsequent run, a wrong password rejects startup with a `Failed to
40
+ * unlock the persisted vault` error that wraps the `submitPassword` rejection.
41
+ *
42
+ * On any failure after the store is opened, the store is closed (and the wallet
43
+ * destroyed, if constructed). On a first-run failure, the on-disk database is
44
+ * also removed so a retry does not latch onto an orphaned partial vault — this
45
+ * covers in-process failures only; a hard crash (SIGKILL/power loss) mid-import
46
+ * can still leave a vault on disk.
47
+ *
48
+ * @param config - Wallet configuration.
49
+ * @param config.databasePath - Path to the SQLite database file (or
50
+ * `':memory:'` for ephemeral use).
51
+ * @param config.password - The wallet password. Optional on subsequent runs;
52
+ * when omitted, the daemon starts with a locked keyring. Required on first
53
+ * run (to import the SRP).
54
+ * @param config.srp - The secret recovery phrase (BIP-39 mnemonic). Used
55
+ * only on first run.
56
+ * @param config.infuraProjectId - The Infura project ID for the
57
+ * `NetworkController`.
58
+ * @param config.log - Optional logger for persistence-write and teardown
59
+ * failures. Without it, failures fall back to `console.error` (which a detached
60
+ * daemon's `stdio: 'ignore'` discards).
61
+ * @returns The `Wallet` and a `dispose` handle that tears it down.
62
+ */
63
+ export declare function createWallet({ databasePath, password, srp, infuraProjectId, log, }: CreateWalletConfig): Promise<CreateWalletResult>;
64
+ //# sourceMappingURL=wallet-factory.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wallet-factory.d.mts","sourceRoot":"","sources":["../../src/daemon/wallet-factory.ts"],"names":[],"mappings":"AAeA,OAAO,EAGL,MAAM,EACP,yBAAyB;AAY1B,OAAO,KAAK,EAAE,QAAQ,EAAE,GAAG,EAAE,sBAAqB;AAClD,OAAO,KAAK,EAAE,MAAM,EAAE,oBAAmB;AAIzC,MAAM,MAAM,kBAAkB,GAAG;IAC/B,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,GAAG,EAAE,GAAG,CAAC;IACT,eAAe,EAAE,MAAM,CAAC;IACxB,GAAG,CAAC,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf;;;;;;;OAOG;IACH,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9B,CAAC;AAmIF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AACH,wBAAsB,YAAY,CAAC,EACjC,YAAY,EACZ,QAAQ,EACR,GAAG,EACH,eAAe,EACf,GAAG,GACJ,EAAE,kBAAkB,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAwHlD"}