@cogcoin/client 1.1.4 → 1.1.5

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 (83) hide show
  1. package/README.md +4 -5
  2. package/dist/bitcoind/progress/tty-renderer.js +3 -2
  3. package/dist/bitcoind/service.js +1 -1
  4. package/dist/cli/command-registry.d.ts +39 -0
  5. package/dist/cli/command-registry.js +1132 -0
  6. package/dist/cli/commands/client-admin.js +6 -56
  7. package/dist/cli/commands/mining-admin.js +9 -32
  8. package/dist/cli/commands/mining-read.js +15 -56
  9. package/dist/cli/commands/mining-runtime.js +258 -57
  10. package/dist/cli/commands/service-runtime.js +1 -64
  11. package/dist/cli/commands/status.js +2 -15
  12. package/dist/cli/commands/update.js +6 -21
  13. package/dist/cli/commands/wallet-admin.js +18 -120
  14. package/dist/cli/commands/wallet-mutation.js +4 -7
  15. package/dist/cli/commands/wallet-read.js +31 -138
  16. package/dist/cli/context.js +2 -4
  17. package/dist/cli/mining-format.js +8 -2
  18. package/dist/cli/mutation-command-groups.d.ts +11 -11
  19. package/dist/cli/mutation-command-groups.js +9 -18
  20. package/dist/cli/mutation-json.d.ts +1 -17
  21. package/dist/cli/mutation-json.js +1 -28
  22. package/dist/cli/mutation-success.d.ts +0 -1
  23. package/dist/cli/mutation-success.js +0 -19
  24. package/dist/cli/output.d.ts +1 -10
  25. package/dist/cli/output.js +52 -481
  26. package/dist/cli/parse.d.ts +1 -1
  27. package/dist/cli/parse.js +38 -695
  28. package/dist/cli/runner.js +28 -113
  29. package/dist/cli/types.d.ts +7 -8
  30. package/dist/cli/update-notifier.js +1 -1
  31. package/dist/cli/wallet-format.js +1 -1
  32. package/dist/wallet/lifecycle/managed-core.d.ts +23 -0
  33. package/dist/wallet/lifecycle/managed-core.js +257 -0
  34. package/dist/wallet/lifecycle/repair-mining.d.ts +49 -0
  35. package/dist/wallet/lifecycle/repair-mining.js +304 -0
  36. package/dist/wallet/lifecycle/repair-runtime.d.ts +36 -0
  37. package/dist/wallet/lifecycle/repair-runtime.js +206 -0
  38. package/dist/wallet/lifecycle/repair.d.ts +11 -0
  39. package/dist/wallet/lifecycle/repair.js +368 -0
  40. package/dist/wallet/lifecycle/setup.d.ts +16 -0
  41. package/dist/wallet/lifecycle/setup.js +430 -0
  42. package/dist/wallet/lifecycle/types.d.ts +125 -0
  43. package/dist/wallet/lifecycle/types.js +1 -0
  44. package/dist/wallet/lifecycle.d.ts +4 -165
  45. package/dist/wallet/lifecycle.js +3 -1656
  46. package/dist/wallet/mining/candidate.d.ts +60 -0
  47. package/dist/wallet/mining/candidate.js +290 -0
  48. package/dist/wallet/mining/competitiveness.d.ts +22 -0
  49. package/dist/wallet/mining/competitiveness.js +640 -0
  50. package/dist/wallet/mining/control.js +7 -251
  51. package/dist/wallet/mining/cycle.d.ts +39 -0
  52. package/dist/wallet/mining/cycle.js +542 -0
  53. package/dist/wallet/mining/engine-state.d.ts +66 -0
  54. package/dist/wallet/mining/engine-state.js +211 -0
  55. package/dist/wallet/mining/engine-types.d.ts +173 -0
  56. package/dist/wallet/mining/engine-types.js +1 -0
  57. package/dist/wallet/mining/engine-utils.d.ts +7 -0
  58. package/dist/wallet/mining/engine-utils.js +75 -0
  59. package/dist/wallet/mining/events.d.ts +2 -0
  60. package/dist/wallet/mining/events.js +19 -0
  61. package/dist/wallet/mining/lifecycle.d.ts +71 -0
  62. package/dist/wallet/mining/lifecycle.js +355 -0
  63. package/dist/wallet/mining/projection.d.ts +61 -0
  64. package/dist/wallet/mining/projection.js +319 -0
  65. package/dist/wallet/mining/publish.d.ts +79 -0
  66. package/dist/wallet/mining/publish.js +614 -0
  67. package/dist/wallet/mining/runner.d.ts +12 -418
  68. package/dist/wallet/mining/runner.js +274 -3433
  69. package/dist/wallet/mining/supervisor.d.ts +134 -0
  70. package/dist/wallet/mining/supervisor.js +558 -0
  71. package/dist/wallet/mining/visualizer-sync.d.ts +42 -0
  72. package/dist/wallet/mining/visualizer-sync.js +166 -0
  73. package/dist/wallet/mining/visualizer.d.ts +1 -0
  74. package/dist/wallet/mining/visualizer.js +33 -18
  75. package/dist/wallet/reset.d.ts +1 -1
  76. package/dist/wallet/reset.js +35 -11
  77. package/dist/wallet/runtime.d.ts +0 -6
  78. package/dist/wallet/runtime.js +2 -38
  79. package/dist/wallet/tx/common.d.ts +18 -0
  80. package/dist/wallet/tx/common.js +40 -26
  81. package/package.json +1 -1
  82. package/dist/wallet/state/seed-index.d.ts +0 -43
  83. package/dist/wallet/state/seed-index.js +0 -151
