@cogcoin/client 1.1.9 → 1.1.11

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 (66) hide show
  1. package/README.md +1 -1
  2. package/dist/bitcoind/client/managed-client.d.ts +2 -0
  3. package/dist/bitcoind/client/managed-client.js +6 -0
  4. package/dist/bitcoind/indexer-daemon/background-follow.d.ts +23 -0
  5. package/dist/bitcoind/indexer-daemon/background-follow.js +132 -0
  6. package/dist/bitcoind/indexer-daemon/client.d.ts +12 -0
  7. package/dist/bitcoind/indexer-daemon/client.js +137 -0
  8. package/dist/bitcoind/indexer-daemon/lifecycle.d.ts +30 -0
  9. package/dist/bitcoind/indexer-daemon/lifecycle.js +153 -0
  10. package/dist/bitcoind/indexer-daemon/process.d.ts +35 -0
  11. package/dist/bitcoind/indexer-daemon/process.js +140 -0
  12. package/dist/bitcoind/indexer-daemon/runtime.d.ts +23 -0
  13. package/dist/bitcoind/indexer-daemon/runtime.js +204 -0
  14. package/dist/bitcoind/indexer-daemon/server.d.ts +12 -0
  15. package/dist/bitcoind/indexer-daemon/server.js +87 -0
  16. package/dist/bitcoind/indexer-daemon/snapshot-leases.d.ts +23 -0
  17. package/dist/bitcoind/indexer-daemon/snapshot-leases.js +139 -0
  18. package/dist/bitcoind/indexer-daemon/status.d.ts +23 -0
  19. package/dist/bitcoind/indexer-daemon/status.js +282 -0
  20. package/dist/bitcoind/indexer-daemon/types.d.ts +141 -0
  21. package/dist/bitcoind/indexer-daemon/types.js +1 -0
  22. package/dist/bitcoind/indexer-daemon-main.js +14 -665
  23. package/dist/bitcoind/indexer-daemon.d.ts +4 -132
  24. package/dist/bitcoind/indexer-daemon.js +2 -417
  25. package/dist/bitcoind/managed-bitcoind-service-config.d.ts +30 -0
  26. package/dist/bitcoind/managed-bitcoind-service-config.js +202 -0
  27. package/dist/bitcoind/managed-bitcoind-service-lifecycle.d.ts +28 -0
  28. package/dist/bitcoind/managed-bitcoind-service-lifecycle.js +296 -0
  29. package/dist/bitcoind/managed-bitcoind-service-process.d.ts +8 -0
  30. package/dist/bitcoind/managed-bitcoind-service-process.js +48 -0
  31. package/dist/bitcoind/managed-bitcoind-service-replica.d.ts +8 -0
  32. package/dist/bitcoind/managed-bitcoind-service-replica.js +142 -0
  33. package/dist/bitcoind/managed-bitcoind-service-status.d.ts +42 -0
  34. package/dist/bitcoind/managed-bitcoind-service-status.js +170 -0
  35. package/dist/bitcoind/managed-bitcoind-service-types.d.ts +36 -0
  36. package/dist/bitcoind/managed-bitcoind-service-types.js +1 -0
  37. package/dist/bitcoind/service.d.ts +7 -63
  38. package/dist/bitcoind/service.js +7 -797
  39. package/dist/cli/mining-format.js +6 -1
  40. package/dist/cli/wallet-format/balance.js +1 -1
  41. package/dist/client/default-client.d.ts +3 -1
  42. package/dist/client/default-client.js +22 -0
  43. package/dist/types.d.ts +13 -1
  44. package/dist/wallet/fs/atomic.d.ts +11 -2
  45. package/dist/wallet/fs/atomic.js +45 -5
  46. package/dist/wallet/mining/cycle.js +4 -4
  47. package/dist/wallet/mining/engine-types.d.ts +1 -0
  48. package/dist/wallet/mining/engine-types.js +9 -1
  49. package/dist/wallet/mining/projection.d.ts +1 -0
  50. package/dist/wallet/mining/projection.js +15 -1
  51. package/dist/wallet/mining/publish.js +3 -6
  52. package/dist/wallet/mining/runner.js +30 -18
  53. package/dist/wallet/mining/visualizer-sync.js +7 -9
  54. package/dist/wallet/mining/visualizer.js +9 -7
  55. package/dist/wallet/read/context.d.ts +4 -10
  56. package/dist/wallet/read/context.js +6 -228
  57. package/dist/wallet/read/local-state.d.ts +36 -0
  58. package/dist/wallet/read/local-state.js +259 -0
  59. package/dist/wallet/read/managed-bitcoind.d.ts +30 -0
  60. package/dist/wallet/read/managed-bitcoind.js +138 -0
  61. package/dist/wallet/read/managed-indexer.d.ts +23 -0
  62. package/dist/wallet/read/managed-indexer.js +87 -0
  63. package/dist/wallet/read/managed-services.d.ts +6 -21
  64. package/dist/wallet/read/managed-services.js +23 -196
  65. package/dist/wallet/read/types.d.ts +1 -0
  66. package/package.json +1 -1
