@cogcoin/client 0.5.4 → 0.5.6

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 (74) hide show
  1. package/README.md +1 -1
  2. package/dist/app-paths.d.ts +2 -0
  3. package/dist/app-paths.js +4 -0
  4. package/dist/art/wallet.txt +9 -9
  5. package/dist/bitcoind/bootstrap/chainstate.d.ts +2 -1
  6. package/dist/bitcoind/bootstrap/chainstate.js +4 -1
  7. package/dist/bitcoind/bootstrap/chunk-manifest.d.ts +14 -0
  8. package/dist/bitcoind/bootstrap/chunk-manifest.js +85 -0
  9. package/dist/bitcoind/bootstrap/chunk-recovery.d.ts +4 -0
  10. package/dist/bitcoind/bootstrap/chunk-recovery.js +122 -0
  11. package/dist/bitcoind/bootstrap/constants.d.ts +3 -1
  12. package/dist/bitcoind/bootstrap/constants.js +3 -1
  13. package/dist/bitcoind/bootstrap/controller.d.ts +10 -2
  14. package/dist/bitcoind/bootstrap/controller.js +56 -12
  15. package/dist/bitcoind/bootstrap/default-snapshot-chunk-manifest.d.ts +2 -0
  16. package/dist/bitcoind/bootstrap/default-snapshot-chunk-manifest.js +2309 -0
  17. package/dist/bitcoind/bootstrap/download.js +177 -83
  18. package/dist/bitcoind/bootstrap/headers.d.ts +16 -2
  19. package/dist/bitcoind/bootstrap/headers.js +124 -14
  20. package/dist/bitcoind/bootstrap/state.d.ts +11 -1
  21. package/dist/bitcoind/bootstrap/state.js +50 -23
  22. package/dist/bitcoind/bootstrap/types.d.ts +12 -1
  23. package/dist/bitcoind/client/factory.js +11 -2
  24. package/dist/bitcoind/client/internal-types.d.ts +1 -0
  25. package/dist/bitcoind/client/managed-client.d.ts +1 -1
  26. package/dist/bitcoind/client/managed-client.js +29 -15
  27. package/dist/bitcoind/client/sync-engine.js +88 -16
  28. package/dist/bitcoind/errors.js +9 -0
  29. package/dist/bitcoind/indexer-daemon.d.ts +7 -0
  30. package/dist/bitcoind/indexer-daemon.js +31 -22
  31. package/dist/bitcoind/processing-start-height.d.ts +7 -0
  32. package/dist/bitcoind/processing-start-height.js +9 -0
  33. package/dist/bitcoind/progress/controller.js +1 -0
  34. package/dist/bitcoind/progress/formatting.js +4 -1
  35. package/dist/bitcoind/retryable-rpc.d.ts +11 -0
  36. package/dist/bitcoind/retryable-rpc.js +30 -0
  37. package/dist/bitcoind/service.d.ts +16 -1
  38. package/dist/bitcoind/service.js +228 -115
  39. package/dist/bitcoind/testing.d.ts +1 -1
  40. package/dist/bitcoind/testing.js +1 -1
  41. package/dist/bitcoind/types.d.ts +10 -0
  42. package/dist/cli/commands/follow.js +9 -0
  43. package/dist/cli/commands/service-runtime.js +150 -134
  44. package/dist/cli/commands/sync.js +9 -0
  45. package/dist/cli/commands/wallet-admin.js +77 -21
  46. package/dist/cli/context.js +4 -2
  47. package/dist/cli/mutation-json.js +2 -0
  48. package/dist/cli/output.js +3 -1
  49. package/dist/cli/parse.d.ts +1 -1
  50. package/dist/cli/parse.js +6 -0
  51. package/dist/cli/preview-json.js +2 -0
  52. package/dist/cli/runner.js +1 -0
  53. package/dist/cli/types.d.ts +6 -3
  54. package/dist/cli/types.js +1 -1
  55. package/dist/cli/wallet-format.js +134 -14
  56. package/dist/wallet/lifecycle.d.ts +6 -0
  57. package/dist/wallet/lifecycle.js +168 -37
  58. package/dist/wallet/read/context.js +10 -4
  59. package/dist/wallet/reset.d.ts +61 -2
  60. package/dist/wallet/reset.js +208 -63
  61. package/dist/wallet/root-resolution.d.ts +20 -0
  62. package/dist/wallet/root-resolution.js +37 -0
  63. package/dist/wallet/runtime.d.ts +3 -0
  64. package/dist/wallet/runtime.js +3 -0
  65. package/dist/wallet/state/crypto.d.ts +3 -0
  66. package/dist/wallet/state/crypto.js +3 -0
  67. package/dist/wallet/state/pending-init.d.ts +24 -0
  68. package/dist/wallet/state/pending-init.js +59 -0
  69. package/dist/wallet/state/provider.d.ts +1 -0
  70. package/dist/wallet/state/provider.js +7 -1
  71. package/dist/wallet/state/storage.d.ts +7 -1
  72. package/dist/wallet/state/storage.js +39 -0
  73. package/dist/wallet/types.d.ts +9 -0
  74. package/package.json +4 -2
@@ -1,3 +1,3 @@
1
1
  import type { ParsedCliArgs } from "./types.js";
