@cogcoin/client 1.1.3 → 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 (88) hide show
  1. package/README.md +4 -5
  2. package/dist/bitcoind/node.js +2 -1
  3. package/dist/bitcoind/progress/tty-renderer.js +3 -2
  4. package/dist/bitcoind/service.js +6 -24
  5. package/dist/bitcoind/types.d.ts +1 -0
  6. package/dist/bitcoind/types.js +1 -0
  7. package/dist/cli/command-registry.d.ts +39 -0
  8. package/dist/cli/command-registry.js +1132 -0
  9. package/dist/cli/commands/client-admin.js +6 -56
  10. package/dist/cli/commands/mining-admin.js +9 -32
  11. package/dist/cli/commands/mining-read.js +15 -56
  12. package/dist/cli/commands/mining-runtime.js +258 -57
  13. package/dist/cli/commands/service-runtime.js +1 -64
  14. package/dist/cli/commands/status.js +2 -15
  15. package/dist/cli/commands/update.js +6 -21
  16. package/dist/cli/commands/wallet-admin.js +18 -120
  17. package/dist/cli/commands/wallet-mutation.js +4 -7
  18. package/dist/cli/commands/wallet-read.js +31 -138
  19. package/dist/cli/context.js +2 -4
  20. package/dist/cli/mining-format.js +8 -2
  21. package/dist/cli/mutation-command-groups.d.ts +11 -11
  22. package/dist/cli/mutation-command-groups.js +9 -18
  23. package/dist/cli/mutation-json.d.ts +1 -17
  24. package/dist/cli/mutation-json.js +1 -28
  25. package/dist/cli/mutation-success.d.ts +0 -1
  26. package/dist/cli/mutation-success.js +0 -19
  27. package/dist/cli/output.d.ts +1 -10
  28. package/dist/cli/output.js +52 -481
  29. package/dist/cli/parse.d.ts +1 -1
  30. package/dist/cli/parse.js +38 -695
  31. package/dist/cli/runner.js +28 -113
  32. package/dist/cli/types.d.ts +7 -8
  33. package/dist/cli/update-notifier.js +1 -1
  34. package/dist/cli/wallet-format.js +1 -1
  35. package/dist/wallet/lifecycle/managed-core.d.ts +23 -0
  36. package/dist/wallet/lifecycle/managed-core.js +257 -0
  37. package/dist/wallet/lifecycle/repair-mining.d.ts +49 -0
  38. package/dist/wallet/lifecycle/repair-mining.js +304 -0
  39. package/dist/wallet/lifecycle/repair-runtime.d.ts +36 -0
  40. package/dist/wallet/lifecycle/repair-runtime.js +206 -0
  41. package/dist/wallet/lifecycle/repair.d.ts +11 -0
  42. package/dist/wallet/lifecycle/repair.js +368 -0
  43. package/dist/wallet/lifecycle/setup.d.ts +16 -0
  44. package/dist/wallet/lifecycle/setup.js +430 -0
  45. package/dist/wallet/lifecycle/types.d.ts +125 -0
  46. package/dist/wallet/lifecycle/types.js +1 -0
  47. package/dist/wallet/lifecycle.d.ts +4 -165
  48. package/dist/wallet/lifecycle.js +3 -1656
  49. package/dist/wallet/mining/candidate.d.ts +60 -0
  50. package/dist/wallet/mining/candidate.js +290 -0
  51. package/dist/wallet/mining/competitiveness.d.ts +22 -0
  52. package/dist/wallet/mining/competitiveness.js +640 -0
  53. package/dist/wallet/mining/control.js +7 -251
  54. package/dist/wallet/mining/cycle.d.ts +39 -0
  55. package/dist/wallet/mining/cycle.js +542 -0
  56. package/dist/wallet/mining/engine-state.d.ts +66 -0
  57. package/dist/wallet/mining/engine-state.js +211 -0
  58. package/dist/wallet/mining/engine-types.d.ts +173 -0
  59. package/dist/wallet/mining/engine-types.js +1 -0
  60. package/dist/wallet/mining/engine-utils.d.ts +7 -0
  61. package/dist/wallet/mining/engine-utils.js +75 -0
  62. package/dist/wallet/mining/events.d.ts +2 -0
  63. package/dist/wallet/mining/events.js +19 -0
  64. package/dist/wallet/mining/lifecycle.d.ts +71 -0
  65. package/dist/wallet/mining/lifecycle.js +355 -0
  66. package/dist/wallet/mining/projection.d.ts +61 -0
  67. package/dist/wallet/mining/projection.js +319 -0
  68. package/dist/wallet/mining/publish.d.ts +79 -0
  69. package/dist/wallet/mining/publish.js +614 -0
  70. package/dist/wallet/mining/runner.d.ts +12 -418
  71. package/dist/wallet/mining/runner.js +274 -3433
  72. package/dist/wallet/mining/supervisor.d.ts +134 -0
  73. package/dist/wallet/mining/supervisor.js +558 -0
  74. package/dist/wallet/mining/visualizer-sync.d.ts +42 -0
  75. package/dist/wallet/mining/visualizer-sync.js +166 -0
  76. package/dist/wallet/mining/visualizer.d.ts +1 -0
  77. package/dist/wallet/mining/visualizer.js +33 -18
  78. package/dist/wallet/read/context.d.ts +5 -1
  79. package/dist/wallet/read/context.js +19 -4
  80. package/dist/wallet/reset.d.ts +1 -1
  81. package/dist/wallet/reset.js +35 -11
  82. package/dist/wallet/runtime.d.ts +0 -6
  83. package/dist/wallet/runtime.js +2 -38
  84. package/dist/wallet/tx/common.d.ts +18 -0
  85. package/dist/wallet/tx/common.js +40 -26
  86. package/package.json +1 -1
  87. package/dist/wallet/state/seed-index.d.ts +0 -43
  88. package/dist/wallet/state/seed-index.js +0 -151