@@ -1,236 +1,13 @@
1
- import { access, constants } from "node:fs/promises";
2
1
  import { readPackageVersionFromDisk } from "../../package-version.js";
3
- import { readSnapshotWithRetry, } from "../../bitcoind/indexer-daemon.js";
4
- import { createRpcClient } from "../../bitcoind/node.js";
2
+ export { readSnapshotWithRetry, } from "../../bitcoind/indexer-daemon.js";
5
3
  import { UNINITIALIZED_WALLET_ROOT_ID } from "../../bitcoind/service-paths.js";
6
- import { attachOrStartManagedBitcoindService } from "../../bitcoind/service.js";
7
- import {} from "../../bitcoind/types.js";
8
- import { normalizeWalletStateRecord, persistWalletCoinControlStateIfNeeded } from "../coin-control.js";
9
- import { persistNormalizedWalletDescriptorStateIfNeeded } from "../descriptor-normalization.js";
10
4
  import { inspectMiningControlPlane } from "../mining/index.js";
11
- import { normalizeMiningStateRecord } from "../mining/state.js";
12
- import { resolveWalletRootIdFromLocalArtifacts } from "../root-resolution.js";
13
5
  import { resolveWalletRuntimePathsForTesting } from "../runtime.js";
14
- import { extractWalletRootIdHintFromWalletStateEnvelope, loadRawWalletStateEnvelope, loadWalletState, } from "../state/storage.js";
15
- import { createDefaultWalletSecretProvider, createWalletSecretReference, inspectClientPasswordSetupReadiness, } from "../state/provider.js";
16
- import { describeClientPasswordLockedMessage, describeClientPasswordMigrationMessage, describeClientPasswordSetupMessage, } from "../state/client-password.js";
6
+ import { createDefaultWalletSecretProvider, } from "../state/provider.js";
17
7
  import { openManagedWalletReadServiceBundle } from "./managed-services.js";
8
+ import { inspectWalletLocalState, readFundingBalanceSummary } from "./local-state.js";
18
9
  import { createWalletReadModel } from "./project.js";
19
10
  const DEFAULT_SERVICE_START_TIMEOUT_MS = 60_000;
