@cogcoin/client 0.5.15 → 1.0.1

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 (174) hide show
  1. package/README.md +80 -25
  2. package/dist/app-paths.d.ts +5 -6
  3. package/dist/app-paths.js +8 -16
  4. package/dist/art/balance.txt +10 -0
  5. package/dist/art/welcome.txt +16 -0
  6. package/dist/bitcoind/bootstrap/controller.d.ts +1 -0
  7. package/dist/bitcoind/bootstrap/controller.js +53 -1
  8. package/dist/bitcoind/client/follow-block-times.d.ts +1 -0
  9. package/dist/bitcoind/client/follow-block-times.js +1 -1
  10. package/dist/bitcoind/client/internal-types.d.ts +7 -3
  11. package/dist/bitcoind/client/managed-client.d.ts +4 -2
  12. package/dist/bitcoind/client/managed-client.js +14 -0
  13. package/dist/bitcoind/client/sync-engine.js +72 -11
  14. package/dist/bitcoind/hash-order.d.ts +4 -0
  15. package/dist/bitcoind/hash-order.js +13 -0
  16. package/dist/bitcoind/indexer-daemon-main.js +11 -3
  17. package/dist/bitcoind/normalize.js +3 -2
  18. package/dist/bitcoind/processing-start-height.d.ts +5 -0
  19. package/dist/bitcoind/processing-start-height.js +7 -0
  20. package/dist/bitcoind/progress/constants.d.ts +4 -0
  21. package/dist/bitcoind/progress/constants.js +4 -0
  22. package/dist/bitcoind/progress/controller.d.ts +2 -1
  23. package/dist/bitcoind/progress/controller.js +3 -3
  24. package/dist/bitcoind/progress/follow-scene.d.ts +6 -2
  25. package/dist/bitcoind/progress/follow-scene.js +29 -6
  26. package/dist/bitcoind/progress/formatting.d.ts +1 -0
  27. package/dist/bitcoind/progress/formatting.js +6 -0
  28. package/dist/bitcoind/progress/train-scene.js +37 -18
  29. package/dist/bitcoind/progress/tty-renderer.d.ts +6 -1
  30. package/dist/bitcoind/progress/tty-renderer.js +8 -4
  31. package/dist/bitcoind/rpc.d.ts +2 -1
  32. package/dist/bitcoind/rpc.js +3 -0
  33. package/dist/bitcoind/types.d.ts +6 -0
  34. package/dist/bytes.d.ts +1 -0
  35. package/dist/bytes.js +3 -0
  36. package/dist/cli/art.d.ts +2 -0
  37. package/dist/cli/art.js +37 -0
  38. package/dist/cli/commands/client-admin.d.ts +2 -0
  39. package/dist/cli/commands/client-admin.js +91 -0
  40. package/dist/cli/commands/follow.js +0 -2
  41. package/dist/cli/commands/mining-admin.js +6 -47
  42. package/dist/cli/commands/mining-read.js +11 -50
  43. package/dist/cli/commands/mining-runtime.js +142 -5
  44. package/dist/cli/commands/service-runtime.js +0 -2
  45. package/dist/cli/commands/status.js +8 -2
  46. package/dist/cli/commands/sync.js +49 -92
  47. package/dist/cli/commands/wallet-admin.js +142 -136
  48. package/dist/cli/commands/wallet-mutation.js +91 -79
  49. package/dist/cli/commands/wallet-read.js +15 -18
  50. package/dist/cli/context.js +5 -14
  51. package/dist/cli/mining-format.d.ts +0 -1
  52. package/dist/cli/mining-format.js +5 -37
  53. package/dist/cli/mining-json.d.ts +0 -18
  54. package/dist/cli/mining-json.js +0 -35
  55. package/dist/cli/mutation-command-groups.d.ts +1 -2
  56. package/dist/cli/mutation-command-groups.js +0 -5
  57. package/dist/cli/mutation-json.d.ts +24 -145
  58. package/dist/cli/mutation-json.js +30 -136
  59. package/dist/cli/mutation-resolved-json.d.ts +0 -7
  60. package/dist/cli/mutation-resolved-json.js +4 -10
  61. package/dist/cli/mutation-success.d.ts +2 -0
  62. package/dist/cli/mutation-success.js +11 -1
  63. package/dist/cli/mutation-text-format.js +1 -3
  64. package/dist/cli/output.d.ts +1 -1
  65. package/dist/cli/output.js +254 -231
  66. package/dist/cli/parse.d.ts +1 -1
  67. package/dist/cli/parse.js +93 -122
  68. package/dist/cli/preview-json.d.ts +17 -120
  69. package/dist/cli/preview-json.js +14 -97
  70. package/dist/cli/prompt.js +8 -13
  71. package/dist/cli/read-json.d.ts +15 -37
  72. package/dist/cli/read-json.js +44 -140
  73. package/dist/cli/runner.js +10 -13
  74. package/dist/cli/sync-progress.d.ts +6 -0
  75. package/dist/cli/sync-progress.js +91 -0
  76. package/dist/cli/types.d.ts +9 -17
  77. package/dist/cli/types.js +0 -2
  78. package/dist/cli/wallet-format.d.ts +1 -0
  79. package/dist/cli/wallet-format.js +208 -144
  80. package/dist/cli/workflow-hints.d.ts +3 -3
  81. package/dist/cli/workflow-hints.js +11 -8
  82. package/dist/client/default-client.d.ts +3 -1
  83. package/dist/client/default-client.js +45 -2
  84. package/dist/client/factory.js +1 -1
  85. package/dist/client/initialization.js +23 -0
  86. package/dist/client/persistence.js +5 -5
  87. package/dist/client/store-adapter.js +1 -0
  88. package/dist/sqlite/checkpoints.d.ts +1 -0
  89. package/dist/sqlite/checkpoints.js +7 -0
  90. package/dist/sqlite/store.js +14 -1
  91. package/dist/types.d.ts +1 -0
  92. package/dist/wallet/coin-control.d.ts +41 -12
  93. package/dist/wallet/coin-control.js +100 -428
  94. package/dist/wallet/descriptor-normalization.d.ts +1 -3
  95. package/dist/wallet/descriptor-normalization.js +0 -16
  96. package/dist/wallet/lifecycle.d.ts +7 -99
  97. package/dist/wallet/lifecycle.js +513 -968
  98. package/dist/wallet/managed-core-wallet.d.ts +13 -0
  99. package/dist/wallet/managed-core-wallet.js +20 -0
  100. package/dist/wallet/mining/constants.d.ts +5 -12
  101. package/dist/wallet/mining/constants.js +5 -12
  102. package/dist/wallet/mining/control.d.ts +1 -13
  103. package/dist/wallet/mining/control.js +45 -349
  104. package/dist/wallet/mining/index.d.ts +4 -5
  105. package/dist/wallet/mining/index.js +2 -3
  106. package/dist/wallet/mining/runner.d.ts +123 -13
  107. package/dist/wallet/mining/runner.js +899 -511
  108. package/dist/wallet/mining/runtime-artifacts.js +23 -3
  109. package/dist/wallet/mining/sentence-protocol.d.ts +44 -0
  110. package/dist/wallet/mining/sentence-protocol.js +123 -0
  111. package/dist/wallet/mining/sentences.d.ts +4 -8
  112. package/dist/wallet/mining/sentences.js +3 -52
  113. package/dist/wallet/mining/state.d.ts +11 -6
  114. package/dist/wallet/mining/state.js +7 -6
  115. package/dist/wallet/mining/types.d.ts +2 -30
  116. package/dist/wallet/mining/visualizer.d.ts +31 -3
  117. package/dist/wallet/mining/visualizer.js +135 -13
  118. package/dist/wallet/read/context.d.ts +0 -2
  119. package/dist/wallet/read/context.js +119 -140
  120. package/dist/wallet/read/filter.js +2 -11
  121. package/dist/wallet/read/index.d.ts +1 -1
  122. package/dist/wallet/read/project.js +24 -77
  123. package/dist/wallet/read/types.d.ts +10 -25
  124. package/dist/wallet/reset.d.ts +0 -1
  125. package/dist/wallet/reset.js +60 -138
  126. package/dist/wallet/root-resolution.d.ts +1 -5
  127. package/dist/wallet/root-resolution.js +0 -18
  128. package/dist/wallet/runtime.d.ts +0 -6
  129. package/dist/wallet/runtime.js +0 -8
  130. package/dist/wallet/state/client-password-agent.js +208 -0
  131. package/dist/wallet/state/client-password.d.ts +65 -0
  132. package/dist/wallet/state/client-password.js +952 -0
  133. package/dist/wallet/state/crypto.d.ts +1 -20
  134. package/dist/wallet/state/crypto.js +0 -63
  135. package/dist/wallet/state/provider.d.ts +23 -11
  136. package/dist/wallet/state/provider.js +248 -290
  137. package/dist/wallet/state/storage.d.ts +2 -2
  138. package/dist/wallet/state/storage.js +48 -16
  139. package/dist/wallet/tx/anchor.d.ts +3 -28
  140. package/dist/wallet/tx/anchor.js +349 -1250
  141. package/dist/wallet/tx/bitcoin-transfer.d.ts +35 -0
  142. package/dist/wallet/tx/bitcoin-transfer.js +200 -0
  143. package/dist/wallet/tx/cog.d.ts +5 -1
  144. package/dist/wallet/tx/cog.js +149 -185
  145. package/dist/wallet/tx/common.d.ts +61 -8
  146. package/dist/wallet/tx/common.js +266 -146
  147. package/dist/wallet/tx/domain-admin.d.ts +3 -1
  148. package/dist/wallet/tx/domain-admin.js +61 -99
  149. package/dist/wallet/tx/domain-market.d.ts +5 -1
  150. package/dist/wallet/tx/domain-market.js +221 -228
  151. package/dist/wallet/tx/field.d.ts +4 -10
  152. package/dist/wallet/tx/field.js +83 -924
  153. package/dist/wallet/tx/identity-selector.d.ts +9 -3
  154. package/dist/wallet/tx/identity-selector.js +17 -35
  155. package/dist/wallet/tx/index.d.ts +3 -1
  156. package/dist/wallet/tx/index.js +2 -1
  157. package/dist/wallet/tx/register.d.ts +3 -1
  158. package/dist/wallet/tx/register.js +62 -220
  159. package/dist/wallet/tx/reputation.d.ts +3 -1
  160. package/dist/wallet/tx/reputation.js +58 -95
  161. package/dist/wallet/types.d.ts +8 -122
  162. package/package.json +5 -5
  163. package/dist/wallet/archive.d.ts +0 -4
  164. package/dist/wallet/archive.js +0 -41
  165. package/dist/wallet/mining/hook-protocol.d.ts +0 -47
  166. package/dist/wallet/mining/hook-protocol.js +0 -161
  167. package/dist/wallet/mining/hook-runner.js +0 -52
  168. package/dist/wallet/mining/hooks.d.ts +0 -38
  169. package/dist/wallet/mining/hooks.js +0 -520
  170. package/dist/wallet/state/explicit-lock.d.ts +0 -4
  171. package/dist/wallet/state/explicit-lock.js +0 -19
  172. package/dist/wallet/state/session.d.ts +0 -12
  173. package/dist/wallet/state/session.js +0 -23
  174. /package/dist/wallet/{mining/hook-runner.d.ts → state/client-password-agent.d.ts} +0 -0
