@cogcoin/client 0.5.6 → 0.5.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 (69) hide show
  1. package/README.md +11 -2
  2. package/dist/bitcoind/bootstrap/getblock-archive.d.ts +39 -0
  3. package/dist/bitcoind/bootstrap/getblock-archive.js +548 -0
  4. package/dist/bitcoind/bootstrap.d.ts +1 -0
  5. package/dist/bitcoind/bootstrap.js +1 -0
  6. package/dist/bitcoind/client/factory.js +84 -30
  7. package/dist/bitcoind/client/managed-client.js +2 -1
  8. package/dist/bitcoind/client/sync-engine.js +7 -0
  9. package/dist/bitcoind/errors.js +18 -0
  10. package/dist/bitcoind/indexer-daemon-main.js +78 -0
  11. package/dist/bitcoind/indexer-daemon.d.ts +3 -1
  12. package/dist/bitcoind/indexer-daemon.js +13 -6
  13. package/dist/bitcoind/node.js +2 -0
  14. package/dist/bitcoind/progress/constants.d.ts +1 -0
  15. package/dist/bitcoind/progress/constants.js +1 -0
  16. package/dist/bitcoind/progress/controller.d.ts +22 -0
  17. package/dist/bitcoind/progress/controller.js +48 -23
  18. package/dist/bitcoind/progress/formatting.js +25 -0
  19. package/dist/bitcoind/progress/render-policy.d.ts +35 -0
  20. package/dist/bitcoind/progress/render-policy.js +81 -0
  21. package/dist/bitcoind/service-paths.js +2 -6
  22. package/dist/bitcoind/service.d.ts +5 -1
  23. package/dist/bitcoind/service.js +93 -54
  24. package/dist/bitcoind/testing.d.ts +1 -1
  25. package/dist/bitcoind/testing.js +1 -1
  26. package/dist/bitcoind/types.d.ts +35 -1
  27. package/dist/cli/commands/follow.js +2 -0
  28. package/dist/cli/commands/getblock-archive-restart.d.ts +5 -0
  29. package/dist/cli/commands/getblock-archive-restart.js +15 -0
  30. package/dist/cli/commands/mining-admin.js +4 -0
  31. package/dist/cli/commands/mining-read.js +8 -5
  32. package/dist/cli/commands/mining-runtime.js +4 -0
  33. package/dist/cli/commands/status.js +2 -0
  34. package/dist/cli/commands/sync.js +2 -0
  35. package/dist/cli/commands/wallet-admin.js +29 -3
  36. package/dist/cli/commands/wallet-mutation.js +57 -4
  37. package/dist/cli/commands/wallet-read.js +2 -0
  38. package/dist/cli/context.js +5 -3
  39. package/dist/cli/mutation-command-groups.d.ts +2 -1
  40. package/dist/cli/mutation-command-groups.js +5 -0
  41. package/dist/cli/mutation-json.d.ts +18 -2
  42. package/dist/cli/mutation-json.js +47 -0
  43. package/dist/cli/mutation-success.d.ts +1 -0
  44. package/dist/cli/mutation-success.js +2 -2
  45. package/dist/cli/output.js +84 -1
  46. package/dist/cli/parse.d.ts +1 -1
  47. package/dist/cli/parse.js +127 -3
  48. package/dist/cli/preview-json.d.ts +10 -1
  49. package/dist/cli/preview-json.js +30 -0
  50. package/dist/cli/prompt.js +1 -1
  51. package/dist/cli/runner.js +3 -0
  52. package/dist/cli/types.d.ts +11 -4
  53. package/dist/cli/wallet-format.js +6 -0
  54. package/dist/wallet/lifecycle.d.ts +15 -1
  55. package/dist/wallet/lifecycle.js +147 -83
  56. package/dist/wallet/mining/visualizer.d.ts +11 -6
  57. package/dist/wallet/mining/visualizer.js +32 -15
  58. package/dist/wallet/reset.js +39 -27
  59. package/dist/wallet/runtime.d.ts +12 -1
  60. package/dist/wallet/runtime.js +53 -11
  61. package/dist/wallet/state/provider.d.ts +1 -0
  62. package/dist/wallet/state/provider.js +119 -3
  63. package/dist/wallet/state/seed-index.d.ts +43 -0
  64. package/dist/wallet/state/seed-index.js +151 -0
  65. package/dist/wallet/tx/anchor.d.ts +22 -0
  66. package/dist/wallet/tx/anchor.js +215 -8
  67. package/dist/wallet/tx/index.d.ts +1 -1
  68. package/dist/wallet/tx/index.js +1 -1
  69. package/package.json +1 -1
