@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
@@ -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) {
@@ -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;
@@ -416,35 +416,16 @@ export async function pauseMiningForWalletMutation(options) {
416
416
  });
417
417
  }
418
418
  export async function buildWalletMutationTransaction(options) {
419
- const availableFundingMinConf = options.availableFundingMinConf ?? 1;
420
- const availableFundingUtxos = (await options.rpc.listUnspent(options.walletName, availableFundingMinConf))
421
- .filter((entry) => isSpendableFundingUtxo(entry, options.plan.allowedFundingScriptPubKeyHex, availableFundingMinConf));
422
- const availableFundingValueByKey = new Map(availableFundingUtxos.map((entry) => [
423
- outpointKey({ txid: entry.txid, vout: entry.vout }),
424
- btcNumberToSats(entry.amount),
425
- ]));
426
- const validationPlan = {
427
- ...options.plan,
428
- eligibleFundingOutpointKeys: new Set([
429
- ...options.plan.eligibleFundingOutpointKeys,
430
- ...availableFundingUtxos.map((entry) => outpointKey({ txid: entry.txid, vout: entry.vout })),
431
- ]),
432
- };
433
419
  const temporaryBuilderLockedOutpoints = [];
434
420
  try {
435
- const funded = await options.rpc.walletCreateFundedPsbt(options.walletName, options.plan.fixedInputs, options.plan.outputs, 0, {
436
- add_inputs: true,
437
- include_unsafe: false,
438
- minconf: availableFundingMinConf,
439
- changeAddress: options.plan.changeAddress,
440
- ...(options.plan.changePosition == null ? {} : { changePosition: options.plan.changePosition }),
441
- lockUnspents: false,
442
- fee_rate: options.feeRate ?? DEFAULT_WALLET_MUTATION_FEE_RATE_SAT_VB,
443
- replaceable: true,
444
- subtractFeeFromOutputs: [],
421
+ const { funded, decoded } = await fundAndValidateWalletMutationDraft({
422
+ rpc: options.rpc,
423
+ walletName: options.walletName,
424
+ plan: options.plan,
425
+ validateFundedDraft: options.validateFundedDraft,
426
+ feeRate: options.feeRate,
427
+ availableFundingMinConf: options.availableFundingMinConf,
445
428
  });
446
- const decoded = await options.rpc.decodePsbt(funded.psbt);
447
- options.validateFundedDraft(decoded, funded, validationPlan);
448
429
  let signed;
449
430
  let finalized;
450
431
  let rawHex;
@@ -490,6 +471,39 @@ export async function buildWalletMutationTransaction(options) {
490
471
  throw error;
491
472
  }
492
473
  }
474
+ export async function fundAndValidateWalletMutationDraft(options) {
475
+ const availableFundingMinConf = options.availableFundingMinConf ?? 1;
476
+ const availableFundingUtxos = (await options.rpc.listUnspent(options.walletName, availableFundingMinConf))
477
+ .filter((entry) => isSpendableFundingUtxo(entry, options.plan.allowedFundingScriptPubKeyHex, availableFundingMinConf));
478
+ const availableFundingValueByKey = new Map(availableFundingUtxos.map((entry) => [
479
+ outpointKey({ txid: entry.txid, vout: entry.vout }),
480
+ btcNumberToSats(entry.amount),
481
+ ]));
482
+ const validationPlan = {
483
+ ...options.plan,
484
+ eligibleFundingOutpointKeys: new Set([
485
+ ...options.plan.eligibleFundingOutpointKeys,
486
+ ...availableFundingUtxos.map((entry) => outpointKey({ txid: entry.txid, vout: entry.vout })),
487
+ ]),
488
+ };
489
+ const funded = await options.rpc.walletCreateFundedPsbt(options.walletName, options.plan.fixedInputs, options.plan.outputs, 0, {
490
+ add_inputs: true,
491
+ include_unsafe: false,
492
+ minconf: availableFundingMinConf,
493
+ changeAddress: options.plan.changeAddress,
494
+ ...(options.plan.changePosition == null ? {} : { changePosition: options.plan.changePosition }),
495
+ lockUnspents: false,
496
+ fee_rate: options.feeRate ?? DEFAULT_WALLET_MUTATION_FEE_RATE_SAT_VB,
497
+ replaceable: true,
498
+ subtractFeeFromOutputs: [],
499
+ });
500
+ const decoded = await options.rpc.decodePsbt(funded.psbt);
501
+ options.validateFundedDraft(decoded, funded, validationPlan);
502
+ return {
503
+ funded,
504
+ decoded,
505
+ };
506
+ }
493
507
  export async function buildWalletMutationTransactionWithReserveFallback(options) {
494
508
  return buildWalletMutationTransaction({
495
509
  rpc: options.rpc,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cogcoin/client",
3
- "version": "1.1.4",
3
+ "version": "1.1.5",
4
4
  "description": "Store-backed Cogcoin client with wallet flows, SQLite persistence, and managed Bitcoin Core integration.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -1,43 +0,0 @@
1
- import type { WalletSeedKind, WalletRuntimePaths } from "../runtime.js";
2
- import { type RawWalletStateEnvelope } from "./storage.js";
3
- export interface WalletSeedRecord {
4
- name: string;
5
- kind: WalletSeedKind;
6
- walletRootId: string;
7
- createdAtUnixMs: number;
8
- restoredAtUnixMs: number | null;
9
- }
10
- export interface WalletSeedIndexV1 {
11
- schemaVersion: 1;
12
- lastWrittenAtUnixMs: number;
13
- seeds: WalletSeedRecord[];
14
- }
15
- export declare function normalizeWalletSeedName(name: string): string;
16
- export declare function isValidWalletSeedName(name: string): boolean;
17
- export declare function assertValidImportedWalletSeedName(name: string): string;
18
- export declare function findWalletSeedRecord(index: WalletSeedIndexV1, seedName: string): WalletSeedRecord | null;
19
- export declare function loadWalletSeedIndex(options: {
20
- paths: Pick<WalletRuntimePaths, "seedRegistryPath" | "walletStatePath" | "walletStateBackupPath">;
21
- nowUnixMs?: number;
22
- loadRawWalletStateEnvelope?: (paths: {
23
- primaryPath: string;
24
- backupPath: string;
25
- }) => Promise<RawWalletStateEnvelope | null>;
26
- }): Promise<WalletSeedIndexV1>;
27
- export declare function saveWalletSeedIndex(paths: Pick<WalletRuntimePaths, "seedRegistryPath">, index: WalletSeedIndexV1): Promise<void>;
28
- export declare function ensureMainWalletSeedIndexRecord(options: {
29
- paths: Pick<WalletRuntimePaths, "seedRegistryPath" | "walletStatePath" | "walletStateBackupPath">;
30
- walletRootId: string;
31
- nowUnixMs?: number;
32
- }): Promise<WalletSeedIndexV1>;
33
- export declare function addImportedWalletSeedRecord(options: {
34
- paths: Pick<WalletRuntimePaths, "seedRegistryPath" | "walletStatePath" | "walletStateBackupPath">;
35
- seedName: string;
36
- walletRootId: string;
37
- nowUnixMs?: number;
38
- }): Promise<WalletSeedIndexV1>;
39
- export declare function removeWalletSeedRecord(options: {
40
- paths: Pick<WalletRuntimePaths, "seedRegistryPath" | "walletStatePath" | "walletStateBackupPath">;
41
- seedName: string;
42
- nowUnixMs?: number;
43
- }): Promise<WalletSeedIndexV1>;