@cogcoin/client 0.5.4 → 0.5.6

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 (74) hide show
  1. package/README.md +1 -1
  2. package/dist/app-paths.d.ts +2 -0
  3. package/dist/app-paths.js +4 -0
  4. package/dist/art/wallet.txt +9 -9
  5. package/dist/bitcoind/bootstrap/chainstate.d.ts +2 -1
  6. package/dist/bitcoind/bootstrap/chainstate.js +4 -1
  7. package/dist/bitcoind/bootstrap/chunk-manifest.d.ts +14 -0
  8. package/dist/bitcoind/bootstrap/chunk-manifest.js +85 -0
  9. package/dist/bitcoind/bootstrap/chunk-recovery.d.ts +4 -0
  10. package/dist/bitcoind/bootstrap/chunk-recovery.js +122 -0
  11. package/dist/bitcoind/bootstrap/constants.d.ts +3 -1
  12. package/dist/bitcoind/bootstrap/constants.js +3 -1
  13. package/dist/bitcoind/bootstrap/controller.d.ts +10 -2
  14. package/dist/bitcoind/bootstrap/controller.js +56 -12
  15. package/dist/bitcoind/bootstrap/default-snapshot-chunk-manifest.d.ts +2 -0
  16. package/dist/bitcoind/bootstrap/default-snapshot-chunk-manifest.js +2309 -0
  17. package/dist/bitcoind/bootstrap/download.js +177 -83
  18. package/dist/bitcoind/bootstrap/headers.d.ts +16 -2
  19. package/dist/bitcoind/bootstrap/headers.js +124 -14
  20. package/dist/bitcoind/bootstrap/state.d.ts +11 -1
  21. package/dist/bitcoind/bootstrap/state.js +50 -23
  22. package/dist/bitcoind/bootstrap/types.d.ts +12 -1
  23. package/dist/bitcoind/client/factory.js +11 -2
  24. package/dist/bitcoind/client/internal-types.d.ts +1 -0
  25. package/dist/bitcoind/client/managed-client.d.ts +1 -1
  26. package/dist/bitcoind/client/managed-client.js +29 -15
  27. package/dist/bitcoind/client/sync-engine.js +88 -16
  28. package/dist/bitcoind/errors.js +9 -0
  29. package/dist/bitcoind/indexer-daemon.d.ts +7 -0
  30. package/dist/bitcoind/indexer-daemon.js +31 -22
  31. package/dist/bitcoind/processing-start-height.d.ts +7 -0
  32. package/dist/bitcoind/processing-start-height.js +9 -0
  33. package/dist/bitcoind/progress/controller.js +1 -0
  34. package/dist/bitcoind/progress/formatting.js +4 -1
  35. package/dist/bitcoind/retryable-rpc.d.ts +11 -0
  36. package/dist/bitcoind/retryable-rpc.js +30 -0
  37. package/dist/bitcoind/service.d.ts +16 -1
  38. package/dist/bitcoind/service.js +228 -115
  39. package/dist/bitcoind/testing.d.ts +1 -1
  40. package/dist/bitcoind/testing.js +1 -1
  41. package/dist/bitcoind/types.d.ts +10 -0
  42. package/dist/cli/commands/follow.js +9 -0
  43. package/dist/cli/commands/service-runtime.js +150 -134
  44. package/dist/cli/commands/sync.js +9 -0
  45. package/dist/cli/commands/wallet-admin.js +77 -21
  46. package/dist/cli/context.js +4 -2
  47. package/dist/cli/mutation-json.js +2 -0
  48. package/dist/cli/output.js +3 -1
  49. package/dist/cli/parse.d.ts +1 -1
  50. package/dist/cli/parse.js +6 -0
  51. package/dist/cli/preview-json.js +2 -0
  52. package/dist/cli/runner.js +1 -0
  53. package/dist/cli/types.d.ts +6 -3
  54. package/dist/cli/types.js +1 -1
  55. package/dist/cli/wallet-format.js +134 -14
  56. package/dist/wallet/lifecycle.d.ts +6 -0
  57. package/dist/wallet/lifecycle.js +168 -37
  58. package/dist/wallet/read/context.js +10 -4
  59. package/dist/wallet/reset.d.ts +61 -2
  60. package/dist/wallet/reset.js +208 -63
  61. package/dist/wallet/root-resolution.d.ts +20 -0
  62. package/dist/wallet/root-resolution.js +37 -0
  63. package/dist/wallet/runtime.d.ts +3 -0
  64. package/dist/wallet/runtime.js +3 -0
  65. package/dist/wallet/state/crypto.d.ts +3 -0
  66. package/dist/wallet/state/crypto.js +3 -0
  67. package/dist/wallet/state/pending-init.d.ts +24 -0
  68. package/dist/wallet/state/pending-init.js +59 -0
  69. package/dist/wallet/state/provider.d.ts +1 -0
  70. package/dist/wallet/state/provider.js +7 -1
  71. package/dist/wallet/state/storage.d.ts +7 -1
  72. package/dist/wallet/state/storage.js +39 -0
  73. package/dist/wallet/types.d.ts +9 -0
  74. package/package.json +4 -2
@@ -1,6 +1,8 @@
1
1
  import { dirname } from "node:path";
2
2
  import { loadBundledGenesisParameters } from "@cogcoin/indexer";
3
+ import { resolveCogcoinProcessingStartHeight } from "../../bitcoind/processing-start-height.js";
3
4
  import { UNINITIALIZED_WALLET_ROOT_ID, resolveManagedServicePaths } from "../../bitcoind/service-paths.js";
5
+ import { resolveWalletRootIdFromLocalArtifacts, } from "../../wallet/root-resolution.js";
4
6
  import { writeLine } from "../io.js";