@@ -11,7 +11,7 @@ import { readPortableWalletArchive, writePortableWalletArchive } from "./archive
11
11
  import { normalizeWalletDescriptorState, persistNormalizedWalletDescriptorStateIfNeeded, persistWalletStateUpdate, resolveNormalizedWalletDescriptorState, stripDescriptorChecksum, } from "./descriptor-normalization.js";
12
12
  import { acquireFileLock, clearOrphanedFileLock } from "./fs/lock.js";
13
13
  import { createInternalCoreWalletPassphrase, createMnemonicConfirmationChallenge, deriveWalletMaterialFromMnemonic, generateWalletMaterial, isEnglishMnemonicWord, validateEnglishMnemonic, } from "./material.js";
14
- import { resolveWalletRuntimePathsForTesting } from "./runtime.js";
14
+ import { deriveWalletRuntimePathsForSeed, resolveWalletRuntimePathsForTesting, } from "./runtime.js";
15
15
  import { requestMiningGenerationPreemption } from "./mining/coordination.js";
16
16
  import { loadClientConfig } from "./mining/config.js";
17
17
  import { inspectMiningHookState } from "./mining/hooks.js";
@@ -21,6 +21,7 @@ import { renderWalletMnemonicRevealArt } from "./mnemonic-art.js";
21
21
  import { clearWalletExplicitLock, loadWalletExplicitLock, saveWalletExplicitLock, } from "./state/explicit-lock.js";
22
22
  import { clearWalletPendingInitializationState, loadWalletPendingInitializationStateOrNull, saveWalletPendingInitializationState, } from "./state/pending-init.js";
23
23
  import { clearUnlockSession, loadUnlockSession, saveUnlockSession } from "./state/session.js";
24
+ import { addImportedWalletSeedRecord, assertValidImportedWalletSeedName, ensureMainWalletSeedIndexRecord, findWalletSeedRecord, loadWalletSeedIndex, removeWalletSeedRecord, } from "./state/seed-index.js";
24
25
  import { createDefaultWalletSecretProvider, createWalletPendingInitSecretReference, createWalletRootId, createWalletSecretReference, } from "./state/provider.js";
25
26
  import { extractWalletRootIdHintFromWalletStateEnvelope, loadRawWalletStateEnvelope, loadWalletState, saveWalletState, } from "./state/storage.js";
26
27
  export const DEFAULT_UNLOCK_DURATION_MS = 15 * 60 * 1000;