@@ -1,6 +1,6 @@
1
1
  import { createDefaultContext } from "./context.js";
2
2
  import { writeLine } from "./io.js";
3
- import { classifyCliError, createCommandJsonErrorEnvelope, createErrorEnvelope, formatCliTextError, inferOutputMode, isStructuredOutputMode, writeJsonValue, } from "./output.js";
3
+ import { classifyCliError, formatCliTextError } from "./output.js";
4
4
  import { HELP_TEXT, parseCliArgs } from "./parse.js";
5
5
  import { runFollowCommand } from "./commands/follow.js";
6
6
  import { runClientAdminCommand } from "./commands/client-admin.js";
@@ -15,14 +15,6 @@ import { runWalletAdminCommand } from "./commands/wallet-admin.js";
15
15
  import { runWalletMutationCommand } from "./commands/wallet-mutation.js";
16
16
  import { runWalletReadCommand } from "./commands/wallet-read.js";
17
17
  import { maybeNotifyAboutCliUpdate } from "./update-notifier.js";
18
- import { findWalletSeedRecord, loadWalletSeedIndex } from "../wallet/state/seed-index.js";
19
- function commandUsesExistingWalletSeed(parsed) {
20
- return parsed.seedName !== null
21
- && parsed.seedName !== "main"
22
- && parsed.command !== "restore"
23
- && parsed.command !== "wallet-delete"
24
- && parsed.command !== "wallet-restore";
25
- }
26
18
  export async function runCli(argv, contextOverrides = {}) {
27
19
  const context = createDefaultContext(contextOverrides);
28
20
  let parsed;
@@ -31,10 +23,6 @@ export async function runCli(argv, contextOverrides = {}) {
31
23
  }
32
24
  catch (error) {
33
25
  const classified = classifyCliError(error);
34
- if (isStructuredOutputMode(inferOutputMode(argv))) {
35
- writeJsonValue(context.stdout, createErrorEnvelope("cogcoin/cli/v1", `cogcoin ${argv.join(" ")}`.trim(), classified.errorCode, classified.message));
36
- return classified.exitCode;
37
- }
38
26
  writeLine(context.stderr, classified.message);
39
27
  writeLine(context.stderr, HELP_TEXT.trimEnd());
40
28
  return classified.exitCode;
@@ -44,115 +32,42 @@ export async function runCli(argv, contextOverrides = {}) {
44
32
  return 0;
45
33
  }
46
34
  if (parsed.help || parsed.command === null) {
47
- if (parsed.command === null && isStructuredOutputMode(parsed.outputMode)) {
48
- writeJsonValue(context.stdout, createErrorEnvelope("cogcoin/cli/v1", "cogcoin", "cli_missing_command", "cli_missing_command"));
49
- return 2;
50
- }
51
35
  writeLine(context.stdout, HELP_TEXT.trimEnd());
52
36
  return parsed.help ? 0 : 2;
53
37
  }
54
38
  await maybeNotifyAboutCliUpdate(parsed, context);
55
39
  try {
56
- if (parsed.command === "update") {
57
- return runUpdateCommand(parsed, context);
58
- }
59
- if (commandUsesExistingWalletSeed(parsed)) {
60
- const mainPaths = context.resolveWalletRuntimePaths("main");
61
- const seedIndex = await loadWalletSeedIndex({
62
- paths: mainPaths,
63
- });
64
- if (seedIndex.seeds.length > 0 && findWalletSeedRecord(seedIndex, parsed.seedName) === null) {
65
- throw new Error("wallet_seed_not_found");
66
- }
67
- }
68
- if (parsed.command === "sync") {
69
- return runSyncCommand(parsed, context);
70
- }
71
- if (parsed.command === "follow") {
72
- return runFollowCommand(parsed, context);
73
- }
74
- if (parsed.command === "status") {
75
- return runStatusCommand(parsed, context);
40
+ switch (parsed.commandFamily) {
41
+ case "update":
42
+ return runUpdateCommand(parsed, context);
43
+ case "sync":
44
+ return runSyncCommand(parsed, context);
45
+ case "follow":
46
+ return runFollowCommand(parsed, context);
47
+ case "status":
48
+ return runStatusCommand(parsed, context);
49
+ case "client-admin":
50
+ return runClientAdminCommand(parsed, context);
51
+ case "service-runtime":
52
+ return runServiceRuntimeCommand(parsed, context);
53
+ case "wallet-admin":
54
+ return runWalletAdminCommand(parsed, context);
55
+ case "wallet-mutation":
56
+ return runWalletMutationCommand(parsed, context);
57
+ case "wallet-read":
58
+ return runWalletReadCommand(parsed, context);
59
+ case "mining-admin":
60
+ return runMiningAdminCommand(parsed, context);
61
+ case "mining-runtime":
62
+ return runMiningRuntimeCommand(parsed, context);
63
+ case "mining-read":
64
+ return runMiningReadCommand(parsed, context);
65
+ default:
66
+ return runWalletReadCommand(parsed, context);
76
67
  }
77
- if (parsed.command === "client-lock"
78
- || parsed.command === "client-unlock"
79
- || parsed.command === "client-change-password") {
80
- return runClientAdminCommand(parsed, context);
81
- }
82
- if (parsed.command === "bitcoin-start"
83
- || parsed.command === "bitcoin-stop"
84
- || parsed.command === "bitcoin-status"
85
- || parsed.command === "indexer-start"
86
- || parsed.command === "indexer-stop"
87
- || parsed.command === "indexer-status") {
88
- return runServiceRuntimeCommand(parsed, context);
89
- }
90
- if (parsed.command === "mine"
91
- || parsed.command === "mine-start"
92
- || parsed.command === "mine-stop") {
93
- return runMiningRuntimeCommand(parsed, context);
94
- }
95
- if (parsed.command === "mine-setup"
96
- || parsed.command === "mine-prompt") {
97
- return runMiningAdminCommand(parsed, context);
98
- }
99
- if (parsed.command === "init"
100
- || parsed.command === "restore"
101
- || parsed.command === "reset"
102
- || parsed.command === "repair"
103
- || parsed.command === "wallet-init"
104
- || parsed.command === "wallet-delete"
105
- || parsed.command === "wallet-restore"
106
- || parsed.command === "wallet-show-mnemonic") {
107
- return runWalletAdminCommand(parsed, context);
108
- }
109
- if (parsed.command === "anchor"
110
- || parsed.command === "domain-anchor"
111
- || parsed.command === "register"
112
- || parsed.command === "domain-register"
113
- || parsed.command === "transfer"
114
- || parsed.command === "domain-transfer"
115
- || parsed.command === "sell"
116
- || parsed.command === "domain-sell"
117
- || parsed.command === "unsell"
118
- || parsed.command === "domain-unsell"
119
- || parsed.command === "buy"
120
- || parsed.command === "domain-buy"
121
- || parsed.command === "domain-endpoint-set"
122
- || parsed.command === "domain-endpoint-clear"
123
- || parsed.command === "domain-delegate-set"
124
- || parsed.command === "domain-delegate-clear"
125
- || parsed.command === "domain-miner-set"
126
- || parsed.command === "domain-miner-clear"
127
- || parsed.command === "domain-canonical"
128
- || parsed.command === "field-create"
129
- || parsed.command === "field-set"
130
- || parsed.command === "field-clear"
131
- || parsed.command === "bitcoin-transfer"
132
- || parsed.command === "send"
133
- || parsed.command === "claim"
134
- || parsed.command === "reclaim"
135
- || parsed.command === "cog-send"
136
- || parsed.command === "cog-claim"
137
- || parsed.command === "cog-reclaim"
138
- || parsed.command === "cog-lock"
139
- || parsed.command === "rep-give"
140
- || parsed.command === "rep-revoke") {
141
- return runWalletMutationCommand(parsed, context);
142
- }
143
- if (parsed.command === "mine-prompt-list"
144
- || parsed.command === "mine-status"
145
- || parsed.command === "mine-log") {
146
- return runMiningReadCommand(parsed, context);
147
- }
148
- return runWalletReadCommand(parsed, context);
149
68
  }
150
69
  catch (error) {
151
70
  const classified = classifyCliError(error);
152
- if (isStructuredOutputMode(parsed.outputMode)) {
153
- writeJsonValue(context.stdout, createCommandJsonErrorEnvelope(parsed, error));
154
- return classified.exitCode;
155
- }
156
71
  const formatted = formatCliTextError(error);
157
72
  if (formatted !== null) {
158
73
  for (const line of formatted) {
@@ -7,15 +7,15 @@ import { attachOrStartManagedBitcoindService, probeManagedBitcoindService, stopM
7
7
  import { openSqliteStore } from "../sqlite/index.js";
8
8
  import type { ClientStoreAdapter } from "../types.js";
9
9
  import type { WalletRuntimePaths } from "../wallet/runtime.js";
10
- import type { WalletPrompter, initializeWallet, deleteImportedWalletSeed, previewResetWallet, repairWallet, resetWallet, restoreWalletFromMnemonic, showWalletMnemonic } from "../wallet/lifecycle.js";
10
+ import type { WalletPrompter, initializeWallet, previewResetWallet, repairWallet, resetWallet, showWalletMnemonic } from "../wallet/lifecycle.js";
11
11
  import type { openWalletReadContext } from "../wallet/read/index.js";
12
12
  import { loadRawWalletStateEnvelope, loadWalletState } from "../wallet/state/storage.js";
13
13
  import type { WalletSecretProvider } from "../wallet/state/provider.js";
14
14
  import type { ensureBuiltInMiningSetupIfNeeded, followMiningLog, inspectMiningControlPlane, inspectMiningDomainPromptState, readMiningLog, runForegroundMining, setupBuiltInMining, startBackgroundMining, stopBackgroundMining, updateMiningDomainPrompt } from "../wallet/mining/index.js";
15
15
  import type { anchorDomain, transferBitcoin, 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";
16
+ import type { CommandHandlerFamily, CommandName } from "./command-registry.js";
17
+ export type { CommandHandlerFamily, CommandName } from "./command-registry.js";
16
18
  export type ProgressOutput = "auto" | "tty" | "none";
17
- export type OutputMode = "text" | "json" | "preview-json";
18
- export type CommandName = "init" | "restore" | "reset" | "repair" | "update" | "sync" | "status" | "client-lock" | "client-change-password" | "client-unlock" | "follow" | "bitcoin-start" | "bitcoin-stop" | "bitcoin-status" | "bitcoin-transfer" | "indexer-start" | "indexer-stop" | "indexer-status" | "anchor" | "domain-anchor" | "register" | "domain-register" | "transfer" | "domain-transfer" | "sell" | "domain-sell" | "unsell" | "domain-unsell" | "buy" | "domain-buy" | "domain-endpoint-set" | "domain-endpoint-clear" | "domain-delegate-set" | "domain-delegate-clear" | "domain-miner-set" | "domain-miner-clear" | "domain-canonical" | "field-list" | "field-show" | "field-create" | "field-set" | "field-clear" | "send" | "claim" | "reclaim" | "cog-send" | "cog-claim" | "cog-reclaim" | "cog-lock" | "rep-give" | "rep-revoke" | "cog-balance" | "cog-locks" | "mine" | "mine-start" | "mine-stop" | "mine-setup" | "mine-prompt" | "mine-prompt-list" | "mine-status" | "mine-log" | "wallet-init" | "wallet-delete" | "wallet-restore" | "wallet-show-mnemonic" | "wallet-status" | "wallet-address" | "wallet-ids" | "address" | "ids" | "balance" | "locks" | "domain-list" | "domains" | "domain-show" | "show" | "fields" | "field";
19
19
  export interface WritableLike {
20
20
  isTTY?: boolean;
21
21
  write(chunk: string): void;
@@ -29,14 +29,15 @@ export interface SignalSource {
29
29
  }
30
30
  export interface ParsedCliArgs {
31
31
  command: CommandName | null;
32
+ commandFamily: CommandHandlerFamily | null;
33
+ invokedCommandTokens: readonly string[] | null;
34
+ invokedCommandPath: string | null;
32
35
  args: string[];
33
36
  help: boolean;
34
37
  version: boolean;
35
- outputMode: OutputMode;
36
38
  dbPath: string | null;
37
39
  dataDir: string | null;
38
40
  progressOutput: ProgressOutput;
39
- seedName: string | null;
40
41
  unlockFor: string | null;
41
42
  assumeYes: boolean;
42
43
  force: boolean;
@@ -125,9 +126,7 @@ export interface CliRunnerContext {
125
126
  loadWalletState?: typeof loadWalletState;
126
127
  loadRawWalletStateEnvelope?: typeof loadRawWalletStateEnvelope;
127
128
  initializeWallet?: typeof initializeWallet;
128
- restoreWalletFromMnemonic?: typeof restoreWalletFromMnemonic;
129
129
  previewResetWallet?: typeof previewResetWallet;
130
- deleteImportedWalletSeed?: typeof deleteImportedWalletSeed;
131
130
  showWalletMnemonic?: typeof showWalletMnemonic;
132
131
  registerDomain?: typeof registerDomain;
133
132
  anchorDomain?: typeof anchorDomain;
@@ -170,7 +169,7 @@ export interface CliRunnerContext {
170
169
  resolveDefaultBitcoindDataDir?: () => string;
171
170
  resolveDefaultClientDatabasePath?: () => string;
172
171
  resolveUpdateCheckStatePath?: () => string;
173
- resolveWalletRuntimePaths?: (seedName?: string | null) => WalletRuntimePaths;
172
+ resolveWalletRuntimePaths?: () => WalletRuntimePaths;
174
173
  }
175
174
  export interface StopSignalWatcher {
176
175
  cleanup(): void;
@@ -4,7 +4,7 @@ function isEligibleForUpdateNotification(parsed, context) {
4
4
  if (parsed.command === "update") {
5
5
  return false;
6
6
  }
7
- if (parsed.outputMode !== "text" || parsed.help || parsed.version) {
7
+ if (parsed.help || parsed.version) {
8
8
  return false;
9
9
  }
10
10
  return context.stdout.isTTY === true || context.stderr.isTTY === true;
@@ -88,7 +88,7 @@ export function getRepairRecommendation(context) {
88
88
  return null;
89
89
  }
90
90
  if (context.localState.availability === "uninitialized") {
91
- return "Run `cogcoin init` to create a new local wallet root.";
91
+ return "Run `cogcoin init` to create or restore a wallet.";
92
92
  }
93
93
  if (context.localState.availability === "local-state-corrupt") {
94
94
  return "Run `cogcoin repair` to recover local wallet state.";
@@ -0,0 +1,23 @@
1
+ import { attachOrStartManagedBitcoindService } from "../../bitcoind/service.js";
2
+ import { createRpcClient } from "../../bitcoind/node.js";
3
+ import type { ManagedCoreWalletReplicaStatus } from "../../bitcoind/types.js";
4
+ import type { WalletRuntimePaths } from "../runtime.js";
5
+ import { type WalletSecretProvider } from "../state/provider.js";
6
+ import type { WalletStateV1 } from "../types.js";
7
+ import type { WalletLifecycleRpcClient, WalletManagedCoreDependencies, WalletLifecycleResolvedContext } from "./types.js";
8
+ export declare function sanitizeWalletName(walletRootId: string): string;
9
+ export declare function normalizeLoadedWalletStateIfNeeded(options: WalletLifecycleResolvedContext & {
10
+ state: WalletStateV1;
11
+ source: "primary" | "backup";
12
+ dataDir?: string;
13
+ } & WalletManagedCoreDependencies): Promise<{
14
+ state: WalletStateV1;
15
+ source: "primary" | "backup";
16
+ }>;
17
+ export declare function importDescriptorIntoManagedCoreWallet(state: WalletStateV1, provider: WalletSecretProvider, paths: WalletRuntimePaths, dataDir: string, nowUnixMs: number, attachService?: typeof attachOrStartManagedBitcoindService, rpcFactory?: (config: Parameters<typeof createRpcClient>[0]) => WalletLifecycleRpcClient): Promise<WalletStateV1>;
18
+ export declare function recreateManagedCoreWalletReplica(state: WalletStateV1, provider: WalletSecretProvider, paths: WalletRuntimePaths, dataDir: string, nowUnixMs: number, options?: WalletManagedCoreDependencies): Promise<WalletStateV1>;
19
+ export declare function verifyManagedCoreWalletReplica(state: WalletStateV1, dataDir: string, dependencies?: WalletManagedCoreDependencies & {
20
+ nodeHandle?: {
21
+ rpc: Parameters<typeof createRpcClient>[0];
22
+ };
23
+ }): Promise<ManagedCoreWalletReplicaStatus>;
@@ -0,0 +1,257 @@
1
+ import { access, constants, rename } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ import { attachOrStartManagedBitcoindService, createManagedWalletReplica } from "../../bitcoind/service.js";
4
+ import { createRpcClient } from "../../bitcoind/node.js";
5
+ import { persistWalletCoinControlStateIfNeeded, normalizeWalletStateRecord } from "../coin-control.js";
6
+ import { persistNormalizedWalletDescriptorStateIfNeeded, resolveNormalizedWalletDescriptorState, } from "../descriptor-normalization.js";
7
+ import { normalizeMiningStateRecord } from "../mining/state.js";
8
+ import { createWalletSecretReference } from "../state/provider.js";
9
+ import { saveWalletState } from "../state/storage.js";
10
+ import { withUnlockedManagedCoreWallet } from "../managed-core-wallet.js";
11
+ export function sanitizeWalletName(walletRootId) {
12
+ return `cogcoin-${walletRootId}`.replace(/[^a-zA-Z0-9._-]+/g, "-").slice(0, 63);
13
+ }
14
+ async function pathExists(path) {
15
+ try {
16
+ await access(path, constants.F_OK);
17
+ return true;
18
+ }
19
+ catch {
20
+ return false;
21
+ }
22
+ }
23
+ export async function normalizeLoadedWalletStateIfNeeded(options) {
24
+ let state = options.state;
25
+ let source = options.source;
26
+ if (options.dataDir !== undefined) {
27
+ const node = await (options.attachService ?? attachOrStartManagedBitcoindService)({
28
+ dataDir: options.dataDir,
29
+ chain: "main",
30
+ startHeight: 0,
31
+ walletRootId: state.walletRootId,
32
+ });
33
+ try {
34
+ const normalized = await persistNormalizedWalletDescriptorStateIfNeeded({
35
+ state,
36
+ access: {
37
+ provider: options.provider,
38
+ secretReference: createWalletSecretReference(state.walletRootId),
39
+ },
40
+ paths: options.paths,
41
+ nowUnixMs: options.nowUnixMs,
42
+ replacePrimary: options.source === "backup",
43
+ rpc: (options.rpcFactory ?? createRpcClient)(node.rpc),
44
+ });
45
+ state = normalized.state;
46
+ source = normalized.changed ? "primary" : options.source;
47
+ const coinControl = await persistWalletCoinControlStateIfNeeded({
48
+ state,
49
+ access: {
50
+ provider: options.provider,
51
+ secretReference: createWalletSecretReference(state.walletRootId),
52
+ },
53
+ paths: options.paths,
54
+ nowUnixMs: options.nowUnixMs,
55
+ replacePrimary: source === "backup",
56
+ rpc: createRpcClient(node.rpc),
57
+ });
58
+ state = coinControl.state;
59
+ source = coinControl.changed ? "primary" : source;
60
+ }
61
+ finally {
62
+ await node.stop?.().catch(() => undefined);
63
+ }
64
+ }
65
+ return {
66
+ state: normalizeWalletStateRecord({
67
+ ...state,
68
+ miningState: normalizeMiningStateRecord(state.miningState),
69
+ }),
70
+ source,
71
+ };
72
+ }
73
+ export async function importDescriptorIntoManagedCoreWallet(state, provider, paths, dataDir, nowUnixMs, attachService = attachOrStartManagedBitcoindService, rpcFactory = createRpcClient) {
74
+ const node = await attachService({
75
+ dataDir,
76
+ chain: "main",
77
+ startHeight: 0,
78
+ walletRootId: state.walletRootId,
79
+ managedWalletPassphrase: state.managedCoreWallet.internalPassphrase,
80
+ });
81
+ const rpc = rpcFactory(node.rpc);
82
+ await createManagedWalletReplica(rpc, state.walletRootId, {
83
+ managedWalletPassphrase: state.managedCoreWallet.internalPassphrase,
84
+ });
85
+ const normalizedDescriptors = await resolveNormalizedWalletDescriptorState(state, rpc);
86
+ const walletName = sanitizeWalletName(state.walletRootId);
87
+ await withUnlockedManagedCoreWallet({
88
+ rpc,
89
+ walletName,
90
+ internalPassphrase: state.managedCoreWallet.internalPassphrase,
91
+ run: async () => {
92
+ const importResults = await rpc.importDescriptors(walletName, [{
93
+ desc: normalizedDescriptors.privateExternal,
94
+ timestamp: state.walletBirthTime,
95
+ active: false,
96
+ internal: false,
97
+ range: [0, state.descriptor.rangeEnd],
98
+ }]);
99
+ if (!importResults.every((result) => result.success)) {
100
+ throw new Error(`wallet_descriptor_import_failed_${JSON.stringify(importResults)}`);
101
+ }
102
+ },
103
+ });
104
+ const derivedFunding = await rpc.deriveAddresses(normalizedDescriptors.publicExternal, [0, 0]);
105
+ if (derivedFunding[0] !== state.funding.address) {
106
+ throw new Error("wallet_funding_address_verification_failed");
107
+ }
108
+ const descriptors = await rpc.listDescriptors(walletName);
109
+ const importedDescriptor = descriptors.descriptors.find((entry) => entry.desc === normalizedDescriptors.publicExternal);
110
+ if (importedDescriptor == null) {
111
+ throw new Error("wallet_descriptor_not_present_after_import");
112
+ }
113
+ const verifiedReplica = {
114
+ walletRootId: state.walletRootId,
115
+ walletName,
116
+ loaded: true,
117
+ descriptors: true,
118
+ privateKeysEnabled: true,
119
+ created: false,
120
+ proofStatus: "ready",
121
+ descriptorChecksum: normalizedDescriptors.checksum,
122
+ fundingAddress0: state.funding.address,
123
+ fundingScriptPubKeyHex0: state.funding.scriptPubKeyHex,
124
+ message: null,
125
+ };
126
+ const nextState = {
127
+ ...state,
128
+ stateRevision: state.stateRevision + 1,
129
+ lastWrittenAtUnixMs: nowUnixMs,
130
+ descriptor: {
131
+ ...state.descriptor,
132
+ privateExternal: normalizedDescriptors.privateExternal,
133
+ publicExternal: normalizedDescriptors.publicExternal,
134
+ checksum: normalizedDescriptors.checksum,
135
+ },
136
+ managedCoreWallet: {
137
+ ...state.managedCoreWallet,
138
+ walletName,
139
+ descriptorChecksum: normalizedDescriptors.checksum,
140
+ walletAddress: verifiedReplica.fundingAddress0 ?? null,
141
+ walletScriptPubKeyHex: verifiedReplica.fundingScriptPubKeyHex0 ?? null,
142
+ proofStatus: "ready",
143
+ lastImportedAtUnixMs: nowUnixMs,
144
+ lastVerifiedAtUnixMs: nowUnixMs,
145
+ },
146
+ };
147
+ await saveWalletState({
148
+ primaryPath: paths.walletStatePath,
149
+ backupPath: paths.walletStateBackupPath,
150
+ }, nextState, {
151
+ provider,
152
+ secretReference: createWalletSecretReference(state.walletRootId),
153
+ });
154
+ return nextState;
155
+ }
156
+ export async function recreateManagedCoreWalletReplica(state, provider, paths, dataDir, nowUnixMs, options = {}) {
157
+ const walletName = sanitizeWalletName(state.walletRootId);
158
+ const walletDir = join(dataDir, "wallets", walletName);
159
+ const quarantineDir = `${walletDir}.quarantine-${nowUnixMs}`;
160
+ const node = await (options.attachService ?? attachOrStartManagedBitcoindService)({
161
+ dataDir,
162
+ chain: "main",
163
+ startHeight: 0,
164
+ walletRootId: state.walletRootId,
165
+ managedWalletPassphrase: state.managedCoreWallet.internalPassphrase,
166
+ });
167
+ const rpc = (options.rpcFactory ?? createRpcClient)(node.rpc);
168
+ if (rpc.unloadWallet != null) {
169
+ await rpc.unloadWallet(walletName, false).catch(() => undefined);
170
+ }
171
+ if (await pathExists(walletDir)) {
172
+ await rename(walletDir, quarantineDir).catch(() => undefined);
173
+ }
174
+ return importDescriptorIntoManagedCoreWallet({
175
+ ...state,
176
+ managedCoreWallet: {
177
+ ...state.managedCoreWallet,
178
+ proofStatus: "not-proven",
179
+ },
180
+ }, provider, paths, dataDir, nowUnixMs, options.attachService, options.rpcFactory);
181
+ }
182
+ export async function verifyManagedCoreWalletReplica(state, dataDir, dependencies = {}) {
183
+ const walletName = state.managedCoreWallet.walletName;
184
+ try {
185
+ const node = dependencies.nodeHandle ?? await (dependencies.attachService ?? attachOrStartManagedBitcoindService)({
186
+ dataDir,
187
+ chain: "main",
188
+ startHeight: 0,
189
+ walletRootId: state.walletRootId,
190
+ });
191
+ const rpc = (dependencies.rpcFactory ?? createRpcClient)(node.rpc);
192
+ const info = await rpc.getWalletInfo(walletName);
193
+ const descriptors = await rpc.listDescriptors(walletName);
194
+ const matchingDescriptor = state.managedCoreWallet.descriptorChecksum === null
195
+ ? null
196
+ : descriptors.descriptors.find((entry) => entry.desc.endsWith(`#${state.managedCoreWallet.descriptorChecksum}`));
197
+ if (matchingDescriptor == null) {
198
+ return {
199
+ walletRootId: state.walletRootId,
200
+ walletName,
201
+ loaded: true,
202
+ descriptors: info.descriptors,
203
+ privateKeysEnabled: info.private_keys_enabled,
204
+ created: false,
205
+ proofStatus: "missing",
206
+ descriptorChecksum: state.managedCoreWallet.descriptorChecksum,
207
+ fundingAddress0: state.managedCoreWallet.walletAddress,
208
+ fundingScriptPubKeyHex0: state.managedCoreWallet.walletScriptPubKeyHex,
209
+ message: "Expected descriptor is missing from the managed Core wallet.",
210
+ };
211
+ }
212
+ const derived = await rpc.deriveAddresses(state.descriptor.publicExternal, [0, 0]);
213
+ if (derived[0] !== state.funding.address) {
214
+ return {
215
+ walletRootId: state.walletRootId,
216
+ walletName,
217
+ loaded: true,
218
+ descriptors: info.descriptors,
219
+ privateKeysEnabled: info.private_keys_enabled,
220
+ created: false,
221
+ proofStatus: "mismatch",
222
+ descriptorChecksum: state.managedCoreWallet.descriptorChecksum,
223
+ fundingAddress0: derived[0] ?? null,
224
+ fundingScriptPubKeyHex0: null,
225
+ message: "The managed Core wallet funding address does not match the trusted wallet state.",
226
+ };
227
+ }
228
+ return {
229
+ walletRootId: state.walletRootId,
230
+ walletName,
231
+ loaded: true,
232
+ descriptors: info.descriptors,
233
+ privateKeysEnabled: info.private_keys_enabled,
234
+ created: false,
235
+ proofStatus: "ready",
236
+ descriptorChecksum: state.managedCoreWallet.descriptorChecksum,
237
+ fundingAddress0: state.funding.address,
238
+ fundingScriptPubKeyHex0: state.funding.scriptPubKeyHex,
239
+ message: null,
240
+ };
241
+ }
242
+ catch (error) {
243
+ return {
244
+ walletRootId: state.walletRootId,
245
+ walletName,
246
+ loaded: false,
247
+ descriptors: false,
248
+ privateKeysEnabled: false,
249
+ created: false,
250
+ proofStatus: "not-proven",
251
+ descriptorChecksum: state.managedCoreWallet.descriptorChecksum,
252
+ fundingAddress0: state.managedCoreWallet.walletAddress,
253
+ fundingScriptPubKeyHex0: state.managedCoreWallet.walletScriptPubKeyHex,
254
+ message: error instanceof Error ? error.message : String(error),
255
+ };
256
+ }
257
+ }
@@ -0,0 +1,49 @@
1
+ import type { MiningRuntimeStatusV1 } from "../mining/types.js";
2
+ import type { WalletRuntimePaths } from "../runtime.js";
3
+ import { type WalletSecretProvider } from "../state/provider.js";
4
+ import type { WalletStateV1 } from "../types.js";
5
+ import type { WalletPrompter, WalletRepairResult } from "./types.js";
6
+ export declare function createSilentNonInteractivePrompter(): WalletPrompter;
7
+ export declare function applyRepairStoppedMiningState(state: WalletStateV1): WalletStateV1;
8
+ export declare function createStoppedMiningRuntimeSnapshotForRepair(options: {
9
+ state: WalletStateV1;
10
+ snapshot: MiningRuntimeStatusV1 | null;
11
+ nowUnixMs: number;
12
+ }): MiningRuntimeStatusV1;
13
+ export declare function persistRepairState(options: {
14
+ state: WalletStateV1;
15
+ provider: WalletSecretProvider;
16
+ paths: WalletRuntimePaths;
17
+ nowUnixMs: number;
18
+ replacePrimary?: boolean;
19
+ }): Promise<WalletStateV1>;
20
+ export declare function cleanupMiningForRepair(options: {
21
+ paths: WalletRuntimePaths;
22
+ state: WalletStateV1;
23
+ snapshot: MiningRuntimeStatusV1 | null;
24
+ nowUnixMs: number;
25
+ }): Promise<{
26
+ preRepairRunMode: WalletRepairResult["miningPreRepairRunMode"];
27
+ }>;
28
+ export declare function canResumeBackgroundMiningAfterRepair(options: {
29
+ provider: WalletSecretProvider;
30
+ paths: WalletRuntimePaths;
31
+ repairedState: WalletStateV1;
32
+ bitcoindPostRepairHealth: WalletRepairResult["bitcoindPostRepairHealth"];
33
+ indexerPostRepairHealth: WalletRepairResult["indexerPostRepairHealth"];
34
+ }): Promise<boolean>;
35
+ export declare function resumeBackgroundMiningAfterRepair(options: {
36
+ miningPreRepairRunMode: WalletRepairResult["miningPreRepairRunMode"];
37
+ provider: WalletSecretProvider;
38
+ paths: WalletRuntimePaths;
39
+ repairedState: WalletStateV1;
40
+ bitcoindPostRepairHealth: WalletRepairResult["bitcoindPostRepairHealth"];
41
+ indexerPostRepairHealth: WalletRepairResult["indexerPostRepairHealth"];
42
+ dataDir: string;
43
+ databasePath: string;
44
+ startBackgroundMining?: typeof import("../mining/runner.js").startBackgroundMining;
45
+ }): Promise<{
46
+ miningResumeAction: WalletRepairResult["miningResumeAction"];
47
+ miningPostRepairRunMode: WalletRepairResult["miningPostRepairRunMode"];
48
+ miningResumeError: string | null;
49
+ }>;