@cogcoin/client 1.1.6 → 1.1.7

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 (109) hide show
  1. package/README.md +2 -2
  2. package/dist/bitcoind/indexer-daemon.js +29 -79
  3. package/dist/bitcoind/managed-runtime/bitcoind-runtime.d.ts +20 -0
  4. package/dist/bitcoind/managed-runtime/bitcoind-runtime.js +74 -0
  5. package/dist/bitcoind/managed-runtime/bitcoind-status.d.ts +11 -0
  6. package/dist/bitcoind/managed-runtime/bitcoind-status.js +44 -0
  7. package/dist/bitcoind/managed-runtime/indexer-runtime.d.ts +15 -0
  8. package/dist/bitcoind/managed-runtime/indexer-runtime.js +82 -0
  9. package/dist/bitcoind/managed-runtime/types.d.ts +40 -0
  10. package/dist/bitcoind/node.d.ts +2 -2
  11. package/dist/bitcoind/node.js +2 -2
  12. package/dist/bitcoind/rpc.d.ts +2 -1
  13. package/dist/bitcoind/rpc.js +53 -3
  14. package/dist/bitcoind/service.js +46 -126
  15. package/dist/cli/command-registry.d.ts +1 -1
  16. package/dist/cli/command-registry.js +2 -64
  17. package/dist/cli/commands/client-admin.js +3 -18
  18. package/dist/cli/commands/mining-runtime.js +4 -60
  19. package/dist/cli/commands/wallet-admin.js +6 -6
  20. package/dist/cli/context.js +1 -3
  21. package/dist/cli/mining-json.d.ts +1 -22
  22. package/dist/cli/mining-json.js +0 -23
  23. package/dist/cli/output.js +16 -2
  24. package/dist/cli/parse.js +0 -2
  25. package/dist/cli/preview-json.d.ts +1 -22
  26. package/dist/cli/preview-json.js +0 -19
  27. package/dist/cli/types.d.ts +1 -3
  28. package/dist/cli/wallet-format.js +1 -1
  29. package/dist/cli/workflow-hints.d.ts +1 -2
  30. package/dist/cli/workflow-hints.js +5 -8
  31. package/dist/wallet/lifecycle/context.js +0 -1
  32. package/dist/wallet/lifecycle/repair-mining.d.ts +1 -5
  33. package/dist/wallet/lifecycle/repair-mining.js +5 -39
  34. package/dist/wallet/lifecycle/repair.js +0 -3
  35. package/dist/wallet/lifecycle/setup.js +10 -8
  36. package/dist/wallet/lifecycle/types.d.ts +1 -4
  37. package/dist/wallet/managed-core-wallet.d.ts +2 -0
  38. package/dist/wallet/managed-core-wallet.js +27 -1
  39. package/dist/wallet/mining/candidate.d.ts +1 -0
  40. package/dist/wallet/mining/candidate.js +38 -6
  41. package/dist/wallet/mining/competitiveness.d.ts +1 -0
  42. package/dist/wallet/mining/competitiveness.js +6 -0
  43. package/dist/wallet/mining/cycle.d.ts +2 -0
  44. package/dist/wallet/mining/cycle.js +14 -4
  45. package/dist/wallet/mining/engine-types.d.ts +1 -0
  46. package/dist/wallet/mining/index.d.ts +1 -1
  47. package/dist/wallet/mining/index.js +1 -1
  48. package/dist/wallet/mining/publish.d.ts +3 -0
  49. package/dist/wallet/mining/publish.js +78 -6
  50. package/dist/wallet/mining/runner.d.ts +0 -32
  51. package/dist/wallet/mining/runner.js +59 -104
  52. package/dist/wallet/mining/stop.d.ts +7 -0
  53. package/dist/wallet/mining/stop.js +23 -0
  54. package/dist/wallet/mining/supervisor.d.ts +2 -36
  55. package/dist/wallet/mining/supervisor.js +139 -246
  56. package/dist/wallet/read/context.d.ts +1 -5
  57. package/dist/wallet/read/context.js +20 -204
  58. package/dist/wallet/read/managed-services.d.ts +33 -0
  59. package/dist/wallet/read/managed-services.js +222 -0
  60. package/dist/wallet/state/client-password/bootstrap.d.ts +2 -0
  61. package/dist/wallet/state/client-password/bootstrap.js +3 -0
  62. package/dist/wallet/state/client-password/context.d.ts +10 -0
  63. package/dist/wallet/state/client-password/context.js +46 -0
  64. package/dist/wallet/state/client-password/crypto.d.ts +34 -0
  65. package/dist/wallet/state/client-password/crypto.js +117 -0
  66. package/dist/wallet/state/client-password/files.d.ts +10 -0
  67. package/dist/wallet/state/client-password/files.js +109 -0
  68. package/dist/wallet/state/client-password/legacy-cleanup.d.ts +11 -0
  69. package/dist/wallet/state/client-password/legacy-cleanup.js +338 -0
  70. package/dist/wallet/state/client-password/messages.d.ts +3 -0
  71. package/dist/wallet/state/client-password/messages.js +9 -0
  72. package/dist/wallet/state/client-password/migration.d.ts +4 -0
  73. package/dist/wallet/state/client-password/migration.js +32 -0
  74. package/dist/wallet/state/client-password/prompts.d.ts +12 -0
  75. package/dist/wallet/state/client-password/prompts.js +79 -0
  76. package/dist/wallet/state/client-password/protected-secrets.d.ts +13 -0
  77. package/dist/wallet/state/client-password/protected-secrets.js +90 -0
  78. package/dist/wallet/state/client-password/readiness.d.ts +4 -0
  79. package/dist/wallet/state/client-password/readiness.js +48 -0
  80. package/dist/wallet/state/client-password/references.d.ts +1 -0
  81. package/dist/wallet/state/client-password/references.js +56 -0
  82. package/dist/wallet/state/client-password/rotation.d.ts +6 -0
  83. package/dist/wallet/state/client-password/rotation.js +98 -0
  84. package/dist/wallet/state/client-password/session-policy.d.ts +6 -0
  85. package/dist/wallet/state/client-password/session-policy.js +28 -0
  86. package/dist/wallet/state/client-password/session.d.ts +19 -0
  87. package/dist/wallet/state/client-password/session.js +170 -0
  88. package/dist/wallet/state/client-password/setup.d.ts +8 -0
  89. package/dist/wallet/state/client-password/setup.js +49 -0
  90. package/dist/wallet/state/client-password/types.d.ts +82 -0
  91. package/dist/wallet/state/client-password/types.js +5 -0
  92. package/dist/wallet/state/client-password.d.ts +7 -38
  93. package/dist/wallet/state/client-password.js +52 -937
  94. package/dist/wallet/tx/anchor.js +123 -216
  95. package/dist/wallet/tx/cog.js +294 -489
  96. package/dist/wallet/tx/common.d.ts +2 -0
  97. package/dist/wallet/tx/common.js +2 -0
  98. package/dist/wallet/tx/domain-admin.js +111 -220
  99. package/dist/wallet/tx/domain-market.js +401 -681
  100. package/dist/wallet/tx/executor.d.ts +176 -0
  101. package/dist/wallet/tx/executor.js +302 -0
  102. package/dist/wallet/tx/field.js +109 -215
  103. package/dist/wallet/tx/register.js +158 -269
  104. package/dist/wallet/tx/reputation.js +120 -227
  105. package/package.json +1 -1
  106. package/dist/wallet/mining/worker-main.d.ts +0 -1
  107. package/dist/wallet/mining/worker-main.js +0 -17
  108. package/dist/wallet/state/client-password-agent.d.ts +0 -1
  109. package/dist/wallet/state/client-password-agent.js +0 -211
