@metamask-previews/wallet-cli 0.0.0-preview-9298fa429 → 0.0.0-preview-574b60e35
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +1 -0
- package/dist/daemon/daemon-entry.cjs +245 -0
- package/dist/daemon/daemon-entry.cjs.map +1 -0
- package/dist/daemon/daemon-entry.d.cts +2 -0
- package/dist/daemon/daemon-entry.d.cts.map +1 -0
- package/dist/daemon/daemon-entry.d.mts +2 -0
- package/dist/daemon/daemon-entry.d.mts.map +1 -0
- package/dist/daemon/daemon-entry.mjs +243 -0
- package/dist/daemon/daemon-entry.mjs.map +1 -0
- package/dist/daemon/data-dir.cjs +23 -0
- package/dist/daemon/data-dir.cjs.map +1 -0
- package/dist/daemon/data-dir.d.cts +14 -0
- package/dist/daemon/data-dir.d.cts.map +1 -0
- package/dist/daemon/data-dir.d.mts +14 -0
- package/dist/daemon/data-dir.d.mts.map +1 -0
- package/dist/daemon/data-dir.mjs +19 -0
- package/dist/daemon/data-dir.mjs.map +1 -0
- package/dist/daemon/types.cjs.map +1 -1
- package/dist/daemon/types.d.cts +5 -0
- package/dist/daemon/types.d.cts.map +1 -1
- package/dist/daemon/types.d.mts +5 -0
- package/dist/daemon/types.d.mts.map +1 -1
- package/dist/daemon/types.mjs.map +1 -1
- package/dist/daemon/wallet-factory.cjs +234 -0
- package/dist/daemon/wallet-factory.cjs.map +1 -0
- package/dist/daemon/wallet-factory.d.cts +55 -0
- package/dist/daemon/wallet-factory.d.cts.map +1 -0
- package/dist/daemon/wallet-factory.d.mts +55 -0
- package/dist/daemon/wallet-factory.d.mts.map +1 -0
- package/dist/daemon/wallet-factory.mjs +230 -0
- package/dist/daemon/wallet-factory.mjs.map +1 -0
- package/dist/persistence/persistence.cjs.map +1 -1
- package/dist/persistence/persistence.d.cts +1 -1
- package/dist/persistence/persistence.d.cts.map +1 -1
- package/dist/persistence/persistence.d.mts +1 -1
- package/dist/persistence/persistence.d.mts.map +1 -1
- package/dist/persistence/persistence.mjs.map +1 -1
- package/package.json +3 -1
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Create the daemon's data directory (if it does not exist) and restrict it to
|
|
3
|
+
* the owning user.
|
|
4
|
+
*
|
|
5
|
+
* The mode is `0o700` (owner-only). The daemon exposes the full wallet
|
|
6
|
+
* messenger over the socket inside this directory, so anyone who can traverse
|
|
7
|
+
* the dir can also `connect()` to the socket. Restricting to the owning user is
|
|
8
|
+
* the only access-control boundary. We `chmod` after `mkdir` because the `mode`
|
|
9
|
+
* option is ignored when the directory already exists.
|
|
10
|
+
*
|
|
11
|
+
* @param dataDir - The data directory to create and lock down.
|
|
12
|
+
*/
|
|
13
|
+
export declare function ensureOwnerOnlyDirectory(dataDir: string): Promise<void>;
|
|
14
|
+
//# sourceMappingURL=data-dir.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"data-dir.d.mts","sourceRoot":"","sources":["../../src/daemon/data-dir.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;GAWG;AACH,wBAAsB,wBAAwB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAG7E"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { mkdirSync } from "node:fs";
|
|
2
|
+
import { chmod } from "node:fs/promises";
|
|
3
|
+
/**
|
|
4
|
+
* Create the daemon's data directory (if it does not exist) and restrict it to
|
|
5
|
+
* the owning user.
|
|
6
|
+
*
|
|
7
|
+
* The mode is `0o700` (owner-only). The daemon exposes the full wallet
|
|
8
|
+
* messenger over the socket inside this directory, so anyone who can traverse
|
|
9
|
+
* the dir can also `connect()` to the socket. Restricting to the owning user is
|
|
10
|
+
* the only access-control boundary. We `chmod` after `mkdir` because the `mode`
|
|
11
|
+
* option is ignored when the directory already exists.
|
|
12
|
+
*
|
|
13
|
+
* @param dataDir - The data directory to create and lock down.
|
|
14
|
+
*/
|
|
15
|
+
export async function ensureOwnerOnlyDirectory(dataDir) {
|
|
16
|
+
mkdirSync(dataDir, { recursive: true, mode: 0o700 });
|
|
17
|
+
await chmod(dataDir, 0o700);
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=data-dir.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"data-dir.mjs","sourceRoot":"","sources":["../../src/daemon/data-dir.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,gBAAgB;AACpC,OAAO,EAAE,KAAK,EAAE,yBAAyB;AAEzC;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,KAAK,UAAU,wBAAwB,CAAC,OAAe;IAC5D,SAAS,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IACrD,MAAM,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;AAC9B,CAAC","sourcesContent":["import { mkdirSync } from 'node:fs';\nimport { chmod } from 'node:fs/promises';\n\n/**\n * Create the daemon's data directory (if it does not exist) and restrict it to\n * the owning user.\n *\n * The mode is `0o700` (owner-only). The daemon exposes the full wallet\n * messenger over the socket inside this directory, so anyone who can traverse\n * the dir can also `connect()` to the socket. Restricting to the owning user is\n * the only access-control boundary. We `chmod` after `mkdir` because the `mode`\n * option is ignored when the directory already exists.\n *\n * @param dataDir - The data directory to create and lock down.\n */\nexport async function ensureOwnerOnlyDirectory(dataDir: string): Promise<void> {\n mkdirSync(dataDir, { recursive: true, mode: 0o700 });\n await chmod(dataDir, 0o700);\n}\n"]}
|
|
@@ -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 * 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":["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"]}
|
package/dist/daemon/types.d.cts
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
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;
|
|
2
7
|
/**
|
|
3
8
|
* A function that handles a JSON-RPC method call.
|
|
4
9
|
*
|
|
@@ -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;;;;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,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"}
|
package/dist/daemon/types.d.mts
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
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;
|
|
2
7
|
/**
|
|
3
8
|
* A function that handles a JSON-RPC method call.
|
|
4
9
|
*
|
|
@@ -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;;;;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,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 +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 * 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":["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"]}
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createWallet = void 0;
|
|
4
|
+
const remote_feature_flag_controller_1 = require("@metamask/remote-feature-flag-controller");
|
|
5
|
+
const storage_service_1 = require("@metamask/storage-service");
|
|
6
|
+
const wallet_1 = require("@metamask/wallet");
|
|
7
|
+
const promises_1 = require("node:fs/promises");
|
|
8
|
+
const KeyValueStore_1 = require("../persistence/KeyValueStore.cjs");
|
|
9
|
+
const persistence_1 = require("../persistence/persistence.cjs");
|
|
10
|
+
const IN_MEMORY_DATABASE_PATH = ':memory:';
|
|
11
|
+
/**
|
|
12
|
+
* Build the per-instance options the daemon's `Wallet` is constructed with.
|
|
13
|
+
*
|
|
14
|
+
* Returns a fresh set on every call so the metadata probe and the real wallet
|
|
15
|
+
* never share adapter/service instances (the probe is destroyed, which may
|
|
16
|
+
* tear those instances down).
|
|
17
|
+
*
|
|
18
|
+
* Only the slots wired on `@metamask/wallet` today are populated:
|
|
19
|
+
* - `storageService` — backed by an in-memory adapter. This is the wallet's
|
|
20
|
+
* large-blob store (snap source, caches), distinct from the SQLite
|
|
21
|
+
* `KeyValueStore` that persists controller state; no wired controller
|
|
22
|
+
* offloads durable data to it yet, so in-memory suffices.
|
|
23
|
+
* - `connectivityController` — the `AlwaysOnlineAdapter` exported for
|
|
24
|
+
* node-like hosts that have no platform connectivity signal.
|
|
25
|
+
* - `networkController` — the Infura project ID used to reach Infura RPC
|
|
26
|
+
* endpoints.
|
|
27
|
+
* - `remoteFeatureFlagController` — a `ClientConfigApiService` fetching real
|
|
28
|
+
* flags over the network.
|
|
29
|
+
* - `approvalController` — a no-op `showApprovalRequest` (the daemon is
|
|
30
|
+
* headless).
|
|
31
|
+
*
|
|
32
|
+
* The optional `keyringController` slot is intentionally omitted so the
|
|
33
|
+
* controller's built-in defaults (e.g. the PBKDF2 encryptor) apply.
|
|
34
|
+
*
|
|
35
|
+
* @param infuraProjectId - The Infura project ID for the `NetworkController`.
|
|
36
|
+
* @returns The `instanceOptions` for the `Wallet` constructor.
|
|
37
|
+
*/
|
|
38
|
+
function buildInstanceOptions(infuraProjectId) {
|
|
39
|
+
return {
|
|
40
|
+
approvalController: {
|
|
41
|
+
// TODO: surface approval requests over the daemon transport.
|
|
42
|
+
showApprovalRequest: () => undefined,
|
|
43
|
+
},
|
|
44
|
+
connectivityController: {
|
|
45
|
+
connectivityAdapter: new wallet_1.AlwaysOnlineAdapter(),
|
|
46
|
+
},
|
|
47
|
+
networkController: {
|
|
48
|
+
infuraProjectId,
|
|
49
|
+
},
|
|
50
|
+
remoteFeatureFlagController: {
|
|
51
|
+
clientConfigApiService: new remote_feature_flag_controller_1.ClientConfigApiService({
|
|
52
|
+
fetch: globalThis.fetch,
|
|
53
|
+
config: {
|
|
54
|
+
// TODO: switch to a CLI-specific `ClientType` once one exists; until
|
|
55
|
+
// then `Extension` buckets feature flags as an extension client.
|
|
56
|
+
client: remote_feature_flag_controller_1.ClientType.Extension,
|
|
57
|
+
distribution: remote_feature_flag_controller_1.DistributionType.Main,
|
|
58
|
+
environment: remote_feature_flag_controller_1.EnvironmentType.Production,
|
|
59
|
+
},
|
|
60
|
+
}),
|
|
61
|
+
getMetaMetricsId: () => 'cli',
|
|
62
|
+
clientVersion: '0.0.0',
|
|
63
|
+
},
|
|
64
|
+
storageService: {
|
|
65
|
+
storage: new storage_service_1.InMemoryStorageAdapter(),
|
|
66
|
+
},
|
|
67
|
+
// TODO(#8975): add the `transactionController` slot once it is wired.
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Create a configured `Wallet` for daemon use, backed by a SQLite key-value
|
|
72
|
+
* store for controller-state persistence.
|
|
73
|
+
*
|
|
74
|
+
* Loads any previously-persisted controller state from the store, seeds the
|
|
75
|
+
* wallet with it, then subscribes the store to subsequent state changes so all
|
|
76
|
+
* persist-flagged properties are written through.
|
|
77
|
+
*
|
|
78
|
+
* If the store does not yet contain a keyring vault (first run), the supplied
|
|
79
|
+
* secret recovery phrase is imported. On subsequent runs the persisted vault is
|
|
80
|
+
* reused — `password`/`srp` go unused and the wallet starts locked; the caller
|
|
81
|
+
* unlocks it via `KeyringController:submitPassword` before any keyring-bound
|
|
82
|
+
* operation.
|
|
83
|
+
*
|
|
84
|
+
* On any failure after the store is opened, the store is closed (and the wallet
|
|
85
|
+
* destroyed, if constructed). On a first-run failure, the on-disk database is
|
|
86
|
+
* also removed so a retry does not latch onto an orphaned partial vault — this
|
|
87
|
+
* covers in-process failures only; a hard crash (SIGKILL/power loss) mid-import
|
|
88
|
+
* can still leave a vault on disk.
|
|
89
|
+
*
|
|
90
|
+
* @param config - Wallet configuration.
|
|
91
|
+
* @param config.databasePath - Path to the SQLite database file (or
|
|
92
|
+
* `':memory:'` for ephemeral use).
|
|
93
|
+
* @param config.password - The wallet password.
|
|
94
|
+
* @param config.srp - The secret recovery phrase (BIP-39 mnemonic).
|
|
95
|
+
* @param config.infuraProjectId - The Infura project ID for the
|
|
96
|
+
* `NetworkController`.
|
|
97
|
+
* @param config.log - Optional logger for persistence-write and teardown
|
|
98
|
+
* failures. Without it, failures fall back to `console.error` (which a detached
|
|
99
|
+
* daemon's `stdio: 'ignore'` discards).
|
|
100
|
+
* @returns The `Wallet` and a `dispose` handle that tears it down.
|
|
101
|
+
*/
|
|
102
|
+
async function createWallet({ databasePath, password, srp, infuraProjectId, log, }) {
|
|
103
|
+
const logFn = log ?? ((message) => console.error(message));
|
|
104
|
+
const store = new KeyValueStore_1.KeyValueStore(databasePath);
|
|
105
|
+
let wallet;
|
|
106
|
+
let unsubscribe;
|
|
107
|
+
let wasFirstRun = false;
|
|
108
|
+
try {
|
|
109
|
+
const state = await loadPersistedState(store, infuraProjectId, logFn);
|
|
110
|
+
wasFirstRun = !hasPersistedKeyring(state);
|
|
111
|
+
wallet = new wallet_1.Wallet({
|
|
112
|
+
state,
|
|
113
|
+
instanceOptions: buildInstanceOptions(infuraProjectId),
|
|
114
|
+
});
|
|
115
|
+
unsubscribe = (0, persistence_1.subscribeToChanges)(wallet.messenger, wallet.controllerMetadata, store, logFn);
|
|
116
|
+
// Complete post-construction controller setup before serving requests —
|
|
117
|
+
// e.g. `NetworkController.init` applies the selected network so a provider
|
|
118
|
+
// is available. `init` settles every step independently; a rejected step
|
|
119
|
+
// leaves the wallet only partially usable, so abort startup (the catch
|
|
120
|
+
// below tears down and, on first run, removes the partial database) rather
|
|
121
|
+
// than serving a degraded daemon.
|
|
122
|
+
const initResults = await wallet.init();
|
|
123
|
+
const initFailures = initResults.filter((result) => result.status === 'rejected');
|
|
124
|
+
for (const failure of initFailures) {
|
|
125
|
+
logFn(`Wallet init step failed: ${String(failure.reason)}`);
|
|
126
|
+
}
|
|
127
|
+
if (initFailures.length > 0) {
|
|
128
|
+
const firstReason = String(initFailures[0].reason);
|
|
129
|
+
throw new Error(`Wallet initialization failed (${initFailures.length} step(s)); refusing to serve a partially initialized wallet. First failure: ${firstReason}`);
|
|
130
|
+
}
|
|
131
|
+
if (wasFirstRun) {
|
|
132
|
+
await (0, wallet_1.importSecretRecoveryPhrase)(wallet, password, srp);
|
|
133
|
+
}
|
|
134
|
+
let disposePromise;
|
|
135
|
+
return {
|
|
136
|
+
wallet,
|
|
137
|
+
dispose: async () => (disposePromise ?? (disposePromise = teardown(unsubscribe, wallet, store, logFn))),
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
catch (error) {
|
|
141
|
+
await teardown(unsubscribe, wallet, store, logFn);
|
|
142
|
+
if (wasFirstRun && databasePath !== IN_MEMORY_DATABASE_PATH) {
|
|
143
|
+
// Best-effort cleanup of the on-disk SQLite files (main, WAL, SHM) so a
|
|
144
|
+
// partially-persisted KeyringController vault cannot mislead the next run
|
|
145
|
+
// into skipping SRP import. Covers in-process failures only — a crash
|
|
146
|
+
// (SIGKILL/power loss) mid-import leaves the vault on disk.
|
|
147
|
+
await Promise.all([databasePath, `${databasePath}-wal`, `${databasePath}-shm`].map((path) => (0, promises_1.rm)(path, { force: true }).catch((rmError) => {
|
|
148
|
+
logFn(`Failed to remove ${path} during first-run cleanup: ${String(rmError)}`);
|
|
149
|
+
})));
|
|
150
|
+
}
|
|
151
|
+
throw error;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
exports.createWallet = createWallet;
|
|
155
|
+
/**
|
|
156
|
+
* Load persisted controller state, filtered to currently persist-flagged
|
|
157
|
+
* properties.
|
|
158
|
+
*
|
|
159
|
+
* `loadState` filters against the live controller metadata, but that metadata
|
|
160
|
+
* is only knowable from a constructed `Wallet` — and its output is what seeds
|
|
161
|
+
* the real wallet. So this constructs a short-lived probe purely to read the
|
|
162
|
+
* metadata, then tears it down.
|
|
163
|
+
*
|
|
164
|
+
* TODO: drop the probe once `@metamask/wallet` exposes controller metadata
|
|
165
|
+
* without constructing a `Wallet`.
|
|
166
|
+
*
|
|
167
|
+
* @param store - The key-value store to read from.
|
|
168
|
+
* @param infuraProjectId - The Infura project ID for the probe's
|
|
169
|
+
* `NetworkController`.
|
|
170
|
+
* @param logFn - Logger for a probe-teardown failure.
|
|
171
|
+
* @returns The filtered persisted state, suitable for the `Wallet` `state`
|
|
172
|
+
* option.
|
|
173
|
+
*/
|
|
174
|
+
async function loadPersistedState(store, infuraProjectId, logFn) {
|
|
175
|
+
const probe = new wallet_1.Wallet({
|
|
176
|
+
instanceOptions: buildInstanceOptions(infuraProjectId),
|
|
177
|
+
});
|
|
178
|
+
try {
|
|
179
|
+
return (0, persistence_1.loadState)(store, probe.controllerMetadata);
|
|
180
|
+
}
|
|
181
|
+
finally {
|
|
182
|
+
await probe.destroy().catch((error) => {
|
|
183
|
+
logFn(`Metadata probe destroy failed: ${String(error)}`);
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Persistence-safe teardown of a wallet and its store; see {@link
|
|
189
|
+
* CreateWalletResult.dispose} for the ordering rationale. Each step is
|
|
190
|
+
* best-effort, so a failure is logged and the remaining steps still run.
|
|
191
|
+
*
|
|
192
|
+
* @param unsubscribe - The persistence-subscription unsubscribe function, if
|
|
193
|
+
* one was registered.
|
|
194
|
+
* @param wallet - The wallet to destroy, if one was constructed.
|
|
195
|
+
* @param store - The store to close.
|
|
196
|
+
* @param logFn - Logger for step failures.
|
|
197
|
+
*/
|
|
198
|
+
async function teardown(unsubscribe, wallet, store, logFn) {
|
|
199
|
+
if (unsubscribe) {
|
|
200
|
+
try {
|
|
201
|
+
unsubscribe();
|
|
202
|
+
}
|
|
203
|
+
catch (error) {
|
|
204
|
+
logFn(`Persistence unsubscribe failed during teardown: ${String(error)}`);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
if (wallet) {
|
|
208
|
+
try {
|
|
209
|
+
await wallet.destroy();
|
|
210
|
+
}
|
|
211
|
+
catch (error) {
|
|
212
|
+
logFn(`wallet.destroy() failed during teardown: ${String(error)}`);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
try {
|
|
216
|
+
store.close();
|
|
217
|
+
}
|
|
218
|
+
catch (error) {
|
|
219
|
+
logFn(`store.close() failed during teardown: ${String(error)}`);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Determine whether the loaded state already contains a keyring vault.
|
|
224
|
+
*
|
|
225
|
+
* The KeyringController persists its `vault` once an SRP has been imported, so
|
|
226
|
+
* its presence indicates that first-run setup completed before.
|
|
227
|
+
*
|
|
228
|
+
* @param state - The state loaded from the key-value store.
|
|
229
|
+
* @returns True if a KeyringController vault string is present.
|
|
230
|
+
*/
|
|
231
|
+
function hasPersistedKeyring(state) {
|
|
232
|
+
return typeof state.KeyringController?.vault === 'string';
|
|
233
|
+
}
|
|
234
|
+
//# 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 {\n ClientConfigApiService,\n ClientType,\n DistributionType,\n EnvironmentType,\n} from '@metamask/remote-feature-flag-controller';\nimport { InMemoryStorageAdapter } from '@metamask/storage-service';\nimport type { Json } from '@metamask/utils';\nimport {\n AlwaysOnlineAdapter,\n importSecretRecoveryPhrase,\n Wallet,\n} from '@metamask/wallet';\nimport type { WalletOptions } from '@metamask/wallet';\nimport { rm } from 'node:fs/promises';\n\nimport { KeyValueStore } from '../persistence/KeyValueStore';\nimport { loadState, subscribeToChanges } from '../persistence/persistence';\nimport type { Logger } from './types';\n\nconst IN_MEMORY_DATABASE_PATH = ':memory:';\n\nexport type CreateWalletConfig = {\n databasePath: string;\n password: string;\n srp: string;\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).\n *\n * The optional `keyringController` slot is intentionally omitted so the\n * controller's built-in defaults (e.g. the PBKDF2 encryptor) apply.\n *\n * @param infuraProjectId - The Infura project ID for the `NetworkController`.\n * @returns The `instanceOptions` for the `Wallet` constructor.\n */\nfunction buildInstanceOptions(\n infuraProjectId: string,\n): WalletOptions['instanceOptions'] {\n return {\n approvalController: {\n // TODO: surface approval requests over the daemon transport.\n showApprovalRequest: (): undefined => undefined,\n },\n connectivityController: {\n connectivityAdapter: new AlwaysOnlineAdapter(),\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 // TODO(#8975): add the `transactionController` slot once it is wired.\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. On subsequent runs the persisted vault is\n * reused — `password`/`srp` go unused and the wallet starts locked; the caller\n * unlocks it via `KeyringController:submitPassword` before any keyring-bound\n * operation.\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.\n * @param config.srp - The secret recovery phrase (BIP-39 mnemonic).\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 unsubscribe: (() => void) | undefined;\n let wasFirstRun = false;\n\n try {\n const state = await loadPersistedState(store, infuraProjectId, logFn);\n wasFirstRun = !hasPersistedKeyring(state);\n\n wallet = new Wallet({\n state,\n instanceOptions: buildInstanceOptions(infuraProjectId),\n });\n unsubscribe = subscribeToChanges(\n wallet.messenger,\n wallet.controllerMetadata,\n store,\n logFn,\n );\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 await importSecretRecoveryPhrase(wallet, password, srp);\n }\n\n let disposePromise: Promise<void> | undefined;\n return {\n wallet,\n dispose: async () =>\n (disposePromise ??= teardown(unsubscribe, wallet, store, logFn)),\n };\n } catch (error) {\n await teardown(unsubscribe, wallet, store, logFn);\n\n if (wasFirstRun && databasePath !== IN_MEMORY_DATABASE_PATH) {\n // Best-effort cleanup of the on-disk SQLite files (main, WAL, SHM) so a\n // partially-persisted KeyringController vault cannot mislead the next run\n // into skipping SRP import. Covers in-process failures only — a crash\n // (SIGKILL/power loss) mid-import leaves the vault on disk.\n await Promise.all(\n [databasePath, `${databasePath}-wal`, `${databasePath}-shm`].map(\n (path) =>\n rm(path, { force: true }).catch((rmError: unknown) => {\n logFn(\n `Failed to remove ${path} during first-run cleanup: ${String(rmError)}`,\n );\n }),\n ),\n );\n }\n\n throw error;\n }\n}\n\n/**\n * Load persisted controller state, filtered to currently persist-flagged\n * properties.\n *\n * `loadState` filters against the live controller metadata, but that metadata\n * is only knowable from a constructed `Wallet` — and its output is what seeds\n * the real wallet. So this constructs a short-lived probe purely to read the\n * metadata, then tears it down.\n *\n * TODO: drop the probe once `@metamask/wallet` exposes controller metadata\n * without constructing a `Wallet`.\n *\n * @param store - The key-value store to read from.\n * @param infuraProjectId - The Infura project ID for the probe's\n * `NetworkController`.\n * @param logFn - Logger for a probe-teardown failure.\n * @returns The filtered persisted state, suitable for the `Wallet` `state`\n * option.\n */\nasync function loadPersistedState(\n store: KeyValueStore,\n infuraProjectId: string,\n logFn: Logger,\n): Promise<Record<string, Record<string, Json>>> {\n const probe = new Wallet({\n instanceOptions: buildInstanceOptions(infuraProjectId),\n });\n try {\n return loadState(store, probe.controllerMetadata);\n } finally {\n await probe.destroy().catch((error: unknown) => {\n logFn(`Metadata probe destroy failed: ${String(error)}`);\n });\n }\n}\n\n/**\n * 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 unsubscribe - The persistence-subscription unsubscribe function, if\n * 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 unsubscribe: (() => void) | undefined,\n wallet: Wallet | undefined,\n store: KeyValueStore,\n logFn: Logger,\n): Promise<void> {\n if (unsubscribe) {\n try {\n unsubscribe();\n } catch (error) {\n logFn(`Persistence unsubscribe failed during teardown: ${String(error)}`);\n }\n }\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"]}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { Wallet } from "@metamask/wallet";
|
|
2
|
+
import type { Logger } from "./types.cjs";
|
|
3
|
+
export type CreateWalletConfig = {
|
|
4
|
+
databasePath: string;
|
|
5
|
+
password: string;
|
|
6
|
+
srp: string;
|
|
7
|
+
infuraProjectId: string;
|
|
8
|
+
log?: Logger;
|
|
9
|
+
};
|
|
10
|
+
export type CreateWalletResult = {
|
|
11
|
+
wallet: Wallet;
|
|
12
|
+
/**
|
|
13
|
+
* Tear down everything `createWallet` set up, in the order that keeps
|
|
14
|
+
* in-flight persistence writes valid: stop the state-change subscription,
|
|
15
|
+
* destroy the wallet, then close the store (closing first would cause a
|
|
16
|
+
* teardown-time persistence write to fail). Resilient — a failure in any
|
|
17
|
+
* step is logged and the remaining steps still run — and idempotent (repeat
|
|
18
|
+
* calls coalesce onto the same teardown).
|
|
19
|
+
*/
|
|
20
|
+
dispose: () => Promise<void>;
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* Create a configured `Wallet` for daemon use, backed by a SQLite key-value
|
|
24
|
+
* store for controller-state persistence.
|
|
25
|
+
*
|
|
26
|
+
* Loads any previously-persisted controller state from the store, seeds the
|
|
27
|
+
* wallet with it, then subscribes the store to subsequent state changes so all
|
|
28
|
+
* persist-flagged properties are written through.
|
|
29
|
+
*
|
|
30
|
+
* If the store does not yet contain a keyring vault (first run), the supplied
|
|
31
|
+
* secret recovery phrase is imported. On subsequent runs the persisted vault is
|
|
32
|
+
* reused — `password`/`srp` go unused and the wallet starts locked; the caller
|
|
33
|
+
* unlocks it via `KeyringController:submitPassword` before any keyring-bound
|
|
34
|
+
* operation.
|
|
35
|
+
*
|
|
36
|
+
* On any failure after the store is opened, the store is closed (and the wallet
|
|
37
|
+
* destroyed, if constructed). On a first-run failure, the on-disk database is
|
|
38
|
+
* also removed so a retry does not latch onto an orphaned partial vault — this
|
|
39
|
+
* covers in-process failures only; a hard crash (SIGKILL/power loss) mid-import
|
|
40
|
+
* can still leave a vault on disk.
|
|
41
|
+
*
|
|
42
|
+
* @param config - Wallet configuration.
|
|
43
|
+
* @param config.databasePath - Path to the SQLite database file (or
|
|
44
|
+
* `':memory:'` for ephemeral use).
|
|
45
|
+
* @param config.password - The wallet password.
|
|
46
|
+
* @param config.srp - The secret recovery phrase (BIP-39 mnemonic).
|
|
47
|
+
* @param config.infuraProjectId - The Infura project ID for the
|
|
48
|
+
* `NetworkController`.
|
|
49
|
+
* @param config.log - Optional logger for persistence-write and teardown
|
|
50
|
+
* failures. Without it, failures fall back to `console.error` (which a detached
|
|
51
|
+
* daemon's `stdio: 'ignore'` discards).
|
|
52
|
+
* @returns The `Wallet` and a `dispose` handle that tears it down.
|
|
53
|
+
*/
|
|
54
|
+
export declare function createWallet({ databasePath, password, srp, infuraProjectId, log, }: CreateWalletConfig): Promise<CreateWalletResult>;
|
|
55
|
+
//# 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":"AAQA,OAAO,EAGL,MAAM,EACP,yBAAyB;AAM1B,OAAO,KAAK,EAAE,MAAM,EAAE,oBAAgB;AAItC,MAAM,MAAM,kBAAkB,GAAG;IAC/B,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;IACZ,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;AAgEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAsB,YAAY,CAAC,EACjC,YAAY,EACZ,QAAQ,EACR,GAAG,EACH,eAAe,EACf,GAAG,GACJ,EAAE,kBAAkB,GAAG,OAAO,CAAC,kBAAkB,CAAC,CA0ElD"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { Wallet } from "@metamask/wallet";
|
|
2
|
+
import type { Logger } from "./types.mjs";
|
|
3
|
+
export type CreateWalletConfig = {
|
|
4
|
+
databasePath: string;
|
|
5
|
+
password: string;
|
|
6
|
+
srp: string;
|
|
7
|
+
infuraProjectId: string;
|
|
8
|
+
log?: Logger;
|
|
9
|
+
};
|
|
10
|
+
export type CreateWalletResult = {
|
|
11
|
+
wallet: Wallet;
|
|
12
|
+
/**
|
|
13
|
+
* Tear down everything `createWallet` set up, in the order that keeps
|
|
14
|
+
* in-flight persistence writes valid: stop the state-change subscription,
|
|
15
|
+
* destroy the wallet, then close the store (closing first would cause a
|
|
16
|
+
* teardown-time persistence write to fail). Resilient — a failure in any
|
|
17
|
+
* step is logged and the remaining steps still run — and idempotent (repeat
|
|
18
|
+
* calls coalesce onto the same teardown).
|
|
19
|
+
*/
|
|
20
|
+
dispose: () => Promise<void>;
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* Create a configured `Wallet` for daemon use, backed by a SQLite key-value
|
|
24
|
+
* store for controller-state persistence.
|
|
25
|
+
*
|
|
26
|
+
* Loads any previously-persisted controller state from the store, seeds the
|
|
27
|
+
* wallet with it, then subscribes the store to subsequent state changes so all
|
|
28
|
+
* persist-flagged properties are written through.
|
|
29
|
+
*
|
|
30
|
+
* If the store does not yet contain a keyring vault (first run), the supplied
|
|
31
|
+
* secret recovery phrase is imported. On subsequent runs the persisted vault is
|
|
32
|
+
* reused — `password`/`srp` go unused and the wallet starts locked; the caller
|
|
33
|
+
* unlocks it via `KeyringController:submitPassword` before any keyring-bound
|
|
34
|
+
* operation.
|
|
35
|
+
*
|
|
36
|
+
* On any failure after the store is opened, the store is closed (and the wallet
|
|
37
|
+
* destroyed, if constructed). On a first-run failure, the on-disk database is
|
|
38
|
+
* also removed so a retry does not latch onto an orphaned partial vault — this
|
|
39
|
+
* covers in-process failures only; a hard crash (SIGKILL/power loss) mid-import
|
|
40
|
+
* can still leave a vault on disk.
|
|
41
|
+
*
|
|
42
|
+
* @param config - Wallet configuration.
|
|
43
|
+
* @param config.databasePath - Path to the SQLite database file (or
|
|
44
|
+
* `':memory:'` for ephemeral use).
|
|
45
|
+
* @param config.password - The wallet password.
|
|
46
|
+
* @param config.srp - The secret recovery phrase (BIP-39 mnemonic).
|
|
47
|
+
* @param config.infuraProjectId - The Infura project ID for the
|
|
48
|
+
* `NetworkController`.
|
|
49
|
+
* @param config.log - Optional logger for persistence-write and teardown
|
|
50
|
+
* failures. Without it, failures fall back to `console.error` (which a detached
|
|
51
|
+
* daemon's `stdio: 'ignore'` discards).
|
|
52
|
+
* @returns The `Wallet` and a `dispose` handle that tears it down.
|
|
53
|
+
*/
|
|
54
|
+
export declare function createWallet({ databasePath, password, srp, infuraProjectId, log, }: CreateWalletConfig): Promise<CreateWalletResult>;
|
|
55
|
+
//# 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":"AAQA,OAAO,EAGL,MAAM,EACP,yBAAyB;AAM1B,OAAO,KAAK,EAAE,MAAM,EAAE,oBAAgB;AAItC,MAAM,MAAM,kBAAkB,GAAG;IAC/B,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;IACZ,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;AAgEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAsB,YAAY,CAAC,EACjC,YAAY,EACZ,QAAQ,EACR,GAAG,EACH,eAAe,EACf,GAAG,GACJ,EAAE,kBAAkB,GAAG,OAAO,CAAC,kBAAkB,CAAC,CA0ElD"}
|