@cogcoin/client 1.1.4 → 1.1.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 (107) hide show
  1. package/README.md +4 -5
  2. package/dist/bitcoind/indexer-daemon.d.ts +3 -7
  3. package/dist/bitcoind/indexer-daemon.js +43 -158
  4. package/dist/bitcoind/managed-runtime/bitcoind-policy.d.ts +16 -0
  5. package/dist/bitcoind/managed-runtime/bitcoind-policy.js +177 -0
  6. package/dist/bitcoind/managed-runtime/indexer-policy.d.ts +34 -0
  7. package/dist/bitcoind/managed-runtime/indexer-policy.js +200 -0
  8. package/dist/bitcoind/managed-runtime/status.d.ts +11 -0
  9. package/dist/bitcoind/managed-runtime/status.js +59 -0
  10. package/dist/bitcoind/managed-runtime/types.d.ts +37 -0
  11. package/dist/bitcoind/managed-runtime/types.js +1 -0
  12. package/dist/bitcoind/progress/tty-renderer.js +3 -2
  13. package/dist/bitcoind/service.d.ts +2 -7
  14. package/dist/bitcoind/service.js +46 -94
  15. package/dist/cli/command-registry.d.ts +39 -0
  16. package/dist/cli/command-registry.js +1132 -0
  17. package/dist/cli/commands/client-admin.js +6 -56
  18. package/dist/cli/commands/mining-admin.js +9 -32
  19. package/dist/cli/commands/mining-read.js +15 -56
  20. package/dist/cli/commands/mining-runtime.js +258 -57
  21. package/dist/cli/commands/service-runtime.js +1 -64
  22. package/dist/cli/commands/status.js +2 -15
  23. package/dist/cli/commands/update.js +6 -21
  24. package/dist/cli/commands/wallet-admin.js +18 -120
  25. package/dist/cli/commands/wallet-mutation.js +4 -7
  26. package/dist/cli/commands/wallet-read.js +31 -138
  27. package/dist/cli/context.js +2 -4
  28. package/dist/cli/mining-format.js +8 -2
  29. package/dist/cli/mutation-command-groups.d.ts +11 -11
  30. package/dist/cli/mutation-command-groups.js +9 -18
  31. package/dist/cli/mutation-json.d.ts +1 -17
  32. package/dist/cli/mutation-json.js +1 -28
  33. package/dist/cli/mutation-success.d.ts +0 -1
  34. package/dist/cli/mutation-success.js +0 -19
  35. package/dist/cli/output.d.ts +1 -10
  36. package/dist/cli/output.js +52 -481
  37. package/dist/cli/parse.d.ts +1 -1
  38. package/dist/cli/parse.js +38 -695
  39. package/dist/cli/runner.js +28 -113
  40. package/dist/cli/types.d.ts +7 -8
  41. package/dist/cli/update-notifier.js +1 -1
  42. package/dist/cli/wallet-format.js +1 -1
  43. package/dist/wallet/lifecycle/access.d.ts +5 -0
  44. package/dist/wallet/lifecycle/access.js +79 -0
  45. package/dist/wallet/lifecycle/context.d.ts +26 -0
  46. package/dist/wallet/lifecycle/context.js +58 -0
  47. package/dist/wallet/lifecycle/managed-core.d.ts +15 -0
  48. package/dist/wallet/lifecycle/managed-core.js +197 -0
  49. package/dist/wallet/lifecycle/repair-bitcoind.d.ts +10 -0
  50. package/dist/wallet/lifecycle/repair-bitcoind.js +142 -0
  51. package/dist/wallet/lifecycle/repair-indexer.d.ts +8 -0
  52. package/dist/wallet/lifecycle/repair-indexer.js +117 -0
  53. package/dist/wallet/lifecycle/repair-mining.d.ts +49 -0
  54. package/dist/wallet/lifecycle/repair-mining.js +304 -0
  55. package/dist/wallet/lifecycle/repair-runtime.d.ts +36 -0
  56. package/dist/wallet/lifecycle/repair-runtime.js +206 -0
  57. package/dist/wallet/lifecycle/repair.d.ts +9 -0
  58. package/dist/wallet/lifecycle/repair.js +127 -0
  59. package/dist/wallet/lifecycle/setup-prompts.d.ts +7 -0
  60. package/dist/wallet/lifecycle/setup-prompts.js +88 -0
  61. package/dist/wallet/lifecycle/setup-state.d.ts +26 -0
  62. package/dist/wallet/lifecycle/setup-state.js +159 -0
  63. package/dist/wallet/lifecycle/setup.d.ts +15 -0
  64. package/dist/wallet/lifecycle/setup.js +124 -0
  65. package/dist/wallet/lifecycle/types.d.ts +156 -0
  66. package/dist/wallet/lifecycle/types.js +1 -0
  67. package/dist/wallet/lifecycle.d.ts +4 -165
  68. package/dist/wallet/lifecycle.js +3 -1656
  69. package/dist/wallet/mining/candidate.d.ts +60 -0
  70. package/dist/wallet/mining/candidate.js +290 -0
  71. package/dist/wallet/mining/competitiveness.d.ts +22 -0
  72. package/dist/wallet/mining/competitiveness.js +640 -0
  73. package/dist/wallet/mining/control.js +7 -251
  74. package/dist/wallet/mining/cycle.d.ts +39 -0
  75. package/dist/wallet/mining/cycle.js +542 -0
  76. package/dist/wallet/mining/engine-state.d.ts +66 -0
  77. package/dist/wallet/mining/engine-state.js +211 -0
  78. package/dist/wallet/mining/engine-types.d.ts +173 -0
  79. package/dist/wallet/mining/engine-types.js +1 -0
  80. package/dist/wallet/mining/engine-utils.d.ts +7 -0
  81. package/dist/wallet/mining/engine-utils.js +75 -0
  82. package/dist/wallet/mining/events.d.ts +2 -0
  83. package/dist/wallet/mining/events.js +19 -0
  84. package/dist/wallet/mining/lifecycle.d.ts +71 -0
  85. package/dist/wallet/mining/lifecycle.js +355 -0
  86. package/dist/wallet/mining/projection.d.ts +61 -0
  87. package/dist/wallet/mining/projection.js +319 -0
  88. package/dist/wallet/mining/publish.d.ts +79 -0
  89. package/dist/wallet/mining/publish.js +614 -0
  90. package/dist/wallet/mining/runner.d.ts +12 -418
  91. package/dist/wallet/mining/runner.js +274 -3433
  92. package/dist/wallet/mining/supervisor.d.ts +134 -0
  93. package/dist/wallet/mining/supervisor.js +558 -0
  94. package/dist/wallet/mining/visualizer-sync.d.ts +42 -0
  95. package/dist/wallet/mining/visualizer-sync.js +166 -0
  96. package/dist/wallet/mining/visualizer.d.ts +1 -0
  97. package/dist/wallet/mining/visualizer.js +33 -18
  98. package/dist/wallet/read/context.js +13 -188
  99. package/dist/wallet/reset.d.ts +1 -1
  100. package/dist/wallet/reset.js +35 -11
  101. package/dist/wallet/runtime.d.ts +0 -6
  102. package/dist/wallet/runtime.js +2 -38
  103. package/dist/wallet/tx/common.d.ts +18 -0
  104. package/dist/wallet/tx/common.js +40 -26
  105. package/package.json +1 -1
  106. package/dist/wallet/state/seed-index.d.ts +0 -43
  107. 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) {
@@ -2,6 +2,8 @@ import { access, constants } from "node:fs/promises";
2
2
  import { deserializeIndexerState, loadBundledGenesisParameters } from "@cogcoin/indexer";
3
3
  import { readPackageVersionFromDisk } from "../../package-version.js";
4
4
  import { attachOrStartIndexerDaemon, INDEXER_DAEMON_BACKGROUND_FOLLOW_RECOVERY_FAILED, probeIndexerDaemon, readObservedIndexerDaemonStatus, readSnapshotWithRetry, } from "../../bitcoind/indexer-daemon.js";
5
+ import { deriveManagedBitcoindWalletStatus, resolveManagedBitcoindProbeDecision, } from "../../bitcoind/managed-runtime/bitcoind-policy.js";
6
+ import { deriveManagedIndexerWalletStatus, resolveIndexerDaemonProbeDecision, } from "../../bitcoind/managed-runtime/indexer-policy.js";
5
7
  import { createRpcClient } from "../../bitcoind/node.js";
6
8
  import { UNINITIALIZED_WALLET_ROOT_ID } from "../../bitcoind/service-paths.js";
7
9
  import { attachOrStartManagedBitcoindService, probeManagedBitcoindService, } from "../../bitcoind/service.js";
@@ -19,7 +21,6 @@ import { createDefaultWalletSecretProvider, createWalletSecretReference, inspect
19
21
  import { describeClientPasswordLockedMessage, describeClientPasswordMigrationMessage, describeClientPasswordSetupMessage, } from "../state/client-password.js";
20
22
  import { createWalletReadModel } from "./project.js";
21
23
  const DEFAULT_SERVICE_START_TIMEOUT_MS = 10_000;
22
- const STALE_HEARTBEAT_THRESHOLD_MS = 15_000;
23
24
  const TOLERATED_NODE_HEADER_LEAD_BLOCKS = 2;
24
25
  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
26
  const NODE_CATCHING_UP_MESSAGE = "Bitcoin Core is still catching up to headers.";
@@ -222,135 +223,6 @@ async function inspectWalletLocalState(options = {}) {
222
223
  };
223
224
  }
224
225
  }