@@ -1,15 +1,10 @@
1
1
  import { access, constants } from "node:fs/promises";
2
- import { deserializeIndexerState, loadBundledGenesisParameters } from "@cogcoin/indexer";
3
2
  import { readPackageVersionFromDisk } from "../../package-version.js";
4
- import { attachOrStartIndexerDaemon, INDEXER_DAEMON_BACKGROUND_FOLLOW_RECOVERY_FAILED, probeIndexerDaemon, readObservedIndexerDaemonStatus, readSnapshotWithRetry, } from "../../bitcoind/indexer-daemon.js";
5
- import { deriveManagedBitcoindWalletStatus, resolveManagedBitcoindProbeDecision, } from "../../bitcoind/managed-runtime/bitcoind-policy.js";
6
- import { deriveManagedIndexerWalletStatus, resolveIndexerDaemonProbeDecision, } from "../../bitcoind/managed-runtime/indexer-policy.js";
3
+ import { readSnapshotWithRetry, } from "../../bitcoind/indexer-daemon.js";
7
4
  import { createRpcClient } from "../../bitcoind/node.js";
8
5
  import { UNINITIALIZED_WALLET_ROOT_ID } from "../../bitcoind/service-paths.js";
9
- import { attachOrStartManagedBitcoindService, probeManagedBitcoindService, } from "../../bitcoind/service.js";
10
- import { resolveCogcoinProcessingStartHeight } from "../../bitcoind/processing-start-height.js";
6
+ import { attachOrStartManagedBitcoindService } from "../../bitcoind/service.js";
11
7
  import {} from "../../bitcoind/types.js";
12
- import { verifyManagedCoreWalletReplica, } from "../lifecycle.js";
13
8
  import { normalizeWalletStateRecord, persistWalletCoinControlStateIfNeeded } from "../coin-control.js";
14
9
  import { persistNormalizedWalletDescriptorStateIfNeeded } from "../descriptor-normalization.js";
15
10
  import { inspectMiningControlPlane } from "../mining/index.js";
@@ -19,11 +14,9 @@ import { resolveWalletRuntimePathsForTesting } from "../runtime.js";
19
14
  import { extractWalletRootIdHintFromWalletStateEnvelope, loadRawWalletStateEnvelope, loadWalletState, } from "../state/storage.js";
