@routstr/cocod 0.0.16 → 0.0.18
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/README.md +3 -0
- package/package.json +1 -1
- package/src/cli-shared.ts +77 -4
- package/src/daemon.ts +78 -20
- package/src/routes.ts +47 -7
- package/src/utils/wallet.ts +18 -2
package/README.md
CHANGED
package/package.json
CHANGED
package/src/cli-shared.ts
CHANGED
|
@@ -42,6 +42,64 @@ export async function isDaemonRunning(): Promise<boolean> {
|
|
|
42
42
|
}
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
+
const DAEMON_POLL_INTERVAL_MS = 1_000;
|
|
46
|
+
const DAEMON_SLOW_START_WARNING_MS = 30_000;
|
|
47
|
+
const DAEMON_START_TIMEOUT_MS = 60_000;
|
|
48
|
+
const DAEMON_START_LOG_LINES = 40;
|
|
49
|
+
|
|
50
|
+
function sleep(ms: number): Promise<void> {
|
|
51
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async function waitForDaemonReady(startedAt: number, warningShown: { value: boolean }): Promise<void> {
|
|
55
|
+
for (;;) {
|
|
56
|
+
try {
|
|
57
|
+
const result = await callDaemon("/status");
|
|
58
|
+
if (typeof result.output === "string") {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
} catch {
|
|
62
|
+
// Daemon may not be accepting requests yet
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const elapsedMs = Date.now() - startedAt;
|
|
66
|
+
|
|
67
|
+
if (!warningShown.value && elapsedMs >= DAEMON_SLOW_START_WARNING_MS) {
|
|
68
|
+
warningShown.value = true;
|
|
69
|
+
console.log("Daemon is taking longer than expected, please wait...");
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (elapsedMs >= DAEMON_START_TIMEOUT_MS) {
|
|
73
|
+
throw new Error("Daemon failed to start after 1 minute");
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
await sleep(DAEMON_POLL_INTERVAL_MS);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function printProgressStep(message: string): void {
|
|
81
|
+
console.log(`• ${message}`);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function maybePrintFriendlyProgress(path: string, body?: object): void {
|
|
85
|
+
if (path === "/init") {
|
|
86
|
+
const mintUrl =
|
|
87
|
+
body && "mintUrl" in body && typeof body.mintUrl === "string"
|
|
88
|
+
? body.mintUrl
|
|
89
|
+
: "https://mint.minibits.cash/Bitcoin";
|
|
90
|
+
|
|
91
|
+
printProgressStep("Preparing wallet...");
|
|
92
|
+
printProgressStep(`Connecting to mint: ${mintUrl}`);
|
|
93
|
+
printProgressStep("This can take a few seconds on first run.");
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (path === "/unlock") {
|
|
98
|
+
printProgressStep("Unlocking wallet...");
|
|
99
|
+
printProgressStep("Reconnecting wallet services...");
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
45
103
|
export async function startDaemonProcess(): Promise<void> {
|
|
46
104
|
const proc = Bun.spawn({
|
|
47
105
|
cmd: ["bun", "run", `${import.meta.dir}/index.ts`, "daemon"],
|
|
@@ -51,14 +109,28 @@ export async function startDaemonProcess(): Promise<void> {
|
|
|
51
109
|
});
|
|
52
110
|
proc.unref();
|
|
53
111
|
|
|
54
|
-
|
|
55
|
-
|
|
112
|
+
const startedAt = Date.now();
|
|
113
|
+
const warningShown = { value: false };
|
|
114
|
+
|
|
115
|
+
for (;;) {
|
|
116
|
+
await sleep(DAEMON_POLL_INTERVAL_MS);
|
|
56
117
|
if (await isDaemonRunning()) {
|
|
118
|
+
await waitForDaemonReady(startedAt, warningShown);
|
|
57
119
|
return;
|
|
58
120
|
}
|
|
59
|
-
}
|
|
60
121
|
|
|
61
|
-
|
|
122
|
+
const elapsedMs = Date.now() - startedAt;
|
|
123
|
+
|
|
124
|
+
if (!warningShown.value && elapsedMs >= DAEMON_SLOW_START_WARNING_MS) {
|
|
125
|
+
warningShown.value = true;
|
|
126
|
+
console.log("Daemon is taking longer than expected, please wait...");
|
|
127
|
+
console.log(`Tip: run 'cocod logs --follow' or 'tail -n ${DAEMON_START_LOG_LINES} ~/.cocod/daemon.log' in another terminal.`);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (elapsedMs >= DAEMON_START_TIMEOUT_MS) {
|
|
131
|
+
throw new Error("Daemon failed to start after 1 minute");
|
|
132
|
+
}
|
|
133
|
+
}
|
|
62
134
|
}
|
|
63
135
|
|
|
64
136
|
export async function ensureDaemonRunning(): Promise<void> {
|
|
@@ -76,6 +148,7 @@ export async function handleDaemonCommand(
|
|
|
76
148
|
): Promise<CommandResponse> {
|
|
77
149
|
try {
|
|
78
150
|
await ensureDaemonRunning();
|
|
151
|
+
maybePrintFriendlyProgress(path, options.body);
|
|
79
152
|
const result = await callDaemon(path, options);
|
|
80
153
|
|
|
81
154
|
if (result.error) {
|
package/src/daemon.ts
CHANGED
|
@@ -1,21 +1,90 @@
|
|
|
1
1
|
import { mnemonicToSeedSync } from "@scure/bip39";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { closeSync, openSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { mkdir, unlink } from "node:fs/promises";
|
|
4
|
+
import process from "node:process";
|
|
5
|
+
import { CONFIG_DIR, CONFIG_FILE, SOCKET_PATH, PID_FILE } from "./utils/config.js";
|
|
4
6
|
import { createDaemonLogger, serializeError } from "./utils/logger.js";
|
|
5
7
|
import { DaemonStateManager } from "./utils/state.js";
|
|
6
8
|
import { initializeWallet } from "./utils/wallet.js";
|
|
7
9
|
import { createRouteHandlers, buildRoutes } from "./routes.js";
|
|
8
10
|
import type { WalletConfig } from "./utils/config.js";
|
|
9
11
|
|
|
12
|
+
async function isProcessAlive(pid: number): Promise<boolean> {
|
|
13
|
+
if (!Number.isInteger(pid) || pid <= 0) {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
try {
|
|
18
|
+
process.kill(pid, 0);
|
|
19
|
+
return true;
|
|
20
|
+
} catch {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async function acquirePidLock(logger: ReturnType<typeof createDaemonLogger>): Promise<void> {
|
|
26
|
+
await mkdir(CONFIG_DIR, { recursive: true });
|
|
27
|
+
|
|
28
|
+
const pidFile = Bun.file(PID_FILE);
|
|
29
|
+
if (await pidFile.exists()) {
|
|
30
|
+
const existingPidText = (await pidFile.text()).trim();
|
|
31
|
+
const existingPid = Number.parseInt(existingPidText, 10);
|
|
32
|
+
|
|
33
|
+
if (await isProcessAlive(existingPid)) {
|
|
34
|
+
logger.warn("daemon.start.skipped", {
|
|
35
|
+
reason: "already_running",
|
|
36
|
+
pid: existingPid,
|
|
37
|
+
pidFile: PID_FILE,
|
|
38
|
+
});
|
|
39
|
+
await logger.flush();
|
|
40
|
+
console.error(`Error: Daemon is already running with PID ${existingPid}`);
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
logger.warn("daemon.pid.stale", {
|
|
45
|
+
pid: existingPidText || null,
|
|
46
|
+
pidFile: PID_FILE,
|
|
47
|
+
});
|
|
48
|
+
try {
|
|
49
|
+
await unlink(PID_FILE);
|
|
50
|
+
} catch {
|
|
51
|
+
// File may already be gone
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
try {
|
|
56
|
+
const fd = openSync(PID_FILE, "wx");
|
|
57
|
+
try {
|
|
58
|
+
writeFileSync(fd, `${process.pid}`);
|
|
59
|
+
} finally {
|
|
60
|
+
closeSync(fd);
|
|
61
|
+
}
|
|
62
|
+
} catch {
|
|
63
|
+
const currentPidText = (await Bun.file(PID_FILE).text()).trim();
|
|
64
|
+
const currentPid = Number.parseInt(currentPidText, 10);
|
|
65
|
+
|
|
66
|
+
logger.warn("daemon.start.skipped", {
|
|
67
|
+
reason: "pid_lock_exists",
|
|
68
|
+
pid: Number.isNaN(currentPid) ? currentPidText : currentPid,
|
|
69
|
+
pidFile: PID_FILE,
|
|
70
|
+
});
|
|
71
|
+
await logger.flush();
|
|
72
|
+
console.error("Error: Daemon is already starting or running");
|
|
73
|
+
process.exit(1);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
10
77
|
export async function startDaemon() {
|
|
11
78
|
const stateManager = new DaemonStateManager();
|
|
12
|
-
const logger = createDaemonLogger();
|
|
79
|
+
const logger = createDaemonLogger({ mirrorToConsole: false });
|
|
13
80
|
|
|
14
81
|
logger.info("daemon.start.requested", {
|
|
15
82
|
pidFile: PID_FILE,
|
|
16
83
|
socketPath: SOCKET_PATH,
|
|
17
84
|
});
|
|
18
85
|
|
|
86
|
+
await acquirePidLock(logger);
|
|
87
|
+
|
|
19
88
|
try {
|
|
20
89
|
const testConn = await Bun.connect({
|
|
21
90
|
unix: SOCKET_PATH,
|
|
@@ -31,6 +100,11 @@ export async function startDaemon() {
|
|
|
31
100
|
reason: "already_running",
|
|
32
101
|
socketPath: SOCKET_PATH,
|
|
33
102
|
});
|
|
103
|
+
try {
|
|
104
|
+
await unlink(PID_FILE);
|
|
105
|
+
} catch {
|
|
106
|
+
// File might not exist
|
|
107
|
+
}
|
|
34
108
|
await logger.flush();
|
|
35
109
|
console.error(`Error: Daemon is already running on ${SOCKET_PATH}`);
|
|
36
110
|
process.exit(1);
|
|
@@ -38,25 +112,11 @@ export async function startDaemon() {
|
|
|
38
112
|
// Not running, safe to proceed
|
|
39
113
|
}
|
|
40
114
|
|
|
41
|
-
try {
|
|
42
|
-
await Bun.write(PID_FILE, "");
|
|
43
|
-
await unlink(PID_FILE);
|
|
44
|
-
} catch {
|
|
45
|
-
// Directory creation failed or file didn't exist
|
|
46
|
-
}
|
|
47
|
-
|
|
48
115
|
try {
|
|
49
116
|
await unlink(SOCKET_PATH);
|
|
50
117
|
} catch {
|
|
51
118
|
// File might not exist
|
|
52
119
|
}
|
|
53
|
-
try {
|
|
54
|
-
await unlink(PID_FILE);
|
|
55
|
-
} catch {
|
|
56
|
-
// File might not exist
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
await Bun.write(PID_FILE, process.pid.toString());
|
|
60
120
|
|
|
61
121
|
try {
|
|
62
122
|
const configExists = await Bun.file(CONFIG_FILE).exists();
|
|
@@ -87,6 +147,7 @@ export async function startDaemon() {
|
|
|
87
147
|
}
|
|
88
148
|
} else {
|
|
89
149
|
logger.info("wallet.config_missing");
|
|
150
|
+
logger.info("wallet.uninitialized");
|
|
90
151
|
}
|
|
91
152
|
} catch (error) {
|
|
92
153
|
logger.warn("wallet.config_load_failed", { error: serializeError(error) });
|
|
@@ -160,9 +221,6 @@ export async function startDaemon() {
|
|
|
160
221
|
});
|
|
161
222
|
|
|
162
223
|
logger.info("daemon.started", { socketPath: SOCKET_PATH });
|
|
163
|
-
if (stateManager.isUninitialized()) {
|
|
164
|
-
logger.info("wallet.uninitialized");
|
|
165
|
-
}
|
|
166
224
|
|
|
167
225
|
process.on("unhandledRejection", (error) => {
|
|
168
226
|
logger.error("daemon.unhandled_rejection", { error: serializeError(error) });
|
package/src/routes.ts
CHANGED
|
@@ -36,34 +36,50 @@ export function createRouteHandlers(
|
|
|
36
36
|
},
|
|
37
37
|
"/init": {
|
|
38
38
|
POST: stateManager.requireUninitialized(async (req: Request) => {
|
|
39
|
+
const initLogger = logger?.child?.({ route: "/init" }) ?? logger;
|
|
40
|
+
|
|
39
41
|
try {
|
|
42
|
+
initLogger?.info?.("wallet.init.started");
|
|
43
|
+
|
|
40
44
|
const body = (await req.json()) as {
|
|
41
45
|
mnemonic?: string;
|
|
42
46
|
passphrase?: string;
|
|
43
47
|
mintUrl?: string;
|
|
44
48
|
};
|
|
45
49
|
|
|
50
|
+
initLogger?.info?.("wallet.init.request_parsed", {
|
|
51
|
+
encrypted: Boolean(body.passphrase),
|
|
52
|
+
hasMnemonic: Boolean(body.mnemonic),
|
|
53
|
+
mintUrl: body.mintUrl || "https://mint.minibits.cash/Bitcoin",
|
|
54
|
+
});
|
|
55
|
+
|
|
46
56
|
let mnemonic: string;
|
|
47
57
|
if (body.mnemonic) {
|
|
58
|
+
initLogger?.info?.("wallet.init.validating_mnemonic");
|
|
48
59
|
if (!validateMnemonic(body.mnemonic, wordlist)) {
|
|
60
|
+
initLogger?.warn?.("wallet.init.invalid_mnemonic");
|
|
49
61
|
return Response.json({ error: "Invalid mnemonic" }, { status: 400 });
|
|
50
62
|
}
|
|
51
63
|
mnemonic = body.mnemonic;
|
|
52
64
|
} else {
|
|
65
|
+
initLogger?.info?.("wallet.init.generating_mnemonic");
|
|
53
66
|
mnemonic = generateMnemonic(wordlist, 256);
|
|
54
67
|
}
|
|
55
68
|
|
|
56
69
|
const mintUrl = body.mintUrl || "https://mint.minibits.cash/Bitcoin";
|
|
57
70
|
const encrypted = !!body.passphrase;
|
|
58
71
|
|
|
72
|
+
initLogger?.info?.("wallet.init.resetting_config_file", { configFile: CONFIG_FILE });
|
|
59
73
|
await Bun.write(CONFIG_FILE, "");
|
|
60
74
|
await unlink(CONFIG_FILE);
|
|
61
75
|
|
|
62
76
|
let config: WalletConfig;
|
|
63
77
|
|
|
64
78
|
if (encrypted && body.passphrase) {
|
|
79
|
+
initLogger?.info?.("wallet.init.encrypting_mnemonic");
|
|
65
80
|
const { ciphertext, salt } = await encryptMnemonic(mnemonic, body.passphrase);
|
|
66
81
|
|
|
82
|
+
initLogger?.info?.("wallet.init.writing_salt_file", { saltFile: SALT_FILE });
|
|
67
83
|
await Bun.write(SALT_FILE, salt);
|
|
68
84
|
|
|
69
85
|
config = {
|
|
@@ -75,6 +91,7 @@ export function createRouteHandlers(
|
|
|
75
91
|
};
|
|
76
92
|
|
|
77
93
|
stateManager.setLocked(ciphertext, mintUrl);
|
|
94
|
+
initLogger?.info?.("wallet.init.completed_locked", { mintUrl, state: "LOCKED" });
|
|
78
95
|
} else {
|
|
79
96
|
config = {
|
|
80
97
|
version: 1,
|
|
@@ -84,19 +101,25 @@ export function createRouteHandlers(
|
|
|
84
101
|
createdAt: new Date().toISOString(),
|
|
85
102
|
};
|
|
86
103
|
|
|
87
|
-
|
|
104
|
+
initLogger?.info?.("wallet.init.initializing_wallet_manager", { mintUrl });
|
|
105
|
+
const manager = await initializeWallet(config, undefined, initLogger);
|
|
106
|
+
initLogger?.info?.("wallet.init.wallet_manager_ready", { mintUrl });
|
|
88
107
|
const seed = mnemonicToSeedSync(mnemonic);
|
|
89
108
|
stateManager.setUnlocked(manager, mintUrl, seed);
|
|
109
|
+
initLogger?.info?.("wallet.init.completed_unlocked", { mintUrl, state: "UNLOCKED" });
|
|
90
110
|
}
|
|
91
111
|
|
|
112
|
+
initLogger?.info?.("wallet.init.writing_config_file", { configFile: CONFIG_FILE });
|
|
92
113
|
await Bun.write(CONFIG_FILE, JSON.stringify(config, null, 2));
|
|
93
114
|
|
|
94
115
|
const output = encrypted
|
|
95
116
|
? `Initialized (locked). Mnemonic: ${mnemonic}\nIMPORTANT: Write down this mnemonic and keep it safe!`
|
|
96
117
|
: `Initialized. Mnemonic: ${mnemonic}\nIMPORTANT: Write down this mnemonic and keep it safe!`;
|
|
97
118
|
|
|
119
|
+
initLogger?.info?.("wallet.init.response_ready", { encrypted, mintUrl });
|
|
98
120
|
return Response.json({ output });
|
|
99
121
|
} catch (error) {
|
|
122
|
+
initLogger?.error?.("wallet.init.failed", { error: serializeError(error) });
|
|
100
123
|
const message = error instanceof Error ? error.message : String(error);
|
|
101
124
|
return Response.json({ error: `Init failed: ${message}` }, { status: 500 });
|
|
102
125
|
}
|
|
@@ -104,15 +127,21 @@ export function createRouteHandlers(
|
|
|
104
127
|
},
|
|
105
128
|
"/unlock": {
|
|
106
129
|
POST: stateManager.requireLocked(async (req: Request, state: LockedState) => {
|
|
130
|
+
const unlockLogger = logger?.child?.({ route: "/unlock" }) ?? logger;
|
|
131
|
+
|
|
107
132
|
try {
|
|
133
|
+
unlockLogger?.info?.("wallet.unlock.started", { mintUrl: state.mintUrl });
|
|
108
134
|
const body = (await req.json()) as { passphrase: string };
|
|
109
135
|
|
|
110
136
|
if (!body.passphrase) {
|
|
137
|
+
unlockLogger?.warn?.("wallet.unlock.missing_passphrase");
|
|
111
138
|
return Response.json({ error: "Passphrase required" }, { status: 400 });
|
|
112
139
|
}
|
|
113
140
|
|
|
141
|
+
unlockLogger?.info?.("wallet.unlock.reading_salt_file", { saltFile: SALT_FILE });
|
|
114
142
|
const salt = await Bun.file(SALT_FILE).text();
|
|
115
143
|
const { decryptMnemonic } = await import("./utils/crypto.js");
|
|
144
|
+
unlockLogger?.info?.("wallet.unlock.decrypting_mnemonic");
|
|
116
145
|
const mnemonic = await decryptMnemonic(state.encryptedMnemonic, body.passphrase, salt);
|
|
117
146
|
|
|
118
147
|
const config: WalletConfig = {
|
|
@@ -123,13 +152,22 @@ export function createRouteHandlers(
|
|
|
123
152
|
createdAt: new Date().toISOString(),
|
|
124
153
|
};
|
|
125
154
|
|
|
126
|
-
|
|
155
|
+
unlockLogger?.info?.("wallet.unlock.initializing_wallet_manager", {
|
|
156
|
+
mintUrl: state.mintUrl,
|
|
157
|
+
});
|
|
158
|
+
const manager = await initializeWallet(config, undefined, unlockLogger);
|
|
159
|
+
unlockLogger?.info?.("wallet.unlock.wallet_manager_ready", { mintUrl: state.mintUrl });
|
|
127
160
|
const seed = mnemonicToSeedSync(mnemonic);
|
|
128
161
|
|
|
129
162
|
stateManager.setUnlocked(manager, state.mintUrl, seed);
|
|
163
|
+
unlockLogger?.info?.("wallet.unlock.completed", {
|
|
164
|
+
mintUrl: state.mintUrl,
|
|
165
|
+
state: "UNLOCKED",
|
|
166
|
+
});
|
|
130
167
|
|
|
131
168
|
return Response.json({ output: "Unlocked" });
|
|
132
169
|
} catch (error) {
|
|
170
|
+
unlockLogger?.error?.("wallet.unlock.failed", { error: serializeError(error) });
|
|
133
171
|
const message = error instanceof Error ? error.message : String(error);
|
|
134
172
|
return Response.json({ error: `Unlock failed: ${message}` }, { status: 401 });
|
|
135
173
|
}
|
|
@@ -504,11 +542,13 @@ async function runRoute(
|
|
|
504
542
|
const durationMs = Math.round(performance.now() - startedAt);
|
|
505
543
|
const level = response.status >= 500 ? "error" : response.status >= 400 ? "warn" : "info";
|
|
506
544
|
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
545
|
+
if (path !== "/status") {
|
|
546
|
+
requestLogger?.log?.(level, "request.completed", {
|
|
547
|
+
durationMs,
|
|
548
|
+
state: getState().status,
|
|
549
|
+
status: response.status,
|
|
550
|
+
});
|
|
551
|
+
}
|
|
512
552
|
|
|
513
553
|
return response;
|
|
514
554
|
} catch (error) {
|
package/src/utils/wallet.ts
CHANGED
|
@@ -14,38 +14,54 @@ export async function initializeWallet(
|
|
|
14
14
|
passphrase?: string,
|
|
15
15
|
logger?: Logger,
|
|
16
16
|
): Promise<Manager> {
|
|
17
|
+
const walletLogger = logger?.child?.({ component: "wallet-init" }) ?? logger;
|
|
18
|
+
walletLogger?.info?.("wallet.initialize.started", {
|
|
19
|
+
encrypted: config.encrypted,
|
|
20
|
+
mintUrl: config.mintUrl,
|
|
21
|
+
dbFile: DB_FILE,
|
|
22
|
+
});
|
|
23
|
+
|
|
17
24
|
let mnemonic: string;
|
|
18
25
|
|
|
19
26
|
if (config.encrypted) {
|
|
27
|
+
walletLogger?.info?.("wallet.initialize.decrypting_config_mnemonic", { saltFile: SALT_FILE });
|
|
20
28
|
if (!passphrase) {
|
|
21
29
|
throw new Error("Passphrase required for encrypted wallet");
|
|
22
30
|
}
|
|
23
31
|
const salt = await Bun.file(SALT_FILE).text();
|
|
24
32
|
mnemonic = await decryptMnemonic(config.mnemonic, passphrase, salt);
|
|
25
33
|
} else {
|
|
34
|
+
walletLogger?.info?.("wallet.initialize.using_plaintext_config_mnemonic");
|
|
26
35
|
mnemonic = config.mnemonic;
|
|
27
36
|
}
|
|
28
37
|
|
|
38
|
+
walletLogger?.info?.("wallet.initialize.derived_mnemonic");
|
|
29
39
|
const seed = mnemonicToSeedSync(mnemonic);
|
|
30
40
|
|
|
41
|
+
walletLogger?.info?.("wallet.initialize.opening_database", { dbFile: DB_FILE });
|
|
31
42
|
const repo = new SqliteRepositories({ database: new Database(DB_FILE) });
|
|
32
|
-
const
|
|
33
|
-
|
|
43
|
+
const cocoLogger = walletLogger?.child?.({ component: "coco" }) ?? new ConsoleLogger("Coco", { level: "info" });
|
|
44
|
+
walletLogger?.info?.("wallet.initialize.preparing_signer");
|
|
34
45
|
const sk = privateKeyFromSeedWords(mnemonic);
|
|
35
46
|
const signer = async (t: EventTemplate) => finalizeEvent(t, sk);
|
|
47
|
+
walletLogger?.info?.("wallet.initialize.creating_npc_plugin", { npcUrl: "https://npubx.cash" });
|
|
36
48
|
const npcPlugin = new NPCPlugin("https://npubx.cash", signer, {
|
|
37
49
|
useWebsocket: true,
|
|
38
50
|
logger: cocoLogger,
|
|
39
51
|
});
|
|
52
|
+
walletLogger?.info?.("wallet.initialize.initializing_coco_core", { mintUrl: config.mintUrl });
|
|
40
53
|
const coco = await initializeCoco({
|
|
41
54
|
repo,
|
|
42
55
|
seedGetter: async () => seed,
|
|
43
56
|
logger: cocoLogger,
|
|
44
57
|
});
|
|
45
58
|
|
|
59
|
+
walletLogger?.info?.("wallet.initialize.registering_npc_plugin");
|
|
46
60
|
coco.use(npcPlugin);
|
|
47
61
|
|
|
62
|
+
walletLogger?.info?.("wallet.initialize.adding_trusted_mint", { mintUrl: config.mintUrl });
|
|
48
63
|
await coco.mint.addMint(config.mintUrl, { trusted: true });
|
|
64
|
+
walletLogger?.info?.("wallet.initialize.completed", { mintUrl: config.mintUrl });
|
|
49
65
|
|
|
50
66
|
return coco;
|
|
51
67
|
}
|