@@ -0,0 +1,42 @@
1
+ import type { WalletReadContext } from "../read/index.js";
2
+ import type { WalletStateV1 } from "../types.js";
3
+ import type { MiningRpcClient } from "./engine-types.js";
4
+ import type { MiningRuntimeLoopState } from "./engine-state.js";
5
+ import type { MiningFollowVisualizerState, MiningRecentWinSummary, MiningSentenceBoardEntry } from "./visualizer.js";
6
+ export declare function resolveSettledBoard(options: {
7
+ snapshotState: NonNullable<WalletReadContext["snapshot"]>["state"] | null | undefined;
8
+ snapshotTipHeight: number | null;
9
+ snapshotTipPreviousHashHex?: string | null;
10
+ nodeBestHeight: number | null;
11
+ }): {
12
+ settledBlockHeight: number | null;
13
+ settledBoardEntries: MiningSentenceBoardEntry[];
14
+ };
15
+ export declare function syncMiningUiForCurrentTip(options: {
16
+ loopState: MiningRuntimeLoopState;
17
+ snapshotState: NonNullable<WalletReadContext["snapshot"]>["state"] | null | undefined;
18
+ snapshotTipHeight: number | null;
19
+ snapshotTipPreviousHashHex: string | null;
20
+ nodeBestHeight: number | null;
21
+ nodeBestHash: string | null;
22
+ recentWin: MiningRecentWinSummary | null;
23
+ }): {
24
+ targetBlockHeight: number | null;
25
+ tipKey: string | null;
26
+ tipChanged: boolean;
27
+ };
28
+ export declare function resolveFundingDisplaySats(state: WalletStateV1, rpc: MiningRpcClient): Promise<bigint>;
29
+ export declare function loadMiningVisibleFollowBlockTimes(options: {
30
+ rpc: MiningRpcClient;
31
+ indexedTipHeight: number | null;
32
+ indexedTipHashHex: string | null;
33
+ }): Promise<Record<number, number>>;
34
+ export declare function syncMiningVisualizerBalances(loopState: MiningRuntimeLoopState, readContext: WalletReadContext & {
35
+ localState: {
36
+ availability: "ready";
37
+ state: WalletStateV1;
38
+ };
39
+ }, balanceSats: bigint | null): void;
40
+ export declare function createIndexedMiningFollowVisualizerState(readContext: WalletReadContext): MiningFollowVisualizerState;
41
+ export declare function syncMiningVisualizerBlockTimes(loopState: MiningRuntimeLoopState, blockTimesByHeight: Record<number, number>): void;
42
+ export declare function findRecentMiningWin(snapshotState: NonNullable<WalletReadContext["snapshot"]>["state"] | null | undefined, txid: string | null, targetBlockHeight: number | null): MiningRecentWinSummary | null;
@@ -0,0 +1,166 @@
1
+ import { getBalance, getBlockWinners, lookupDomainById, } from "@cogcoin/indexer/queries";
2
+ import { displayToInternalBlockhash } from "@cogcoin/scoring";
3
+ import { FOLLOW_VISIBLE_PRIOR_BLOCKS } from "../../bitcoind/client/follow-block-times.js";
4
+ import { buildMiningTipKey, resetMiningUiForTip } from "./engine-state.js";
5
+ import { deriveMiningWordIndices, numberToSats, resolveBip39WordsFromIndices, } from "./engine-utils.js";
6
+ import { createEmptyMiningFollowVisualizerState } from "./visualizer.js";
7
+ function resolveSettledWinnerRequiredWords(options) {
8
+ const storedWords = resolveBip39WordsFromIndices(options.bip39WordIndices);
9
+ if (storedWords.length > 0) {
10
+ return storedWords;
11
+ }
12
+ if (options.snapshotTipPreviousHashHex === null
13
+ || options.snapshotTipPreviousHashHex === undefined
14
+ || !Number.isInteger(options.domainId)
15
+ || options.domainId <= 0) {
16
+ return [];
17
+ }
18
+ return resolveBip39WordsFromIndices(deriveMiningWordIndices(Buffer.from(displayToInternalBlockhash(options.snapshotTipPreviousHashHex), "hex"), options.domainId));
19
+ }
20
+ function fallbackSettledWinnerDomainName(domainId) {
21
+ return `domain-${domainId}`;
22
+ }
23
+ function resolveCurrentMinedBlockBoard(options) {
24
+ const settledBlockHeight = options.snapshotTipHeight ?? null;
25
+ if (settledBlockHeight === null) {
26
+ return {
27
+ settledBlockHeight,
28
+ settledBoardEntries: [],
29
+ };
30
+ }
31
+ if (options.snapshotState === null || options.snapshotState === undefined) {
32
+ return {
33
+ settledBlockHeight,
34
+ settledBoardEntries: [],
35
+ };
36
+ }
37
+ const settledBoardEntries = (getBlockWinners(options.snapshotState, settledBlockHeight) ?? [])
38
+ .slice()
39
+ .sort((left, right) => left.rank - right.rank || left.txIndex - right.txIndex)
40
+ .slice(0, 5)
41
+ .map((winner) => ({
42
+ rank: winner.rank,
43
+ domainName: lookupDomainById(options.snapshotState, winner.domainId)?.name ?? fallbackSettledWinnerDomainName(winner.domainId),
44
+ sentence: winner.sentenceText ?? "[unavailable]",
45
+ requiredWords: resolveSettledWinnerRequiredWords({
46
+ domainId: winner.domainId,
47
+ bip39WordIndices: winner.bip39WordIndices,
48
+ snapshotTipPreviousHashHex: options.snapshotTipPreviousHashHex,
49
+ }),
50
+ }));
51
+ return {
52
+ settledBlockHeight,
53
+ settledBoardEntries,
54
+ };
55
+ }
56
+ export function resolveSettledBoard(options) {
57
+ void options.nodeBestHeight;
58
+ return resolveCurrentMinedBlockBoard({
59
+ snapshotState: options.snapshotState,
60
+ snapshotTipHeight: options.snapshotTipHeight,
61
+ snapshotTipPreviousHashHex: options.snapshotTipPreviousHashHex ?? null,
62
+ });
63
+ }
64
+ function syncMiningUiSettledBoard(loopState, snapshotState, snapshotTipHeight, snapshotTipPreviousHashHex) {
65
+ const settledBoard = resolveCurrentMinedBlockBoard({
66
+ snapshotState,
67
+ snapshotTipHeight,
68
+ snapshotTipPreviousHashHex,
69
+ });
70
+ loopState.ui.settledBlockHeight = settledBoard.settledBlockHeight;
71
+ loopState.ui.settledBoardEntries = settledBoard.settledBoardEntries;
72
+ }
73
+ export function syncMiningUiForCurrentTip(options) {
74
+ const targetBlockHeight = options.nodeBestHeight === null
75
+ ? null
76
+ : options.nodeBestHeight + 1;
77
+ const tipKey = buildMiningTipKey(options.nodeBestHash, targetBlockHeight);
78
+ const priorTipKey = options.loopState.currentTipKey;
79
+ const tipChanged = tipKey !== null && tipKey !== priorTipKey;
80
+ if (tipKey !== priorTipKey) {
81
+ options.loopState.currentTipKey = tipKey;
82
+ resetMiningUiForTip(options.loopState, targetBlockHeight);
83
+ if (options.recentWin !== null) {
84
+ options.loopState.ui.recentWin = options.recentWin;
85
+ }
86
+ }
87
+ syncMiningUiSettledBoard(options.loopState, options.snapshotState, options.snapshotTipHeight, options.snapshotTipPreviousHashHex);
88
+ return {
89
+ targetBlockHeight,
90
+ tipKey,
91
+ tipChanged,
92
+ };
93
+ }
94
+ export async function resolveFundingDisplaySats(state, rpc) {
95
+ const utxos = await rpc.listUnspent(state.managedCoreWallet.walletName, 0);
96
+ return utxos.reduce((sum, entry) => {
97
+ if (entry.scriptPubKey !== state.funding.scriptPubKeyHex
98
+ || entry.spendable === false) {
99
+ return sum;
100
+ }
101
+ return sum + numberToSats(entry.amount);
102
+ }, 0n);
103
+ }
104
+ export async function loadMiningVisibleFollowBlockTimes(options) {
105
+ if (options.indexedTipHeight === null || options.indexedTipHashHex === null) {
106
+ return {};
107
+ }
108
+ const blockTimesByHeight = {};
109
+ let currentHeight = options.indexedTipHeight;
110
+ let currentHashHex = options.indexedTipHashHex;
111
+ for (let offset = 0; offset <= FOLLOW_VISIBLE_PRIOR_BLOCKS; offset += 1) {
112
+ if (currentHeight < 0 || currentHashHex === null) {
113
+ break;
114
+ }
115
+ const block = await options.rpc.getBlock(currentHashHex);
116
+ if (typeof block.time === "number") {
117
+ blockTimesByHeight[currentHeight] = block.time;
118
+ }
119
+ currentHashHex = block.previousblockhash ?? null;
120
+ currentHeight -= 1;
121
+ }
122
+ return blockTimesByHeight;
123
+ }
124
+ export function syncMiningVisualizerBalances(loopState, readContext, balanceSats) {
125
+ loopState.ui.fundingAddress = readContext.model?.walletAddress ?? readContext.localState.state.funding.address;
126
+ loopState.ui.balanceCogtoshi = readContext.snapshot === null
127
+ ? null
128
+ : getBalance(readContext.snapshot.state, readContext.localState.state.funding.scriptPubKeyHex);
129
+ loopState.ui.balanceSats = balanceSats;
130
+ }
131
+ export function createIndexedMiningFollowVisualizerState(readContext) {
132
+ const uiState = createEmptyMiningFollowVisualizerState();
133
+ const localState = readContext.localState;
134
+ const settledBoard = resolveCurrentMinedBlockBoard({
135
+ snapshotState: readContext.snapshot?.state ?? null,
136
+ snapshotTipHeight: readContext.snapshot?.tip?.height ?? readContext.indexer.snapshotTip?.height ?? null,
137
+ snapshotTipPreviousHashHex: readContext.snapshot?.tip?.previousHashHex ?? readContext.indexer.snapshotTip?.previousHashHex ?? null,
138
+ });
139
+ uiState.settledBlockHeight = settledBoard.settledBlockHeight;
140
+ uiState.settledBoardEntries = settledBoard.settledBoardEntries;
141
+ if (localState.availability === "ready" && localState.state !== null) {
142
+ uiState.fundingAddress = readContext.model?.walletAddress ?? localState.state.funding.address;
143
+ }
144
+ if (readContext.snapshot !== null && localState.availability === "ready" && localState.state !== null) {
145
+ uiState.balanceCogtoshi = getBalance(readContext.snapshot.state, localState.state.funding.scriptPubKeyHex);
146
+ }
147
+ return uiState;
148
+ }
149
+ export function syncMiningVisualizerBlockTimes(loopState, blockTimesByHeight) {
150
+ loopState.ui.visibleBlockTimesByHeight = { ...blockTimesByHeight };
151
+ }
152
+ export function findRecentMiningWin(snapshotState, txid, targetBlockHeight) {
153
+ if (snapshotState === null || snapshotState === undefined || txid === null || targetBlockHeight === null) {
154
+ return null;
155
+ }
156
+ const winners = getBlockWinners(snapshotState, targetBlockHeight) ?? [];
157
+ const winner = winners.find((entry) => entry.txidHex === txid) ?? null;
158
+ if (winner === null) {
159
+ return null;
160
+ }
161
+ return {
162
+ rank: winner.rank,
163
+ rewardCogtoshi: winner.rewardCogtoshi,
164
+ blockHeight: winner.height,
165
+ };
166
+ }
@@ -25,6 +25,7 @@ export interface MiningRecentWinSummary {
25
25
  export interface MiningFollowVisualizerState {
26
26
  balanceCogtoshi: bigint | null;
27
27
  balanceSats: bigint | null;
28
+ fundingAddress: string | null;
28
29
  visibleBlockTimesByHeight: Record<number, number>;
29
30
  settledBlockHeight: number | null;
30
31
  settledBoardEntries: MiningSentenceBoardEntry[];
@@ -1,5 +1,5 @@
1
1
  import { createBootstrapProgress } from "../../bitcoind/progress/formatting.js";
2
- import { normalizeInlineText, truncateLine } from "../../bitcoind/progress/formatting.js";
2
+ import { centerLine, normalizeInlineText, truncateLine } from "../../bitcoind/progress/formatting.js";
3
3
  import { advanceFollowSceneState, createFollowSceneState, replaceFollowBlockTimes, syncFollowSceneState, } from "../../bitcoind/progress/follow-scene.js";
4
4
  import { DEFAULT_RENDER_CLOCK, resolveTtyRenderPolicy, TtyRenderThrottle, } from "../../bitcoind/progress/render-policy.js";
5
5
  import { TtyProgressRenderer } from "../../bitcoind/progress/tty-renderer.js";
@@ -125,18 +125,27 @@ function formatSentenceSlot(prefix, sentence, requiredWords, lineCount) {
125
125
  return lines;
126
126
  }
127
127
  function formatSentenceRow(entry) {
128
- return formatSentenceSlot(`${entry.rank}. @${entry.domainName}: `, entry.sentence, entry.requiredWords, 2);
128
+ const prefix = `${entry.rank}. @${entry.domainName}: `;
129
+ const highlightedSentence = highlightRequiredWords(normalizeInlineText(entry.sentence), entry.requiredWords);
130
+ return `${prefix}${highlightedSentence}`;
129
131
  }
130
- function formatRequiredWordsLine(words) {
131
- if (words.length === 0) {
132
- return "";
132
+ function formatSentenceBoardTitle(settledBlockHeight) {
133
+ return centerLine(`✎ Block #${settledBlockHeight ?? "-----"} Sentences ✎`, MINING_SENTENCE_BOARD_WRAP_WIDTH);
134
+ }
135
+ function formatProvisionalFundingLine(snapshot, ui) {
136
+ if (snapshot.currentPublishDecision === "publish-paused-insufficient-funds") {
137
+ const fundingAddress = normalizeInlineText(ui.fundingAddress ?? "");
138
+ return fundingAddress.length > 0
139
+ ? `Deposit BTC to ${fundingAddress} to mine.`
140
+ : "Deposit BTC to this wallet address to mine.";
133
141
  }
134
- return `Required words: ${words.map((word) => word.toUpperCase()).join(", ")}`;
142
+ return "";
135
143
  }
136
- function formatProvisionalTxLinkLine(entry, txid) {
137
- if (entry.domainName === null || entry.sentence === null || txid === null) {
144
+ function formatProvisionalTxLinkLine(ui) {
145
+ if (ui.provisionalEntry.domainName === null || ui.provisionalEntry.sentence === null || ui.provisionalBroadcastTxid === null) {
138
146
  return "";
139
147
  }
148
+ const txid = ui.provisionalBroadcastTxid;
140
149
  const normalizedTxid = normalizeInlineText(txid);
141
150
  if (normalizedTxid.length === 0) {
142
151
  return "";
@@ -145,14 +154,23 @@ function formatProvisionalTxLinkLine(entry, txid) {
145
154
  }
146
155
  function formatProvisionalSentenceRow(entry, requiredWords) {
147
156
  if (entry.domainName === null || entry.sentence === null) {
148
- return ["", "", ""];
157
+ return ["", ""];
158
+ }
159
+ return formatSentenceSlot(`@${entry.domainName}: `, entry.sentence, requiredWords, 2);
160
+ }
161
+ function formatProvisionalSectionLines(snapshot, ui) {
162
+ const sentenceLines = formatProvisionalSentenceRow(ui.provisionalEntry, ui.provisionalRequiredWords);
163
+ const fundingLine = formatProvisionalFundingLine(snapshot, ui);
164
+ if (fundingLine.length > 0) {
165
+ return [...sentenceLines, fundingLine];
149
166
  }
150
- return formatSentenceSlot(`@${entry.domainName}: `, entry.sentence, requiredWords, 3);
167
+ return [...sentenceLines, formatProvisionalTxLinkLine(ui)];
151
168
  }
152
169
  export function createEmptyMiningFollowVisualizerState() {
153
170
  return {
154
171
  balanceCogtoshi: null,
155
172
  balanceSats: null,
173
+ fundingAddress: null,
156
174
  visibleBlockTimesByHeight: {},
157
175
  settledBlockHeight: null,
158
176
  settledBoardEntries: [],
@@ -185,6 +203,7 @@ function cloneMiningFollowVisualizerState(state) {
185
203
  ...state.provisionalEntry,
186
204
  },
187
205
  provisionalBroadcastTxid: state.provisionalBroadcastTxid,
206
+ fundingAddress: state.fundingAddress,
188
207
  recentWin: state.recentWin === null
189
208
  ? null
190
209
  : {
@@ -378,18 +397,14 @@ export class MiningFollowVisualizer {
378
397
  artworkCogText: formatCompactCogBalanceText(uiState.balanceCogtoshi),
379
398
  artworkSatText: formatCompactSatBalanceText(uiState.balanceSats),
380
399
  extraLines: [
381
- `✎ Block #${uiState.settledBlockHeight ?? "-----"} Sentences ✎`,
382
- "",
400
+ formatSentenceBoardTitle(uiState.settledBlockHeight),
383
401
  ...Array.from({ length: MINING_SENTENCE_BOARD_SIZE }, (_value, index) => {
384
402
  const entry = uiState.settledBoardEntries[index];
385
403
  return entry === undefined
386
- ? [`${index + 1}.`, ""]
404
+ ? `${index + 1}.`
387
405
  : formatSentenceRow(entry);
388
- }).flat(),
389
- "----------",
390
- formatProvisionalTxLinkLine(uiState.provisionalEntry, uiState.provisionalBroadcastTxid),
391
- formatRequiredWordsLine(uiState.provisionalRequiredWords),
392
- ...formatProvisionalSentenceRow(uiState.provisionalEntry, uiState.provisionalRequiredWords),
406
+ }),
407
+ ...formatProvisionalSectionLines(snapshot, uiState),
393
408
  ],
394
409
  };
395
410
  if (this.#artworkStatusLeftText !== null) {
@@ -1,6 +1,6 @@
1
1
  import { readSnapshotWithRetry } from "../../bitcoind/indexer-daemon.js";
2
2
  import { type WalletSecretProvider } from "../state/provider.js";
3
- import type { WalletLocalStateStatus, WalletReadContext } from "./types.js";
3
+ import type { WalletBitcoindStatus, WalletLocalStateStatus, WalletNodeStatus, WalletReadContext, WalletServiceHealth } from "./types.js";
4
4
  import type { WalletRuntimePaths } from "../runtime.js";
5
5
  declare function inspectWalletLocalState(options?: {
6
6
  dataDir?: string;
@@ -9,6 +9,10 @@ declare function inspectWalletLocalState(options?: {
9
9
  paths?: WalletRuntimePaths;
10
10
  walletControlLockHeld?: boolean;
11
11
  }): Promise<WalletLocalStateStatus>;
12
+ export declare function deriveNodeHealthForTesting(status: WalletNodeStatus | null, bitcoindHealth: WalletBitcoindStatus["health"]): {
13
+ health: WalletServiceHealth;
14
+ message: string | null;
15
+ };
12
16
  export declare function openWalletReadContext(options: {
13
17
  dataDir: string;
14
18
  databasePath: string;
@@ -20,6 +20,9 @@ import { describeClientPasswordLockedMessage, describeClientPasswordMigrationMes
20
20
  import { createWalletReadModel } from "./project.js";
21
21
  const DEFAULT_SERVICE_START_TIMEOUT_MS = 10_000;
22
22
  const STALE_HEARTBEAT_THRESHOLD_MS = 15_000;
23
+ const TOLERATED_NODE_HEADER_LEAD_BLOCKS = 2;
24
+ const TOLERATED_NODE_HEADER_LEAD_MESSAGE = "Bitcoin headers can briefly lead validated blocks; a short 1-2 block lead is normal and is being tolerated.";
25
+ const NODE_CATCHING_UP_MESSAGE = "Bitcoin Core is still catching up to headers.";
23
26
  function btcAmountToSats(value) {
24
27
  return BigInt(Math.round(value * 100_000_000));
25
28
  }
@@ -351,14 +354,23 @@ function deriveBitcoindHealth(options) {
351
354
  function deriveNodeHealth(status, bitcoindHealth) {
352
355
  if (bitcoindHealth !== "ready" || status === null || !status.ready) {
353
356
  return {
354
- health: "unavailable",
355
- message: "Bitcoin service is unavailable.",
357
+ health: "catching-up",
358
+ message: NODE_CATCHING_UP_MESSAGE,
356
359
  };
357
360
  }
358
- if (status.nodeBestHeight !== null && status.nodeHeaderHeight !== null && status.nodeBestHeight < status.nodeHeaderHeight) {
361
+ const headerLead = status.nodeBestHeight !== null && status.nodeHeaderHeight !== null
362
+ ? status.nodeHeaderHeight - status.nodeBestHeight
363
+ : null;
364
+ if (headerLead !== null && headerLead > 0) {
365
+ if (headerLead <= TOLERATED_NODE_HEADER_LEAD_BLOCKS) {
366
+ return {
367
+ health: "synced",
368
+ message: TOLERATED_NODE_HEADER_LEAD_MESSAGE,
369
+ };
370
+ }
359
371
  return {
360
372
  health: "catching-up",
361
- message: "Bitcoin Core is still catching up to headers.",
373
+ message: NODE_CATCHING_UP_MESSAGE,
362
374
  };
363
375
  }
364
376
  return {
@@ -366,6 +378,9 @@ function deriveNodeHealth(status, bitcoindHealth) {
366
378
  message: null,
367
379
  };
368
380
  }
381
+ export function deriveNodeHealthForTesting(status, bitcoindHealth) {
382
+ return deriveNodeHealth(status, bitcoindHealth);
383
+ }
369
384
  function deriveIndexerHealth(options) {
370
385
  const daemonStatus = options.source === "lease"
371
386
  ? options.daemonStatus
@@ -38,7 +38,7 @@ export interface WalletResetPreview {
38
38
  confirmationPhrase: "permanently reset";
39
39
  walletPrompt: null | {
40
40
  defaultAction: "retain-mnemonic";
41
- acceptedInputs: ["", "skip", "delete wallet"];
41
+ acceptedInputs: ["", "skip", "clear wallet entropy"];
42
42
  entropyRetainingResetAvailable: boolean;
43
43
  envelopeSource: "primary" | "backup" | null;
44
44
  };
@@ -14,7 +14,6 @@ import { loadMiningRuntimeStatus } from "./mining/runtime-artifacts.js";
14
14
  import { clearLegacyWalletLockArtifacts, withUnlockedManagedCoreWallet } from "./managed-core-wallet.js";
15
15
  import { resolveWalletRuntimePathsForTesting } from "./runtime.js";
16
16
  import { createDefaultWalletSecretProvider, createWalletRootId, createWalletSecretReference, } from "./state/provider.js";
17
- import { loadWalletSeedIndex } from "./state/seed-index.js";
18
17
  import { extractWalletRootIdHintFromWalletStateEnvelope, loadRawWalletStateEnvelope, loadWalletState, saveWalletState, } from "./state/storage.js";
19
18
  import { confirmTypedAcknowledgement } from "./tx/confirm.js";
20
19
  function sanitizeWalletName(walletRootId) {
@@ -50,6 +49,36 @@ async function readJsonFileOrNull(path) {
50
49
  return null;
51
50
  }
52
51
  }
52
+ async function collectLegacyImportedSeedSecretProviderKeyIds(paths) {
53
+ const seedsRoot = join(paths.stateRoot, "seeds");
54
+ const entries = await readdir(seedsRoot, { withFileTypes: true }).catch((error) => {
55
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") {
56
+ return [];
57
+ }
58
+ throw error;
59
+ });
60
+ const keyIds = new Set();
61
+ for (const entry of entries) {
62
+ if (!entry.isDirectory()) {
63
+ continue;
64
+ }
65
+ const seedRoot = join(seedsRoot, entry.name);
66
+ const candidatePaths = [
67
+ join(seedRoot, "wallet-state.enc"),
68
+ join(seedRoot, "wallet-state.enc.bak"),
69
+ join(seedRoot, "wallet-init-pending.enc"),
70
+ join(seedRoot, "wallet-init-pending.enc.bak"),
71
+ ];
72
+ for (const candidatePath of candidatePaths) {
73
+ const envelope = await readJsonFileOrNull(candidatePath);
74
+ const keyId = envelope?.secretProvider?.keyId?.trim() ?? "";
75
+ if (keyId.length > 0) {
76
+ keyIds.add(keyId);
77
+ }
78
+ }
79
+ }
80
+ return [...keyIds].sort((left, right) => left.localeCompare(right));
81
+ }
53
82
  async function isProcessAlive(pid) {
54
83
  if (pid === null) {
55
84
  return false;
@@ -454,12 +483,7 @@ async function preflightReset(options) {
454
483
  }
455
484
  const tracked = await collectTrackedManagedProcesses(options.paths);
456
485
  const secretProviderKeyId = rawEnvelope?.envelope.secretProvider?.keyId ?? null;
457
- const seedIndex = await loadWalletSeedIndex({
458
- paths: options.paths,
459
- }).catch(() => null);
460
- const importedSeedSecretProviderKeyIds = [...new Set((seedIndex?.seeds ?? [])
461
- .filter((seed) => seed.kind === "imported")
462
- .map((seed) => createWalletSecretReference(seed.walletRootId).keyId))];
486
+ const importedSeedSecretProviderKeyIds = await collectLegacyImportedSeedSecretProviderKeyIds(options.paths);
463
487
  return {
464
488
  dataRoot: options.paths.dataRoot,
465
489
  removedRoots,
@@ -555,8 +579,8 @@ async function resolveResetExecutionDecision(options) {
555
579
  let walletChoice = "";
556
580
  let loadedWalletForEntropyReset = null;
557
581
  if (options.preflight.wallet.present) {
558
- const answer = (await options.prompter.prompt("Wallet reset choice ([Enter] retain base entropy, \"skip\", or \"delete wallet\"): ")).trim();
559
- if (answer !== "" && answer !== "skip" && answer !== "delete wallet") {
582
+ const answer = (await options.prompter.prompt("Wallet reset choice ([Enter] retain base entropy, \"skip\", or \"clear wallet entropy\"): ")).trim();
583
+ if (answer !== "" && answer !== "skip" && answer !== "clear wallet entropy") {
560
584
  throw new Error("reset_wallet_choice_invalid");
561
585
  }
562
586
  walletChoice = answer;
@@ -592,7 +616,7 @@ function determineWalletAction(walletPresent, walletChoice) {
592
616
  if (walletChoice === "skip") {
593
617
  return "kept-unchanged";
594
618
  }
595
- if (walletChoice === "delete wallet") {
619
+ if (walletChoice === "clear wallet entropy") {
596
620
  return "deleted";
597
621
  }
598
622
  return "retain-mnemonic";
@@ -636,7 +660,7 @@ export async function previewResetWallet(options) {
636
660
  walletPrompt: preflight.wallet.present
637
661
  ? {
638
662
  defaultAction: "retain-mnemonic",
639
- acceptedInputs: ["", "skip", "delete wallet"],
663
+ acceptedInputs: ["", "skip", "clear wallet entropy"],
640
664
  entropyRetainingResetAvailable: preflight.wallet.mode === "provider-backed",
641
665
  envelopeSource: preflight.wallet.envelopeSource,
642
666
  }
@@ -1,7 +1,5 @@
1
1
  import type { CogcoinPathResolution } from "../app-paths.js";
2
- export type WalletSeedKind = "main" | "imported";
3
2
  export interface WalletRuntimePathResolution extends CogcoinPathResolution {
4
- seedName?: string | null;
5
3
  }
6
4
  export interface WalletRuntimePaths {
7
5
  dataRoot: string;
@@ -11,9 +9,6 @@ export interface WalletRuntimePaths {
11
9
  walletRuntimeRoot: string;
12
10
  stateRoot: string;
13
11
  walletStateRoot: string;
14
- seedRegistryPath: string;
15
- selectedSeedName: string;
16
- selectedSeedKind: WalletSeedKind;
17
12
  bitcoinDataDir: string;
18
13
  indexerRoot: string;
19
14
  walletStateDirectory: string;
@@ -31,5 +26,4 @@ export interface WalletRuntimePaths {
31
26
  miningEventsPath: string;
32
27
  miningControlLockPath: string;
33
28
  }
34
- export declare function deriveWalletRuntimePathsForSeed(basePaths: WalletRuntimePaths, seedName: string | null | undefined): WalletRuntimePaths;
35
29
  export declare function resolveWalletRuntimePathsForTesting(resolution?: WalletRuntimePathResolution): WalletRuntimePaths;
@@ -1,41 +1,8 @@
1
1
  import { join } from "node:path";
2
2
  import { resolveCogcoinPathsForTesting } from "../app-paths.js";
3
- function resolveSeedLayout(sharedStateRoot, sharedRuntimeRoot, seedName) {
4
- if (seedName === "main") {
5
- return {
6
- seedKind: "main",
7
- walletStateRoot: sharedStateRoot,
8
- walletRuntimeRoot: sharedRuntimeRoot,
9
- };
10
- }
11
- return {
12
- seedKind: "imported",
13
- walletStateRoot: join(sharedStateRoot, "seeds", seedName),
14
- walletRuntimeRoot: join(sharedRuntimeRoot, "seeds", seedName),
15
- };
16
- }
17
- export function deriveWalletRuntimePathsForSeed(basePaths, seedName) {
18
- const resolvedSeedName = seedName ?? "main";
19
- const seedLayout = resolveSeedLayout(basePaths.stateRoot, basePaths.runtimeRoot, resolvedSeedName);
20
- return {
21
- ...basePaths,
22
- walletRuntimeRoot: seedLayout.walletRuntimeRoot,
23
- walletStateRoot: seedLayout.walletStateRoot,
24
- selectedSeedName: resolvedSeedName,
25
- selectedSeedKind: seedLayout.seedKind,
26
- walletStateDirectory: seedLayout.walletStateRoot,
27
- walletStatePath: join(seedLayout.walletStateRoot, "wallet-state.enc"),
28
- walletStateBackupPath: join(seedLayout.walletStateRoot, "wallet-state.enc.bak"),
29
- walletInitPendingPath: join(seedLayout.walletStateRoot, "wallet-init-pending.enc"),
30
- walletInitPendingBackupPath: join(seedLayout.walletStateRoot, "wallet-init-pending.enc.bak"),
31
- miningRoot: join(seedLayout.walletRuntimeRoot, "mining"),
32
- miningStatusPath: join(seedLayout.walletRuntimeRoot, "mining", "status.json"),
33
- miningEventsPath: join(seedLayout.walletRuntimeRoot, "mining", "events.jsonl"),
34
- };
35
- }
36
3
  export function resolveWalletRuntimePathsForTesting(resolution = {}) {
37
4
  const paths = resolveCogcoinPathsForTesting(resolution);
38
- return deriveWalletRuntimePathsForSeed({
5
+ return {
39
6
  dataRoot: paths.dataRoot,
40
7
  clientDataDir: paths.clientDataDir,
41
8
  clientConfigPath: paths.clientConfigPath,
@@ -43,9 +10,6 @@ export function resolveWalletRuntimePathsForTesting(resolution = {}) {
43
10
  walletRuntimeRoot: paths.runtimeRoot,
44
11
  stateRoot: paths.stateRoot,
45
12
  walletStateRoot: paths.stateRoot,
46
- seedRegistryPath: join(paths.stateRoot, "seed-index.json"),
47
- selectedSeedName: "main",
48
- selectedSeedKind: "main",
49
13
  bitcoinDataDir: paths.bitcoinDataDir,
50
14
  indexerRoot: paths.indexerRoot,
51
15
  walletStateDirectory: paths.stateRoot,
@@ -62,5 +26,5 @@ export function resolveWalletRuntimePathsForTesting(resolution = {}) {
62
26
  miningStatusPath: join(paths.runtimeRoot, "mining", "status.json"),
63
27
  miningEventsPath: join(paths.runtimeRoot, "mining", "events.jsonl"),
64
28
  miningControlLockPath: paths.miningControlLockPath,
65
- }, resolution.seedName);
29
+ };
66
30
  }
@@ -168,6 +168,24 @@ export declare function buildWalletMutationTransaction<TPlan>(options: {
168
168
  availableFundingMinConf?: number;
169
169
  temporarilyUnlockedPolicyOutpoints?: readonly OutpointRecord[];
170
170
  }): Promise<BuiltWalletMutationTransaction>;
171
+ export declare function fundAndValidateWalletMutationDraft<TPlan>(options: {
172
+ rpc: WalletMutationRpcClient;
173
+ walletName: string;
174
+ plan: TPlan & {
175
+ fixedInputs: FixedWalletInput[];
176
+ outputs: unknown[];
177
+ changeAddress: string;
178
+ changePosition?: number | null;
179
+ allowedFundingScriptPubKeyHex: string;
180
+ eligibleFundingOutpointKeys: Set<string>;
181
+ };
182
+ validateFundedDraft(decoded: RpcDecodedPsbt, funded: RpcWalletCreateFundedPsbtResult, plan: TPlan): void;
183
+ feeRate?: number;
184
+ availableFundingMinConf?: number;
185
+ }): Promise<{
186
+ funded: RpcWalletCreateFundedPsbtResult;
187
+ decoded: RpcDecodedPsbt;
188
+ }>;
171
189
  export declare function buildWalletMutationTransactionWithReserveFallback<TPlan>(options: {
172
190
  rpc: WalletMutationRpcClient;
173
191
  walletName: string;