@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.
- package/README.md +1 -1
- package/dist/bitcoind/client/managed-client.d.ts +2 -0
- package/dist/bitcoind/client/managed-client.js +6 -0
- package/dist/bitcoind/indexer-daemon/background-follow.d.ts +23 -0
- package/dist/bitcoind/indexer-daemon/background-follow.js +132 -0
- package/dist/bitcoind/indexer-daemon/client.d.ts +12 -0
- package/dist/bitcoind/indexer-daemon/client.js +137 -0
- package/dist/bitcoind/indexer-daemon/lifecycle.d.ts +30 -0
- package/dist/bitcoind/indexer-daemon/lifecycle.js +153 -0
- package/dist/bitcoind/indexer-daemon/process.d.ts +35 -0
- package/dist/bitcoind/indexer-daemon/process.js +140 -0
- package/dist/bitcoind/indexer-daemon/runtime.d.ts +23 -0
- package/dist/bitcoind/indexer-daemon/runtime.js +204 -0
- package/dist/bitcoind/indexer-daemon/server.d.ts +12 -0
- package/dist/bitcoind/indexer-daemon/server.js +87 -0
- package/dist/bitcoind/indexer-daemon/snapshot-leases.d.ts +23 -0
- package/dist/bitcoind/indexer-daemon/snapshot-leases.js +139 -0
- package/dist/bitcoind/indexer-daemon/status.d.ts +23 -0
- package/dist/bitcoind/indexer-daemon/status.js +282 -0
- package/dist/bitcoind/indexer-daemon/types.d.ts +141 -0
- package/dist/bitcoind/indexer-daemon/types.js +1 -0
- package/dist/bitcoind/indexer-daemon-main.js +14 -665
- package/dist/bitcoind/indexer-daemon.d.ts +4 -132
- package/dist/bitcoind/indexer-daemon.js +2 -417
- package/dist/bitcoind/managed-bitcoind-service-config.d.ts +30 -0
- package/dist/bitcoind/managed-bitcoind-service-config.js +202 -0
- package/dist/bitcoind/managed-bitcoind-service-lifecycle.d.ts +28 -0
- package/dist/bitcoind/managed-bitcoind-service-lifecycle.js +296 -0
- package/dist/bitcoind/managed-bitcoind-service-process.d.ts +8 -0
- package/dist/bitcoind/managed-bitcoind-service-process.js +48 -0
- package/dist/bitcoind/managed-bitcoind-service-replica.d.ts +8 -0
- package/dist/bitcoind/managed-bitcoind-service-replica.js +142 -0
- package/dist/bitcoind/managed-bitcoind-service-status.d.ts +42 -0
- package/dist/bitcoind/managed-bitcoind-service-status.js +170 -0
- package/dist/bitcoind/managed-bitcoind-service-types.d.ts +36 -0
- package/dist/bitcoind/managed-bitcoind-service-types.js +1 -0
- package/dist/bitcoind/service.d.ts +7 -63
- package/dist/bitcoind/service.js +7 -797
- package/dist/cli/mining-format.js +6 -1
- package/dist/cli/wallet-format/balance.js +1 -1
- package/dist/client/default-client.d.ts +3 -1
- package/dist/client/default-client.js +22 -0
- package/dist/types.d.ts +13 -1
- package/dist/wallet/fs/atomic.d.ts +11 -2
- package/dist/wallet/fs/atomic.js +45 -5
- package/dist/wallet/mining/cycle.js +4 -4
- package/dist/wallet/mining/engine-types.d.ts +1 -0
- package/dist/wallet/mining/engine-types.js +9 -1
- package/dist/wallet/mining/projection.d.ts +1 -0
- package/dist/wallet/mining/projection.js +15 -1
- package/dist/wallet/mining/publish.js +3 -6
- package/dist/wallet/mining/runner.js +30 -18
- package/dist/wallet/mining/visualizer-sync.js +7 -9
- package/dist/wallet/mining/visualizer.js +9 -7
- package/dist/wallet/read/context.d.ts +4 -10
- package/dist/wallet/read/context.js +6 -228
- package/dist/wallet/read/local-state.d.ts +36 -0
- package/dist/wallet/read/local-state.js +259 -0
- package/dist/wallet/read/managed-bitcoind.d.ts +30 -0
- package/dist/wallet/read/managed-bitcoind.js +138 -0
- package/dist/wallet/read/managed-indexer.d.ts +23 -0
- package/dist/wallet/read/managed-indexer.js +87 -0
- package/dist/wallet/read/managed-services.d.ts +6 -21
- package/dist/wallet/read/managed-services.js +23 -196
- package/dist/wallet/read/types.d.ts +1 -0
- 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
|
-
|
|
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 {
|
|
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
|
|
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,
|
|
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>;
|