20
- function btcAmountToSats(value) {
21
- return BigInt(Math.round(value * 100_000_000));
22
- }
23
- function isSpendableFundingUtxo(entry, fundingScriptPubKeyHex) {
24
- return entry.scriptPubKey === fundingScriptPubKeyHex
25
- && entry.confirmations >= 1
26
- && entry.spendable !== false
27
- && entry.safe !== false;
28
- }
29
- async function pathExists(path) {
30
- try {
31
- await access(path, constants.F_OK);
32
- return true;
33
- }
34
- catch {
35
- return false;
36
- }
37
- }
38
- function isWalletAccessError(error) {
39
- const message = error instanceof Error ? error.message : String(error);
40
- return message.startsWith("wallet_secret_missing_")
41
- || message.startsWith("wallet_secret_provider_")
42
- || message.startsWith("wallet_client_password_")
43
- || message === "wallet_state_legacy_envelope_unsupported";
44
- }
45
- function describeWalletAccessMessage(options) {
46
- const message = options.accessError instanceof Error ? options.accessError.message : String(options.accessError ?? "");
47
- if (message === "wallet_state_legacy_envelope_unsupported") {
48
- return "Wallet state exists but was created by an older Cogcoin wallet format that this version no longer loads directly.";
49
- }
50
- if (message === "wallet_client_password_setup_required") {
51
- return describeClientPasswordSetupMessage();
52
- }
53
- if (message === "wallet_client_password_migration_required") {
54
- return describeClientPasswordMigrationMessage();
55
- }
56
- if (message === "wallet_client_password_locked") {
57
- return describeClientPasswordLockedMessage();
58
- }
59
- if (message.startsWith("wallet_secret_provider_")) {
60
- return "Wallet state exists but the local secret provider is unavailable.";
61
- }
62
- if (message.startsWith("wallet_secret_missing_")) {
63
- return "Wallet state exists but its local secret-provider material is unavailable.";
64
- }
65
- return message.length > 0
66
- ? message
67
- : "Wallet state exists but could not be loaded from the local secret provider.";
68
- }
69
- async function normalizeLoadedWalletStateForRead(options) {
70
- if (options.dataDir === undefined) {
71
- return options.loaded;
72
- }
73
- const node = await attachOrStartManagedBitcoindService({
74
- dataDir: options.dataDir,
75
- chain: "main",
76
- startHeight: 0,
77
- walletRootId: options.loaded.state.walletRootId,
78
- });
79
- try {
80
- const access = {
81
- provider: options.access.provider,
82
- secretReference: createWalletSecretReference(options.loaded.state.walletRootId),
83
- };
84
- const normalized = await persistNormalizedWalletDescriptorStateIfNeeded({
85
- state: options.loaded.state,
86
- access,
87
- paths: options.paths,
88
- nowUnixMs: options.now,
89
- replacePrimary: options.loaded.source === "backup",
90
- rpc: createRpcClient(node.rpc),
91
- });
92
- const coinControl = await persistWalletCoinControlStateIfNeeded({
93
- state: normalized.state,
94
- access,
95
- paths: options.paths,
96
- nowUnixMs: options.now,
97
- replacePrimary: (normalized.changed ? "primary" : options.loaded.source) === "backup",
98
- rpc: createRpcClient(node.rpc),
99
- });
100
- return {
101
- source: coinControl.changed ? "primary" : normalized.changed ? "primary" : options.loaded.source,
102
- state: coinControl.state,
103
- };
104
- }
105
- finally {
106
- await node.stop?.().catch(() => undefined);
107
- }
108
- }
109
- async function inspectWalletLocalState(options = {}) {
110
- const paths = options.paths ?? resolveWalletRuntimePathsForTesting();
111
- const now = options.now ?? Date.now();
112
- const provider = options.secretProvider ?? createDefaultWalletSecretProvider();
113
- const [hasPrimaryStateFile, hasBackupStateFile] = await Promise.all([
114
- pathExists(paths.walletStatePath),
115
- pathExists(paths.walletStateBackupPath),
116
- ]);
117
- const clientPasswordReadiness = await inspectClientPasswordSetupReadiness(provider).catch(() => "ready");
118
- if (!hasPrimaryStateFile && !hasBackupStateFile) {
119
- return {
120
- availability: "uninitialized",
121
- clientPasswordReadiness,
122
- unlockRequired: false,
123
- walletRootId: null,
124
- state: null,
125
- source: null,
126
- hasPrimaryStateFile,
127
- hasBackupStateFile,
128
- message: "Wallet state has not been initialized yet.",
129
- };
130
- }
131
- if (clientPasswordReadiness !== "ready") {
132
- const rawEnvelope = await loadRawWalletStateEnvelope({
133
- primaryPath: paths.walletStatePath,
134
- backupPath: paths.walletStateBackupPath,
135
- }).catch(() => null);
136
- if (rawEnvelope?.envelope.secretProvider == null) {
137
- return {
138
- availability: "local-state-corrupt",
139
- clientPasswordReadiness: "ready",
140
- unlockRequired: false,
141
- walletRootId: extractWalletRootIdHintFromWalletStateEnvelope(rawEnvelope?.envelope ?? null),
142
- state: null,
143
- source: null,
144
- hasPrimaryStateFile,
145
- hasBackupStateFile,
146
- message: "Wallet state exists but was created by an older Cogcoin wallet format that this version no longer loads directly.",
147
- };
148
- }
149
- const resolvedRoot = await resolveWalletRootIdFromLocalArtifacts({
150
- paths,
151
- provider,
152
- }).catch(() => null);
153
- return {
154
- availability: "local-state-corrupt",
155
- clientPasswordReadiness,
156
- unlockRequired: false,
157
- walletRootId: resolvedRoot?.walletRootId ?? null,
158
- state: null,
159
- source: null,
160
- hasPrimaryStateFile,
161
- hasBackupStateFile,
162
- message: clientPasswordReadiness === "migration-required"
163
- ? describeClientPasswordMigrationMessage()
164
- : describeClientPasswordSetupMessage(),
165
- };
166
- }
167
- try {
168
- const loaded = await loadWalletState({
169
- primaryPath: paths.walletStatePath,
170
- backupPath: paths.walletStateBackupPath,
171
- }, {
172
- provider,
173
- });
174
- const normalized = await normalizeLoadedWalletStateForRead({
175
- loaded,
176
- access: { provider },
177
- dataDir: options.dataDir,
178
- now,
179
- paths,
180
- });
181
- return {
182
- availability: "ready",
183
- clientPasswordReadiness,
184
- unlockRequired: false,
185
- walletRootId: normalized.state.walletRootId,
186
- state: normalizeWalletStateRecord({
187
- ...normalized.state,
188
- miningState: normalizeMiningStateRecord(normalized.state.miningState),
189
- }),
190
- source: normalized.source,
191
- hasPrimaryStateFile,
192
- hasBackupStateFile,
193
- message: null,
194
- };
195
- }
196
- catch (error) {
197
- const resolvedRoot = await resolveWalletRootIdFromLocalArtifacts({
198
- paths,
199
- provider,
200
- }).catch(() => null);
201
- const message = error instanceof Error ? error.message : String(error);
202
- return {
203
- availability: "local-state-corrupt",
204
- clientPasswordReadiness,
205
- unlockRequired: message === "wallet_client_password_locked",
206
- walletRootId: resolvedRoot?.walletRootId ?? null,
207
- state: null,
208
- source: null,
209
- hasPrimaryStateFile,
210
- hasBackupStateFile,
211
- message: isWalletAccessError(error)
212
- ? describeWalletAccessMessage({ accessError: error })
213
- : error instanceof Error
214
- ? error.message
215
- : String(error),
216
- };
217
- }
218
- }
219
- async function readFundingSpendableSats(options) {
220
- if (options.state === null || options.rpc === null) {
221
- return null;
222
- }
223
- const state = options.state;
224
- try {
225
- const utxos = await options.rpc.listUnspent(state.managedCoreWallet.walletName, 1);
226
- return utxos.reduce((sum, entry) => isSpendableFundingUtxo(entry, state.funding.scriptPubKeyHex)
227
- ? sum + btcAmountToSats(entry.amount)
228
- : sum, 0n);
229
- }
230
- catch {
231
- return null;
232
- }
233
- }
234
11
  export async function openWalletReadContext(options) {
235
12
  const expectedIndexerBinaryVersion = options.expectedIndexerBinaryVersion === undefined
236
13
  ? await readPackageVersionFromDisk()
@@ -254,7 +31,7 @@ export async function openWalletReadContext(options) {
254
31
  expectedIndexerBinaryVersion,
255
32
  now,
256
33
  });