5
7
  import { createSuccessEnvelope, describeCanonicalCommand, writeHandledCliError, writeJsonValue, } from "../output.js";
6
8
  function formatBool(value) {
@@ -12,66 +14,39 @@ function formatMaybe(value) {
12
14
  function formatCompatibility(value) {
13
15
  return value.replaceAll("-", " ");
14
16
  }
15
- async function resolveEffectiveWalletRootId(dataDir, context) {
16
- const paths = context.resolveWalletRuntimePaths();
17
- try {
18
- const loaded = await context.loadWalletState({
19
- primaryPath: paths.walletStatePath,
20
- backupPath: paths.walletStateBackupPath,
21
- }, {
22
- provider: context.walletSecretProvider,
23
- });
24
- return {
25
- walletRootId: loaded.state.walletRootId,
26
- source: "wallet-state",
27
- };
28
- }
29
- catch {
30
- // fall through
31
- }
32
- try {
33
- const unlockSession = await context.loadUnlockSession(paths.walletUnlockSessionPath, {
34
- provider: context.walletSecretProvider,
35
- });
36
- return {
37
- walletRootId: unlockSession.walletRootId,
38
- source: "unlock-session",
39
- };
40
- }
41
- catch {
42
- // fall through
43
- }
44
- try {
45
- const explicitLock = await context.loadWalletExplicitLock(paths.walletExplicitLockPath);
46
- if (explicitLock?.walletRootId) {
47
- return {
48
- walletRootId: explicitLock.walletRootId,
49
- source: "explicit-lock",
50
- };
51
- }
52
- }
53
- catch {
54
- // fall through
55
- }
56
- const fallbackProbe = await context.probeManagedBitcoindService({
57
- dataDir,
58
- chain: "main",
59
- startHeight: 0,
60
- walletRootId: UNINITIALIZED_WALLET_ROOT_ID,
61
- });
62
- if (fallbackProbe.status?.walletRootId) {
63
- return {
64
- walletRootId: fallbackProbe.status.walletRootId,
65
- source: "bitcoind-status",
66
- };
67
- }
17
+ function serviceStatusEntry(label, value, ok) {
68
18
  return {
19
+ text: `${label}: ${value}`,
20
+ ok,
21
+ };
22
+ }
23
+ function formatServiceStatusSection(header, entries) {
24
+ return [header, ...entries.map((entry) => `${entry.ok ? "✓" : "✗"} ${entry.text}`)].join("\n");
25
+ }
26
+ function formatSectionedServiceStatusReport(options) {
27
+ const parts = [
28
+ `\n⛭ ${options.title} ⛭`,
29
+ ...options.sections.map((section) => formatServiceStatusSection(section.header, section.entries)),
30
+ ];
31
+ if (options.nextStep !== null) {
32
+ parts.push(`Next step: ${options.nextStep}`);
33
+ }
34
+ return parts.join("\n\n");
35
+ }
36
+ async function resolveEffectiveWalletRootId(context) {
37
+ return resolveWalletRootIdFromLocalArtifacts({
38
+ paths: context.resolveWalletRuntimePaths(),
39
+ provider: context.walletSecretProvider,
40
+ loadRawWalletStateEnvelope: context.loadRawWalletStateEnvelope,
41
+ loadUnlockSession: context.loadUnlockSession,
42
+ loadWalletExplicitLock: context.loadWalletExplicitLock,
43
+ }).catch(() => ({
69
44
  walletRootId: UNINITIALIZED_WALLET_ROOT_ID,
70
45
  source: "default-uninitialized",
71
- };
46
+ }));
72
47
  }
73
48
  async function inspectManagedBitcoindStatus(dataDir, context) {
74
- const resolution = await resolveEffectiveWalletRootId(dataDir, context);
49
+ const resolution = await resolveEffectiveWalletRootId(context);
75
50
  const probe = await context.probeManagedBitcoindService({
76
51
  dataDir,
77
52
  chain: "main",
@@ -114,7 +89,7 @@ async function inspectManagedBitcoindStatus(dataDir, context) {
114
89
  };
115
90
  }
116
91
  async function inspectManagedIndexerStatus(dataDir, context) {
117
- const resolution = await resolveEffectiveWalletRootId(dataDir, context);
92
+ const resolution = await resolveEffectiveWalletRootId(context);
118
93
  const runtimeRoot = resolveManagedServicePaths(dataDir, resolution.walletRootId).walletRuntimeRoot;
119
94
  const probe = await context.probeIndexerDaemon({
120
95
  dataDir,
@@ -144,96 +119,137 @@ async function inspectManagedIndexerStatus(dataDir, context) {
144
119
  };
145
120
  }
146
121
  function formatBitcoinStatusReport(payload) {
147
- const lines = [
148
- "Managed Bitcoind Status",
149
- `Bitcoin datadir: ${payload.dataDir}`,
150
- `Wallet root: ${payload.walletRootId}`,
151
- `Wallet root source: ${payload.walletRootSource}`,
152
- `Compatibility: ${formatCompatibility(payload.compatibility)}`,
122
+ const compatibilityOk = payload.compatibility === "compatible";
123
+ const serviceStateOk = payload.service?.state === "ready";
124
+ const nodeOk = payload.node !== null && payload.nodeError === null;
125
+ const managedServiceEntries = [
126
+ serviceStatusEntry("Compatibility", formatCompatibility(payload.compatibility), compatibilityOk),
153
127
  ];
154
128
  if (payload.service !== null) {
155
- lines.push(`Service state: ${payload.service.state}`);
156
- lines.push(`Process id: ${formatMaybe(payload.service.processId)}`);
157
- lines.push(`Service instance: ${payload.service.serviceInstanceId}`);
158
- lines.push(`Runtime root: ${payload.service.runtimeRoot}`);
159
- lines.push(`Chain: ${payload.service.chain}`);
160
- lines.push(`RPC: ${payload.service.rpc.url}`);
161
- lines.push(`RPC cookie: ${payload.service.rpc.cookieFile}`);
162
- lines.push(`ZMQ: ${payload.service.zmq.endpoint}`);
163
- lines.push(`P2P port: ${payload.service.p2pPort}`);
164
- lines.push(`Started at: ${payload.service.startedAtUnixMs}`);
165
- lines.push(`Heartbeat at: ${payload.service.heartbeatAtUnixMs}`);
166
- lines.push(`Updated at: ${payload.service.updatedAtUnixMs}`);
167
- lines.push(`Managed Core wallet: ${payload.service.walletReplica?.proofStatus ?? "unavailable"}`);
129
+ managedServiceEntries.push(serviceStatusEntry("Service state", payload.service.state, serviceStateOk));
130
+ managedServiceEntries.push(serviceStatusEntry("Process id", formatMaybe(payload.service.processId), serviceStateOk));
131
+ managedServiceEntries.push(serviceStatusEntry("Service instance", payload.service.serviceInstanceId, serviceStateOk));
132
+ managedServiceEntries.push(serviceStatusEntry("Runtime root", payload.service.runtimeRoot, serviceStateOk));
133
+ managedServiceEntries.push(serviceStatusEntry("Chain", payload.service.chain, serviceStateOk));
134
+ managedServiceEntries.push(serviceStatusEntry("RPC", payload.service.rpc.url, serviceStateOk));
135
+ managedServiceEntries.push(serviceStatusEntry("RPC cookie", payload.service.rpc.cookieFile, serviceStateOk));
136
+ managedServiceEntries.push(serviceStatusEntry("ZMQ", payload.service.zmq.endpoint, serviceStateOk));
137
+ managedServiceEntries.push(serviceStatusEntry("P2P port", String(payload.service.p2pPort), serviceStateOk));
138
+ managedServiceEntries.push(serviceStatusEntry("Started at", String(payload.service.startedAtUnixMs), serviceStateOk));
139
+ managedServiceEntries.push(serviceStatusEntry("Heartbeat at", String(payload.service.heartbeatAtUnixMs), serviceStateOk));
140
+ managedServiceEntries.push(serviceStatusEntry("Updated at", String(payload.service.updatedAtUnixMs), serviceStateOk));
141
+ managedServiceEntries.push(serviceStatusEntry("Managed Core wallet", payload.service.walletReplica?.proofStatus ?? "unavailable", payload.service.walletReplica?.proofStatus === "ready"));
168
142
  if (payload.service.lastError !== null) {
169
- lines.push(`Service error: ${payload.service.lastError}`);
143
+ managedServiceEntries.push(serviceStatusEntry("Service error", payload.service.lastError, false));
170
144
  }
171
145
  }
172
146
  else {
173
- lines.push("Service state: unavailable");
174
- }
175
- if (payload.node !== null) {
176
- lines.push(`Bitcoin best height: ${payload.node.bestHeight}`);
177
- lines.push(`Bitcoin headers: ${payload.node.headerHeight}`);
178
- lines.push(`Bitcoin best hash: ${payload.node.bestHash}`);
179
- lines.push(`Verification progress: ${formatMaybe(payload.node.verificationProgress)}`);
180
- lines.push(`Initial block download: ${formatBool(payload.node.initialBlockDownload)}`);
181
- lines.push(`Network active: ${formatBool(payload.node.networkActive)}`);
182
- lines.push(`Connections: ${payload.node.connections}`);
183
- lines.push(`Inbound connections: ${formatMaybe(payload.node.inboundConnections)}`);
184
- lines.push(`Outbound connections: ${formatMaybe(payload.node.outboundConnections)}`);
185
- }
186
- else {
187
- lines.push("Bitcoin node: unavailable");
147
+ managedServiceEntries.push(serviceStatusEntry("Service state", "unavailable", false));
188
148
  }
149
+ const bitcoinNodeEntries = payload.node !== null
150
+ ? [
151
+ serviceStatusEntry("Best height", String(payload.node.bestHeight), nodeOk),
152
+ serviceStatusEntry("Headers", String(payload.node.headerHeight), nodeOk),
153
+ serviceStatusEntry("Best hash", payload.node.bestHash, nodeOk),
154
+ serviceStatusEntry("Verification progress", formatMaybe(payload.node.verificationProgress), nodeOk),
155
+ serviceStatusEntry("Initial block download", formatBool(payload.node.initialBlockDownload), nodeOk),
156
+ serviceStatusEntry("Network active", formatBool(payload.node.networkActive), nodeOk),
157
+ serviceStatusEntry("Connections", String(payload.node.connections), nodeOk),
158
+ serviceStatusEntry("Inbound connections", formatMaybe(payload.node.inboundConnections), nodeOk),
159
+ serviceStatusEntry("Outbound connections", formatMaybe(payload.node.outboundConnections), nodeOk),
160
+ ]
161
+ : [serviceStatusEntry("Node state", "unavailable", false)];
189
162
  if (payload.nodeError !== null) {
190
- lines.push(`Node error: ${payload.nodeError}`);
191
- }
192
- if (payload.compatibility === "unreachable") {
193
- lines.push("Recommended next step: Run `cogcoin bitcoin start` to start the managed Bitcoin service.");
163
+ bitcoinNodeEntries.push(serviceStatusEntry("Node error", payload.nodeError, false));
194
164
  }
195
- return `${lines.join("\n")}\n`;
165
+ return formatSectionedServiceStatusReport({
166
+ title: "Bitcoin Status",
167
+ sections: [
168
+ {
169
+ header: "Paths",
170
+ entries: [
171
+ serviceStatusEntry("Bitcoin datadir", payload.dataDir, true),
172
+ serviceStatusEntry("Wallet root", payload.walletRootId, true),
173
+ serviceStatusEntry("Wallet root source", payload.walletRootSource, true),
174
+ ],
175
+ },
176
+ {
177
+ header: "Managed Service",
178
+ entries: managedServiceEntries,
179
+ },
180
+ {
181
+ header: "Bitcoin Node",
182
+ entries: bitcoinNodeEntries,
183
+ },
184
+ ],
185
+ nextStep: payload.compatibility === "unreachable"
186
+ ? "Run `cogcoin bitcoin start` to start the managed Bitcoin service."
187
+ : null,
188
+ });
196
189
  }
197
190
  function formatIndexerStatusReport(payload) {
198
- const lines = [
199
- "Managed Indexer Status",
200
- `Bitcoin datadir: ${payload.dataDir}`,
201
- `Wallet root: ${payload.walletRootId}`,
202
- `Wallet root source: ${payload.walletRootSource}`,
203
- `Compatibility: ${formatCompatibility(payload.compatibility)}`,
204
- `Observed source: ${payload.source}`,
191
+ const compatibilityOk = payload.compatibility === "compatible";
192
+ const observedSourceOk = payload.source === "probe";
193
+ const daemonStateOk = payload.daemon?.state === "synced";
194
+ const managedServiceEntries = [
195
+ serviceStatusEntry("Compatibility", formatCompatibility(payload.compatibility), compatibilityOk),
196
+ serviceStatusEntry("Observed source", payload.source, observedSourceOk),
205
197
  ];
206
198
  if (payload.daemon !== null) {
207
- lines.push(`Daemon state: ${payload.daemon.state}`);
208
- lines.push(`Process id: ${formatMaybe(payload.daemon.processId)}`);
209
- lines.push(`Daemon instance: ${payload.daemon.daemonInstanceId}`);
210
- lines.push(`Runtime root: ${payload.daemon.runtimeRoot}`);
211
- lines.push(`Schema version: ${payload.daemon.schemaVersion}`);
212
- lines.push(`Started at: ${payload.daemon.startedAtUnixMs}`);
213
- lines.push(`Heartbeat at: ${payload.daemon.heartbeatAtUnixMs}`);
214
- lines.push(`Updated at: ${payload.daemon.updatedAtUnixMs}`);
215
- lines.push(`IPC ready: ${formatBool(payload.daemon.ipcReady)}`);
216
- lines.push(`RPC reachable: ${formatBool(payload.daemon.rpcReachable)}`);
217
- lines.push(`Core best height: ${formatMaybe(payload.daemon.coreBestHeight)}`);
218
- lines.push(`Core best hash: ${formatMaybe(payload.daemon.coreBestHash)}`);
219
- lines.push(`Applied tip height: ${formatMaybe(payload.daemon.appliedTipHeight)}`);
220
- lines.push(`Applied tip hash: ${formatMaybe(payload.daemon.appliedTipHash)}`);
221
- lines.push(`Snapshot sequence: ${formatMaybe(payload.daemon.snapshotSeq)}`);
222
- lines.push(`Backlog blocks: ${formatMaybe(payload.daemon.backlogBlocks)}`);
223
- lines.push(`Reorg depth: ${formatMaybe(payload.daemon.reorgDepth)}`);
224
- lines.push(`Active snapshots: ${payload.daemon.activeSnapshotCount}`);
225
- lines.push(`Last applied at: ${formatMaybe(payload.daemon.lastAppliedAtUnixMs)}`);
199
+ managedServiceEntries.push(serviceStatusEntry("Daemon state", payload.daemon.state, daemonStateOk));
200
+ managedServiceEntries.push(serviceStatusEntry("Process id", formatMaybe(payload.daemon.processId), daemonStateOk));
201
+ managedServiceEntries.push(serviceStatusEntry("Daemon instance", payload.daemon.daemonInstanceId, daemonStateOk));
202
+ managedServiceEntries.push(serviceStatusEntry("Runtime root", payload.daemon.runtimeRoot, daemonStateOk));
203
+ managedServiceEntries.push(serviceStatusEntry("Schema version", payload.daemon.schemaVersion, daemonStateOk));
204
+ managedServiceEntries.push(serviceStatusEntry("Started at", String(payload.daemon.startedAtUnixMs), daemonStateOk));
205
+ managedServiceEntries.push(serviceStatusEntry("Heartbeat at", String(payload.daemon.heartbeatAtUnixMs), daemonStateOk));
206
+ managedServiceEntries.push(serviceStatusEntry("Updated at", String(payload.daemon.updatedAtUnixMs), daemonStateOk));
207
+ managedServiceEntries.push(serviceStatusEntry("IPC ready", formatBool(payload.daemon.ipcReady), payload.daemon.ipcReady));
208
+ managedServiceEntries.push(serviceStatusEntry("RPC reachable", formatBool(payload.daemon.rpcReachable), payload.daemon.rpcReachable));
226
209
  if (payload.daemon.lastError !== null) {
227
- lines.push(`Daemon error: ${payload.daemon.lastError}`);
210
+ managedServiceEntries.push(serviceStatusEntry("Daemon error", payload.daemon.lastError, false));
228
211
  }
229
212
  }
230
213
  else {
231
- lines.push("Daemon state: unavailable");
232
- }
233
- if (payload.compatibility === "unreachable") {
234
- lines.push("Recommended next step: Run `cogcoin indexer start` to start the managed Cogcoin indexer.");
214
+ managedServiceEntries.push(serviceStatusEntry("Daemon state", "unavailable", false));
235
215
  }
236
- return `${lines.join("\n")}\n`;
216
+ const indexerStateEntries = payload.daemon !== null
217
+ ? [
218
+ serviceStatusEntry("Core best height", formatMaybe(payload.daemon.coreBestHeight), daemonStateOk),
219
+ serviceStatusEntry("Core best hash", formatMaybe(payload.daemon.coreBestHash), daemonStateOk),
220
+ serviceStatusEntry("Applied tip height", formatMaybe(payload.daemon.appliedTipHeight), daemonStateOk),
221
+ serviceStatusEntry("Applied tip hash", formatMaybe(payload.daemon.appliedTipHash), daemonStateOk),
222
+ serviceStatusEntry("Snapshot sequence", formatMaybe(payload.daemon.snapshotSeq), daemonStateOk),
223
+ serviceStatusEntry("Backlog blocks", formatMaybe(payload.daemon.backlogBlocks), daemonStateOk),
224
+ serviceStatusEntry("Reorg depth", formatMaybe(payload.daemon.reorgDepth), daemonStateOk),
225
+ serviceStatusEntry("Active snapshots", String(payload.daemon.activeSnapshotCount), daemonStateOk),
226
+ serviceStatusEntry("Last applied at", formatMaybe(payload.daemon.lastAppliedAtUnixMs), daemonStateOk),
227
+ ]
228
+ : [serviceStatusEntry("Daemon state", "unavailable", false)];
229
+ return formatSectionedServiceStatusReport({
230
+ title: "Indexer Status",
231
+ sections: [
232
+ {
233
+ header: "Paths",
234
+ entries: [
235
+ serviceStatusEntry("Bitcoin datadir", payload.dataDir, true),
236
+ serviceStatusEntry("Wallet root", payload.walletRootId, true),
237
+ serviceStatusEntry("Wallet root source", payload.walletRootSource, true),
238
+ ],
239
+ },
240
+ {
241
+ header: "Managed Service",
242
+ entries: managedServiceEntries,
243
+ },
244
+ {
245
+ header: "Indexer State",
246
+ entries: indexerStateEntries,
247
+ },
248
+ ],
249
+ nextStep: payload.compatibility === "unreachable"
250
+ ? "Run `cogcoin indexer start` to start the managed Cogcoin indexer."
251
+ : null,
252
+ });
237
253
  }
238
254
  function buildStatusMessages(payload) {
239
255
  const warnings = [];
@@ -287,7 +303,7 @@ export async function runServiceRuntimeCommand(parsed, context) {
287
303
  return 0;
288
304
  }
289
305
  if (parsed.command === "bitcoin-start") {
290
- const resolution = await resolveEffectiveWalletRootId(dataDir, context);
306
+ const resolution = await resolveEffectiveWalletRootId(context);
291
307
  const probe = await context.probeManagedBitcoindService({
292
308
  dataDir,
293
309
  chain: "main",
@@ -298,7 +314,7 @@ export async function runServiceRuntimeCommand(parsed, context) {
298
314
  await context.attachManagedBitcoindService({
299
315
  dataDir,
300
316
  chain: "main",
301
- startHeight: genesis.genesisBlock,
317
+ startHeight: resolveCogcoinProcessingStartHeight(genesis),
302
318
  walletRootId: resolution.walletRootId,
303
319
  });
304
320
  const bitcoindStatus = probe.compatibility === "compatible" ? "already-running" : "started";
@@ -324,7 +340,7 @@ export async function runServiceRuntimeCommand(parsed, context) {
324
340
  return 0;
325
341
  }
326
342
  if (parsed.command === "bitcoin-stop") {
327
- const resolution = await resolveEffectiveWalletRootId(dataDir, context);
343
+ const resolution = await resolveEffectiveWalletRootId(context);
328
344
  const indexer = await context.stopIndexerDaemonService({
329
345
  dataDir,
330
346
  walletRootId: resolution.walletRootId,
@@ -349,7 +365,7 @@ export async function runServiceRuntimeCommand(parsed, context) {
349
365
  return 0;
350
366
  }
351
367
  if (parsed.command === "indexer-start") {
352
- const resolution = await resolveEffectiveWalletRootId(dataDir, context);
368
+ const resolution = await resolveEffectiveWalletRootId(context);
353
369
  const dbPath = parsed.dbPath ?? context.resolveDefaultClientDatabasePath();
354
370
  await context.ensureDirectory(dirname(dbPath));
355
371
  const genesis = await loadBundledGenesisParameters();
@@ -362,7 +378,7 @@ export async function runServiceRuntimeCommand(parsed, context) {
362
378
  await context.attachManagedBitcoindService({
363
379
  dataDir,
364
380
  chain: "main",
365
- startHeight: genesis.genesisBlock,
381
+ startHeight: resolveCogcoinProcessingStartHeight(genesis),
366
382
  walletRootId: resolution.walletRootId,
367
383
  });
368
384
  const indexerProbe = await context.probeIndexerDaemon({
@@ -401,7 +417,7 @@ export async function runServiceRuntimeCommand(parsed, context) {
401
417
  return 0;
402
418
  }
403
419
  if (parsed.command === "indexer-stop") {
404
- const resolution = await resolveEffectiveWalletRootId(dataDir, context);
420
+ const resolution = await resolveEffectiveWalletRootId(context);
405
421
  const indexer = await context.stopIndexerDaemonService({
406
422
  dataDir,
407
423
  walletRootId: resolution.walletRootId,
@@ -1,11 +1,19 @@
1
1
  import { dirname } from "node:path";
2
2
  import { formatManagedSyncErrorMessage } from "../../bitcoind/errors.js";
3
+ import { resolveWalletRootIdFromLocalArtifacts } from "../../wallet/root-resolution.js";
3
4
  import { writeLine } from "../io.js";
4
5
  import { classifyCliError } from "../output.js";
5
6
  import { createStopSignalWatcher, waitForCompletionOrStop } from "../signals.js";
6
7
  export async function runSyncCommand(parsed, context) {
7
8
  const dbPath = parsed.dbPath ?? context.resolveDefaultClientDatabasePath();
8
9
  const dataDir = parsed.dataDir ?? context.resolveDefaultBitcoindDataDir();
10
+ const walletRoot = await resolveWalletRootIdFromLocalArtifacts({
11
+ paths: context.resolveWalletRuntimePaths(),
12
+ provider: context.walletSecretProvider,
13
+ loadRawWalletStateEnvelope: context.loadRawWalletStateEnvelope,
14
+ loadUnlockSession: context.loadUnlockSession,
15
+ loadWalletExplicitLock: context.loadWalletExplicitLock,
16
+ });
9
17
  await context.ensureDirectory(dirname(dbPath));
10
18
  const store = await context.openSqliteStore({ filename: dbPath });
11
19
  let storeOwned = true;
@@ -14,6 +22,7 @@ export async function runSyncCommand(parsed, context) {
14
22
  store,
15
23
  databasePath: dbPath,
16
24
  dataDir,
25
+ walletRootId: walletRoot.walletRootId,
17
26
  progressOutput: parsed.progressOutput,
18
27
  });
19
28
  storeOwned = false;
@@ -21,6 +21,66 @@ function getResetWarnings(result) {
21
21
  ? ["Some existing Cogcoin secret-provider entries could not be discovered from the remaining local wallet artifacts and may need manual cleanup."]
22
22
  : [];
23
23
  }
24
+ function getResetNextSteps(result) {
25
+ return result.walletAction === "deleted" || result.walletAction === "not-present"
26
+ ? ["Run `cogcoin init` to create a new wallet."]
27
+ : ["Run `cogcoin sync` to bootstrap assumeutxo and the managed Bitcoin/indexer state."];
28
+ }
29
+ function formatResetBitcoinDataDirStatus(result) {
30
+ if (result.bitcoinDataDir.status === "outside-reset-scope") {
31
+ return "preserved (outside reset scope)";
32
+ }
33
+ return result.bitcoinDataDir.status;
34
+ }
35
+ function resetTextEntry(label, value, ok) {
36
+ return {
37
+ text: `${label}: ${value}`,
38
+ ok,
39
+ };
40
+ }
41
+ function formatResetSection(header, entries) {
42
+ return [header, ...entries.map((entry) => `${entry.ok ? "✓" : "✗"} ${entry.text}`)].join("\n");
43
+ }
44
+ function formatResetResultText(result) {
45
+ const warnings = getResetWarnings(result);
46
+ const nextStep = getResetNextSteps(result)[0] ?? null;
47
+ const secretCleanupOk = result.secretCleanupStatus !== "unknown" && result.secretCleanupStatus !== "failed";
48
+ const managedCleanupOk = result.stoppedProcesses.survivors === 0;
49
+ const outcomeEntries = [
50
+ resetTextEntry("Wallet action", result.walletAction, true),
51
+ resetTextEntry("Snapshot", result.bootstrapSnapshot.status, true),
52
+ resetTextEntry("Bitcoin datadir", formatResetBitcoinDataDirStatus(result), true),
53
+ resetTextEntry("Secret cleanup", result.secretCleanupStatus, secretCleanupOk),
54
+ ];
55
+ if (result.walletAction !== "retain-mnemonic" && result.walletOldRootId !== null) {
56
+ outcomeEntries.push(resetTextEntry("Previous wallet root", result.walletOldRootId, true));
57
+ }
58
+ if (result.walletAction !== "retain-mnemonic" && result.walletNewRootId !== null) {
59
+ outcomeEntries.push(resetTextEntry("New wallet root", result.walletNewRootId, true));
60
+ }
61
+ const sections = [
62
+ formatResetSection("Paths", [
63
+ resetTextEntry("Data root", result.dataRoot, true),
64
+ ]),
65
+ formatResetSection("Reset Outcome", outcomeEntries),
66
+ formatResetSection("Managed Cleanup", [
67
+ resetTextEntry("Managed bitcoind processes stopped", String(result.stoppedProcesses.managedBitcoind), managedCleanupOk),
68
+ resetTextEntry("Indexer daemons stopped", String(result.stoppedProcesses.indexerDaemon), managedCleanupOk),
69
+ resetTextEntry("Background miners stopped", String(result.stoppedProcesses.backgroundMining), managedCleanupOk),
70
+ ]),
71
+ ];
72
+ if (warnings.length > 0) {
73
+ sections.push(formatResetSection("Warnings", warnings.map((warning) => resetTextEntry("Warning", warning, false))));
74
+ }
75
+ const parts = [
76
+ "\n⛭ Cogcoin Reset ⛭",
77
+ ...sections,
78
+ ];
79
+ if (nextStep !== null) {
80
+ parts.push(`Next step: ${nextStep}`);
81
+ }
82
+ return parts.join("\n\n");
83
+ }
24
84
  export async function runWalletAdminCommand(parsed, context) {
25
85
  const runtimePaths = context.resolveWalletRuntimePaths();
26
86
  const stopWatcher = createOwnedLockCleanupSignalWatcher(context.signalSource, context.forceExit, [
@@ -31,9 +91,9 @@ export async function runWalletAdminCommand(parsed, context) {
31
91
  ]);
32
92
  try {
33
93
  const outcome = await waitForCompletionOrStop((async () => {
34
- const dataDir = parsed.dataDir ?? context.resolveDefaultBitcoindDataDir();
35
94
  const provider = context.walletSecretProvider;
36
95
  if (parsed.command === "init" || parsed.command === "wallet-init") {
96
+ const dataDir = parsed.dataDir ?? context.resolveDefaultBitcoindDataDir();
37
97
  const prompter = createCommandPrompter(parsed, context);
38
98
  const result = await context.initializeWallet({
39
99
  dataDir,
@@ -59,6 +119,7 @@ export async function runWalletAdminCommand(parsed, context) {
59
119
  return 0;
60
120
  }
61
121
  if (parsed.command === "restore" || parsed.command === "wallet-restore") {
122
+ const dataDir = parsed.dataDir ?? context.resolveDefaultBitcoindDataDir();
62
123
  const prompter = createCommandPrompter(parsed, context);
63
124
  const result = await context.restoreWalletFromMnemonic({
64
125
  dataDir,
@@ -88,6 +149,14 @@ export async function runWalletAdminCommand(parsed, context) {
88
149
  }
89
150
  return 0;
90
151
  }
152
+ if (parsed.command === "wallet-show-mnemonic") {
153
+ const prompter = createCommandPrompter(parsed, context);
154
+ await context.showWalletMnemonic({
155
+ provider,
156
+ prompter,
157
+ });
158
+ return 0;
159
+ }
91
160
  const dbPath = parsed.dbPath ?? context.resolveDefaultClientDatabasePath();
92
161
  if (parsed.command === "unlock" || parsed.command === "wallet-unlock") {
93
162
  const durationMs = parseUnlockDurationToMs(parsed.unlockFor);
@@ -105,6 +174,7 @@ export async function runWalletAdminCommand(parsed, context) {
105
174
  return 0;
106
175
  }
107
176
  if (parsed.command === "reset") {
177
+ const dataDir = parsed.dataDir ?? context.resolveDefaultBitcoindDataDir();
108
178
  if (parsed.outputMode === "preview-json") {
109
179
  const preview = await context.previewResetWallet({
110
180
  dataDir,
@@ -122,32 +192,15 @@ export async function runWalletAdminCommand(parsed, context) {
122
192
  if (parsed.outputMode === "json") {
123
193
  writeJsonValue(context.stdout, createMutationSuccessEnvelope(resolveStableMutationJsonSchema(parsed), describeCanonicalCommand(parsed), "completed", buildResetMutationData(result), {
124
194
  warnings: getResetWarnings(result),
125
- nextSteps: result.walletAction === "deleted" || result.walletAction === "not-present"
126
- ? ["Run `cogcoin init` to create a new wallet."]
127
- : ["Run `cogcoin status` to inspect the reset local state."],
195
+ nextSteps: getResetNextSteps(result),
128
196
  }));
129
197
  return 0;
130
198
  }
131
- writeLine(context.stdout, "Cogcoin reset completed.");
132
- writeLine(context.stdout, `Data root: ${result.dataRoot}`);
133
- writeLine(context.stdout, `Wallet action: ${result.walletAction}`);
134
- writeLine(context.stdout, `Snapshot: ${result.bootstrapSnapshot.status}`);
135
- writeLine(context.stdout, `Secret cleanup: ${result.secretCleanupStatus}`);
136
- writeLine(context.stdout, `Managed bitcoind processes stopped: ${result.stoppedProcesses.managedBitcoind}`);
137
- writeLine(context.stdout, `Indexer daemons stopped: ${result.stoppedProcesses.indexerDaemon}`);
138
- writeLine(context.stdout, `Background miners stopped: ${result.stoppedProcesses.backgroundMining}`);
139
- if (result.walletOldRootId !== null) {
140
- writeLine(context.stdout, `Previous wallet root: ${result.walletOldRootId}`);
141
- }
142
- if (result.walletNewRootId !== null) {
143
- writeLine(context.stdout, `New wallet root: ${result.walletNewRootId}`);
144
- }
145
- for (const warning of getResetWarnings(result)) {
146
- writeLine(context.stdout, `Warning: ${warning}`);
147
- }
199
+ writeLine(context.stdout, formatResetResultText(result));
148
200
  return 0;
149
201
  }
150
202
  if (parsed.command === "wallet-export") {
203
+ const dataDir = parsed.dataDir ?? context.resolveDefaultBitcoindDataDir();
151
204
  const prompter = createCommandPrompter(parsed, context);
152
205
  const result = await context.exportWallet({
153
206
  archivePath: parsed.args[0],
@@ -166,6 +219,7 @@ export async function runWalletAdminCommand(parsed, context) {
166
219
  return 0;
167
220
  }
168
221
  if (parsed.command === "wallet-import") {
222
+ const dataDir = parsed.dataDir ?? context.resolveDefaultBitcoindDataDir();
169
223
  const prompter = createCommandPrompter(parsed, context);
170
224
  const result = await context.importWallet({
171
225
  archivePath: parsed.args[0],
@@ -185,6 +239,7 @@ export async function runWalletAdminCommand(parsed, context) {
185
239
  return 0;
186
240
  }
187
241
  if (parsed.command === "wallet-lock") {
242
+ const dataDir = parsed.dataDir ?? context.resolveDefaultBitcoindDataDir();
188
243
  const result = await context.lockWallet({
189
244
  dataDir,
190
245
  provider,
@@ -202,6 +257,7 @@ export async function runWalletAdminCommand(parsed, context) {
202
257
  return 0;
203
258
  }
204
259
  if (parsed.command === "repair") {
260
+ const dataDir = parsed.dataDir ?? context.resolveDefaultBitcoindDataDir();
205
261
  const result = await context.repairWallet({
206
262
  dataDir,
207
263
  databasePath: dbPath,
@@ -6,12 +6,12 @@ import { resolveDefaultBitcoindDataDirForTesting, resolveDefaultClientDatabasePa
6
6
  import { openManagedBitcoindClient } from "../bitcoind/index.js";
7
7
  import { inspectPassiveClientStatus } from "../passive-status.js";
8
8
  import { openSqliteStore } from "../sqlite/index.js";
9
- import { exportWallet, importWallet, initializeWallet, lockWallet, previewResetWallet, repairWallet, resetWallet, restoreWalletFromMnemonic, unlockWallet, } from "../wallet/lifecycle.js";
9
+ import { exportWallet, importWallet, initializeWallet, lockWallet, previewResetWallet, repairWallet, resetWallet, restoreWalletFromMnemonic, showWalletMnemonic, unlockWallet, } from "../wallet/lifecycle.js";
10
10
  import { resolveWalletRuntimePathsForTesting } from "../wallet/runtime.js";
11
11
  import { openWalletReadContext } from "../wallet/read/index.js";
12
12
  import { loadWalletExplicitLock } from "../wallet/state/explicit-lock.js";
13
13
  import { loadUnlockSession } from "../wallet/state/session.js";
14
- import { loadWalletState } from "../wallet/state/storage.js";
14
+ import { loadRawWalletStateEnvelope, loadWalletState } from "../wallet/state/storage.js";
15
15
  import { disableMiningHooks, enableMiningHooks, followMiningLog, inspectMiningControlPlane, readMiningLog, runForegroundMining, setupBuiltInMining, startBackgroundMining, stopBackgroundMining, } from "../wallet/mining/index.js";
16
16
  import { createLazyDefaultWalletSecretProvider } from "../wallet/state/provider.js";
17
17
  import { anchorDomain, buyDomain, claimCogLock, clearDomainDelegate, clearDomainEndpoint, clearDomainMiner, clearField, createField, giveReputation, lockCogToDomain, registerDomain, reclaimCogLock, revokeReputation, sendCog, setField, setDomainCanonical, setDomainDelegate, setDomainEndpoint, setDomainMiner, sellDomain, transferDomain, } from "../wallet/tx/index.js";
@@ -39,6 +39,7 @@ export function createDefaultContext(overrides = {}) {
39
39
  previewResetWallet: overrides.previewResetWallet ?? previewResetWallet,
40
40
  exportWallet: overrides.exportWallet ?? exportWallet,
41
41
  importWallet: overrides.importWallet ?? importWallet,
42
+ showWalletMnemonic: overrides.showWalletMnemonic ?? showWalletMnemonic,
42
43
  unlockWallet: overrides.unlockWallet ?? unlockWallet,
43
44
  lockWallet: overrides.lockWallet ?? lockWallet,
44
45
  registerDomain: overrides.registerDomain ?? registerDomain,
@@ -88,6 +89,7 @@ export function createDefaultContext(overrides = {}) {
88
89
  stopIndexerDaemonService: overrides.stopIndexerDaemonService ?? stopIndexerDaemonService,
89
90
  readPackageVersion: overrides.readPackageVersion ?? readPackageVersionFromDisk,
90
91
  loadWalletState: overrides.loadWalletState ?? loadWalletState,
92
+ loadRawWalletStateEnvelope: overrides.loadRawWalletStateEnvelope ?? loadRawWalletStateEnvelope,
91
93
  loadUnlockSession: overrides.loadUnlockSession ?? loadUnlockSession,
92
94
  loadWalletExplicitLock: overrides.loadWalletExplicitLock ?? loadWalletExplicitLock,
93
95
  resolveDefaultBitcoindDataDir: overrides.resolveDefaultBitcoindDataDir ?? resolveDefaultBitcoindDataDirForTesting,
@@ -150,6 +150,7 @@ export function buildResetMutationData(result) {
150
150
  walletOldRootId: result.walletOldRootId,
151
151
  walletNewRootId: result.walletNewRootId,
152
152
  bootstrapSnapshot: result.bootstrapSnapshot,
153
+ bitcoinDataDir: result.bitcoinDataDir,
153
154
  stoppedProcesses: result.stoppedProcesses,
154
155
  secretCleanupStatus: result.secretCleanupStatus,
155
156
  },
@@ -165,6 +166,7 @@ export function buildResetMutationData(result) {
165
166
  walletOldRootId: result.walletOldRootId,
166
167
  walletNewRootId: result.walletNewRootId,
167
168
  bootstrapSnapshot: result.bootstrapSnapshot,
169
+ bitcoinDataDir: result.bitcoinDataDir,
168
170
  removedPaths: result.removedPaths,
169
171
  },
170
172
  });
@@ -335,7 +335,7 @@ export function createCliErrorPresentation(errorCode, fallbackMessage) {
335
335
  return {
336
336
  what: "Mnemonic confirmation failed.",
337
337
  why: "The requested recovery-phrase confirmation word did not match, so wallet initialization was canceled before it could finish.",
338
- next: "Run `cogcoin init` again and re-enter the requested confirmation words carefully.",
338
+ next: "Run `cogcoin init` again and re-enter the requested confirmation words carefully. The same recovery phrase will be shown until confirmation succeeds.",
339
339
  };
340
340
  }
341
341
  if (errorCode === "wallet_restore_mnemonic_invalid") {
@@ -735,6 +735,8 @@ export function describeCanonicalCommand(parsed) {
735
735
  case "restore":
736
736
  case "wallet-restore":
737
737
  return "cogcoin restore";
738
+ case "wallet-show-mnemonic":
739
+ return "cogcoin wallet show-mnemonic";
738
740
  case "unlock":
739
741
  case "wallet-unlock":
740
742
  return "cogcoin unlock";