2
- export declare const HELP_TEXT = "Usage: cogcoin <command> [options]\n\nCommands:\n status Show wallet-aware local service and chain status\n status --output json Emit the stable v1 machine-readable status envelope\n bitcoin start Start the managed Bitcoin daemon\n bitcoin stop Stop the managed Bitcoin daemon and paired indexer\n bitcoin status Show managed Bitcoin daemon status without starting it\n indexer start Start the managed Cogcoin indexer (and bitcoind if needed)\n indexer stop Stop the managed Cogcoin indexer only\n indexer status Show managed Cogcoin indexer status without starting it\n init Initialize a new local wallet root\n init --output json Emit the stable v1 machine-readable init result envelope\n restore Restore a fresh local wallet from a 24-word mnemonic; run sync afterward\n reset Factory-reset local Cogcoin state with interactive retention prompts\n repair Recover bounded wallet/indexer/runtime state\n unlock Clear an explicit wallet lock and unlock for a limited duration\n wallet address Alias for address\n wallet ids Alias for ids\n hooks enable mining Enable a validated custom mining hook\n hooks disable mining Return to built-in mining hooks\n hooks status Show mining hook validation and trust status\n mine Run the miner in the foreground\n mine start Start the miner as a background worker\n mine stop Stop the active background miner\n mine setup Configure the built-in mining provider\n mine setup --output json\n Emit the stable v1 machine-readable mine setup result envelope\n mine status Show mining control-plane health and readiness\n mine log Show recent mining control-plane events\n anchor <domain> Anchor an owned unanchored domain with the Tx1/Tx2 family\n register <domain> [--from <identity>]\n Register a root domain or subdomain\n transfer <domain> --to <btc-target>\n Transfer an unanchored domain to another BTC identity\n sell <domain> <price> List an unanchored domain for sale in COG\n unsell <domain> Clear an active domain listing\n buy <domain> [--from <identity>]\n Buy an unanchored listed domain in COG\n send <amount> --to <btc-target>\n Send COG from one local identity to another BTC target\n claim <lock-id> --preimage <32-byte-hex>\n Claim an active COG lock before timeout\n reclaim <lock-id> Reclaim an expired COG lock as the original locker\n cog lock <amount> --to-domain <domain> (--for <blocks-or-duration> | --until-height <height>) --condition <sha256hex>\n Lock COG to an anchored recipient domain\n wallet status Show detailed wallet-local status and service health\n wallet init Initialize a new local wallet root\n wallet restore Restore a fresh local wallet from a 24-word mnemonic; run sync afterward\n wallet unlock Clear an explicit wallet lock and unlock for a limited duration\n wallet lock Lock the local wallet and disable on-demand unlock\n wallet export <path> Export a portable encrypted wallet archive\n wallet import <path> Import a portable encrypted wallet archive\n address Show the BTC funding identity for this wallet\n ids List locally controlled identities\n balance Show per-identity COG balances\n locks Show locally related active COG locks\n domain list Alias for domains\n domain show <domain> Alias for show <domain>\n domains [--anchored] [--listed] [--mineable]\n Show locally related domains\n show <domain> Show one domain and its local-wallet relationship\n fields <domain> List current fields on a domain\n field <domain> <field> Show one current field value\n field create <domain> <field>\n Create a new anchored field, optionally with an initial value\n field set <domain> <field>\n Update an existing anchored field value\n field clear <domain> <field>\n Clear an existing anchored field value\n rep give <source-domain> <target-domain> <amount>\n Burn COG as anchored-domain reputation support\n rep revoke <source-domain> <target-domain> <amount>\n Revoke visible support without refunding burned COG\n\nOptions:\n --db <path> Override the SQLite database path\n --data-dir <path> Override the managed bitcoin datadir\n --for <duration> Unlock duration like 15m, 2h, or 1d when unlocking explicitly\n --message <text> Founding message text for anchor\n --to <btc-target> Transfer or send target as an address or spk:<hex>\n --from <identity> Resolve sender identity as id:<n>, domain:<name>, address, or spk:<hex>\n --to-domain <domain>\n Recipient domain for cog lock\n --condition <sha256hex>\n 32-byte lock condition hash\n --until-height <height>\n Absolute timeout height for cog lock\n --preimage <32-byte-hex>\n Claim preimage for an active lock\n --review <text> Optional public review text for reputation operations\n --text <utf8> UTF-8 payload text for endpoint or field writes\n --json <json> UTF-8 payload JSON text for endpoint or field writes\n --bytes <spec> Payload bytes as hex:<hex> or @<path>\n --permanent Create the field as permanent\n --format <spec> Advanced field format as raw:<u8>\n --value <spec> Advanced field value as hex:<hex>, @<path>, or utf8:<text>\n --claimable Show only currently claimable locks\n --reclaimable Show only currently reclaimable locks\n --anchored Show only anchored domains\n --listed Show only currently listed domains\n --mineable Show only locally mineable root domains\n --limit <n> Limit list rows (1..1000)\n --all Show all rows for list commands\n --verify Run full custom-hook verification\n --follow Follow mining log output\n --output <mode> Output mode: text, json, or preview-json\n --progress <mode> Progress output mode: auto, tty, or none\n --force-race Allow a visible root registration race\n --yes Approve eligible plain yes/no mutation confirmations non-interactively\n --help Show help\n --version Show package version\n\nQuickstart:\n 1. Run `cogcoin init` to create the wallet.\n 2. Run `cogcoin sync` to bootstrap assumeutxo and the managed Bitcoin/indexer state.\n 3. Run `cogcoin address`, then fund the wallet with about 0.0015 BTC so you can buy a 6+ character domain to start mining and still keep BTC available for mining transaction fees.\n\nExamples:\n cogcoin status --output json\n cogcoin bitcoin status\n cogcoin indexer status\n cogcoin init --output json\n cogcoin wallet address\n cogcoin domain list --mineable\n cogcoin register alpha-child\n cogcoin register weatherbot --from id:1\n cogcoin anchor alpha\n cogcoin buy alpha\n cogcoin buy alpha --from id:1\n cogcoin field create alpha bio --text \"hello\"\n cogcoin rep give alpha beta 10 --review \"great operator\"\n cogcoin hooks status\n cogcoin mine setup --output json\n cogcoin register alpha-child --output preview-json\n cogcoin mine status\n";
2
+ export declare const HELP_TEXT = "Usage: cogcoin <command> [options]\n\nCommands:\n status Show wallet-aware local service and chain status\n status --output json Emit the stable v1 machine-readable status envelope\n bitcoin start Start the managed Bitcoin daemon\n bitcoin stop Stop the managed Bitcoin daemon and paired indexer\n bitcoin status Show managed Bitcoin daemon status without starting it\n indexer start Start the managed Cogcoin indexer (and bitcoind if needed)\n indexer stop Stop the managed Cogcoin indexer only\n indexer status Show managed Cogcoin indexer status without starting it\n init Initialize a new local wallet root\n init --output json Emit the stable v1 machine-readable init result envelope\n restore Restore a fresh local wallet from a 24-word mnemonic; run sync afterward\n reset Factory-reset local Cogcoin state with interactive retention prompts\n repair Recover bounded wallet/indexer/runtime state\n unlock Clear an explicit wallet lock and unlock for a limited duration\n wallet address Alias for address\n wallet ids Alias for ids\n hooks enable mining Enable a validated custom mining hook\n hooks disable mining Return to built-in mining hooks\n hooks status Show mining hook validation and trust status\n mine Run the miner in the foreground\n mine start Start the miner as a background worker\n mine stop Stop the active background miner\n mine setup Configure the built-in mining provider\n mine setup --output json\n Emit the stable v1 machine-readable mine setup result envelope\n mine status Show mining control-plane health and readiness\n mine log Show recent mining control-plane events\n anchor <domain> Anchor an owned unanchored domain with the Tx1/Tx2 family\n register <domain> [--from <identity>]\n Register a root domain or subdomain\n transfer <domain> --to <btc-target>\n Transfer an unanchored domain to another BTC identity\n sell <domain> <price> List an unanchored domain for sale in COG\n unsell <domain> Clear an active domain listing\n buy <domain> [--from <identity>]\n Buy an unanchored listed domain in COG\n send <amount> --to <btc-target>\n Send COG from one local identity to another BTC target\n claim <lock-id> --preimage <32-byte-hex>\n Claim an active COG lock before timeout\n reclaim <lock-id> Reclaim an expired COG lock as the original locker\n cog lock <amount> --to-domain <domain> (--for <blocks-or-duration> | --until-height <height>) --condition <sha256hex>\n Lock COG to an anchored recipient domain\n wallet status Show detailed wallet-local status and service health\n wallet init Initialize a new local wallet root\n wallet restore Restore a fresh local wallet from a 24-word mnemonic; run sync afterward\n wallet show-mnemonic Reveal the initialized wallet recovery phrase after typed confirmation\n wallet unlock Clear an explicit wallet lock and unlock for a limited duration\n wallet lock Lock the local wallet and disable on-demand unlock\n wallet export <path> Export a portable encrypted wallet archive\n wallet import <path> Import a portable encrypted wallet archive\n address Show the BTC funding identity for this wallet\n ids List locally controlled identities\n balance Show per-identity COG balances\n locks Show locally related active COG locks\n domain list Alias for domains\n domain show <domain> Alias for show <domain>\n domains [--anchored] [--listed] [--mineable]\n Show locally related domains\n show <domain> Show one domain and its local-wallet relationship\n fields <domain> List current fields on a domain\n field <domain> <field> Show one current field value\n field create <domain> <field>\n Create a new anchored field, optionally with an initial value\n field set <domain> <field>\n Update an existing anchored field value\n field clear <domain> <field>\n Clear an existing anchored field value\n rep give <source-domain> <target-domain> <amount>\n Burn COG as anchored-domain reputation support\n rep revoke <source-domain> <target-domain> <amount>\n Revoke visible support without refunding burned COG\n\nOptions:\n --db <path> Override the SQLite database path\n --data-dir <path> Override the managed bitcoin datadir\n --for <duration> Unlock duration like 15m, 2h, or 1d when unlocking explicitly\n --message <text> Founding message text for anchor\n --to <btc-target> Transfer or send target as an address or spk:<hex>\n --from <identity> Resolve sender identity as id:<n>, domain:<name>, address, or spk:<hex>\n --to-domain <domain>\n Recipient domain for cog lock\n --condition <sha256hex>\n 32-byte lock condition hash\n --until-height <height>\n Absolute timeout height for cog lock\n --preimage <32-byte-hex>\n Claim preimage for an active lock\n --review <text> Optional public review text for reputation operations\n --text <utf8> UTF-8 payload text for endpoint or field writes\n --json <json> UTF-8 payload JSON text for endpoint or field writes\n --bytes <spec> Payload bytes as hex:<hex> or @<path>\n --permanent Create the field as permanent\n --format <spec> Advanced field format as raw:<u8>\n --value <spec> Advanced field value as hex:<hex>, @<path>, or utf8:<text>\n --claimable Show only currently claimable locks\n --reclaimable Show only currently reclaimable locks\n --anchored Show only anchored domains\n --listed Show only currently listed domains\n --mineable Show only locally mineable root domains\n --limit <n> Limit list rows (1..1000)\n --all Show all rows for list commands\n --verify Run full custom-hook verification\n --follow Follow mining log output\n --output <mode> Output mode: text, json, or preview-json\n --progress <mode> Progress output mode: auto, tty, or none\n --force-race Allow a visible root registration race\n --yes Approve eligible plain yes/no mutation confirmations non-interactively\n --help Show help\n --version Show package version\n\nQuickstart:\n 1. Run `cogcoin init` to create the wallet.\n 2. Run `cogcoin sync` to bootstrap assumeutxo and the managed Bitcoin/indexer state.\n 3. Run `cogcoin address`, then fund the wallet with about 0.0015 BTC so you can buy a 6+ character domain to start mining and still keep BTC available for mining transaction fees.\n\nExamples:\n cogcoin status --output json\n cogcoin bitcoin status\n cogcoin indexer status\n cogcoin init --output json\n cogcoin wallet address\n cogcoin domain list --mineable\n cogcoin register alpha-child\n cogcoin register weatherbot --from id:1\n cogcoin anchor alpha\n cogcoin buy alpha\n cogcoin buy alpha --from id:1\n cogcoin field create alpha bio --text \"hello\"\n cogcoin rep give alpha beta 10 --review \"great operator\"\n cogcoin hooks status\n cogcoin mine setup --output json\n cogcoin register alpha-child --output preview-json\n cogcoin mine status\n";
3
3
  export declare function parseCliArgs(argv: string[]): ParsedCliArgs;