257
- const fundingSpendableSats = await readFundingSpendableSats({
34
+ const { fundingDisplaySats, fundingSpendableSats, } = await readFundingBalanceSummary({
258
35
  state: localState.state,
259
36
  rpc: managedServices.node.rpc,
260
37
  });
@@ -281,6 +58,7 @@ export async function openWalletReadContext(options) {
281
58
  model: localState.state === null
282
59
  ? null
283
60
  : createWalletReadModel(localState.state, managedServices.snapshot),
61
+ fundingDisplaySats,
284
62
  fundingSpendableSats,
285
63
  mining,
286
64
  async close() {
@@ -288,4 +66,4 @@ export async function openWalletReadContext(options) {
288
66
  },
289
67
  };
290
68
  }
291
- export { inspectWalletLocalState, readSnapshotWithRetry, };
69
+ export { inspectWalletLocalState, };
@@ -0,0 +1,36 @@
1
+ import { attachOrStartManagedBitcoindService } from "../../bitcoind/service.js";
2
+ import { createRpcClient } from "../../bitcoind/node.js";
3
+ import { type WalletRuntimePaths } from "../runtime.js";
4
+ import { type WalletSecretProvider } from "../state/provider.js";
5
+ import type { WalletLocalStateStatus } from "./types.js";
6
+ type WalletLocalStateDeps = {
7
+ attachOrStartManagedBitcoindService: typeof attachOrStartManagedBitcoindService;
8
+ createRpcClient: typeof createRpcClient;
9
+ };
10
+ export interface FundingBalanceSummary {
11
+ fundingDisplaySats: bigint | null;
12
+ fundingSpendableSats: bigint | null;
13
+ }
14
+ export declare function inspectWalletLocalStateWithDependencies(options?: {
15
+ dataDir?: string;
16
+ secretProvider?: WalletSecretProvider;
17
+ now?: number;
18
+ paths?: WalletRuntimePaths;
19
+ walletControlLockHeld?: boolean;
20
+ }, dependencies?: WalletLocalStateDeps): Promise<WalletLocalStateStatus>;
21
+ export declare function inspectWalletLocalState(options?: {
22
+ dataDir?: string;
23
+ secretProvider?: WalletSecretProvider;
24
+ now?: number;
25
+ paths?: WalletRuntimePaths;
26
+ walletControlLockHeld?: boolean;
27
+ }): Promise<WalletLocalStateStatus>;
28
+ export declare function readFundingSpendableSats(options: {
29
+ state: WalletLocalStateStatus["state"];
30
+ rpc: ReturnType<typeof createRpcClient> | null;
31
+ }): Promise<bigint | null>;
32
+ export declare function readFundingBalanceSummary(options: {
33
+ state: WalletLocalStateStatus["state"];
34
+ rpc: Pick<ReturnType<typeof createRpcClient>, "listUnspent"> | null;
35
+ }): Promise<FundingBalanceSummary>;
36
+ export {};
@@ -0,0 +1,259 @@
1
+ import { access, constants } from "node:fs/promises";
2
+ import { normalizeWalletStateRecord, persistWalletCoinControlStateIfNeeded } from "../coin-control.js";
3
+ import { persistNormalizedWalletDescriptorStateIfNeeded } from "../descriptor-normalization.js";
4
+ import { attachOrStartManagedBitcoindService } from "../../bitcoind/service.js";
5
+ import { createRpcClient } from "../../bitcoind/node.js";
6
+ import { normalizeMiningStateRecord } from "../mining/state.js";
7
+ import { resolveWalletRootIdFromLocalArtifacts } from "../root-resolution.js";
8
+ import { resolveWalletRuntimePathsForTesting } from "../runtime.js";
9
+ import { extractWalletRootIdHintFromWalletStateEnvelope, loadRawWalletStateEnvelope, loadWalletState, } from "../state/storage.js";
10
+ import { createDefaultWalletSecretProvider, createWalletSecretReference, inspectClientPasswordSetupReadiness, } from "../state/provider.js";
11
+ import { describeClientPasswordLockedMessage, describeClientPasswordMigrationMessage, describeClientPasswordSetupMessage, } from "../state/client-password.js";
12
+ const defaultWalletLocalStateDeps = {
13
+ attachOrStartManagedBitcoindService,
14
+ createRpcClient,
15
+ };
16
+ function btcAmountToSats(value) {
17
+ return BigInt(Math.round(value * 100_000_000));
18
+ }
19
+ function isSpendableFundingUtxo(entry, fundingScriptPubKeyHex) {
20
+ return entry.scriptPubKey === fundingScriptPubKeyHex
21
+ && entry.confirmations >= 1
22
+ && entry.spendable !== false
23
+ && entry.safe !== false;
24
+ }
25
+ function isDisplayFundingUtxo(entry, fundingScriptPubKeyHex) {
26
+ return entry.scriptPubKey === fundingScriptPubKeyHex
27
+ && entry.spendable !== false;
28
+ }
29
+ async function pathExists(path) {
30
+ try {
31
+ await access(path, constants.F_OK);
32
+ return true;
33
+ }
34
+ catch {
35
+ return false;
36
+ }
37
+ }
38
+ function isWalletAccessError(error) {
39
+ const message = error instanceof Error ? error.message : String(error);
40
+ return message.startsWith("wallet_secret_missing_")
41
+ || message.startsWith("wallet_secret_provider_")
42
+ || message.startsWith("wallet_client_password_")
43
+ || message === "wallet_state_legacy_envelope_unsupported";
44
+ }
45
+ function describeWalletAccessMessage(options) {
46
+ const message = options.accessError instanceof Error ? options.accessError.message : String(options.accessError ?? "");
47
+ if (message === "wallet_state_legacy_envelope_unsupported") {
48
+ return "Wallet state exists but was created by an older Cogcoin wallet format that this version no longer loads directly.";
49
+ }
50
+ if (message === "wallet_client_password_setup_required") {
51
+ return describeClientPasswordSetupMessage();
52
+ }
53
+ if (message === "wallet_client_password_migration_required") {
54
+ return describeClientPasswordMigrationMessage();
55
+ }
56
+ if (message === "wallet_client_password_locked") {
57
+ return describeClientPasswordLockedMessage();
58
+ }
59
+ if (message.startsWith("wallet_secret_provider_")) {
60
+ return "Wallet state exists but the local secret provider is unavailable.";
61
+ }
62
+ if (message.startsWith("wallet_secret_missing_")) {
63
+ return "Wallet state exists but its local secret-provider material is unavailable.";
64
+ }
65
+ return message.length > 0
66
+ ? message
67
+ : "Wallet state exists but could not be loaded from the local secret provider.";
68
+ }
69
+ async function normalizeLoadedWalletStateForRead(options) {
70
+ if (options.dataDir === undefined) {
71
+ return options.loaded;
72
+ }
73
+ const node = await options.dependencies.attachOrStartManagedBitcoindService({
74
+ dataDir: options.dataDir,
75
+ chain: "main",
76
+ startHeight: 0,
77
+ walletRootId: options.loaded.state.walletRootId,
78
+ });
79
+ try {
80
+ const access = {
81
+ provider: options.access.provider,
82
+ secretReference: createWalletSecretReference(options.loaded.state.walletRootId),
83
+ };
84
+ const normalized = await persistNormalizedWalletDescriptorStateIfNeeded({
85
+ state: options.loaded.state,
86
+ access,
87
+ paths: options.paths,
88
+ nowUnixMs: options.now,
89
+ replacePrimary: options.loaded.source === "backup",
90
+ rpc: options.dependencies.createRpcClient(node.rpc),
91
+ });
92
+ const coinControl = await persistWalletCoinControlStateIfNeeded({
93
+ state: normalized.state,
94
+ access,
95
+ paths: options.paths,
96
+ nowUnixMs: options.now,
97
+ replacePrimary: (normalized.changed ? "primary" : options.loaded.source) === "backup",
98
+ rpc: options.dependencies.createRpcClient(node.rpc),
99
+ });
100
+ return {
101
+ source: coinControl.changed ? "primary" : normalized.changed ? "primary" : options.loaded.source,
102
+ state: coinControl.state,
103
+ };
104
+ }
105
+ finally {
106
+ await node.stop?.().catch(() => undefined);
107
+ }
108
+ }
109
+ export async function inspectWalletLocalStateWithDependencies(options = {}, dependencies = defaultWalletLocalStateDeps) {
110
+ const paths = options.paths ?? resolveWalletRuntimePathsForTesting();
111
+ const now = options.now ?? Date.now();
112
+ const provider = options.secretProvider ?? createDefaultWalletSecretProvider();
113
+ const [hasPrimaryStateFile, hasBackupStateFile] = await Promise.all([
114
+ pathExists(paths.walletStatePath),
115
+ pathExists(paths.walletStateBackupPath),
116
+ ]);
117
+ const clientPasswordReadiness = await inspectClientPasswordSetupReadiness(provider).catch(() => "ready");
118
+ if (!hasPrimaryStateFile && !hasBackupStateFile) {
119
+ return {
120
+ availability: "uninitialized",
121
+ clientPasswordReadiness,
122
+ unlockRequired: false,
123
+ walletRootId: null,
124
+ state: null,
125
+ source: null,
126
+ hasPrimaryStateFile,
127
+ hasBackupStateFile,
128
+ message: "Wallet state has not been initialized yet.",
129
+ };
130
+ }
131
+ if (clientPasswordReadiness !== "ready") {
132
+ const rawEnvelope = await loadRawWalletStateEnvelope({
133
+ primaryPath: paths.walletStatePath,
134
+ backupPath: paths.walletStateBackupPath,
135
+ }).catch(() => null);
136
+ if (rawEnvelope?.envelope.secretProvider == null) {
137
+ return {
138
+ availability: "local-state-corrupt",
139
+ clientPasswordReadiness: "ready",
140
+ unlockRequired: false,
141
+ walletRootId: extractWalletRootIdHintFromWalletStateEnvelope(rawEnvelope?.envelope ?? null),
142
+ state: null,
143
+ source: null,
144
+ hasPrimaryStateFile,
145
+ hasBackupStateFile,
146
+ message: "Wallet state exists but was created by an older Cogcoin wallet format that this version no longer loads directly.",
147
+ };
148
+ }
149
+ const resolvedRoot = await resolveWalletRootIdFromLocalArtifacts({
150
+ paths,
151
+ provider,
152
+ }).catch(() => null);
153
+ return {
154
+ availability: "local-state-corrupt",
155
+ clientPasswordReadiness,
156
+ unlockRequired: false,
157
+ walletRootId: resolvedRoot?.walletRootId ?? null,
158
+ state: null,
159
+ source: null,
160
+ hasPrimaryStateFile,
161
+ hasBackupStateFile,
162
+ message: clientPasswordReadiness === "migration-required"
163
+ ? describeClientPasswordMigrationMessage()
164
+ : describeClientPasswordSetupMessage(),
165
+ };
166
+ }
167
+ try {
168
+ const loaded = await loadWalletState({
169
+ primaryPath: paths.walletStatePath,
170
+ backupPath: paths.walletStateBackupPath,
171
+ }, {
172
+ provider,
173
+ });
174
+ const normalized = await normalizeLoadedWalletStateForRead({
175
+ loaded,
176
+ access: { provider },
177
+ dataDir: options.dataDir,
178
+ now,
179
+ paths,
180
+ dependencies,
181
+ });
182
+ return {
183
+ availability: "ready",
184
+ clientPasswordReadiness,
185
+ unlockRequired: false,
186
+ walletRootId: normalized.state.walletRootId,
187
+ state: normalizeWalletStateRecord({
188
+ ...normalized.state,
189
+ miningState: normalizeMiningStateRecord(normalized.state.miningState),
190
+ }),
191
+ source: normalized.source,
192
+ hasPrimaryStateFile,
193
+ hasBackupStateFile,
194
+ message: null,
195
+ };
196
+ }
197
+ catch (error) {
198
+ const resolvedRoot = await resolveWalletRootIdFromLocalArtifacts({
199
+ paths,
200
+ provider,
201
+ }).catch(() => null);
202
+ const message = error instanceof Error ? error.message : String(error);
203
+ return {
204
+ availability: "local-state-corrupt",
205
+ clientPasswordReadiness,
206
+ unlockRequired: message === "wallet_client_password_locked",
207
+ walletRootId: resolvedRoot?.walletRootId ?? null,
208
+ state: null,
209
+ source: null,
210
+ hasPrimaryStateFile,
211
+ hasBackupStateFile,
212
+ message: isWalletAccessError(error)
213
+ ? describeWalletAccessMessage({ accessError: error })
214
+ : error instanceof Error
215
+ ? error.message
216
+ : String(error),
217
+ };
218
+ }
219
+ }
220
+ export async function inspectWalletLocalState(options = {}) {
221
+ return inspectWalletLocalStateWithDependencies(options);
222
+ }
223
+ export async function readFundingSpendableSats(options) {
224
+ return (await readFundingBalanceSummary(options)).fundingSpendableSats;
225
+ }
226
+ export async function readFundingBalanceSummary(options) {
227
+ if (options.state === null || options.rpc === null) {
228
+ return {
229
+ fundingDisplaySats: null,
230
+ fundingSpendableSats: null,
231
+ };
232
+ }
233
+ const state = options.state;
234
+ try {
235
+ const utxos = await options.rpc.listUnspent(state.managedCoreWallet.walletName, 0);
236
+ let fundingDisplaySats = 0n;
237
+ let fundingSpendableSats = 0n;
238
+ for (const entry of utxos) {
239
+ if (!isDisplayFundingUtxo(entry, state.funding.scriptPubKeyHex)) {
240
+ continue;
241
+ }
242
+ const amountSats = btcAmountToSats(entry.amount);
243
+ fundingDisplaySats += amountSats;
244
+ if (isSpendableFundingUtxo(entry, state.funding.scriptPubKeyHex)) {
245
+ fundingSpendableSats += amountSats;
246
+ }
247
+ }
248
+ return {
249
+ fundingDisplaySats,
250
+ fundingSpendableSats,
251
+ };
252
+ }
253
+ catch {
254
+ return {
255
+ fundingDisplaySats: null,
256
+ fundingSpendableSats: null,
257
+ };
258
+ }
259
+ }
@@ -0,0 +1,30 @@
1
+ import { loadBundledGenesisParameters } from "@cogcoin/indexer";
2
+ import type { ManagedWalletNodeConnection } from "../../bitcoind/managed-runtime/types.js";
3
+ import { createRpcClient } from "../../bitcoind/node.js";
4
+ import { attachOrStartManagedBitcoindService, probeManagedBitcoindService } from "../../bitcoind/service.js";
5
+ import type { ManagedBitcoindNodeHandle } from "../../bitcoind/types.js";
6
+ import { verifyManagedCoreWalletReplica } from "../lifecycle.js";
7
+ import type { WalletBitcoindStatus, WalletLocalStateStatus, WalletNodeStatus, WalletServiceHealth } from "./types.js";
8
+ export type ManagedWalletBitcoindReadDeps = {
9
+ loadBundledGenesisParameters: typeof loadBundledGenesisParameters;
10
+ probeManagedBitcoindService: typeof probeManagedBitcoindService;
11
+ attachOrStartManagedBitcoindService: typeof attachOrStartManagedBitcoindService;
12
+ createRpcClient: typeof createRpcClient;
13
+ verifyManagedCoreWalletReplica: typeof verifyManagedCoreWalletReplica;
14
+ };
15
+ export interface ManagedWalletBitcoindReadState {
16
+ node: ManagedWalletNodeConnection<ManagedBitcoindNodeHandle, ReturnType<typeof createRpcClient>>;
17
+ bitcoind: WalletBitcoindStatus;
18
+ nodeHealth: WalletServiceHealth;
19
+ nodeMessage: string | null;
20
+ }
21
+ export declare function deriveNodeHealthForTesting(status: WalletNodeStatus | null, bitcoindHealth: WalletBitcoindStatus["health"]): {
22
+ health: WalletServiceHealth;
23
+ message: string | null;
24
+ };
25
+ export declare function openManagedWalletBitcoindReadState(options: {
26
+ dataDir: string;
27
+ walletRootId: string;
28
+ localState: WalletLocalStateStatus;
29
+ startupTimeoutMs: number;
30
+ }, dependencies?: ManagedWalletBitcoindReadDeps): Promise<ManagedWalletBitcoindReadState>;