@@ -57,7 +58,7 @@ function resolvePendingInitializationStoragePaths(paths) {
57
58
  async function clearPendingInitialization(paths, provider) {
58
59
  await clearWalletPendingInitializationState(resolvePendingInitializationStoragePaths(paths), {
59
60
  provider,
60
- secretReference: createWalletPendingInitSecretReference(paths.stateRoot),
61
+ secretReference: createWalletPendingInitSecretReference(paths.walletStateRoot),
61
62
  });
62
63
  }
63
64
  async function loadOrCreatePendingInitializationMaterial(options) {
@@ -73,7 +74,7 @@ async function loadOrCreatePendingInitializationMaterial(options) {
73
74
  await clearPendingInitialization(options.paths, options.provider);
74
75
  }
75
76
  const material = generateWalletMaterial();
76
- const secretReference = createWalletPendingInitSecretReference(options.paths.stateRoot);
77
+ const secretReference = createWalletPendingInitSecretReference(options.paths.walletStateRoot);
77
78
  const pendingState = {
78
79
  schemaVersion: 1,
79
80
  createdAtUnixMs: options.nowUnixMs,
@@ -372,7 +373,7 @@ async function promptForArchivePassphrase(prompter, promptPrefix) {
372
373
  async function promptForRestoreMnemonic(prompter) {
373
374
  const words = [];
374
375
  for (let index = 0; index < 24; index += 1) {
375
- const word = (await promptHiddenValue(prompter, `Word ${index + 1} of 24: `)).toLowerCase();
376
+ const word = (await promptRequiredValue(prompter, `Word ${index + 1} of 24: `)).toLowerCase();
376
377
  if (!isEnglishMnemonicWord(word)) {
377
378
  throw new Error("wallet_restore_mnemonic_invalid");
378
379
  }
@@ -405,6 +406,12 @@ async function confirmOverwriteIfNeeded(prompter, path) {
405
406
  throw new Error("wallet_export_overwrite_declined");
406
407
  }
407
408
  }
409
+ async function confirmYesNo(prompter, message) {
410
+ const answer = (await prompter.prompt(message)).trim().toLowerCase();
411
+ if (answer !== "yes") {
412
+ throw new Error("wallet_delete_confirmation_required");
413
+ }
414
+ }
408
415
  async function readManagedSnapshotTip(options) {
409
416
  const daemon = await attachOrStartIndexerDaemon({
410
417
  dataDir: options.dataDir,
@@ -693,6 +700,16 @@ function createSilentNonInteractivePrompter() {
693
700
  },
694
701
  };
695
702
  }
703
+ function resolveMainWalletPaths(paths) {
704
+ return deriveWalletRuntimePathsForSeed(paths, "main");
705
+ }
706
+ async function loadSharedWalletSeedIndex(paths, nowUnixMs) {
707
+ const mainPaths = resolveMainWalletPaths(paths);
708
+ return await loadWalletSeedIndex({
709
+ paths: mainPaths,
710
+ nowUnixMs,
711
+ });
712
+ }
696
713
  function applyRepairStoppedMiningState(state) {
697
714
  const miningState = normalizeMiningStateRecord(state.miningState);
698
715
  return {
@@ -1125,6 +1142,9 @@ export async function initializeWallet(options) {
1125
1142
  const nowUnixMs = options.nowUnixMs ?? Date.now();
1126
1143
  const unlockDurationMs = options.unlockDurationMs ?? DEFAULT_UNLOCK_DURATION_MS;
1127
1144
  const paths = options.paths ?? resolveWalletRuntimePathsForTesting();
1145
+ if (paths.selectedSeedName !== "main") {
1146
+ throw new Error("wallet_init_seed_not_supported");
1147
+ }
1128
1148
  const controlLock = await acquireFileLock(paths.walletControlLockPath, {
1129
1149
  purpose: "wallet-init",
1130
1150
  walletRootId: null,
@@ -1185,6 +1205,11 @@ export async function initializeWallet(options) {
1185
1205
  secretReference,
1186
1206
  });
1187
1207
  await clearPendingInitialization(paths, provider);
1208
+ await ensureMainWalletSeedIndexRecord({
1209
+ paths: resolveMainWalletPaths(paths),
1210
+ walletRootId,
1211
+ nowUnixMs,
1212
+ });
1188
1213
  return {
1189
1214
  walletRootId,
1190
1215
  fundingAddress: verifiedState.funding.address,
@@ -1404,6 +1429,9 @@ export async function importWallet(options) {
1404
1429
  const nowUnixMs = options.nowUnixMs ?? Date.now();
1405
1430
  const unlockDurationMs = options.unlockDurationMs ?? DEFAULT_UNLOCK_DURATION_MS;
1406
1431
  const paths = options.paths ?? resolveWalletRuntimePathsForTesting();
1432
+ if (paths.selectedSeedName !== "main") {
1433
+ throw new Error("wallet_import_seed_not_supported");
1434
+ }
1407
1435
  const controlLock = await acquireFileLock(paths.walletControlLockPath, {
1408
1436
  purpose: "wallet-import",
1409
1437
  walletRootId: null,
@@ -1467,6 +1495,11 @@ export async function importWallet(options) {
1467
1495
  databasePath: options.databasePath,
1468
1496
  walletRootId: importedState.walletRootId,
1469
1497
  }).then((daemon) => daemon.close());
1498
+ await ensureMainWalletSeedIndexRecord({
1499
+ paths: resolveMainWalletPaths(paths),
1500
+ walletRootId: importedState.walletRootId,
1501
+ nowUnixMs,
1502
+ });
1470
1503
  return {
1471
1504
  archivePath: options.archivePath,
1472
1505
  walletRootId: importedState.walletRootId,
@@ -1487,98 +1520,129 @@ export async function restoreWalletFromMnemonic(options) {
1487
1520
  const nowUnixMs = options.nowUnixMs ?? Date.now();
1488
1521
  const unlockDurationMs = options.unlockDurationMs ?? DEFAULT_UNLOCK_DURATION_MS;
1489
1522
  const paths = options.paths ?? resolveWalletRuntimePathsForTesting();
1523
+ const seedName = assertValidImportedWalletSeedName(paths.selectedSeedName);
1490
1524
  const controlLock = await acquireFileLock(paths.walletControlLockPath, {
1491
1525
  purpose: "wallet-restore",
1492
1526
  walletRootId: null,
1493
1527
  });
1494
1528
  try {
1495
- const rawEnvelope = await loadRawWalletStateEnvelope({
1529
+ const mainPaths = resolveMainWalletPaths(paths);
1530
+ const seedIndex = await loadSharedWalletSeedIndex(paths, nowUnixMs);
1531
+ if (findWalletSeedRecord(seedIndex, "main") === null) {
1532
+ throw new Error("wallet_restore_requires_main_wallet");
1533
+ }
1534
+ if (findWalletSeedRecord(seedIndex, seedName) !== null) {
1535
+ throw new Error("wallet_seed_name_exists");
1536
+ }
1537
+ await ensureWalletNotInitialized(paths, provider);
1538
+ let promptPhaseStarted = false;
1539
+ let mnemonicPhrase;
1540
+ try {
1541
+ promptPhaseStarted = true;
1542
+ mnemonicPhrase = await promptForRestoreMnemonic(options.prompter);
1543
+ }
1544
+ finally {
1545
+ if (promptPhaseStarted) {
1546
+ await options.prompter.clearSensitiveDisplay?.("restore-mnemonic-entry");
1547
+ }
1548
+ }
1549
+ await clearPendingInitialization(paths, provider);
1550
+ const material = deriveWalletMaterialFromMnemonic(mnemonicPhrase);
1551
+ const walletRootId = createWalletRootId();
1552
+ const internalCoreWalletPassphrase = createInternalCoreWalletPassphrase();
1553
+ const secretReference = createWalletSecretReference(walletRootId);
1554
+ const secret = randomBytes(32);
1555
+ await provider.storeSecret(secretReference.keyId, secret);
1556
+ const initialState = createInitialWalletState({
1557
+ walletRootId,
1558
+ nowUnixMs,
1559
+ material,
1560
+ internalCoreWalletPassphrase,
1561
+ });
1562
+ await clearUnlockSession(paths.walletUnlockSessionPath);
1563
+ await clearWalletExplicitLock(paths.walletExplicitLockPath);
1564
+ await saveWalletState({
1496
1565
  primaryPath: paths.walletStatePath,
1497
1566
  backupPath: paths.walletStateBackupPath,
1567
+ }, initialState, {
1568
+ provider,
1569
+ secretReference,
1570
+ });
1571
+ const restoredState = await recreateManagedCoreWalletReplica(initialState, provider, paths, options.dataDir, nowUnixMs, {
1572
+ attachService: options.attachService,
1573
+ rpcFactory: options.rpcFactory,
1574
+ });
1575
+ const unlockUntilUnixMs = nowUnixMs + unlockDurationMs;
1576
+ await clearWalletExplicitLock(paths.walletExplicitLockPath);
1577
+ await saveUnlockSession(paths.walletUnlockSessionPath, createUnlockSession(restoredState, unlockUntilUnixMs, secretReference.keyId, nowUnixMs), {
1578
+ provider,
1579
+ secretReference,
1498
1580
  });
1499
- const replacementStateExists = rawEnvelope !== null
1500
- || await pathExists(paths.walletStatePath)
1501
- || await pathExists(paths.walletStateBackupPath);
1502
- const replacementCoreWalletExists = await detectExistingManagedWalletReplica(options.dataDir);
1503
- const mnemonicPhrase = await promptForRestoreMnemonic(options.prompter);
1504
1581
  await clearPendingInitialization(paths, provider);
1505
- if (replacementStateExists || replacementCoreWalletExists) {
1506
- await confirmRestoreReplacement(options.prompter);
1582
+ await addImportedWalletSeedRecord({
1583
+ paths: mainPaths,
1584
+ seedName,
1585
+ walletRootId,
1586
+ nowUnixMs,
1587
+ });
1588
+ return {
1589
+ seedName,
1590
+ walletRootId,
1591
+ fundingAddress: restoredState.funding.address,
1592
+ unlockUntilUnixMs,
1593
+ state: restoredState,
1594
+ warnings: [],
1595
+ };
1596
+ }
1597
+ finally {
1598
+ await controlLock.release();
1599
+ }
1600
+ }
1601
+ export async function deleteImportedWalletSeed(options) {
1602
+ const provider = options.provider ?? createDefaultWalletSecretProvider();
1603
+ const nowUnixMs = options.nowUnixMs ?? Date.now();
1604
+ const paths = options.paths ?? resolveWalletRuntimePathsForTesting();
1605
+ if ((paths.selectedSeedName ?? "main") === "main") {
1606
+ throw new Error("wallet_delete_main_not_supported");
1607
+ }
1608
+ const seedName = assertValidImportedWalletSeedName(paths.selectedSeedName);
1609
+ const controlLock = await acquireFileLock(paths.walletControlLockPath, {
1610
+ purpose: "wallet-delete",
1611
+ walletRootId: null,
1612
+ });
1613
+ try {
1614
+ const mainPaths = resolveMainWalletPaths(paths);
1615
+ const seedIndex = await loadSharedWalletSeedIndex(paths, nowUnixMs);
1616
+ const seedRecord = findWalletSeedRecord(seedIndex, seedName);
1617
+ if (seedRecord === null) {
1618
+ throw new Error("wallet_seed_not_found");
1507
1619
  }
1508
- let previousWalletRootId = extractWalletRootIdHintFromWalletStateEnvelope(rawEnvelope?.envelope ?? null);
1509
- try {
1510
- const loaded = await loadWalletState({
1511
- primaryPath: paths.walletStatePath,
1512
- backupPath: paths.walletStateBackupPath,
1513
- }, {
1514
- provider,
1515
- });
1516
- previousWalletRootId = loaded.state.walletRootId;
1620
+ if (seedRecord.kind !== "imported") {
1621
+ throw new Error("wallet_delete_main_not_supported");
1517
1622
  }
1518
- catch {
1519
- previousWalletRootId = previousWalletRootId ?? null;
1623
+ if (!options.assumeYes) {
1624
+ await confirmYesNo(options.prompter, `Delete imported seed "${seedName}" and release its local wallet artifacts? Type yes to continue: `);
1520
1625
  }
1521
- const miningLock = await acquireFileLock(paths.miningControlLockPath, {
1522
- purpose: "wallet-restore",
1523
- walletRootId: previousWalletRootId,
1626
+ await clearUnlockSession(paths.walletUnlockSessionPath).catch(() => undefined);
1627
+ await clearWalletExplicitLock(paths.walletExplicitLockPath).catch(() => undefined);
1628
+ await clearPendingInitialization(paths, provider).catch(() => undefined);
1629
+ await provider.deleteSecret(createWalletSecretReference(seedRecord.walletRootId).keyId).catch(() => undefined);
1630
+ await rm(paths.walletStateRoot, { recursive: true, force: true }).catch(() => undefined);
1631
+ await rm(paths.walletRuntimeRoot, { recursive: true, force: true }).catch(() => undefined);
1632
+ await rm(join(options.dataDir, "wallets", sanitizeWalletName(seedRecord.walletRootId)), {
1633
+ recursive: true,
1634
+ force: true,
1635
+ }).catch(() => undefined);
1636
+ await removeWalletSeedRecord({
1637
+ paths: mainPaths,
1638
+ seedName,
1639
+ nowUnixMs,
1524
1640
  });
1525
- try {
1526
- const warnings = [];
1527
- const material = deriveWalletMaterialFromMnemonic(mnemonicPhrase);
1528
- const walletRootId = createWalletRootId();
1529
- const internalCoreWalletPassphrase = createInternalCoreWalletPassphrase();
1530
- const secretReference = createWalletSecretReference(walletRootId);
1531
- const secret = randomBytes(32);
1532
- await provider.storeSecret(secretReference.keyId, secret);
1533
- const initialState = createInitialWalletState({
1534
- walletRootId,
1535
- nowUnixMs,
1536
- material,
1537
- internalCoreWalletPassphrase,
1538
- });
1539
- await clearUnlockSession(paths.walletUnlockSessionPath);
1540
- await clearWalletExplicitLock(paths.walletExplicitLockPath);
1541
- await saveWalletState({
1542
- primaryPath: paths.walletStatePath,
1543
- backupPath: paths.walletStateBackupPath,
1544
- }, initialState, {
1545
- provider,
1546
- secretReference,
1547
- });
1548
- const restoredState = await recreateManagedCoreWalletReplica(initialState, provider, paths, options.dataDir, nowUnixMs, {
1549
- attachService: options.attachService,
1550
- rpcFactory: options.rpcFactory,
1551
- });
1552
- const unlockUntilUnixMs = nowUnixMs + unlockDurationMs;
1553
- await clearWalletExplicitLock(paths.walletExplicitLockPath);
1554
- await saveUnlockSession(paths.walletUnlockSessionPath, createUnlockSession(restoredState, unlockUntilUnixMs, secretReference.keyId, nowUnixMs), {
1555
- provider,
1556
- secretReference,
1557
- });
1558
- await clearPendingInitialization(paths, provider);
1559
- if (previousWalletRootId !== null && previousWalletRootId !== walletRootId) {
1560
- try {
1561
- await clearPreviousManagedWalletRuntime({
1562
- dataDir: options.dataDir,
1563
- walletRootId: previousWalletRootId,
1564
- });
1565
- }
1566
- catch (error) {
1567
- warnings.push(formatRestoreCleanupWarning(error));
1568
- }
1569
- await provider.deleteSecret(createWalletSecretReference(previousWalletRootId).keyId).catch(() => undefined);
1570
- }
1571
- return {
1572
- walletRootId,
1573
- fundingAddress: restoredState.funding.address,
1574
- unlockUntilUnixMs,
1575
- state: restoredState,
1576
- warnings,
1577
- };
1578
- }
1579
- finally {
1580
- await miningLock.release();
1581
- }
1641
+ return {
1642
+ seedName,
1643
+ walletRootId: seedRecord.walletRootId,
1644
+ deleted: true,
1645
+ };
1582
1646
  }
1583
1647
  finally {
1584
1648
  await controlLock.release();
@@ -1,9 +1,10 @@
1
- import type { ProgressOutputMode } from "../../bitcoind/types.js";
1
+ import type { BootstrapProgress, ProgressOutputMode } from "../../bitcoind/types.js";
2
+ import { createFollowSceneState } from "../../bitcoind/progress/follow-scene.js";
3
+ import { type RenderClock, type TtyRenderStream } from "../../bitcoind/progress/render-policy.js";
2
4
  import type { MiningRuntimeStatusV1 } from "./types.js";
3
- interface RenderStream {
4
- isTTY?: boolean;
5
- columns?: number;
6
- write(chunk: string): boolean | void;
5
+ interface VisualizerRendererLike {
6
+ renderFollowScene(progress: BootstrapProgress, cogcoinSyncHeight: number | null, cogcoinSyncTargetHeight: number | null, followScene: ReturnType<typeof createFollowSceneState>, statusFieldText?: string): void;
7
+ close(): void;
7
8
  }
8
9
  export declare function describeMiningVisualizerStatus(snapshot: MiningRuntimeStatusV1): string;
9
10
  export declare function describeMiningVisualizerProgress(snapshot: MiningRuntimeStatusV1): string;
@@ -11,7 +12,11 @@ export declare class MiningFollowVisualizer {
11
12
  #private;
12
13
  constructor(options?: {
13
14
  progressOutput?: ProgressOutputMode;
14
- stream?: RenderStream;
15
+ stream?: TtyRenderStream;
16
+ platform?: NodeJS.Platform;
17
+ env?: NodeJS.ProcessEnv;
18
+ clock?: RenderClock;
19
+ rendererFactory?: (stream: TtyRenderStream) => VisualizerRendererLike;
15
20
  });
16
21
  update(snapshot: MiningRuntimeStatusV1): void;
17
22
  close(): void;
@@ -1,5 +1,6 @@
1
1
  import { createBootstrapProgress } from "../../bitcoind/progress/formatting.js";
2
2
  import { createFollowSceneState, syncFollowSceneState, } from "../../bitcoind/progress/follow-scene.js";
3
+ import { DEFAULT_RENDER_CLOCK, resolveTtyRenderPolicy, TtyRenderThrottle, } from "../../bitcoind/progress/render-policy.js";
3
4
  import { TtyProgressRenderer } from "../../bitcoind/progress/tty-renderer.js";
4
5
  const VISUALIZER_PROGRESS_SNAPSHOT = {
5
6
  url: "",
@@ -8,15 +9,6 @@ const VISUALIZER_PROGRESS_SNAPSHOT = {
8
9
  sha256: "",
9
10
  sizeBytes: 1,
10
11
  };
11
- function shouldRenderToTty(progressOutput, stream) {
12
- if (progressOutput === "none") {
13
- return false;
14
- }
15
- if (progressOutput === "tty") {
16
- return true;
17
- }
18
- return stream.isTTY === true;
19
- }
20
12
  export function describeMiningVisualizerStatus(snapshot) {
21
13
  switch (snapshot.currentPhase) {
22
14
  case "resuming":
@@ -99,24 +91,52 @@ export function describeMiningVisualizerProgress(snapshot) {
99
91
  }
100
92
  export class MiningFollowVisualizer {
101
93
  #renderer;
94
+ #clock;
95
+ #renderThrottle;
102
96
  #progress = createBootstrapProgress("follow_tip", VISUALIZER_PROGRESS_SNAPSHOT);
103
97
  #scene = createFollowSceneState();
98
+ #latestSnapshot = null;
104
99
  constructor(options = {}) {
105
100
  const stream = options.stream ?? process.stderr;
106
101
  const progressOutput = options.progressOutput ?? "auto";
107
- this.#renderer = shouldRenderToTty(progressOutput, stream)
108
- ? new TtyProgressRenderer(stream)
102
+ const renderPolicy = resolveTtyRenderPolicy(progressOutput, stream, {
103
+ platform: options.platform,
104
+ env: options.env,
105
+ });
106
+ this.#clock = options.clock ?? DEFAULT_RENDER_CLOCK;
107
+ this.#renderer = renderPolicy.enabled
108
+ ? options.rendererFactory?.(stream) ?? new TtyProgressRenderer(stream)
109
109
  : null;
110
+ this.#renderThrottle = new TtyRenderThrottle({
111
+ clock: this.#clock,
112
+ intervalMs: renderPolicy.repaintIntervalMs,
113
+ onRender: () => {
114
+ this.#renderLatestSnapshot();
115
+ },
116
+ throttled: renderPolicy.linuxHeadlessThrottle,
117
+ });
110
118
  }
111
119
  update(snapshot) {
112
120
  if (this.#renderer === null) {
113
121
  return;
114
122
  }
123
+ this.#latestSnapshot = snapshot;
124
+ this.#renderThrottle.request();
125
+ }
126
+ close() {
127
+ this.#renderThrottle.flush();
128
+ this.#renderer?.close();
129
+ }
130
+ #renderLatestSnapshot() {
131
+ if (this.#renderer === null || this.#latestSnapshot === null) {
132
+ return;
133
+ }
134
+ const snapshot = this.#latestSnapshot;
115
135
  const indexedHeight = snapshot.indexerTipHeight ?? snapshot.coreBestHeight ?? null;
116
136
  const nodeHeight = snapshot.coreBestHeight ?? indexedHeight;
117
137
  this.#progress.phase = "follow_tip";
118
138
  this.#progress.message = describeMiningVisualizerProgress(snapshot);
119
- this.#progress.updatedAt = Date.now();
139
+ this.#progress.updatedAt = this.#clock.now();
120
140
  this.#progress.blocks = nodeHeight;
121
141
  this.#progress.targetHeight = nodeHeight;
122
142
  this.#progress.etaSeconds = null;
@@ -128,7 +148,4 @@ export class MiningFollowVisualizer {
128
148
  });
129
149
  this.#renderer.renderFollowScene(this.#progress, indexedHeight, nodeHeight, this.#scene, describeMiningVisualizerStatus(snapshot));
130
150
  }
131
- close() {
132
- this.#renderer?.close();
133
- }
134
151
  }
@@ -13,6 +13,7 @@ import { loadMiningRuntimeStatus } from "./mining/runtime-artifacts.js";
13
13
  import { resolveWalletRuntimePathsForTesting } from "./runtime.js";
14
14
  import { loadWalletExplicitLock } from "./state/explicit-lock.js";
15
15
  import { createDefaultWalletSecretProvider, createWalletRootId, createWalletSecretReference, } from "./state/provider.js";
16
+ import { loadWalletSeedIndex } from "./state/seed-index.js";
16
17
  import { extractWalletRootIdHintFromWalletStateEnvelope, loadRawWalletStateEnvelope, loadWalletState, saveWalletState, } from "./state/storage.js";
17
18
  import { confirmTypedAcknowledgement } from "./tx/confirm.js";
18
19
  function sanitizeWalletName(walletRootId) {
@@ -519,6 +520,12 @@ async function preflightReset(options) {
519
520
  }
520
521
  const tracked = await collectTrackedManagedProcesses(options.paths);
521
522
  const secretProviderKeyId = rawEnvelope?.envelope.secretProvider?.keyId ?? null;
523
+ const seedIndex = await loadWalletSeedIndex({
524
+ paths: options.paths,
525
+ }).catch(() => null);
526
+ const importedSeedSecretProviderKeyIds = [...new Set((seedIndex?.seeds ?? [])
527
+ .filter((seed) => seed.kind === "imported")
528
+ .map((seed) => createWalletSecretReference(seed.walletRootId).keyId))];
522
529
  return {
523
530
  dataRoot: options.paths.dataRoot,
524
531
  removedRoots,
@@ -531,6 +538,7 @@ async function preflightReset(options) {
531
538
  : "passphrase-wrapped",
532
539
  envelopeSource: rawEnvelope?.source ?? null,
533
540
  secretProviderKeyId,
541
+ importedSeedSecretProviderKeyIds,
534
542
  explicitLock,
535
543
  rawEnvelope,
536
544
  },
@@ -719,7 +727,8 @@ export async function previewResetWallet(options) {
719
727
  : null,
720
728
  },
721
729
  trackedProcessKinds: preflight.trackedProcessKinds,
722
- willDeleteOsSecrets: preflight.wallet.secretProviderKeyId !== null,
730
+ willDeleteOsSecrets: preflight.wallet.secretProviderKeyId !== null
731
+ || preflight.wallet.importedSeedSecretProviderKeyIds.length > 0,
723
732
  removedPaths,
724
733
  };
725
734
  }
@@ -766,9 +775,7 @@ export async function resetWallet(options) {
766
775
  let rootsDeleted = false;
767
776
  let committed = false;
768
777
  let newProviderKeyId = null;
769
- let secretCleanupStatus = preflight.wallet.secretProviderKeyId === null
770
- ? "not-found"
771
- : "not-found";
778
+ let secretCleanupStatus = "not-found";
772
779
  const deletedSecretRefs = [];
773
780
  const failedSecretRefs = [];
774
781
  const preservedSecretRefs = [];
@@ -850,41 +857,46 @@ export async function resetWallet(options) {
850
857
  await restoreStagedArtifacts(stagedSnapshotArtifacts);
851
858
  }
852
859
  committed = true;
853
- if (walletAction === "deleted") {
854
- if (preflight.wallet.secretProviderKeyId !== null) {
855
- try {
856
- await provider.deleteSecret(preflight.wallet.secretProviderKeyId);
857
- deletedSecretRefs.push(preflight.wallet.secretProviderKeyId);
858
- secretCleanupStatus = "deleted";
859
- }
860
- catch {
861
- failedSecretRefs.push(preflight.wallet.secretProviderKeyId);
862
- secretCleanupStatus = "failed";
863
- throw new Error("reset_secret_cleanup_failed");
864
- }
865
- }
866
- }
867
- else if (walletAction === "retain-mnemonic" && preflight.wallet.secretProviderKeyId !== null) {
860
+ const deleteTrackedSecretReference = async (keyId) => {
868
861
  try {
869
- if (preflight.wallet.secretProviderKeyId !== newProviderKeyId) {
870
- await provider.deleteSecret(preflight.wallet.secretProviderKeyId);
871
- deletedSecretRefs.push(preflight.wallet.secretProviderKeyId);
872
- secretCleanupStatus = "deleted";
873
- }
862
+ await provider.deleteSecret(keyId);
863
+ deletedSecretRefs.push(keyId);
874
864
  }
875
865
  catch {
876
- failedSecretRefs.push(preflight.wallet.secretProviderKeyId);
866
+ failedSecretRefs.push(keyId);
877
867
  secretCleanupStatus = "failed";
878
868
  throw new Error("reset_secret_cleanup_failed");
879
869
  }
870
+ };
871
+ for (const importedSecretKeyId of preflight.wallet.importedSeedSecretProviderKeyIds) {
872
+ await deleteTrackedSecretReference(importedSecretKeyId);
873
+ }
874
+ if (walletAction === "deleted") {
875
+ if (preflight.wallet.secretProviderKeyId !== null) {
876
+ await deleteTrackedSecretReference(preflight.wallet.secretProviderKeyId);
877
+ }
878
+ }
879
+ else if (walletAction === "retain-mnemonic" && preflight.wallet.secretProviderKeyId !== null) {
880
+ if (preflight.wallet.secretProviderKeyId !== newProviderKeyId) {
881
+ await deleteTrackedSecretReference(preflight.wallet.secretProviderKeyId);
882
+ }
880
883
  }
881
884
  else if (preflight.wallet.secretProviderKeyId !== null) {
882
885
  preservedSecretRefs.push(preflight.wallet.secretProviderKeyId);
883
886
  }
884
- if (preflight.wallet.secretProviderKeyId === null && preflight.wallet.present && preflight.wallet.rawEnvelope === null) {
887
+ if (failedSecretRefs.length > 0) {
888
+ secretCleanupStatus = "failed";
889
+ }
890
+ else if (deletedSecretRefs.length > 0) {
891
+ secretCleanupStatus = "deleted";
892
+ }
893
+ else if (preflight.wallet.secretProviderKeyId === null
894
+ && preflight.wallet.importedSeedSecretProviderKeyIds.length === 0
895
+ && preflight.wallet.present
896
+ && preflight.wallet.rawEnvelope === null) {
885
897
  secretCleanupStatus = "unknown";
886
898
  }
887
- else if (deletedSecretRefs.length === 0 && failedSecretRefs.length === 0) {
899
+ else if (deletedSecretRefs.length === 0) {
888
900
  secretCleanupStatus = "not-found";
889
901
  }
890
902
  return {
@@ -1,13 +1,23 @@
1
1
  import type { CogcoinPathResolution } from "../app-paths.js";
2
+ export type WalletSeedKind = "main" | "imported";
3
+ export interface WalletRuntimePathResolution extends CogcoinPathResolution {
4
+ seedName?: string | null;
5
+ }
2
6
  export interface WalletRuntimePaths {
3
7
  dataRoot: string;
4
8
  clientDataDir: string;
5
9
  clientConfigPath: string;
6
10
  runtimeRoot: string;
11
+ walletRuntimeRoot: string;
7
12
  hooksRoot: string;
8
13
  stateRoot: string;
14
+ walletStateRoot: string;
15
+ seedRegistryPath: string;
16
+ selectedSeedName: string;
17
+ selectedSeedKind: WalletSeedKind;
9
18
  bitcoinDataDir: string;
10
19
  indexerRoot: string;
20
+ walletStateDirectory: string;
11
21
  walletStatePath: string;
12
22
  walletStateBackupPath: string;
13
23
  walletInitPendingPath: string;
@@ -27,4 +37,5 @@ export interface WalletRuntimePaths {
27
37
  miningEventsPath: string;
28
38
  miningControlLockPath: string;
29
39
  }
30
- export declare function resolveWalletRuntimePathsForTesting(resolution?: CogcoinPathResolution): WalletRuntimePaths;
40
+ export declare function deriveWalletRuntimePathsForSeed(basePaths: WalletRuntimePaths, seedName: string | null | undefined): WalletRuntimePaths;
41
+ export declare function resolveWalletRuntimePathsForTesting(resolution?: WalletRuntimePathResolution): WalletRuntimePaths;