@@ -1,7 +1,87 @@
1
1
  import { createBootstrapProgress } from "../../bitcoind/progress/formatting.js";
2
- import { createFollowSceneState, syncFollowSceneState, } from "../../bitcoind/progress/follow-scene.js";
2
+ import { advanceFollowSceneState, createFollowSceneState, replaceFollowBlockTimes, syncFollowSceneState, } from "../../bitcoind/progress/follow-scene.js";
3
3
  import { DEFAULT_RENDER_CLOCK, resolveTtyRenderPolicy, TtyRenderThrottle, } from "../../bitcoind/progress/render-policy.js";
4
4
  import { TtyProgressRenderer } from "../../bitcoind/progress/tty-renderer.js";
5
+ const MINING_ARTWORK_COG_WIDTH = 22;
6
+ const MINING_SENTENCE_BOARD_SIZE = 5;
7
+ function formatCogAmountWithDecimals(value, { maxFractionDigits, minFractionDigits, }) {
8
+ const sign = value < 0n ? "-" : "";
9
+ const absolute = value < 0n ? -value : value;
10
+ const whole = absolute / 100000000n;
11
+ const fraction = absolute % 100000000n;
12
+ const paddedFraction = fraction.toString().padStart(8, "0");
13
+ const clampedMax = Math.max(0, Math.min(8, maxFractionDigits));
14
+ const clampedMin = Math.max(0, Math.min(clampedMax, minFractionDigits));
15
+ let fractionText = paddedFraction.slice(0, clampedMax);
16
+ while (fractionText.length > clampedMin && fractionText.endsWith("0")) {
17
+ fractionText = fractionText.slice(0, -1);
18
+ }
19
+ if (fractionText.length === 0 && clampedMin > 0) {
20
+ fractionText = "".padEnd(clampedMin, "0");
21
+ }
22
+ return fractionText.length > 0
23
+ ? `${sign}${whole.toString()}.${fractionText}`
24
+ : `${sign}${whole.toString()}`;
25
+ }
26
+ function formatCompactCogBalanceText(balanceCogtoshi) {
27
+ if (balanceCogtoshi === null) {
28
+ return null;
29
+ }
30
+ for (let digits = 4; digits >= 1; digits -= 1) {
31
+ const cogSegment = `${formatCogAmountWithDecimals(balanceCogtoshi, {
32
+ maxFractionDigits: digits,
33
+ minFractionDigits: 1,
34
+ })} COG`;
35
+ if (cogSegment.length <= MINING_ARTWORK_COG_WIDTH) {
36
+ return cogSegment;
37
+ }
38
+ }
39
+ return `${formatCogAmountWithDecimals(balanceCogtoshi, {
40
+ maxFractionDigits: 1,
41
+ minFractionDigits: 1,
42
+ })} COG`;
43
+ }
44
+ function formatCompactSatBalanceText(balanceSats) {
45
+ return balanceSats === null ? null : `${balanceSats.toString()} SAT`;
46
+ }
47
+ function formatRewardCogAmount(value) {
48
+ return `${formatCogAmountWithDecimals(value, {
49
+ maxFractionDigits: 8,
50
+ minFractionDigits: 1,
51
+ })} COG`;
52
+ }
53
+ function formatSentenceRow(rank, domainName, sentence) {
54
+ return `${rank}. @${domainName}: ${sentence}`;
55
+ }
56
+ function formatRequiredWordsLine(words) {
57
+ if (words.length === 0) {
58
+ return "";
59
+ }
60
+ return `Required words: ${words.map((word) => word.toUpperCase()).join(", ")}`;
61
+ }
62
+ function formatProvisionalSentenceRow(entry) {
63
+ if (entry.domainName === null || entry.sentence === null) {
64
+ return "";
65
+ }
66
+ return `@${entry.domainName}: ${entry.sentence}`;
67
+ }
68
+ export function createEmptyMiningFollowVisualizerState() {
69
+ return {
70
+ balanceCogtoshi: null,
71
+ balanceSats: null,
72
+ visibleBlockTimesByHeight: {},
73
+ settledBlockHeight: null,
74
+ settledBoardEntries: [],
75
+ provisionalRequiredWords: [],
76
+ provisionalEntry: {
77
+ domainName: null,
78
+ sentence: null,
79
+ },
80
+ latestSentence: null,
81
+ latestTxid: null,
82
+ recentWin: null,
83
+ };
84
+ }
5
85
  const VISUALIZER_PROGRESS_SNAPSHOT = {
6
86
  url: "",
7
87
  filename: "mining-follow-visualizer",
@@ -9,14 +89,15 @@ const VISUALIZER_PROGRESS_SNAPSHOT = {
9
89
  sha256: "",
10
90
  sizeBytes: 1,
11
91
  };
12
- export function describeMiningVisualizerStatus(snapshot) {
92
+ export function describeMiningVisualizerStatus(snapshot, ui = createEmptyMiningFollowVisualizerState()) {
93
+ if (ui.recentWin !== null) {
94
+ return `You got #${ui.recentWin.rank} and mined ${formatRewardCogAmount(ui.recentWin.rewardCogtoshi)} in block #${ui.recentWin.blockHeight}`;
95
+ }
13
96
  switch (snapshot.currentPhase) {
14
97
  case "resuming":
15
98
  return "Resuming after suspend";
16
99
  case "waiting-provider":
17
- return snapshot.providerState === "hook-error"
18
- ? "Waiting for hook"
19
- : "Waiting for provider";
100
+ return "Waiting for provider";
20
101
  case "waiting-indexer":
21
102
  return snapshot.indexerDaemonState === "reorging"
22
103
  ? "Indexer replaying reorg"
@@ -54,7 +135,7 @@ export function describeMiningVisualizerStatus(snapshot) {
54
135
  if (snapshot.currentPublishDecision === "indeterminate-mempool-gate") {
55
136
  return "Mempool gate indeterminate";
56
137
  }
57
- if (snapshot.liveMiningFamilyInMempool) {
138
+ if (snapshot.livePublishInMempool) {
58
139
  return "Waiting for next block";
59
140
  }
60
141
  return "Waiting for next block";
@@ -78,7 +159,7 @@ export function describeMiningVisualizerProgress(snapshot) {
78
159
  return "Scoring mining candidates for the current tip.";
79
160
  case "publishing":
80
161
  return snapshot.currentPublishDecision === "fee-bump"
81
- ? "Publishing a fee bump for the live mining family."
162
+ ? "Publishing a fee bump for the live mining transaction."
82
163
  : snapshot.currentPublishDecision === "replacing"
83
164
  || snapshot.currentPublishDecision === "replaced"
84
165
  ? "Replacing the live mining transaction for the current tip."
@@ -95,7 +176,9 @@ export class MiningFollowVisualizer {
95
176
  #renderThrottle;
96
177
  #progress = createBootstrapProgress("follow_tip", VISUALIZER_PROGRESS_SNAPSHOT);
97
178
  #scene = createFollowSceneState();
179
+ #ticker = null;
98
180
  #latestSnapshot = null;
181
+ #latestUiState = createEmptyMiningFollowVisualizerState();
99
182
  constructor(options = {}) {
100
183
  const stream = options.stream ?? process.stderr;
101
184
  const progressOutput = options.progressOutput ?? "auto";
@@ -115,23 +198,51 @@ export class MiningFollowVisualizer {
115
198
  },
116
199
  throttled: renderPolicy.linuxHeadlessThrottle,
117
200
  });
201
+ if (this.#renderer !== null) {
202
+ this.#ticker = this.#clock.setInterval(() => {
203
+ this.#advanceAndRender();
204
+ }, renderPolicy.repaintIntervalMs);
205
+ }
118
206
  }
119
- update(snapshot) {
207
+ update(snapshot, uiState) {
120
208
  if (this.#renderer === null) {
121
209
  return;
122
210
  }
123
211
  this.#latestSnapshot = snapshot;
212
+ if (uiState !== undefined) {
213
+ this.#latestUiState = uiState;
214
+ }
215
+ replaceFollowBlockTimes(this.#scene, this.#latestUiState.visibleBlockTimesByHeight);
216
+ const indexedHeight = snapshot.indexerTipHeight ?? snapshot.coreBestHeight ?? null;
217
+ const nodeHeight = snapshot.coreBestHeight ?? indexedHeight;
218
+ syncFollowSceneState(this.#scene, {
219
+ indexedHeight,
220
+ nodeHeight,
221
+ liveActivated: true,
222
+ });
124
223
  this.#renderThrottle.request();
125
224
  }
126
225
  close() {
226
+ if (this.#ticker !== null) {
227
+ this.#clock.clearInterval(this.#ticker);
228
+ this.#ticker = null;
229
+ }
127
230
  this.#renderThrottle.flush();
128
231
  this.#renderer?.close();
129
232
  }
233
+ #advanceAndRender() {
234
+ if (this.#renderer === null || this.#latestSnapshot === null) {
235
+ return;
236
+ }
237
+ advanceFollowSceneState(this.#scene, this.#clock.now());
238
+ this.#renderThrottle.request();
239
+ }
130
240
  #renderLatestSnapshot() {
131
241
  if (this.#renderer === null || this.#latestSnapshot === null) {
132
242
  return;
133
243
  }
134
244
  const snapshot = this.#latestSnapshot;
245
+ const uiState = this.#latestUiState;
135
246
  const indexedHeight = snapshot.indexerTipHeight ?? snapshot.coreBestHeight ?? null;
136
247
  const nodeHeight = snapshot.coreBestHeight ?? indexedHeight;
137
248
  this.#progress.phase = "follow_tip";
@@ -141,11 +252,22 @@ export class MiningFollowVisualizer {
141
252
  this.#progress.targetHeight = nodeHeight;
142
253
  this.#progress.etaSeconds = null;
143
254
  this.#progress.lastError = snapshot.lastError;
144
- syncFollowSceneState(this.#scene, {
145
- indexedHeight,
146
- nodeHeight,
147
- liveActivated: true,
255
+ this.#renderer.renderFollowScene(this.#progress, indexedHeight, nodeHeight, this.#scene, describeMiningVisualizerStatus(snapshot, uiState), {
256
+ artworkCogText: formatCompactCogBalanceText(uiState.balanceCogtoshi),
257
+ artworkSatText: formatCompactSatBalanceText(uiState.balanceSats),
258
+ extraLines: [
259
+ `✎ Block #${uiState.settledBlockHeight ?? "-----"} Sentences ✎`,
260
+ "",
261
+ ...Array.from({ length: MINING_SENTENCE_BOARD_SIZE }, (_value, index) => {
262
+ const entry = uiState.settledBoardEntries[index];
263
+ return entry === undefined
264
+ ? `${index + 1}.`
265
+ : formatSentenceRow(entry.rank, entry.domainName, entry.sentence);
266
+ }),
267
+ "----------",
268
+ formatRequiredWordsLine(uiState.provisionalRequiredWords),
269
+ formatProvisionalSentenceRow(uiState.provisionalEntry),
270
+ ],
148
271
  });
149
- this.#renderer.renderFollowScene(this.#progress, indexedHeight, nodeHeight, this.#scene, describeMiningVisualizerStatus(snapshot));
150
272
  }
151
273
  }
@@ -4,7 +4,6 @@ import type { WalletLocalStateStatus, WalletReadContext } from "./types.js";
4
4
  import type { WalletRuntimePaths } from "../runtime.js";
5
5
  declare function inspectWalletLocalState(options?: {
6
6
  dataDir?: string;
7
- passphrase?: Uint8Array | string;
8
7
  secretProvider?: WalletSecretProvider;
9
8
  now?: number;
10
9
  paths?: WalletRuntimePaths;
@@ -13,7 +12,6 @@ declare function inspectWalletLocalState(options?: {
13
12
  export declare function openWalletReadContext(options: {
14
13
  dataDir: string;
15
14
  databasePath: string;
16
- walletStatePassphrase?: Uint8Array | string;
17
15
  secretProvider?: WalletSecretProvider;
18
16
  walletControlLockHeld?: boolean;
19
17
  startupTimeoutMs?: number;
@@ -6,19 +6,28 @@ import { UNINITIALIZED_WALLET_ROOT_ID } from "../../bitcoind/service-paths.js";
6
6
  import { attachOrStartManagedBitcoindService, probeManagedBitcoindService, } from "../../bitcoind/service.js";
7
7
  import { resolveCogcoinProcessingStartHeight } from "../../bitcoind/processing-start-height.js";
8
8
  import {} from "../../bitcoind/types.js";
9
- import { loadOrAutoUnlockWalletState, verifyManagedCoreWalletReplica, } from "../lifecycle.js";
9
+ import { verifyManagedCoreWalletReplica, } from "../lifecycle.js";
10
10
  import { normalizeWalletStateRecord, persistWalletCoinControlStateIfNeeded } from "../coin-control.js";
11
11
  import { persistNormalizedWalletDescriptorStateIfNeeded } from "../descriptor-normalization.js";
12
12
  import { inspectMiningControlPlane } from "../mining/index.js";
13
13
  import { normalizeMiningStateRecord } from "../mining/state.js";
14
14
  import { resolveWalletRootIdFromLocalArtifacts } from "../root-resolution.js";
15
15
  import { resolveWalletRuntimePathsForTesting } from "../runtime.js";
16
- import { loadWalletExplicitLock } from "../state/explicit-lock.js";
17
- import { loadWalletState } from "../state/storage.js";
18
- import { createDefaultWalletSecretProvider, createWalletSecretReference, } from "../state/provider.js";
16
+ import { extractWalletRootIdHintFromWalletStateEnvelope, loadRawWalletStateEnvelope, loadWalletState, } from "../state/storage.js";
17
+ import { createDefaultWalletSecretProvider, createWalletSecretReference, inspectClientPasswordSetupReadiness, } from "../state/provider.js";
18
+ import { describeClientPasswordLockedMessage, describeClientPasswordMigrationMessage, describeClientPasswordSetupMessage, } from "../state/client-password.js";
19
19
  import { createWalletReadModel } from "./project.js";
20
20
  const DEFAULT_SERVICE_START_TIMEOUT_MS = 10_000;
21
21
  const STALE_HEARTBEAT_THRESHOLD_MS = 15_000;
22
+ function btcAmountToSats(value) {
23
+ return BigInt(Math.round(value * 100_000_000));
24
+ }
25
+ function isSpendableFundingUtxo(entry, fundingScriptPubKeyHex) {
26
+ return entry.scriptPubKey === fundingScriptPubKeyHex
27
+ && entry.confirmations >= 1
28
+ && entry.spendable !== false
29
+ && entry.safe !== false;
30
+ }
22
31
  async function pathExists(path) {
23
32
  try {
24
33
  await access(path, constants.F_OK);
@@ -28,19 +37,26 @@ async function pathExists(path) {
28
37
  return false;
29
38
  }
30
39
  }
31
- function isLockedWalletAccessError(error) {
40
+ function isWalletAccessError(error) {
32
41
  const message = error instanceof Error ? error.message : String(error);
33
- return message === "wallet_envelope_missing_secret_provider"
34
- || message.startsWith("wallet_secret_missing_")
35
- || message.startsWith("wallet_secret_provider_");
42
+ return message.startsWith("wallet_secret_missing_")
43
+ || message.startsWith("wallet_secret_provider_")
44
+ || message.startsWith("wallet_client_password_")
45
+ || message === "wallet_state_legacy_envelope_unsupported";
36
46
  }
37
- function describeLockedWalletMessage(options) {
38
- if (options.explicitlyLocked) {
39
- return "Wallet state exists but is explicitly locked until `cogcoin unlock` is run.";
40
- }
47
+ function describeWalletAccessMessage(options) {
41
48
  const message = options.accessError instanceof Error ? options.accessError.message : String(options.accessError ?? "");
42
- if (message === "wallet_envelope_missing_secret_provider") {
43
- return "Wallet state exists but requires the local wallet-state passphrase.";
49
+ if (message === "wallet_state_legacy_envelope_unsupported") {
50
+ return "Wallet state exists but was created by an older Cogcoin wallet format that this version no longer loads directly.";
51
+ }
52
+ if (message === "wallet_client_password_setup_required") {
53
+ return describeClientPasswordSetupMessage();
54
+ }
55
+ if (message === "wallet_client_password_migration_required") {
56
+ return describeClientPasswordMigrationMessage();
57
+ }
58
+ if (message === "wallet_client_password_locked") {
59
+ return describeClientPasswordLockedMessage();
44
60
  }
45
61
  if (message.startsWith("wallet_secret_provider_")) {
46
62
  return "Wallet state exists but the local secret provider is unavailable.";
@@ -48,9 +64,9 @@ function describeLockedWalletMessage(options) {
48
64
  if (message.startsWith("wallet_secret_missing_")) {
49
65
  return "Wallet state exists but its local secret-provider material is unavailable.";
50
66
  }
51
- return options.hasUnlockSessionFile
52
- ? "Wallet state exists but the unlock session is expired, invalid, or belongs to a different wallet root."
53
- : "Wallet state exists but is currently locked.";
67
+ return message.length > 0
68
+ ? message
69
+ : "Wallet state exists but could not be loaded from the local secret provider.";
54
70
  }
55
71
  async function normalizeLoadedWalletStateForRead(options) {
56
72
  if (options.dataDir === undefined) {
@@ -63,12 +79,10 @@ async function normalizeLoadedWalletStateForRead(options) {
63
79
  walletRootId: options.loaded.state.walletRootId,
64
80
  });
65
81
  try {
66
- const access = typeof options.access === "string" || options.access instanceof Uint8Array
67
- ? options.access
68
- : {
69
- provider: options.access.provider,
70
- secretReference: createWalletSecretReference(options.loaded.state.walletRootId),
71
- };
82
+ const access = {
83
+ provider: options.access.provider,
84
+ secretReference: createWalletSecretReference(options.loaded.state.walletRootId),
85
+ };
72
86
  const normalized = await persistNormalizedWalletDescriptorStateIfNeeded({
73
87
  state: options.loaded.state,
74
88
  access,
@@ -98,166 +112,109 @@ async function inspectWalletLocalState(options = {}) {
98
112
  const paths = options.paths ?? resolveWalletRuntimePathsForTesting();
99
113
  const now = options.now ?? Date.now();
100
114
  const provider = options.secretProvider ?? createDefaultWalletSecretProvider();
101
- const [hasPrimaryStateFile, hasBackupStateFile, hasUnlockSessionFile] = await Promise.all([
115
+ const [hasPrimaryStateFile, hasBackupStateFile] = await Promise.all([
102
116
  pathExists(paths.walletStatePath),
103
117
  pathExists(paths.walletStateBackupPath),
104
- pathExists(paths.walletUnlockSessionPath),
105
118
  ]);
119
+ const clientPasswordReadiness = await inspectClientPasswordSetupReadiness(provider).catch(() => "ready");
106
120
  if (!hasPrimaryStateFile && !hasBackupStateFile) {
107
121
  return {
108
122
  availability: "uninitialized",
123
+ clientPasswordReadiness,
124
+ unlockRequired: false,
109
125
  walletRootId: null,
110
126
  state: null,
111
127
  source: null,
112
- unlockUntilUnixMs: null,
113
128
  hasPrimaryStateFile,
114
129
  hasBackupStateFile,
115
- hasUnlockSessionFile,
116
130
  message: "Wallet state has not been initialized yet.",
117
131
  };
118
132
  }
119
- if (options.passphrase === undefined) {
120
- try {
121
- const unlocked = await loadOrAutoUnlockWalletState({
122
- provider,
123
- nowUnixMs: now,
124
- paths,
125
- dataDir: options.dataDir,
126
- controlLockHeld: options.walletControlLockHeld,
127
- });
128
- if (unlocked === null) {
129
- const explicitLock = await loadWalletExplicitLock(paths.walletExplicitLockPath);
130
- const hasUnlockSessionFileNow = await pathExists(paths.walletUnlockSessionPath);
131
- try {
132
- const loaded = await loadWalletState({
133
- primaryPath: paths.walletStatePath,
134
- backupPath: paths.walletStateBackupPath,
135
- }, {
136
- provider,
137
- });
138
- await normalizeLoadedWalletStateForRead({
139
- loaded,
140
- access: { provider },
141
- dataDir: options.dataDir,
142
- now,
143
- paths,
144
- });
145
- return {
146
- availability: "locked",
147
- walletRootId: loaded.state.walletRootId,
148
- state: null,
149
- source: loaded.source,
150
- unlockUntilUnixMs: null,
151
- hasPrimaryStateFile,
152
- hasBackupStateFile,
153
- hasUnlockSessionFile: hasUnlockSessionFileNow,
154
- message: describeLockedWalletMessage({
155
- explicitlyLocked: explicitLock?.walletRootId === loaded.state.walletRootId,
156
- hasUnlockSessionFile: hasUnlockSessionFileNow,
157
- }),
158
- };
159
- }
160
- catch (error) {
161
- const resolvedRoot = await resolveWalletRootIdFromLocalArtifacts({
162
- paths,
163
- provider,
164
- }).catch(() => null);
165
- if (isLockedWalletAccessError(error)) {
166
- return {
167
- availability: "locked",
168
- walletRootId: resolvedRoot?.walletRootId ?? null,
169
- state: null,
170
- source: null,
171
- unlockUntilUnixMs: null,
172
- hasPrimaryStateFile,
173
- hasBackupStateFile,
174
- hasUnlockSessionFile: hasUnlockSessionFileNow,
175
- message: describeLockedWalletMessage({
176
- accessError: error,
177
- explicitlyLocked: false,
178
- hasUnlockSessionFile: hasUnlockSessionFileNow,
179
- }),
180
- };
181
- }
182
- return {
183
- availability: "local-state-corrupt",
184
- walletRootId: resolvedRoot?.walletRootId ?? null,
185
- state: null,
186
- source: null,
187
- unlockUntilUnixMs: null,
188
- hasPrimaryStateFile,
189
- hasBackupStateFile,
190
- hasUnlockSessionFile: hasUnlockSessionFileNow,
191
- message: error instanceof Error ? error.message : String(error),
192
- };
193
- }
194
- }
195
- return {
196
- availability: "ready",
197
- walletRootId: unlocked.state.walletRootId,
198
- state: normalizeWalletStateRecord({
199
- ...unlocked.state,
200
- miningState: normalizeMiningStateRecord(unlocked.state.miningState),
201
- }),
202
- source: unlocked.source,
203
- unlockUntilUnixMs: unlocked.session.unlockUntilUnixMs,
204
- hasPrimaryStateFile,
205
- hasBackupStateFile,
206
- hasUnlockSessionFile: true,
207
- message: null,
208
- };
209
- }
210
- catch (error) {
133
+ if (clientPasswordReadiness !== "ready") {
134
+ const rawEnvelope = await loadRawWalletStateEnvelope({
135
+ primaryPath: paths.walletStatePath,
136
+ backupPath: paths.walletStateBackupPath,
137
+ }).catch(() => null);
138
+ if (rawEnvelope?.envelope.secretProvider == null) {
211
139
  return {
212
140
  availability: "local-state-corrupt",
213
- walletRootId: null,
141
+ clientPasswordReadiness: "ready",
142
+ unlockRequired: false,
143
+ walletRootId: extractWalletRootIdHintFromWalletStateEnvelope(rawEnvelope?.envelope ?? null),
214
144
  state: null,
215
145
  source: null,
216
- unlockUntilUnixMs: null,
217
146
  hasPrimaryStateFile,
218
147
  hasBackupStateFile,
219
- hasUnlockSessionFile,
220
- message: error instanceof Error ? error.message : String(error),
148
+ message: "Wallet state exists but was created by an older Cogcoin wallet format that this version no longer loads directly.",
221
149
  };
222
150
  }
151
+ const resolvedRoot = await resolveWalletRootIdFromLocalArtifacts({
152
+ paths,
153
+ provider,
154
+ }).catch(() => null);
155
+ return {
156
+ availability: "local-state-corrupt",
157
+ clientPasswordReadiness,
158
+ unlockRequired: false,
159
+ walletRootId: resolvedRoot?.walletRootId ?? null,
160
+ state: null,
161
+ source: null,
162
+ hasPrimaryStateFile,
163
+ hasBackupStateFile,
164
+ message: clientPasswordReadiness === "migration-required"
165
+ ? describeClientPasswordMigrationMessage()
166
+ : describeClientPasswordSetupMessage(),
167
+ };
223
168
  }
224
169
  try {
225
- const loaded = await normalizeLoadedWalletStateForRead({
226
- loaded: await loadWalletState({
227
- primaryPath: paths.walletStatePath,
228
- backupPath: paths.walletStateBackupPath,
229
- }, options.passphrase),
230
- access: options.passphrase,
170
+ const loaded = await loadWalletState({
171
+ primaryPath: paths.walletStatePath,
172
+ backupPath: paths.walletStateBackupPath,
173
+ }, {
174
+ provider,
175
+ });
176
+ const normalized = await normalizeLoadedWalletStateForRead({
177
+ loaded,
178
+ access: { provider },
231
179
  dataDir: options.dataDir,
232
180
  now,
233
181
  paths,
234
182
  });
235
183
  return {
236
184
  availability: "ready",
237
- walletRootId: loaded.state.walletRootId,
185
+ clientPasswordReadiness,
186
+ unlockRequired: false,
187
+ walletRootId: normalized.state.walletRootId,
238
188
  state: normalizeWalletStateRecord({
239
- ...loaded.state,
240
- miningState: normalizeMiningStateRecord(loaded.state.miningState),
189
+ ...normalized.state,
190
+ miningState: normalizeMiningStateRecord(normalized.state.miningState),
241
191
  }),
242
- source: loaded.source,
243
- unlockUntilUnixMs: null,
192
+ source: normalized.source,
244
193
  hasPrimaryStateFile,
245
194
  hasBackupStateFile,
246
- hasUnlockSessionFile,
247
195
  message: null,
248
196
  };
249
197
  }
250
198
  catch (error) {
199
+ const resolvedRoot = await resolveWalletRootIdFromLocalArtifacts({
200
+ paths,
201
+ provider,
202
+ }).catch(() => null);
203
+ const message = error instanceof Error ? error.message : String(error);
251
204
  return {
252
205
  availability: "local-state-corrupt",
253
- walletRootId: null,
206
+ clientPasswordReadiness,
207
+ unlockRequired: message === "wallet_client_password_locked",
208
+ walletRootId: resolvedRoot?.walletRootId ?? null,
254
209
  state: null,
255
210
  source: null,
256
- unlockUntilUnixMs: null,
257
211
  hasPrimaryStateFile,
258
212
  hasBackupStateFile,
259
- hasUnlockSessionFile,
260
- message: error instanceof Error ? error.message : String(error),
213
+ message: isWalletAccessError(error)
214
+ ? describeWalletAccessMessage({ accessError: error })
215
+ : error instanceof Error
216
+ ? error.message
217
+ : String(error),
261
218
  };
262
219
  }
263
220
  }
@@ -467,6 +424,7 @@ async function attachNodeStatus(options) {
467
424
  if (probe.compatibility !== "compatible" && probe.compatibility !== "unreachable") {
468
425
  return {
469
426
  handle: null,
427
+ rpc: null,
470
428
  status: null,
471
429
  observedStatus: probe.status,
472
430
  error: probe.error,
@@ -500,6 +458,7 @@ async function attachNodeStatus(options) {
500
458
  };
501
459
  return {
502
460
  handle,
461
+ rpc,
503
462
  status,
504
463
  observedStatus: serviceStatus ?? null,
505
464
  error: null,
@@ -508,18 +467,33 @@ async function attachNodeStatus(options) {
508
467
  catch (error) {
509
468
  return {
510
469
  handle: null,
470
+ rpc: null,
511
471
  status: null,
512
472
  observedStatus: null,
513
473
  error: error instanceof Error ? error.message : String(error),
514
474
  };
515
475
  }
516
476
  }
477
+ async function readFundingSpendableSats(options) {
478
+ if (options.state === null || options.rpc === null) {
479
+ return null;
480
+ }
481
+ const state = options.state;
482
+ try {
483
+ const utxos = await options.rpc.listUnspent(state.managedCoreWallet.walletName, 1);
484
+ return utxos.reduce((sum, entry) => isSpendableFundingUtxo(entry, state.funding.scriptPubKeyHex)
485
+ ? sum + btcAmountToSats(entry.amount)
486
+ : sum, 0n);
487
+ }
488
+ catch {
489
+ return null;
490
+ }
491
+ }
517
492
  export async function openWalletReadContext(options) {
518
493
  const startupTimeoutMs = options.startupTimeoutMs ?? DEFAULT_SERVICE_START_TIMEOUT_MS;
519
494
  const now = options.now ?? Date.now();
520
495
  const localState = await inspectWalletLocalState({
521
496
  dataDir: options.dataDir,
522
- passphrase: options.walletStatePassphrase,
523
497
  secretProvider: options.secretProvider,
524
498
  walletControlLockHeld: options.walletControlLockHeld,
525
499
  now,
@@ -611,6 +585,10 @@ export async function openWalletReadContext(options) {
611
585
  now,
612
586
  startupError: daemonError,
613
587
  });
588
+ const fundingSpendableSats = await readFundingSpendableSats({
589
+ state: localState.state,
590
+ rpc: node.rpc,
591
+ });
614
592
  const mining = await inspectMiningControlPlane({
615
593
  provider: options.secretProvider,
616
594
  localState,
@@ -634,6 +612,7 @@ export async function openWalletReadContext(options) {
634
612
  model: localState.state === null
635
613
  ? null
636
614
  : createWalletReadModel(localState.state, snapshot),
615
+ fundingSpendableSats,
637
616
  mining,
638
617
  async close() {
639
618
  await daemonClient?.close().catch(() => undefined);
@@ -4,20 +4,11 @@ export function isRootDomainName(name) {
4
4
  }
5
5
  export function isMineableWalletDomain(context, domain) {
6
6
  const state = context.localState.state;
7
- const model = context.model;
8
7
  const snapshot = context.snapshot;
9
- if (state === null || model === null || snapshot === null) {
8
+ if (state === null || context.model === null || snapshot === null) {
10
9
  return false;
11
10
  }
12
- if (!isRootDomainName(domain.name) || domain.anchored !== true || domain.readOnly || domain.ownerLocalIndex === null || domain.domainId === null) {
13
- return false;
14
- }
15
- const localRecord = state.domains.find((entry) => entry.name === domain.name);
16
- const ownerIdentity = model.identities.find((identity) => identity.index === domain.ownerLocalIndex);
17
- if (localRecord?.currentCanonicalAnchorOutpoint === null
18
- || localRecord?.currentCanonicalAnchorOutpoint === undefined
19
- || ownerIdentity?.address == null
20
- || ownerIdentity.readOnly) {
11
+ if (!isRootDomainName(domain.name) || domain.anchored !== true || domain.readOnly || domain.localRelationship !== "local" || domain.domainId === null) {
21
12
  return false;
22
13
  }
23
14
  const chainDomain = lookupDomain(snapshot.state, domain.name);
@@ -1,4 +1,4 @@
1
1
  export { openWalletReadContext, inspectWalletLocalState, readSnapshotWithRetry } from "./context.js";
2
2
  export { filterWalletDomains, isMineableWalletDomain, isRootDomainName, type WalletDomainFilterOptions, } from "./filter.js";
3
3
  export { createFieldPreview, createWalletReadModel, findDomainField, findWalletLock, findWalletDomain, formatFieldFormat, listDomainFields, listWalletLocks, } from "./project.js";
4
- export type { WalletBitcoindStatus, WalletDomainDetailsView, WalletDomainView, WalletFieldView, WalletIdentityView, WalletIndexerStatus, WalletLocalStateStatus, WalletLockView, WalletNodeStatus, WalletReadContext, WalletReadModel, WalletServiceHealth, WalletSnapshotView, WalletStateAvailability, } from "./types.js";
4
+ export type { WalletBitcoindStatus, WalletDomainDetailsView, WalletDomainView, WalletFieldView, WalletIndexerStatus, WalletLocalStateStatus, WalletLockView, WalletNodeStatus, WalletReadContext, WalletReadModel, WalletServiceHealth, WalletSnapshotView, WalletStateAvailability, } from "./types.js";