225
- function mapIndexerStartupError(message) {
226
- switch (message) {
227
- case "indexer_daemon_start_timeout":
228
- return {
229
- health: "starting",
230
- message: "Indexer daemon is still starting.",
231
- };
232
- case "indexer_daemon_service_version_mismatch":
233
- return {
234
- health: "service-version-mismatch",
235
- message: "The live indexer daemon is running an incompatible service API version.",
236
- };
237
- case "indexer_daemon_schema_mismatch":
238
- return {
239
- health: "schema-mismatch",
240
- message: "The live indexer daemon is using an incompatible sqlite schema.",
241
- };
242
- case "indexer_daemon_wallet_root_mismatch":
243
- return {
244
- health: "wallet-root-mismatch",
245
- message: "The live indexer daemon belongs to a different wallet root.",
246
- };
247
- case "indexer_daemon_protocol_error":
248
- return {
249
- health: "unavailable",
250
- message: "The live indexer daemon socket responded with an invalid or incomplete protocol exchange.",
251
- };
252
- case INDEXER_DAEMON_BACKGROUND_FOLLOW_RECOVERY_FAILED:
253
- return {
254
- health: "failed",
255
- message: "The managed indexer daemon could not recover automatic background follow.",
256
- };
257
- default:
258
- return {
259
- health: "unavailable",
260
- message,
261
- };
262
- }
263
- }
264
- function mapBitcoindStartupError(message) {
265
- switch (message) {
266
- case "managed_bitcoind_service_start_timeout":
267
- return {
268
- health: "starting",
269
- status: null,
270
- message: "Managed bitcoind service is still starting.",
271
- };
272
- case "managed_bitcoind_service_version_mismatch":
273
- return {
274
- health: "service-version-mismatch",
275
- status: null,
276
- message: "The live managed bitcoind service is running an incompatible service version.",
277
- };
278
- case "managed_bitcoind_wallet_root_mismatch":
279
- return {
280
- health: "wallet-root-mismatch",
281
- status: null,
282
- message: "The live managed bitcoind service belongs to a different wallet root.",
283
- };
284
- case "managed_bitcoind_runtime_mismatch":
285
- return {
286
- health: "runtime-mismatch",
287
- status: null,
288
- message: "The live managed bitcoind service runtime does not match this wallet's expected data directory or chain.",
289
- };
290
- case "managed_bitcoind_protocol_error":
291
- return {
292
- health: "unavailable",
293
- status: null,
294
- message: "The managed bitcoind runtime artifacts are invalid or incomplete.",
295
- };
296
- default:
297
- return {
298
- health: "unavailable",
299
- status: null,
300
- message,
301
- };
302
- }
303
- }
304
- function deriveBitcoindHealth(options) {
305
- if (options.startupError !== null) {
306
- const mapped = mapBitcoindStartupError(options.startupError);
307
- return {
308
- ...mapped,
309
- status: options.status,
310
- };
311
- }
312
- if (options.status === null) {
313
- return {
314
- health: "unavailable",
315
- status: null,
316
- message: "Managed bitcoind service is unavailable.",
317
- };
318
- }
319
- if (options.status.state === "starting") {
320
- return {
321
- health: "starting",
322
- status: options.status,
323
- message: options.status.lastError ?? "Managed bitcoind service is still starting.",
324
- };
325
- }
326
- if (options.status.state === "failed") {
327
- return {
328
- health: "failed",
329
- status: options.status,
330
- message: options.status.lastError ?? "Managed bitcoind service refresh failed.",
331
- };
332
- }
333
- const proofStatus = options.nodeStatus?.walletReplica?.proofStatus;
334
- if (proofStatus === "missing") {
335
- return {
336
- health: "replica-missing",
337
- status: options.status,
338
- message: options.nodeStatus?.walletReplicaMessage ?? "Managed Core wallet replica is missing.",
339
- };
340
- }
341
- if (proofStatus === "mismatch") {
342
- return {
343
- health: "replica-mismatch",
344
- status: options.status,
345
- message: options.nodeStatus?.walletReplicaMessage ?? "Managed Core wallet replica does not match trusted wallet state.",
346
- };
347
- }
348
- return {
349
- health: "ready",
350
- status: options.status,
351
- message: options.nodeStatus?.walletReplicaMessage ?? options.status.lastError,
352
- };
353
- }
354
226
  function deriveNodeHealth(status, bitcoindHealth) {
355
227
  if (bitcoindHealth !== "ready" || status === null || !status.ready) {
356
228
  return {
@@ -381,58 +253,6 @@ function deriveNodeHealth(status, bitcoindHealth) {
381
253
  export function deriveNodeHealthForTesting(status, bitcoindHealth) {
382
254
  return deriveNodeHealth(status, bitcoindHealth);
383
255
  }
384
- function deriveIndexerHealth(options) {
385
- const daemonStatus = options.source === "lease"
386
- ? options.daemonStatus
387
- : options.observedStatus ?? options.daemonStatus;
388
- const snapshotTip = options.snapshot?.tip ?? null;
389
- const daemonInstanceId = options.snapshot?.daemonInstanceId ?? daemonStatus?.daemonInstanceId ?? null;
390
- const snapshotSeq = options.snapshot?.snapshotSeq ?? daemonStatus?.snapshotSeq ?? null;
391
- const openedAtUnixMs = options.snapshot?.openedAtUnixMs ?? null;
392
- const source = daemonStatus === null && options.snapshot === null ? "none" : options.source;
393
- const createResult = (health, message) => ({
394
- health,
395
- status: daemonStatus,
396
- message,
397
- snapshotTip,
398
- source,
399
- daemonInstanceId,
400
- snapshotSeq,
401
- openedAtUnixMs,
402
- });
403
- if (options.startupError !== null) {
404
- const mapped = mapIndexerStartupError(options.startupError);
405
- return createResult(mapped.health, mapped.message);
406
- }
407
- if (daemonStatus === null) {
408
- return createResult("unavailable", "Indexer daemon is unavailable.");
409
- }
410
- if ((options.now - daemonStatus.heartbeatAtUnixMs) > STALE_HEARTBEAT_THRESHOLD_MS) {
411
- return createResult("stale-heartbeat", "Indexer daemon heartbeat is stale.");
412
- }
413
- if (daemonStatus.state === "schema-mismatch") {
414
- return createResult("schema-mismatch", daemonStatus.lastError ?? "Indexer daemon sqlite schema is incompatible.");
415
- }
416
- if (daemonStatus.state === "failed") {
417
- return createResult("failed", daemonStatus.lastError ?? "Indexer daemon refresh failed.");
418
- }
419
- if (daemonStatus.state === "service-version-mismatch") {
420
- return createResult("service-version-mismatch", "Indexer daemon service API is incompatible.");
421
- }
422
- if (options.snapshot === null) {
423
- if (daemonStatus.state === "reorging") {
424
- return createResult("reorging", "Indexer daemon is replaying a reorg and refreshing the coherent snapshot.");
425
- }
426
- return createResult(daemonStatus.state === "catching-up" ? "catching-up" : "starting", "Indexer snapshot is not ready yet.");
427
- }
428
- if (daemonStatus.state === "catching-up") {
429
- return createResult("catching-up", "Indexer daemon is still catching up to the managed Bitcoin tip.");
430
- }
431
- if (daemonStatus.state === "reorging") {
432
- return createResult("reorging", "Indexer daemon is replaying a reorg and refreshing the coherent snapshot.");
433
- }
434
- return createResult("synced", null);
435
- }
436
256
  async function attachNodeStatus(options) {
437
257
  try {
438
258
  const probe = await probeManagedBitcoindService({
@@ -442,13 +262,14 @@ async function attachNodeStatus(options) {
442
262
  walletRootId: options.walletRootId,
443
263
  startupTimeoutMs: options.startupTimeoutMs,
444
264
  });
445
- if (probe.compatibility !== "compatible" && probe.compatibility !== "unreachable") {
265
+ const decision = resolveManagedBitcoindProbeDecision(probe);
266
+ if (decision.action === "reject") {
446
267
  return {
447
268
  handle: null,
448
269
  rpc: null,
449
270
  status: null,
450
271
  observedStatus: probe.status,
451
- error: probe.error,
272
+ error: decision.error,
452
273
  };
453
274
  }
454
275
  const genesis = await loadBundledGenesisParameters();
@@ -539,7 +360,7 @@ export async function openWalletReadContext(options) {
539
360
  walletReplicaMessage: verifiedReplica.message ?? null,
540
361
  };
541
362
  }
542
- const bitcoind = deriveBitcoindHealth({
363
+ const bitcoind = deriveManagedBitcoindWalletStatus({
543
364
  status: node.observedStatus,
544
365
  nodeStatus: node.status,
545
366
  startupError: node.error,
@@ -556,7 +377,11 @@ export async function openWalletReadContext(options) {
556
377
  dataDir: options.dataDir,
557
378
  walletRootId,
558
379
  });
559
- if (probe.compatibility === "compatible" || probe.compatibility === "unreachable") {
380
+ const probeDecision = resolveIndexerDaemonProbeDecision({
381
+ probe,
382
+ expectedBinaryVersion: expectedIndexerBinaryVersion,
383
+ });
384
+ if (probeDecision.action !== "reject") {
560
385
  await probe.client?.close().catch(() => undefined);
561
386
  daemonClient = await attachOrStartIndexerDaemon({
562
387
  dataDir: options.dataDir,
@@ -570,7 +395,7 @@ export async function openWalletReadContext(options) {
570
395
  else {
571
396
  observedDaemonStatus = probe.status;
572
397
  indexerSource = probe.status === null ? "none" : "probe";
573
- daemonError = probe.error;
398
+ daemonError = probeDecision.error;
574
399
  }
575
400
  if (daemonClient !== null) {
576
401
  const lease = await readSnapshotWithRetry(daemonClient, walletRootId);
@@ -604,7 +429,7 @@ export async function openWalletReadContext(options) {
604
429
  }
605
430
  }
606
431
  }
607
- const indexer = deriveIndexerHealth({
432
+ const indexer = deriveManagedIndexerWalletStatus({
608
433
  daemonStatus,
609
434
  observedStatus: observedDaemonStatus,
610
435
  snapshot,
@@ -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
  };