20
15
  import { createDefaultWalletSecretProvider, createWalletSecretReference, inspectClientPasswordSetupReadiness, } from "../state/provider.js";
21
16
  import { describeClientPasswordLockedMessage, describeClientPasswordMigrationMessage, describeClientPasswordSetupMessage, } from "../state/client-password.js";
17
+ import { openManagedWalletReadServiceBundle } from "./managed-services.js";
22
18
  import { createWalletReadModel } from "./project.js";
23
19
  const DEFAULT_SERVICE_START_TIMEOUT_MS = 10_000;
24
- const TOLERATED_NODE_HEADER_LEAD_BLOCKS = 2;
25
- const TOLERATED_NODE_HEADER_LEAD_MESSAGE = "Bitcoin headers can briefly lead validated blocks; a short 1-2 block lead is normal and is being tolerated.";
26
- const NODE_CATCHING_UP_MESSAGE = "Bitcoin Core is still catching up to headers.";
27
20
  function btcAmountToSats(value) {
28
21
  return BigInt(Math.round(value * 100_000_000));
29
22
  }
@@ -223,99 +216,6 @@ async function inspectWalletLocalState(options = {}) {
223
216
  };
224
217
  }
225
218
  }
226
- function deriveNodeHealth(status, bitcoindHealth) {
227
- if (bitcoindHealth !== "ready" || status === null || !status.ready) {
228
- return {
229
- health: "catching-up",
230
- message: NODE_CATCHING_UP_MESSAGE,
231
- };
232
- }
233
- const headerLead = status.nodeBestHeight !== null && status.nodeHeaderHeight !== null
234
- ? status.nodeHeaderHeight - status.nodeBestHeight
235
- : null;
236
- if (headerLead !== null && headerLead > 0) {
237
- if (headerLead <= TOLERATED_NODE_HEADER_LEAD_BLOCKS) {
238
- return {
239
- health: "synced",
240
- message: TOLERATED_NODE_HEADER_LEAD_MESSAGE,
241
- };
242
- }
243
- return {
244
- health: "catching-up",
245
- message: NODE_CATCHING_UP_MESSAGE,
246
- };
247
- }
248
- return {
249
- health: "synced",
250
- message: null,
251
- };
252
- }
253
- export function deriveNodeHealthForTesting(status, bitcoindHealth) {
254
- return deriveNodeHealth(status, bitcoindHealth);
255
- }
256
- async function attachNodeStatus(options) {
257
- try {
258
- const probe = await probeManagedBitcoindService({
259
- dataDir: options.dataDir,
260
- chain: "main",
261
- startHeight: 0,
262
- walletRootId: options.walletRootId,
263
- startupTimeoutMs: options.startupTimeoutMs,
264
- });
265
- const decision = resolveManagedBitcoindProbeDecision(probe);
266
- if (decision.action === "reject") {
267
- return {
268
- handle: null,
269
- rpc: null,
270
- status: null,
271
- observedStatus: probe.status,
272
- error: decision.error,
273
- };
274
- }
275
- const genesis = await loadBundledGenesisParameters();
276
- const handle = await attachOrStartManagedBitcoindService({
277
- dataDir: options.dataDir,
278
- chain: "main",
279
- startHeight: resolveCogcoinProcessingStartHeight(genesis),
280
- walletRootId: options.walletRootId,
281
- startupTimeoutMs: options.startupTimeoutMs,
282
- });
283
- const rpc = createRpcClient(handle.rpc);
284
- const [chainInfo, serviceStatus] = await Promise.all([
285
- rpc.getBlockchainInfo(),
286
- handle.refreshServiceStatus?.(),
287
- ]);
288
- const status = {
289
- ready: true,
290
- chain: chainInfo.chain,
291
- pid: handle.pid,
292
- walletRootId: handle.walletRootId ?? null,
293
- nodeBestHeight: chainInfo.blocks,
294
- nodeBestHashHex: chainInfo.bestblockhash,
295
- nodeHeaderHeight: chainInfo.headers,
296
- serviceUpdatedAtUnixMs: serviceStatus?.updatedAtUnixMs ?? null,
297
- serviceStatus: serviceStatus ?? null,
298
- walletReplica: serviceStatus?.walletReplica ?? null,
299
- walletReplicaMessage: serviceStatus?.walletReplica?.message ?? null,
300
- };
301
- return {
302
- handle,
303
- rpc,
304
- status,
305
- observedStatus: serviceStatus ?? null,
306
- error: null,
307
- };
308
- }
309
- catch (error) {
310
- return {
311
- handle: null,
312
- rpc: null,
313
- status: null,
314
- observedStatus: null,
315
- error: error instanceof Error ? error.message : String(error),
316
- };
317
- }
318
- }
319
219
  async function readFundingSpendableSats(options) {
320
220
  if (options.state === null || options.rpc === null) {
321
221
  return null;
@@ -345,109 +245,26 @@ export async function openWalletReadContext(options) {
345
245
  paths: options.paths,
346
246
  });
347
247
  const walletRootId = localState.walletRootId ?? UNINITIALIZED_WALLET_ROOT_ID;
348
- const node = await attachNodeStatus({
248
+ const managedServices = await openManagedWalletReadServiceBundle({
349
249
  dataDir: options.dataDir,
250
+ databasePath: options.databasePath,
350
251
  walletRootId,
252
+ localState,
351
253
  startupTimeoutMs,
352
- });
353
- if (localState.state !== null && node.status !== null) {
354
- const verifiedReplica = await verifyManagedCoreWalletReplica(localState.state, options.dataDir, {
355
- nodeHandle: node.handle ?? undefined,
356
- });
357
- node.status = {
358
- ...node.status,
359
- walletReplica: verifiedReplica,
360
- walletReplicaMessage: verifiedReplica.message ?? null,
361
- };
362
- }
363
- const bitcoind = deriveManagedBitcoindWalletStatus({
364
- status: node.observedStatus,
365
- nodeStatus: node.status,
366
- startupError: node.error,
367
- });
368
- const nodeDerived = deriveNodeHealth(node.status, bitcoind.health);
369
- let daemonClient = null;
370
- let daemonStatus = null;
371
- let observedDaemonStatus = null;
372
- let snapshot = null;
373
- let indexerSource = "none";
374
- let daemonError = null;
375
- try {
376
- const probe = await probeIndexerDaemon({
377
- dataDir: options.dataDir,
378
- walletRootId,
379
- });
380
- const probeDecision = resolveIndexerDaemonProbeDecision({
381
- probe,
382
- expectedBinaryVersion: expectedIndexerBinaryVersion,
383
- });
384
- if (probeDecision.action !== "reject") {
385
- await probe.client?.close().catch(() => undefined);
386
- daemonClient = await attachOrStartIndexerDaemon({
387
- dataDir: options.dataDir,
388
- databasePath: options.databasePath,
389
- walletRootId,
390
- startupTimeoutMs,
391
- ensureBackgroundFollow: true,
392
- expectedBinaryVersion: expectedIndexerBinaryVersion,
393
- });
394
- }
395
- else {
396
- observedDaemonStatus = probe.status;
397
- indexerSource = probe.status === null ? "none" : "probe";
398
- daemonError = probeDecision.error;
399
- }
400
- if (daemonClient !== null) {
401
- const lease = await readSnapshotWithRetry(daemonClient, walletRootId);
402
- daemonStatus = lease.status;
403
- observedDaemonStatus = lease.status;
404
- snapshot = {
405
- tip: lease.payload.tip,
406
- state: deserializeIndexerState(Buffer.from(lease.payload.stateBase64, "base64")),
407
- source: "lease",
408
- daemonInstanceId: lease.payload.daemonInstanceId,
409
- snapshotSeq: lease.payload.snapshotSeq,
410
- openedAtUnixMs: lease.payload.openedAtUnixMs,
411
- };
412
- indexerSource = "lease";
413
- }
414
- }
415
- catch (error) {
416
- daemonError = error instanceof Error ? error.message : String(error);
417
- if (daemonError === INDEXER_DAEMON_BACKGROUND_FOLLOW_RECOVERY_FAILED) {
418
- await daemonClient?.close().catch(() => undefined);
419
- await node.handle?.stop().catch(() => undefined);
420
- throw error;
421
- }
422
- if (observedDaemonStatus === null) {
423
- observedDaemonStatus = await readObservedIndexerDaemonStatus({
424
- dataDir: options.dataDir,
425
- walletRootId,
426
- }).catch(() => null);
427
- if (observedDaemonStatus !== null) {
428
- indexerSource = "status-file";
429
- }
430
- }
431
- }
432
- const indexer = deriveManagedIndexerWalletStatus({
433
- daemonStatus,
434
- observedStatus: observedDaemonStatus,
435
- snapshot,
436
- source: indexerSource,
254
+ expectedIndexerBinaryVersion,
437
255
  now,
438
- startupError: daemonError,
439
256
  });
440
257
  const fundingSpendableSats = await readFundingSpendableSats({
441
258
  state: localState.state,
442
- rpc: node.rpc,
259
+ rpc: managedServices.node.rpc,
443
260
  });
444
261
  const mining = await inspectMiningControlPlane({
445
262
  provider: options.secretProvider,
446
263
  localState,
447
- bitcoind,
448
- nodeStatus: node.status,
449
- nodeHealth: nodeDerived.health,
450
- indexer,
264
+ bitcoind: managedServices.bitcoind,
265
+ nodeStatus: managedServices.node.status,
266
+ nodeHealth: managedServices.nodeHealth,
267
+ indexer: managedServices.indexer,
451
268
  nowUnixMs: now,
452
269
  paths: options.paths,
453
270
  });
@@ -455,20 +272,19 @@ export async function openWalletReadContext(options) {
455
272
  dataDir: options.dataDir,
456
273
  databasePath: options.databasePath,
457
274
  localState,
458
- bitcoind,
459
- nodeStatus: node.status,
460
- nodeHealth: nodeDerived.health,
461
- nodeMessage: nodeDerived.message,
462
- indexer,
463
- snapshot,
275
+ bitcoind: managedServices.bitcoind,
276
+ nodeStatus: managedServices.node.status,
277
+ nodeHealth: managedServices.nodeHealth,
278
+ nodeMessage: managedServices.nodeMessage,
279
+ indexer: managedServices.indexer,
280
+ snapshot: managedServices.snapshot,
464
281
  model: localState.state === null
465
282
  ? null
466
- : createWalletReadModel(localState.state, snapshot),
283
+ : createWalletReadModel(localState.state, managedServices.snapshot),
467
284
  fundingSpendableSats,
468
285
  mining,
469
286
  async close() {
470
- await daemonClient?.close().catch(() => undefined);
471
- await node.handle?.stop().catch(() => undefined);
287
+ await managedServices.close();
472
288
  },
473
289
  };
474
290
  }
@@ -0,0 +1,33 @@
1
+ import { loadBundledGenesisParameters } from "@cogcoin/indexer";
2
+ import { attachOrStartIndexerDaemon, probeIndexerDaemon, readObservedIndexerDaemonStatus, readSnapshotWithRetry, type IndexerDaemonClient } from "../../bitcoind/indexer-daemon.js";
3
+ import type { ManagedWalletReadServiceBundle } from "../../bitcoind/managed-runtime/types.js";
4
+ import { createRpcClient } from "../../bitcoind/node.js";
5
+ import { attachOrStartManagedBitcoindService, probeManagedBitcoindService } from "../../bitcoind/service.js";
6
+ import type { ManagedBitcoindNodeHandle } from "../../bitcoind/types.js";
7
+ import { verifyManagedCoreWalletReplica } from "../lifecycle.js";
8
+ import type { WalletBitcoindStatus, WalletLocalStateStatus, WalletNodeStatus, WalletServiceHealth } from "./types.js";
9
+ type ManagedWalletReadServiceDeps = {
10
+ loadBundledGenesisParameters: typeof loadBundledGenesisParameters;
11
+ probeManagedBitcoindService: typeof probeManagedBitcoindService;
12
+ attachOrStartManagedBitcoindService: typeof attachOrStartManagedBitcoindService;
13
+ createRpcClient: typeof createRpcClient;
14
+ verifyManagedCoreWalletReplica: typeof verifyManagedCoreWalletReplica;
15
+ probeIndexerDaemon: typeof probeIndexerDaemon;
16
+ attachOrStartIndexerDaemon: typeof attachOrStartIndexerDaemon;
17
+ readSnapshotWithRetry: typeof readSnapshotWithRetry;
18
+ readObservedIndexerDaemonStatus: typeof readObservedIndexerDaemonStatus;
19
+ };
20
+ export declare function deriveNodeHealthForTesting(status: WalletNodeStatus | null, bitcoindHealth: WalletBitcoindStatus["health"]): {
21
+ health: WalletServiceHealth;
22
+ message: string | null;
23
+ };
24
+ export declare function openManagedWalletReadServiceBundle(options: {
25
+ dataDir: string;
26
+ databasePath: string;
27
+ walletRootId: string;
28
+ localState: WalletLocalStateStatus;
29
+ startupTimeoutMs: number;
30
+ expectedIndexerBinaryVersion: string | null;
31
+ now: number;
32
+ }, dependencies?: ManagedWalletReadServiceDeps): Promise<ManagedWalletReadServiceBundle<ManagedBitcoindNodeHandle, ReturnType<typeof createRpcClient>, IndexerDaemonClient>>;
33
+ export {};
@@ -0,0 +1,222 @@
1
+ import { deserializeIndexerState, loadBundledGenesisParameters } from "@cogcoin/indexer";
2
+ import { attachOrStartIndexerDaemon, INDEXER_DAEMON_BACKGROUND_FOLLOW_RECOVERY_FAILED, probeIndexerDaemon, readObservedIndexerDaemonStatus, readSnapshotWithRetry, } from "../../bitcoind/indexer-daemon.js";
3
+ import { deriveManagedBitcoindWalletStatus, resolveManagedBitcoindProbeDecision, } from "../../bitcoind/managed-runtime/bitcoind-policy.js";
4
+ import { deriveManagedIndexerWalletStatus, resolveIndexerDaemonProbeDecision, } from "../../bitcoind/managed-runtime/indexer-policy.js";
5
+ import { createRpcClient } from "../../bitcoind/node.js";
6
+ import { resolveCogcoinProcessingStartHeight } from "../../bitcoind/processing-start-height.js";
7
+ import { attachOrStartManagedBitcoindService, probeManagedBitcoindService, } from "../../bitcoind/service.js";
8
+ import { verifyManagedCoreWalletReplica } from "../lifecycle.js";
9
+ const TOLERATED_NODE_HEADER_LEAD_BLOCKS = 2;
10
+ const TOLERATED_NODE_HEADER_LEAD_MESSAGE = "Bitcoin headers can briefly lead validated blocks; a short 1-2 block lead is normal and is being tolerated.";
11
+ const NODE_CATCHING_UP_MESSAGE = "Bitcoin Core is still catching up to headers.";
12
+ const defaultManagedWalletReadServiceDeps = {
13
+ loadBundledGenesisParameters,
14
+ probeManagedBitcoindService,
15
+ attachOrStartManagedBitcoindService,
16
+ createRpcClient,
17
+ verifyManagedCoreWalletReplica,
18
+ probeIndexerDaemon,
19
+ attachOrStartIndexerDaemon,
20
+ readSnapshotWithRetry,
21
+ readObservedIndexerDaemonStatus,
22
+ };
23
+ function deriveNodeHealth(status, bitcoindHealth) {
24
+ if (bitcoindHealth !== "ready" || status === null || !status.ready) {
25
+ return {
26
+ health: "catching-up",
27
+ message: NODE_CATCHING_UP_MESSAGE,
28
+ };
29
+ }
30
+ const headerLead = status.nodeBestHeight !== null && status.nodeHeaderHeight !== null
31
+ ? status.nodeHeaderHeight - status.nodeBestHeight
32
+ : null;
33
+ if (headerLead !== null && headerLead > 0) {
34
+ if (headerLead <= TOLERATED_NODE_HEADER_LEAD_BLOCKS) {
35
+ return {
36
+ health: "synced",
37
+ message: TOLERATED_NODE_HEADER_LEAD_MESSAGE,
38
+ };
39
+ }
40
+ return {
41
+ health: "catching-up",
42
+ message: NODE_CATCHING_UP_MESSAGE,
43
+ };
44
+ }
45
+ return {
46
+ health: "synced",
47
+ message: null,
48
+ };
49
+ }
50
+ export function deriveNodeHealthForTesting(status, bitcoindHealth) {
51
+ return deriveNodeHealth(status, bitcoindHealth);
52
+ }
53
+ async function attachNodeStatus(options, dependencies) {
54
+ try {
55
+ const probe = await dependencies.probeManagedBitcoindService({
56
+ dataDir: options.dataDir,
57
+ chain: "main",
58
+ startHeight: 0,
59
+ walletRootId: options.walletRootId,
60
+ startupTimeoutMs: options.startupTimeoutMs,
61
+ });
62
+ const decision = resolveManagedBitcoindProbeDecision(probe);
63
+ if (decision.action === "reject") {
64
+ return {
65
+ handle: null,
66
+ rpc: null,
67
+ status: null,
68
+ observedStatus: probe.status,
69
+ error: decision.error,
70
+ };
71
+ }
72
+ const genesis = await dependencies.loadBundledGenesisParameters();
73
+ const handle = await dependencies.attachOrStartManagedBitcoindService({
74
+ dataDir: options.dataDir,
75
+ chain: "main",
76
+ startHeight: resolveCogcoinProcessingStartHeight(genesis),
77
+ walletRootId: options.walletRootId,
78
+ startupTimeoutMs: options.startupTimeoutMs,
79
+ });
80
+ const rpc = dependencies.createRpcClient(handle.rpc);
81
+ const [chainInfo, serviceStatus] = await Promise.all([
82
+ rpc.getBlockchainInfo(),
83
+ handle.refreshServiceStatus?.(),
84
+ ]);
85
+ const status = {
86
+ ready: true,
87
+ chain: chainInfo.chain,
88
+ pid: handle.pid,
89
+ walletRootId: handle.walletRootId ?? null,
90
+ nodeBestHeight: chainInfo.blocks,
91
+ nodeBestHashHex: chainInfo.bestblockhash,
92
+ nodeHeaderHeight: chainInfo.headers,
93
+ serviceUpdatedAtUnixMs: serviceStatus?.updatedAtUnixMs ?? null,
94
+ serviceStatus: serviceStatus ?? null,
95
+ walletReplica: serviceStatus?.walletReplica ?? null,
96
+ walletReplicaMessage: serviceStatus?.walletReplica?.message ?? null,
97
+ };
98
+ return {
99
+ handle,
100
+ rpc,
101
+ status,
102
+ observedStatus: serviceStatus ?? null,
103
+ error: null,
104
+ };
105
+ }
106
+ catch (error) {
107
+ return {
108
+ handle: null,
109
+ rpc: null,
110
+ status: null,
111
+ observedStatus: null,
112
+ error: error instanceof Error ? error.message : String(error),
113
+ };
114
+ }
115
+ }
116
+ export async function openManagedWalletReadServiceBundle(options, dependencies = defaultManagedWalletReadServiceDeps) {
117
+ const node = await attachNodeStatus({
118
+ dataDir: options.dataDir,
119
+ walletRootId: options.walletRootId,
120
+ startupTimeoutMs: options.startupTimeoutMs,
121
+ }, dependencies);
122
+ if (options.localState.state !== null && node.status !== null) {
123
+ const verifiedReplica = await dependencies.verifyManagedCoreWalletReplica(options.localState.state, options.dataDir, {
124
+ nodeHandle: node.handle ?? undefined,
125
+ });
126
+ node.status = {
127
+ ...node.status,
128
+ walletReplica: verifiedReplica,
129
+ walletReplicaMessage: verifiedReplica.message ?? null,
130
+ };
131
+ }
132
+ const bitcoind = deriveManagedBitcoindWalletStatus({
133
+ status: node.observedStatus,
134
+ nodeStatus: node.status,
135
+ startupError: node.error,
136
+ });
137
+ const nodeDerived = deriveNodeHealth(node.status, bitcoind.health);
138
+ let daemonClient = null;
139
+ let daemonStatus = null;
140
+ let observedDaemonStatus = null;
141
+ let snapshot = null;
142
+ let indexerSource = "none";
143
+ let daemonError = null;
144
+ try {
145
+ const probe = await dependencies.probeIndexerDaemon({
146
+ dataDir: options.dataDir,
147
+ walletRootId: options.walletRootId,
148
+ });
149
+ const probeDecision = resolveIndexerDaemonProbeDecision({
150
+ probe,
151
+ expectedBinaryVersion: options.expectedIndexerBinaryVersion,
152
+ });
153
+ if (probeDecision.action !== "reject") {
154
+ await probe.client?.close().catch(() => undefined);
155
+ daemonClient = await dependencies.attachOrStartIndexerDaemon({
156
+ dataDir: options.dataDir,
157
+ databasePath: options.databasePath,
158
+ walletRootId: options.walletRootId,
159
+ startupTimeoutMs: options.startupTimeoutMs,
160
+ ensureBackgroundFollow: true,
161
+ expectedBinaryVersion: options.expectedIndexerBinaryVersion,
162
+ });
163
+ }
164
+ else {
165
+ observedDaemonStatus = probe.status;
166
+ indexerSource = probe.status === null ? "none" : "probe";
167
+ daemonError = probeDecision.error;
168
+ }
169
+ if (daemonClient !== null) {
170
+ const lease = await dependencies.readSnapshotWithRetry(daemonClient, options.walletRootId);
171
+ daemonStatus = lease.status;
172
+ observedDaemonStatus = lease.status;
173
+ snapshot = {
174
+ tip: lease.payload.tip,
175
+ state: deserializeIndexerState(Buffer.from(lease.payload.stateBase64, "base64")),
176
+ source: "lease",
177
+ daemonInstanceId: lease.payload.daemonInstanceId,
178
+ snapshotSeq: lease.payload.snapshotSeq,
179
+ openedAtUnixMs: lease.payload.openedAtUnixMs,
180
+ };
181
+ indexerSource = "lease";
182
+ }
183
+ }
184
+ catch (error) {
185
+ daemonError = error instanceof Error ? error.message : String(error);
186
+ if (daemonError === INDEXER_DAEMON_BACKGROUND_FOLLOW_RECOVERY_FAILED) {
187
+ await daemonClient?.close().catch(() => undefined);
188
+ await node.handle?.stop().catch(() => undefined);
189
+ throw error;
190
+ }
191
+ if (observedDaemonStatus === null) {
192
+ observedDaemonStatus = await dependencies.readObservedIndexerDaemonStatus({
193
+ dataDir: options.dataDir,
194
+ walletRootId: options.walletRootId,
195
+ }).catch(() => null);
196
+ if (observedDaemonStatus !== null) {
197
+ indexerSource = "status-file";
198
+ }
199
+ }
200
+ }
201
+ const indexer = deriveManagedIndexerWalletStatus({
202
+ daemonStatus,
203
+ observedStatus: observedDaemonStatus,
204
+ snapshot,
205
+ source: indexerSource,
206
+ now: options.now,
207
+ startupError: daemonError,
208
+ });
209
+ return {
210
+ node,
211
+ bitcoind,
212
+ nodeHealth: nodeDerived.health,
213
+ nodeMessage: nodeDerived.message,
214
+ daemonClient,
215
+ indexer,
216
+ snapshot,
217
+ async close() {
218
+ await daemonClient?.close().catch(() => undefined);
219
+ await node.handle?.stop().catch(() => undefined);
220
+ },
221
+ };
222
+ }
@@ -0,0 +1,2 @@
1
+ import type { ClientPasswordAgentBootstrapState } from "./types.js";
2
+ export declare function createAgentBootstrapState(state: ClientPasswordAgentBootstrapState): ClientPasswordAgentBootstrapState;
@@ -0,0 +1,3 @@
1
+ export function createAgentBootstrapState(state) {
2
+ return state;
3
+ }
@@ -0,0 +1,10 @@
1
+ import type { WalletRuntimePaths } from "../../runtime.js";
2
+ import type { ClientPasswordResolvedContext, ClientPasswordStorageOptions } from "./types.js";
3
+ export declare function resolveLocalSecretFilePath(directoryPath: string, keyId: string): string;
4
+ export declare function resolveClientPasswordStatePath(directoryPath: string): string;
5
+ export declare function resolveClientPasswordRotationJournalPath(directoryPath: string): string;
6
+ export declare function isMissingFileError(error: unknown): boolean;
7
+ export declare function createRuntimeError(code: string, cause?: unknown): Error;
8
+ export declare function resolveClientPasswordContext(options: ClientPasswordStorageOptions): ClientPasswordResolvedContext;
9
+ export declare function resolveClientPasswordStorageOptionsForWalletPaths(paths: Pick<WalletRuntimePaths, "stateRoot" | "runtimeRoot">, platform?: NodeJS.Platform): ClientPasswordStorageOptions;
10
+ export declare function createLegacyKeychainServiceName(): string;
@@ -0,0 +1,46 @@
1
+ import { join } from "node:path";
2
+ function sanitizeSecretKeyId(keyId) {
3
+ return keyId.replace(/[^a-zA-Z0-9._-]+/g, "-");
4
+ }
5
+ export function resolveLocalSecretFilePath(directoryPath, keyId) {
6
+ return join(directoryPath, `${sanitizeSecretKeyId(keyId)}.secret`);
7
+ }
8
+ export function resolveClientPasswordStatePath(directoryPath) {
9
+ return join(directoryPath, "client-password.json");
10
+ }
11
+ export function resolveClientPasswordRotationJournalPath(directoryPath) {
12
+ return join(directoryPath, "client-password-rotation.json");
13
+ }
14
+ export function isMissingFileError(error) {
15
+ return error instanceof Error
16
+ && "code" in error
17
+ && error.code === "ENOENT";
18
+ }
19
+ export function createRuntimeError(code, cause) {
20
+ return cause === undefined ? new Error(code) : new Error(code, { cause });
21
+ }
22
+ export function resolveClientPasswordContext(options) {
23
+ return {
24
+ ...options,
25
+ legacyMacKeychainReader: options.legacyMacKeychainReader ?? null,
26
+ passwordStatePath: resolveClientPasswordStatePath(options.directoryPath),
27
+ rotationJournalPath: resolveClientPasswordRotationJournalPath(options.directoryPath),
28
+ };
29
+ }
30
+ export function resolveClientPasswordStorageOptionsForWalletPaths(paths, platform = process.platform) {
31
+ return {
32
+ platform,
33
+ stateRoot: paths.stateRoot,
34
+ runtimeRoot: paths.runtimeRoot,
35
+ directoryPath: join(paths.stateRoot, "secrets"),
36
+ runtimeErrorCode: platform === "win32"
37
+ ? "wallet_secret_provider_windows_runtime_error"
38
+ : platform === "darwin"
39
+ ? "wallet_secret_provider_macos_runtime_error"
40
+ : "wallet_secret_provider_linux_runtime_error",
41
+ legacyMacKeychainReader: null,
42
+ };
43
+ }
44
+ export function createLegacyKeychainServiceName() {
45
+ return "org.cogcoin.wallet";
46
+ }
@@ -0,0 +1,34 @@
1
+ import type { ClientPasswordStateV1, WrappedSecretEnvelopeV1 } from "./types.js";
2
+ export declare const CLIENT_PASSWORD_DEFAULT_UNLOCK_SECONDS = 3600;
3
+ export declare const CLIENT_PASSWORD_SETUP_AUTO_UNLOCK_SECONDS = 86400;
4
+ export declare function zeroizeBuffer(buffer: Uint8Array | null | undefined): void;
5
+ export declare function derivePasswordKey(passwordBytes: Uint8Array, saltBytes: Uint8Array): Promise<Buffer>;
6
+ export declare function createClientPasswordState(options: {
7
+ passwordBytes: Uint8Array;
8
+ passwordHint: string;
9
+ }): Promise<{
10
+ state: ClientPasswordStateV1;
11
+ derivedKey: Buffer;
12
+ }>;
13
+ export declare function createWrappedSecretEnvelope(secret: Uint8Array, derivedKey: Uint8Array): WrappedSecretEnvelopeV1;
14
+ export declare function verifyPassword(options: {
15
+ state: ClientPasswordStateV1;
16
+ passwordBytes: Uint8Array;
17
+ }): Promise<Buffer | null>;
18
+ export declare function decryptWrappedSecretEnvelope(envelope: WrappedSecretEnvelopeV1, derivedKey: Uint8Array): Uint8Array;
19
+ export declare function encryptSessionSecretBase64(options: {
20
+ key: Uint8Array;
21
+ secretBase64: string;
22
+ }): {
23
+ nonce: string;
24
+ tag: string;
25
+ ciphertext: string;
26
+ };
27
+ export declare function decryptSessionSecretBase64(options: {
28
+ key: Uint8Array;
29
+ envelope: {
30
+ nonce: string;
31
+ tag: string;
32
+ ciphertext: string;
33
+ };
34
+ }): string;