package/dist/cli/parse.js CHANGED
@@ -48,6 +48,7 @@ Commands:
48
48
  wallet status Show detailed wallet-local status and service health
49
49
  wallet init Initialize a new local wallet root
50
50
  wallet restore Restore a fresh local wallet from a 24-word mnemonic; run sync afterward
51
+ wallet show-mnemonic Reveal the initialized wallet recovery phrase after typed confirmation
51
52
  wallet unlock Clear an explicit wallet lock and unlock for a limited duration
52
53
  wallet lock Lock the local wallet and disable on-demand unlock
53
54
  wallet export <path> Export a portable encrypted wallet archive
@@ -451,6 +452,11 @@ export function parseCliArgs(argv) {
451
452
  index += 1;
452
453
  continue;
453
454
  }
455
+ if (subcommand === "show-mnemonic") {
456
+ command = "wallet-show-mnemonic";
457
+ index += 1;
458
+ continue;
459
+ }
454
460
  if (subcommand === "unlock") {
455
461
  command = "wallet-unlock";
456
462
  index += 1;
@@ -234,6 +234,7 @@ export function buildResetPreviewData(result) {
234
234
  trackedProcessKinds: result.trackedProcessKinds,
235
235
  willDeleteOsSecrets: result.willDeleteOsSecrets,
236
236
  bootstrapSnapshot: result.bootstrapSnapshot,
237
+ bitcoinDataDir: result.bitcoinDataDir,
237
238
  walletPrompt: result.walletPrompt,
238
239
  },
239
240
  operation: {
@@ -241,6 +242,7 @@ export function buildResetPreviewData(result) {
241
242
  confirmationPhrase: result.confirmationPhrase,
242
243
  walletPrompt: result.walletPrompt,
243
244
  bootstrapSnapshot: result.bootstrapSnapshot,
245
+ bitcoinDataDir: result.bitcoinDataDir,
244
246
  trackedProcessKinds: result.trackedProcessKinds,
245
247
  willDeleteOsSecrets: result.willDeleteOsSecrets,
246
248
  removedPaths: result.removedPaths,
@@ -77,6 +77,7 @@ export async function runCli(argv, contextOverrides = {}) {
77
77
  || parsed.command === "wallet-import"
78
78
  || parsed.command === "wallet-init"
79
79
  || parsed.command === "wallet-restore"
80
+ || parsed.command === "wallet-show-mnemonic"
80
81
  || parsed.command === "wallet-unlock"
81
82
  || parsed.command === "wallet-lock") {
82
83
  return runWalletAdminCommand(parsed, context);
@@ -5,17 +5,17 @@ import { attachOrStartManagedBitcoindService, probeManagedBitcoindService, stopM
5
5
  import { openSqliteStore } from "../sqlite/index.js";
6
6
  import type { ClientStoreAdapter } from "../types.js";
7
7
  import type { WalletRuntimePaths } from "../wallet/runtime.js";
8
- import type { exportWallet, WalletPrompter, importWallet, initializeWallet, lockWallet, previewResetWallet, repairWallet, resetWallet, restoreWalletFromMnemonic, unlockWallet } from "../wallet/lifecycle.js";
8
+ import type { exportWallet, WalletPrompter, importWallet, initializeWallet, lockWallet, previewResetWallet, repairWallet, resetWallet, restoreWalletFromMnemonic, showWalletMnemonic, unlockWallet } from "../wallet/lifecycle.js";
9
9
  import type { openWalletReadContext } from "../wallet/read/index.js";
10
10
  import { loadWalletExplicitLock } from "../wallet/state/explicit-lock.js";
11
11
  import { loadUnlockSession } from "../wallet/state/session.js";
12
- import { loadWalletState } from "../wallet/state/storage.js";
12
+ import { loadRawWalletStateEnvelope, loadWalletState } from "../wallet/state/storage.js";
13
13
  import type { WalletSecretProvider } from "../wallet/state/provider.js";
14
14
  import type { disableMiningHooks, enableMiningHooks, followMiningLog, inspectMiningControlPlane, readMiningLog, runForegroundMining, setupBuiltInMining, startBackgroundMining, stopBackgroundMining } from "../wallet/mining/index.js";
15
15
  import type { anchorDomain, buyDomain, claimCogLock, clearDomainDelegate, clearDomainEndpoint, clearDomainMiner, clearField, createField, giveReputation, lockCogToDomain, registerDomain, reclaimCogLock, revokeReputation, sendCog, setField, setDomainCanonical, setDomainDelegate, setDomainEndpoint, setDomainMiner, sellDomain, transferDomain } from "../wallet/tx/index.js";
16
16
  export type ProgressOutput = "auto" | "tty" | "none";
17
17
  export type OutputMode = "text" | "json" | "preview-json";
18
- export type CommandName = "init" | "restore" | "reset" | "repair" | "sync" | "status" | "follow" | "bitcoin-start" | "bitcoin-stop" | "bitcoin-status" | "indexer-start" | "indexer-stop" | "indexer-status" | "unlock" | "anchor" | "domain-anchor" | "register" | "domain-register" | "transfer" | "domain-transfer" | "sell" | "domain-sell" | "unsell" | "domain-unsell" | "buy" | "domain-buy" | "domain-endpoint-set" | "domain-endpoint-clear" | "domain-delegate-set" | "domain-delegate-clear" | "domain-miner-set" | "domain-miner-clear" | "domain-canonical" | "field-list" | "field-show" | "field-create" | "field-set" | "field-clear" | "send" | "claim" | "reclaim" | "cog-send" | "cog-claim" | "cog-reclaim" | "cog-lock" | "rep-give" | "rep-revoke" | "cog-balance" | "cog-locks" | "hooks-mining-enable" | "hooks-mining-disable" | "hooks-mining-status" | "mine" | "mine-start" | "mine-stop" | "mine-setup" | "mine-status" | "mine-log" | "wallet-export" | "wallet-import" | "wallet-init" | "wallet-restore" | "wallet-lock" | "wallet-unlock" | "wallet-status" | "wallet-address" | "wallet-ids" | "address" | "ids" | "balance" | "locks" | "domain-list" | "domains" | "domain-show" | "show" | "fields" | "field";
18
+ export type CommandName = "init" | "restore" | "reset" | "repair" | "sync" | "status" | "follow" | "bitcoin-start" | "bitcoin-stop" | "bitcoin-status" | "indexer-start" | "indexer-stop" | "indexer-status" | "unlock" | "anchor" | "domain-anchor" | "register" | "domain-register" | "transfer" | "domain-transfer" | "sell" | "domain-sell" | "unsell" | "domain-unsell" | "buy" | "domain-buy" | "domain-endpoint-set" | "domain-endpoint-clear" | "domain-delegate-set" | "domain-delegate-clear" | "domain-miner-set" | "domain-miner-clear" | "domain-canonical" | "field-list" | "field-show" | "field-create" | "field-set" | "field-clear" | "send" | "claim" | "reclaim" | "cog-send" | "cog-claim" | "cog-reclaim" | "cog-lock" | "rep-give" | "rep-revoke" | "cog-balance" | "cog-locks" | "hooks-mining-enable" | "hooks-mining-disable" | "hooks-mining-status" | "mine" | "mine-start" | "mine-stop" | "mine-setup" | "mine-status" | "mine-log" | "wallet-export" | "wallet-import" | "wallet-init" | "wallet-restore" | "wallet-show-mnemonic" | "wallet-lock" | "wallet-unlock" | "wallet-status" | "wallet-address" | "wallet-ids" | "address" | "ids" | "balance" | "locks" | "domain-list" | "domains" | "domain-show" | "show" | "fields" | "field";
19
19
  export interface WritableLike {
20
20
  isTTY?: boolean;
21
21
  write(chunk: string): void;
@@ -93,6 +93,7 @@ export interface CliRunnerContext {
93
93
  store: ClientStoreAdapter;
94
94
  databasePath?: string;
95
95
  dataDir?: string;
96
+ walletRootId?: string;
96
97
  progressOutput?: ProgressOutput;
97
98
  }) => Promise<ManagedClientLike>;
98
99
  attachManagedBitcoindService?: typeof attachOrStartManagedBitcoindService;
@@ -106,6 +107,7 @@ export interface CliRunnerContext {
106
107
  inspectPassiveClientStatus?: typeof inspectPassiveClientStatus;
107
108
  openWalletReadContext?: typeof openWalletReadContext;
108
109
  loadWalletState?: typeof loadWalletState;
110
+ loadRawWalletStateEnvelope?: typeof loadRawWalletStateEnvelope;
109
111
  loadUnlockSession?: typeof loadUnlockSession;
110
112
  loadWalletExplicitLock?: typeof loadWalletExplicitLock;
111
113
  initializeWallet?: typeof initializeWallet;
@@ -113,6 +115,7 @@ export interface CliRunnerContext {
113
115
  previewResetWallet?: typeof previewResetWallet;
114
116
  exportWallet?: typeof exportWallet;
115
117
  importWallet?: typeof importWallet;
118
+ showWalletMnemonic?: typeof showWalletMnemonic;
116
119
  unlockWallet?: typeof unlockWallet;
117
120
  lockWallet?: typeof lockWallet;
118
121
  registerDomain?: typeof registerDomain;
package/dist/cli/types.js CHANGED
@@ -5,4 +5,4 @@ import { attachOrStartManagedBitcoindService, probeManagedBitcoindService, stopM
5
5
  import { openSqliteStore } from "../sqlite/index.js";
6
6
  import { loadWalletExplicitLock } from "../wallet/state/explicit-lock.js";
7
7
  import { loadUnlockSession } from "../wallet/state/session.js";
8
- import { loadWalletState } from "../wallet/state/storage.js";
8
+ import { loadRawWalletStateEnvelope, loadWalletState } from "../wallet/state/storage.js";
@@ -329,24 +329,144 @@ function appendWalletAvailability(lines, context) {
329
329
  lines.push(`Mutation note: ${mutationRecommendation}`);
330
330
  }
331
331
  }
332
- export function formatWalletOverviewReport(context) {
332
+ function overviewEntry(text, ok) {
333
+ return { text, ok };
334
+ }
335
+ function formatOverviewSection(header, entries) {
336
+ return [header, ...entries.map((entry) => `${entry.ok ? "✓" : "✗"} ${entry.text}`)].join("\n");
337
+ }
338
+ function isMiningOverviewOk(mining) {
339
+ return mining.runtime.bitcoindHealth === "ready"
340
+ && mining.runtime.nodeHealth === "synced"
341
+ && mining.runtime.indexerHealth === "synced"
342
+ && mining.runtime.miningState !== "repair-required"
343
+ && mining.runtime.miningState !== "paused-stale"
344
+ && !(mining.runtime.miningState === "paused" && mining.runtime.liveMiningFamilyInMempool === true);
345
+ }
346
+ function buildOverviewPathsSection(context) {
347
+ return [
348
+ overviewEntry(`DB path: ${context.databasePath}`, true),
349
+ overviewEntry(`Bitcoin datadir: ${context.dataDir}`, true),
350
+ ];
351
+ }
352
+ function buildOverviewWalletSection(context) {
353
+ const walletRoot = context.model?.walletRootId ?? context.localState.walletRootId ?? context.nodeStatus?.walletRootId ?? "none";
333
354
  const lines = [
334
- "Cogcoin Status",
335
- `DB path: ${context.databasePath}`,
336
- `Bitcoin datadir: ${context.dataDir}`,
355
+ overviewEntry(`State: ${context.localState.availability}`, context.localState.availability === "ready"),
356
+ overviewEntry(`Root: ${walletRoot}`, walletRoot !== "none"),
357
+ overviewEntry(`Unlock: ${formatUnlockExpiry(context.localState.unlockUntilUnixMs)}`, context.localState.unlockUntilUnixMs !== null),
337
358
  ];
338
- appendWalletAvailability(lines, context);
339
- appendServiceSummary(lines, context);
340
- if (context.model !== null) {
341
- lines.push(`Local identities: ${context.model.identities.length}`);
342
- lines.push(`Locally related domains: ${context.model.domains.length}`);
343
- lines.push(`Read-only identities: ${context.model.readOnlyIdentityCount}`);
359
+ if (context.localState.message !== null) {
360
+ lines.push(overviewEntry(`Note: ${context.localState.message}`, false));
344
361
  }
345
- else {
346
- lines.push("Wallet-derived sections: unavailable");
362
+ const nodeStatus = context.nodeStatus;
363
+ const replica = nodeStatus?.walletReplica ?? null;
364
+ if (replica !== null) {
365
+ lines.push(overviewEntry(`Managed Core wallet: ${replica.proofStatus ?? "not-proven"}`, replica.proofStatus === "ready"));
347
366
  }
348
- appendPendingMutationSummary(lines, context);
349
- return lines.join("\n");
367
+ if (nodeStatus?.walletReplicaMessage) {
368
+ lines.push(overviewEntry(`Managed Core note: ${nodeStatus.walletReplicaMessage}`, replica?.proofStatus === "ready"));
369
+ }
370
+ return lines;
371
+ }
372
+ function buildOverviewServicesSection(context) {
373
+ const bitcoindOk = context.bitcoind.health === "ready";
374
+ const nodeOk = context.nodeHealth === "synced";
375
+ const indexerOk = context.indexer.health === "synced";
376
+ const lines = [
377
+ overviewEntry(`Managed bitcoind: ${formatServiceHealth(context.bitcoind.health)}`, bitcoindOk),
378
+ ];
379
+ if (context.bitcoind.message !== null) {
380
+ lines.push(overviewEntry(`Managed bitcoind note: ${context.bitcoind.message}`, bitcoindOk));
381
+ }
382
+ lines.push(overviewEntry(`Bitcoin service: ${formatServiceHealth(context.nodeHealth)}`, nodeOk));
383
+ if (context.nodeStatus !== null) {
384
+ lines.push(overviewEntry(`Bitcoin best height: ${formatMaybe(context.nodeStatus.nodeBestHeight)}`, nodeOk));
385
+ lines.push(overviewEntry(`Bitcoin headers: ${formatMaybe(context.nodeStatus.nodeHeaderHeight)}`, nodeOk));
386
+ }
387
+ if (context.nodeMessage !== null) {
388
+ lines.push(overviewEntry(`Bitcoin note: ${context.nodeMessage}`, nodeOk));
389
+ }
390
+ lines.push(overviewEntry(`Indexer service: ${formatServiceHealth(context.indexer.health)}`, indexerOk));
391
+ lines.push(overviewEntry(`Indexer truth source: ${formatIndexerTruthSource(context.indexer.source)}`, indexerOk));
392
+ if (context.indexer.daemonInstanceId !== null && context.indexer.daemonInstanceId !== undefined) {
393
+ lines.push(overviewEntry(`Indexer daemon instance: ${context.indexer.daemonInstanceId}`, indexerOk));
394
+ }
395
+ if (context.indexer.snapshotSeq !== null && context.indexer.snapshotSeq !== undefined) {
396
+ lines.push(overviewEntry(`Indexer snapshot sequence: ${context.indexer.snapshotSeq}`, indexerOk));
397
+ }
398
+ if (context.indexer.status?.reorgDepth !== null && context.indexer.status?.reorgDepth !== undefined) {
399
+ lines.push(overviewEntry(`Indexer reorg depth: ${context.indexer.status.reorgDepth}`, indexerOk));
400
+ }
401
+ lines.push(overviewEntry(`Indexer tip height: ${context.indexer.snapshotTip === null ? "unavailable" : context.indexer.snapshotTip.height}`, indexerOk));
402
+ if (context.indexer.message !== null) {
403
+ lines.push(overviewEntry(`Indexer note: ${context.indexer.message}`, indexerOk));
404
+ }
405
+ if (context.mining !== undefined) {
406
+ const miningOk = isMiningOverviewOk(context.mining);
407
+ lines.push(overviewEntry(`Mining: ${formatMiningSummaryLine(context.mining)}`, miningOk));
408
+ if (context.mining.runtime.note !== null) {
409
+ lines.push(overviewEntry(`Mining note: ${context.mining.runtime.note}`, miningOk));
410
+ }
411
+ }
412
+ return lines;
413
+ }
414
+ function buildOverviewLocalInventorySection(context) {
415
+ if (context.model === null) {
416
+ return [overviewEntry("Status: Wallet-derived sections unavailable", false)];
417
+ }
418
+ return [
419
+ overviewEntry(`Local identities: ${context.model.identities.length}`, true),
420
+ overviewEntry(`Locally related domains: ${context.model.domains.length}`, true),
421
+ overviewEntry(`Read-only identities: ${context.model.readOnlyIdentityCount}`, true),
422
+ ];
423
+ }
424
+ function buildOverviewPendingWorkSection(context) {
425
+ const pendingFamilies = (context.localState.state?.proactiveFamilies ?? [])
426
+ .filter((family) => (family.type === "anchor" || family.type === "field")
427
+ && family.status !== "confirmed"
428
+ && family.status !== "canceled");
429
+ const pendingMutations = (context.localState.state?.pendingMutations ?? [])
430
+ .filter((mutation) => mutation.status !== "confirmed" && mutation.status !== "canceled");
431
+ if (pendingFamilies.length === 0 && pendingMutations.length === 0) {
432
+ return [overviewEntry("Status: none", true)];
433
+ }
434
+ const lines = [];
435
+ for (const family of pendingFamilies) {
436
+ const label = family.type === "field"
437
+ ? `Field family: ${family.domainName ?? "unknown"}.${family.fieldName ?? "unknown"}`
438
+ : `Anchor family: ${family.domainName ?? "unknown"}`;
439
+ lines.push(overviewEntry(`${label} ${family.status}${family.currentStep === null || family.currentStep === undefined ? "" : ` step ${family.currentStep}`}${family.reservedDedicatedIndex == null ? "" : ` index ${family.reservedDedicatedIndex}`}`, false));
440
+ }
441
+ for (const mutation of pendingMutations) {
442
+ lines.push(overviewEntry(`Mutation: ${formatPendingMutationSummaryLabel(mutation)} ${mutation.status} sender spk:${mutation.senderScriptPubKeyHex}${mutation.priceCogtoshi === undefined || mutation.priceCogtoshi === null ? "" : ` price ${formatCogAmount(mutation.priceCogtoshi)}`}${mutation.amountCogtoshi === undefined || mutation.amountCogtoshi === null ? "" : ` amount ${formatCogAmount(mutation.amountCogtoshi)}`}${isReputationMutation(mutation) ? "" : mutation.recipientDomainName === undefined || mutation.recipientDomainName === null ? "" : ` domain ${mutation.recipientDomainName}`}${mutation.lockId === undefined || mutation.lockId === null ? "" : ` lock ${mutation.lockId}`}${mutation.recipientScriptPubKeyHex === undefined || mutation.recipientScriptPubKeyHex === null ? "" : ` recipient spk:${mutation.recipientScriptPubKeyHex}`}${mutation.kind === "endpoint" ? (mutation.endpointValueHex === "" ? " endpoint clear" : ` endpoint-bytes ${(mutation.endpointValueHex?.length ?? 0) / 2}`) : ""}${mutation.kind === "field-create" || mutation.kind === "field-set" ? ` format ${formatFieldFormat(mutation.fieldFormat ?? 0)}` : ""}${mutation.kind === "field-clear" ? " clear" : ""}${mutation.reviewPayloadHex === undefined || mutation.reviewPayloadHex === null ? "" : " review"}`, false));
443
+ }
444
+ return lines;
445
+ }
446
+ function getOverviewNextStep(context) {
447
+ const repairRecommendation = getRepairRecommendation(context);
448
+ if (repairRecommendation !== null) {
449
+ return repairRecommendation;
450
+ }
451
+ if (getBootstrapSyncNextStep(context) !== null) {
452
+ return "Run `cogcoin sync` to bootstrap assumeutxo and the managed Bitcoin/indexer state.";
453
+ }
454
+ return getMutationRecommendation(context);
455
+ }
456
+ export function formatWalletOverviewReport(context) {
457
+ const parts = [
458
+ "\n⛭ Cogcoin Status ⛭",
459
+ formatOverviewSection("Paths", buildOverviewPathsSection(context)),
460
+ formatOverviewSection("Wallet", buildOverviewWalletSection(context)),
461
+ formatOverviewSection("Services", buildOverviewServicesSection(context)),
462
+ formatOverviewSection("Local Inventory", buildOverviewLocalInventorySection(context)),
463
+ formatOverviewSection("Pending Work", buildOverviewPendingWorkSection(context)),
464
+ ];
465
+ const nextStep = getOverviewNextStep(context);
466
+ if (nextStep !== null) {
467
+ parts.push(`Next step: ${nextStep}`);
468
+ }
469
+ return parts.join("\n\n");
350
470
  }
351
471
  export function formatDetailedWalletStatusReport(context) {
352
472
  const lines = ["Cogcoin Wallet Status"];
@@ -156,6 +156,12 @@ export declare function initializeWallet(options: {
156
156
  attachService?: typeof attachOrStartManagedBitcoindService;
157
157
  rpcFactory?: (config: Parameters<typeof createRpcClient>[0]) => WalletLifecycleRpcClient;
158
158
  }): Promise<WalletInitializationResult>;
159
+ export declare function showWalletMnemonic(options: {
160
+ provider?: WalletSecretProvider;
161
+ prompter: WalletPrompter;
162
+ nowUnixMs?: number;
163
+ paths?: WalletRuntimePaths;
164
+ }): Promise<void>;
159
165
  export declare function unlockWallet(options?: {
160
166
  provider?: WalletSecretProvider;
161
167
  nowUnixMs?: number;
@@ -3,7 +3,7 @@ import { access, constants, mkdir, readFile, readdir, rename, rm } from "node:fs
3
3
  import { dirname, join } from "node:path";
4
4
  import { openClient } from "../client.js";
5
5
  import { attachOrStartIndexerDaemon, probeIndexerDaemon, readSnapshotWithRetry, } from "../bitcoind/indexer-daemon.js";
6
- import { attachOrStartManagedBitcoindService, createManagedWalletReplica, probeManagedBitcoindService, } from "../bitcoind/service.js";
6
+ import { attachOrStartManagedBitcoindService, createManagedWalletReplica, probeManagedBitcoindService, withClaimedUninitializedManagedRuntime, } from "../bitcoind/service.js";
7
7
  import { resolveManagedServicePaths } from "../bitcoind/service-paths.js";
8
8
  import { createRpcClient } from "../bitcoind/node.js";
9
9
  import { openSqliteStore } from "../sqlite/index.js";
@@ -19,9 +19,10 @@ import { loadMiningRuntimeStatus, saveMiningRuntimeStatus } from "./mining/runti
19
19
  import { normalizeMiningStateRecord } from "./mining/state.js";
20
20
  import { renderWalletMnemonicRevealArt } from "./mnemonic-art.js";
21
21
  import { clearWalletExplicitLock, loadWalletExplicitLock, saveWalletExplicitLock, } from "./state/explicit-lock.js";
22
+ import { clearWalletPendingInitializationState, loadWalletPendingInitializationStateOrNull, saveWalletPendingInitializationState, } from "./state/pending-init.js";
22
23
  import { clearUnlockSession, loadUnlockSession, saveUnlockSession } from "./state/session.js";
23
- import { createDefaultWalletSecretProvider, createWalletRootId, createWalletSecretReference, } from "./state/provider.js";
24
- import { loadWalletState, saveWalletState } from "./state/storage.js";
24
+ import { createDefaultWalletSecretProvider, createWalletPendingInitSecretReference, createWalletRootId, createWalletSecretReference, } from "./state/provider.js";
25
+ import { extractWalletRootIdHintFromWalletStateEnvelope, loadRawWalletStateEnvelope, loadWalletState, saveWalletState, } from "./state/storage.js";
25
26
  export const DEFAULT_UNLOCK_DURATION_MS = 15 * 60 * 1000;
26
27
  export { previewResetWallet, resetWallet, } from "./reset.js";
27
28
  function sanitizeWalletName(walletRootId) {
@@ -47,20 +48,52 @@ async function readJsonFileOrNull(path) {
47
48
  return null;
48
49
  }
49
50
  }
50
- async function loadRawWalletEnvelope(paths) {
51
- const primary = await readJsonFileOrNull(paths.walletStatePath);
52
- if (primary !== null) {
53
- return primary;
54
- }
55
- return readJsonFileOrNull(paths.walletStateBackupPath);
51
+ function resolvePendingInitializationStoragePaths(paths) {
52
+ return {
53
+ primaryPath: paths.walletInitPendingPath,
54
+ backupPath: paths.walletInitPendingBackupPath,
55
+ };
56
56
  }
57
- function extractWalletRootIdFromEnvelope(envelope) {
58
- const keyId = envelope?.secretProvider?.keyId ?? null;
59
- const prefix = "wallet-state:";
60
- if (keyId === null || !keyId.startsWith(prefix)) {
61
- return null;
57
+ async function clearPendingInitialization(paths, provider) {
58
+ await clearWalletPendingInitializationState(resolvePendingInitializationStoragePaths(paths), {
59
+ provider,
60
+ secretReference: createWalletPendingInitSecretReference(paths.stateRoot),
61
+ });
62
+ }
63
+ async function loadOrCreatePendingInitializationMaterial(options) {
64
+ try {
65
+ const loaded = await loadWalletPendingInitializationStateOrNull(resolvePendingInitializationStoragePaths(options.paths), {
66
+ provider: options.provider,
67
+ });
68
+ if (loaded !== null) {
69
+ return deriveWalletMaterialFromMnemonic(loaded.state.mnemonic.phrase);
70
+ }
71
+ }
72
+ catch {
73
+ await clearPendingInitialization(options.paths, options.provider);
74
+ }
75
+ const material = generateWalletMaterial();
76
+ const secretReference = createWalletPendingInitSecretReference(options.paths.stateRoot);
77
+ const pendingState = {
78
+ schemaVersion: 1,
79
+ createdAtUnixMs: options.nowUnixMs,
80
+ mnemonic: {
81
+ phrase: material.mnemonic.phrase,
82
+ language: material.mnemonic.language,
83
+ },
84
+ };
85
+ await options.provider.storeSecret(secretReference.keyId, randomBytes(32));
86
+ try {
87
+ await saveWalletPendingInitializationState(resolvePendingInitializationStoragePaths(options.paths), pendingState, {
88
+ provider: options.provider,
89
+ secretReference,
90
+ });
91
+ }
92
+ catch (error) {
93
+ await options.provider.deleteSecret(secretReference.keyId).catch(() => undefined);
94
+ throw error;
62
95
  }
63
- return keyId.slice(prefix.length);
96
+ return material;
64
97
  }
65
98
  function createInitialWalletState(options) {
66
99
  return {
@@ -351,10 +384,10 @@ async function promptForRestoreMnemonic(prompter) {
351
384
  }
352
385
  return phrase;
353
386
  }
354
- async function confirmTypedAcknowledgement(prompter, expected, message) {
387
+ async function confirmTypedAcknowledgement(prompter, expected, message, errorCode = "wallet_typed_confirmation_rejected") {
355
388
  const answer = (await prompter.prompt(message)).trim();
356
389
  if (answer !== expected) {
357
- throw new Error("wallet_typed_confirmation_rejected");
390
+ throw new Error(errorCode);
358
391
  }
359
392
  }
360
393
  async function confirmRestoreReplacement(prompter) {
@@ -780,11 +813,29 @@ export function parseUnlockDurationToMs(raw) {
780
813
  }
781
814
  return duration;
782
815
  }
783
- async function ensureWalletNotInitialized(paths) {
816
+ async function ensureWalletNotInitialized(paths, provider) {
784
817
  if (await pathExists(paths.walletStatePath) || await pathExists(paths.walletStateBackupPath)) {
818
+ await clearPendingInitialization(paths, provider);
785
819
  throw new Error("wallet_already_initialized");
786
820
  }
787
821
  }
822
+ function isWalletSecretAccessError(error) {
823
+ const message = error instanceof Error ? error.message : String(error);
824
+ return message === "wallet_envelope_missing_secret_provider"
825
+ || message.startsWith("wallet_secret_missing_")
826
+ || message.startsWith("wallet_secret_provider_");
827
+ }
828
+ function writeMnemonicReveal(prompter, phrase, introLines) {
829
+ const words = phrase.trim().split(/\s+/);
830
+ for (const line of introLines) {
831
+ prompter.writeLine(line);
832
+ }
833
+ for (const line of renderWalletMnemonicRevealArt(words)) {
834
+ prompter.writeLine(line);
835
+ }
836
+ prompter.writeLine("Single-line copy:");
837
+ prompter.writeLine(phrase);
838
+ }
788
839
  async function confirmMnemonic(prompter, words) {
789
840
  const challenge = createMnemonicConfirmationChallenge(words);
790
841
  for (const entry of challenge) {
@@ -1079,17 +1130,19 @@ export async function initializeWallet(options) {
1079
1130
  walletRootId: null,
1080
1131
  });
1081
1132
  try {
1082
- await ensureWalletNotInitialized(paths);
1083
- const material = generateWalletMaterial();
1133
+ await ensureWalletNotInitialized(paths, provider);
1134
+ const material = await loadOrCreatePendingInitializationMaterial({
1135
+ provider,
1136
+ paths,
1137
+ nowUnixMs,
1138
+ });
1084
1139
  let mnemonicRevealed = false;
1085
- options.prompter.writeLine("Cogcoin Wallet Initialization");
1086
- options.prompter.writeLine("Write down this 24-word recovery phrase. It will only be shown once:");
1087
- options.prompter.writeLine("");
1088
- for (const line of renderWalletMnemonicRevealArt(material.mnemonic.words)) {
1089
- options.prompter.writeLine(line);
1090
- }
1091
- options.prompter.writeLine("Single-line copy:");
1092
- options.prompter.writeLine(material.mnemonic.phrase);
1140
+ writeMnemonicReveal(options.prompter, material.mnemonic.phrase, [
1141
+ "Cogcoin Wallet Initialization",
1142
+ "Write down this 24-word recovery phrase.",
1143
+ "The same phrase will be shown again until confirmation succeeds:",
1144
+ "",
1145
+ ]);
1093
1146
  mnemonicRevealed = true;
1094
1147
  try {
1095
1148
  await confirmMnemonic(options.prompter, material.mnemonic.words);
@@ -1112,20 +1165,26 @@ export async function initializeWallet(options) {
1112
1165
  material,
1113
1166
  internalCoreWalletPassphrase,
1114
1167
  });
1115
- await saveWalletState({
1116
- primaryPath: paths.walletStatePath,
1117
- backupPath: paths.walletStateBackupPath,
1118
- }, initialState, {
1119
- provider,
1120
- secretReference,
1168
+ const verifiedState = await withClaimedUninitializedManagedRuntime({
1169
+ dataDir: options.dataDir,
1170
+ walletRootId,
1171
+ }, async () => {
1172
+ await saveWalletState({
1173
+ primaryPath: paths.walletStatePath,
1174
+ backupPath: paths.walletStateBackupPath,
1175
+ }, initialState, {
1176
+ provider,
1177
+ secretReference,
1178
+ });
1179
+ return importDescriptorIntoManagedCoreWallet(initialState, provider, paths, options.dataDir, nowUnixMs, options.attachService, options.rpcFactory);
1121
1180
  });
1122
- const verifiedState = await importDescriptorIntoManagedCoreWallet(initialState, provider, paths, options.dataDir, nowUnixMs, options.attachService, options.rpcFactory);
1123
1181
  const unlockUntilUnixMs = nowUnixMs + unlockDurationMs;
1124
1182
  await clearWalletExplicitLock(paths.walletExplicitLockPath);
1125
1183
  await saveUnlockSession(paths.walletUnlockSessionPath, createUnlockSession(verifiedState, unlockUntilUnixMs, secretReference.keyId, nowUnixMs), {
1126
1184
  provider,
1127
1185
  secretReference,
1128
1186
  });
1187
+ await clearPendingInitialization(paths, provider);
1129
1188
  return {
1130
1189
  walletRootId,
1131
1190
  fundingAddress: verifiedState.funding.address,
@@ -1137,6 +1196,71 @@ export async function initializeWallet(options) {
1137
1196
  await controlLock.release();
1138
1197
  }
1139
1198
  }
1199
+ export async function showWalletMnemonic(options) {
1200
+ if (!options.prompter.isInteractive) {
1201
+ throw new Error("wallet_show_mnemonic_requires_tty");
1202
+ }
1203
+ const provider = options.provider ?? createDefaultWalletSecretProvider();
1204
+ const nowUnixMs = options.nowUnixMs ?? Date.now();
1205
+ const paths = options.paths ?? resolveWalletRuntimePathsForTesting();
1206
+ const controlLock = await acquireFileLock(paths.walletControlLockPath, {
1207
+ purpose: "wallet-show-mnemonic",
1208
+ walletRootId: null,
1209
+ });
1210
+ try {
1211
+ const [hasPrimaryStateFile, hasBackupStateFile] = await Promise.all([
1212
+ pathExists(paths.walletStatePath),
1213
+ pathExists(paths.walletStateBackupPath),
1214
+ ]);
1215
+ if (!hasPrimaryStateFile && !hasBackupStateFile) {
1216
+ throw new Error("wallet_uninitialized");
1217
+ }
1218
+ const unlocked = await loadOrAutoUnlockWalletState({
1219
+ provider,
1220
+ nowUnixMs,
1221
+ paths,
1222
+ controlLockHeld: true,
1223
+ });
1224
+ if (unlocked === null) {
1225
+ try {
1226
+ await loadWalletState({
1227
+ primaryPath: paths.walletStatePath,
1228
+ backupPath: paths.walletStateBackupPath,
1229
+ }, {
1230
+ provider,
1231
+ });
1232
+ }
1233
+ catch (error) {
1234
+ if (isWalletSecretAccessError(error)) {
1235
+ throw new Error("wallet_locked");
1236
+ }
1237
+ throw new Error("local-state-corrupt");
1238
+ }
1239
+ throw new Error("wallet_locked");
1240
+ }
1241
+ await confirmTypedAcknowledgement(options.prompter, "show mnemonic", "Type \"show mnemonic\" to continue: ", "wallet_show_mnemonic_typed_ack_required");
1242
+ let mnemonicRevealed = false;
1243
+ writeMnemonicReveal(options.prompter, unlocked.state.mnemonic.phrase, [
1244
+ "Cogcoin Wallet Recovery Phrase",
1245
+ "This 24-word recovery phrase controls the wallet.",
1246
+ "",
1247
+ ]);
1248
+ mnemonicRevealed = true;
1249
+ try {
1250
+ await options.prompter.prompt("Press Enter to clear the recovery phrase from the screen: ");
1251
+ }
1252
+ finally {
1253
+ if (mnemonicRevealed) {
1254
+ await Promise.resolve()
1255
+ .then(() => options.prompter.clearSensitiveDisplay?.("mnemonic-reveal"))
1256
+ .catch(() => undefined);
1257
+ }
1258
+ }
1259
+ }
1260
+ finally {
1261
+ await controlLock.release();
1262
+ }
1263
+ }
1140
1264
  export async function unlockWallet(options = {}) {
1141
1265
  const provider = options.provider ?? createDefaultWalletSecretProvider();
1142
1266
  const nowUnixMs = options.nowUnixMs ?? Date.now();
@@ -1290,6 +1414,7 @@ export async function importWallet(options) {
1290
1414
  const replacementStateExists = await pathExists(paths.walletStatePath) || await pathExists(paths.walletStateBackupPath);
1291
1415
  const importedWalletDir = join(options.dataDir, "wallets", sanitizeWalletName(payload.walletRootId));
1292
1416
  const replacementCoreWalletExists = await pathExists(importedWalletDir);
1417
+ await clearPendingInitialization(paths, provider);
1293
1418
  if (replacementStateExists || replacementCoreWalletExists) {
1294
1419
  await confirmTypedAcknowledgement(options.prompter, "IMPORT", "Type IMPORT to replace the existing local wallet state and managed Core wallet replica: ");
1295
1420
  }
@@ -1333,6 +1458,7 @@ export async function importWallet(options) {
1333
1458
  provider,
1334
1459
  secretReference,
1335
1460
  });
1461
+ await clearPendingInitialization(paths, provider);
1336
1462
  if (previousWalletRootId !== null && previousWalletRootId !== payload.walletRootId) {
1337
1463
  await provider.deleteSecret(createWalletSecretReference(previousWalletRootId).keyId).catch(() => undefined);
1338
1464
  }
@@ -1366,16 +1492,20 @@ export async function restoreWalletFromMnemonic(options) {
1366
1492
  walletRootId: null,
1367
1493
  });
1368
1494
  try {
1369
- const rawEnvelope = await loadRawWalletEnvelope(paths);
1495
+ const rawEnvelope = await loadRawWalletStateEnvelope({
1496
+ primaryPath: paths.walletStatePath,
1497
+ backupPath: paths.walletStateBackupPath,
1498
+ });
1370
1499
  const replacementStateExists = rawEnvelope !== null
1371
1500
  || await pathExists(paths.walletStatePath)
1372
1501
  || await pathExists(paths.walletStateBackupPath);
1373
1502
  const replacementCoreWalletExists = await detectExistingManagedWalletReplica(options.dataDir);
1374
1503
  const mnemonicPhrase = await promptForRestoreMnemonic(options.prompter);
1504
+ await clearPendingInitialization(paths, provider);
1375
1505
  if (replacementStateExists || replacementCoreWalletExists) {
1376
1506
  await confirmRestoreReplacement(options.prompter);
1377
1507
  }
1378
- let previousWalletRootId = extractWalletRootIdFromEnvelope(rawEnvelope);
1508
+ let previousWalletRootId = extractWalletRootIdHintFromWalletStateEnvelope(rawEnvelope?.envelope ?? null);
1379
1509
  try {
1380
1510
  const loaded = await loadWalletState({
1381
1511
  primaryPath: paths.walletStatePath,
@@ -1425,6 +1555,7 @@ export async function restoreWalletFromMnemonic(options) {
1425
1555
  provider,
1426
1556
  secretReference,
1427
1557
  });
1558
+ await clearPendingInitialization(paths, provider);
1428
1559
  if (previousWalletRootId !== null && previousWalletRootId !== walletRootId) {
1429
1560
  try {
1430
1561
  await clearPreviousManagedWalletRuntime({