@cogcoin/client 0.5.2 → 0.5.4

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 (58) hide show
  1. package/README.md +14 -3
  2. package/dist/app-paths.d.ts +1 -0
  3. package/dist/app-paths.js +2 -0
  4. package/dist/art/wallet.txt +10 -0
  5. package/dist/bitcoind/indexer-daemon.d.ts +9 -0
  6. package/dist/bitcoind/indexer-daemon.js +51 -14
  7. package/dist/bitcoind/service.d.ts +9 -0
  8. package/dist/bitcoind/service.js +65 -24
  9. package/dist/bitcoind/testing.d.ts +2 -2
  10. package/dist/bitcoind/testing.js +2 -2
  11. package/dist/cli/commands/service-runtime.d.ts +2 -0
  12. package/dist/cli/commands/service-runtime.js +432 -0
  13. package/dist/cli/commands/wallet-admin.js +227 -132
  14. package/dist/cli/commands/wallet-mutation.js +597 -580
  15. package/dist/cli/context.js +23 -1
  16. package/dist/cli/mutation-json.d.ts +17 -1
  17. package/dist/cli/mutation-json.js +42 -0
  18. package/dist/cli/output.js +113 -2
  19. package/dist/cli/parse.d.ts +1 -1
  20. package/dist/cli/parse.js +69 -4
  21. package/dist/cli/preview-json.d.ts +19 -1
  22. package/dist/cli/preview-json.js +31 -0
  23. package/dist/cli/prompt.js +40 -12
  24. package/dist/cli/runner.js +12 -0
  25. package/dist/cli/signals.d.ts +1 -0
  26. package/dist/cli/signals.js +44 -0
  27. package/dist/cli/types.d.ts +24 -2
  28. package/dist/cli/types.js +6 -0
  29. package/dist/cli/wallet-format.js +3 -0
  30. package/dist/cli/workflow-hints.d.ts +1 -0
  31. package/dist/cli/workflow-hints.js +3 -0
  32. package/dist/wallet/fs/lock.d.ts +2 -0
  33. package/dist/wallet/fs/lock.js +32 -0
  34. package/dist/wallet/lifecycle.d.ts +30 -1
  35. package/dist/wallet/lifecycle.js +394 -40
  36. package/dist/wallet/material.d.ts +2 -0
  37. package/dist/wallet/material.js +8 -1
  38. package/dist/wallet/mining/control.js +4 -4
  39. package/dist/wallet/mining/runner.js +2 -2
  40. package/dist/wallet/mnemonic-art.d.ts +2 -0
  41. package/dist/wallet/mnemonic-art.js +54 -0
  42. package/dist/wallet/read/context.d.ts +2 -0
  43. package/dist/wallet/read/context.js +64 -17
  44. package/dist/wallet/reset.d.ts +61 -0
  45. package/dist/wallet/reset.js +781 -0
  46. package/dist/wallet/runtime.d.ts +1 -0
  47. package/dist/wallet/runtime.js +1 -0
  48. package/dist/wallet/state/explicit-lock.d.ts +4 -0
  49. package/dist/wallet/state/explicit-lock.js +19 -0
  50. package/dist/wallet/tx/anchor.js +1 -0
  51. package/dist/wallet/tx/cog.js +3 -0
  52. package/dist/wallet/tx/domain-admin.js +1 -0
  53. package/dist/wallet/tx/domain-market.js +3 -0
  54. package/dist/wallet/tx/field.js +2 -0
  55. package/dist/wallet/tx/register.js +1 -0
  56. package/dist/wallet/tx/reputation.js +1 -0
  57. package/dist/wallet/types.d.ts +5 -0
  58. package/package.json +3 -3
@@ -0,0 +1,54 @@
1
+ import { readFileSync } from "node:fs";
2
+ const WALLET_ART_WIDTH = 80;
3
+ const WALLET_ART_SLOT_WIDTH = 8;
4
+ const WALLET_ART_PLACEHOLDER_WORD = "achieved";
5
+ const WALLET_ART_WORD_COUNT = 24;
6
+ let walletArtTemplateCache = null;
7
+ function normalizeWalletArtTemplate(raw) {
8
+ const lines = raw.replaceAll("\r\n", "\n").split("\n");
9
+ if (lines[lines.length - 1] === "") {
10
+ lines.pop();
11
+ }
12
+ for (const line of lines) {
13
+ if (line.length !== WALLET_ART_WIDTH) {
14
+ throw new Error(`wallet_art_template_width_invalid_${line.length}`);
15
+ }
16
+ }
17
+ const template = lines.join("\n");
18
+ for (let index = 1; index <= WALLET_ART_WORD_COUNT; index += 1) {
19
+ if (!template.includes(`${index}.${WALLET_ART_PLACEHOLDER_WORD}`)) {
20
+ throw new Error(`wallet_art_template_placeholder_missing_${index}`);
21
+ }
22
+ }
23
+ return lines;
24
+ }
25
+ function loadWalletArtTemplate() {
26
+ if (walletArtTemplateCache !== null) {
27
+ return walletArtTemplateCache;
28
+ }
29
+ walletArtTemplateCache = normalizeWalletArtTemplate(readFileSync(new URL("../art/wallet.txt", import.meta.url), "utf8"));
30
+ return walletArtTemplateCache;
31
+ }
32
+ function formatWalletArtWord(word) {
33
+ const normalized = word ?? "";
34
+ if (normalized.length > WALLET_ART_SLOT_WIDTH) {
35
+ throw new Error(`wallet_art_word_too_wide_${normalized.length}`);
36
+ }
37
+ return normalized.padEnd(WALLET_ART_SLOT_WIDTH, " ");
38
+ }
39
+ export function renderWalletMnemonicRevealArt(words) {
40
+ const rendered = loadWalletArtTemplate().map((line) => {
41
+ let next = line;
42
+ for (let index = 0; index < WALLET_ART_WORD_COUNT; index += 1) {
43
+ next = next.replace(`${index + 1}.${WALLET_ART_PLACEHOLDER_WORD}`, `${index + 1}.${formatWalletArtWord(words[index])}`);
44
+ }
45
+ return next;
46
+ });
47
+ if (rendered.some((line) => line.includes(`.${WALLET_ART_PLACEHOLDER_WORD}`))) {
48
+ throw new Error("wallet_art_render_placeholder_unreplaced");
49
+ }
50
+ return rendered;
51
+ }
52
+ export function loadWalletArtTemplateForTesting() {
53
+ return [...loadWalletArtTemplate()];
54
+ }
@@ -8,12 +8,14 @@ declare function inspectWalletLocalState(options?: {
8
8
  secretProvider?: WalletSecretProvider;
9
9
  now?: number;
10
10
  paths?: WalletRuntimePaths;
11
+ walletControlLockHeld?: boolean;
11
12
  }): Promise<WalletLocalStateStatus>;
12
13
  export declare function openWalletReadContext(options: {
13
14
  dataDir: string;
14
15
  databasePath: string;
15
16
  walletStatePassphrase?: Uint8Array | string;
16
17
  secretProvider?: WalletSecretProvider;
18
+ walletControlLockHeld?: boolean;
17
19
  startupTimeoutMs?: number;
18
20
  now?: number;
19
21
  paths?: WalletRuntimePaths;
@@ -5,11 +5,12 @@ import { createRpcClient } from "../../bitcoind/node.js";
5
5
  import { UNINITIALIZED_WALLET_ROOT_ID } from "../../bitcoind/service-paths.js";
6
6
  import { attachOrStartManagedBitcoindService, probeManagedBitcoindService, } from "../../bitcoind/service.js";
7
7
  import {} from "../../bitcoind/types.js";
8
- import { loadUnlockedWalletState, verifyManagedCoreWalletReplica, } from "../lifecycle.js";
8
+ import { loadOrAutoUnlockWalletState, verifyManagedCoreWalletReplica, } from "../lifecycle.js";
9
9
  import { persistNormalizedWalletDescriptorStateIfNeeded } from "../descriptor-normalization.js";
10
10
  import { inspectMiningControlPlane } from "../mining/index.js";
11
11
  import { normalizeMiningStateRecord } from "../mining/state.js";
12
12
  import { resolveWalletRuntimePathsForTesting } from "../runtime.js";
13
+ import { loadWalletExplicitLock } from "../state/explicit-lock.js";
13
14
  import { loadWalletState } from "../state/storage.js";
14
15
  import { createDefaultWalletSecretProvider, createWalletSecretReference, } from "../state/provider.js";
15
16
  import { createWalletReadModel } from "./project.js";
@@ -24,6 +25,30 @@ async function pathExists(path) {
24
25
  return false;
25
26
  }
26
27
  }
28
+ function isLockedWalletAccessError(error) {
29
+ const message = error instanceof Error ? error.message : String(error);
30
+ return message === "wallet_envelope_missing_secret_provider"
31
+ || message.startsWith("wallet_secret_missing_")
32
+ || message.startsWith("wallet_secret_provider_");
33
+ }
34
+ function describeLockedWalletMessage(options) {
35
+ if (options.explicitlyLocked) {
36
+ return "Wallet state exists but is explicitly locked until `cogcoin unlock` is run.";
37
+ }
38
+ const message = options.accessError instanceof Error ? options.accessError.message : String(options.accessError ?? "");
39
+ if (message === "wallet_envelope_missing_secret_provider") {
40
+ return "Wallet state exists but requires the local wallet-state passphrase.";
41
+ }
42
+ if (message.startsWith("wallet_secret_provider_")) {
43
+ return "Wallet state exists but the local secret provider is unavailable.";
44
+ }
45
+ if (message.startsWith("wallet_secret_missing_")) {
46
+ return "Wallet state exists but its local secret-provider material is unavailable.";
47
+ }
48
+ return options.hasUnlockSessionFile
49
+ ? "Wallet state exists but the unlock session is expired, invalid, or belongs to a different wallet root."
50
+ : "Wallet state exists but is currently locked.";
51
+ }
27
52
  async function normalizeLoadedWalletStateForRead(options) {
28
53
  if (options.dataDir === undefined) {
29
54
  return options.loaded;
@@ -82,13 +107,16 @@ async function inspectWalletLocalState(options = {}) {
82
107
  if (options.passphrase === undefined) {
83
108
  try {
84
109
  const provider = options.secretProvider ?? createDefaultWalletSecretProvider();
85
- const unlocked = await loadUnlockedWalletState({
110
+ const unlocked = await loadOrAutoUnlockWalletState({
86
111
  provider,
87
112
  nowUnixMs: now,
88
113
  paths,
89
114
  dataDir: options.dataDir,
115
+ controlLockHeld: options.walletControlLockHeld,
90
116
  });
91
117
  if (unlocked === null) {
118
+ const explicitLock = await loadWalletExplicitLock(paths.walletExplicitLockPath);
119
+ const hasUnlockSessionFileNow = await pathExists(paths.walletUnlockSessionPath);
92
120
  try {
93
121
  const loaded = await loadWalletState({
94
122
  primaryPath: paths.walletStatePath,
@@ -103,8 +131,39 @@ async function inspectWalletLocalState(options = {}) {
103
131
  now,
104
132
  paths,
105
133
  });
134
+ return {
135
+ availability: "locked",
136
+ walletRootId: loaded.state.walletRootId,
137
+ state: null,
138
+ source: loaded.source,
139
+ unlockUntilUnixMs: null,
140
+ hasPrimaryStateFile,
141
+ hasBackupStateFile,
142
+ hasUnlockSessionFile: hasUnlockSessionFileNow,
143
+ message: describeLockedWalletMessage({
144
+ explicitlyLocked: explicitLock?.walletRootId === loaded.state.walletRootId,
145
+ hasUnlockSessionFile: hasUnlockSessionFileNow,
146
+ }),
147
+ };
106
148
  }
107
149
  catch (error) {
150
+ if (isLockedWalletAccessError(error)) {
151
+ return {
152
+ availability: "locked",
153
+ walletRootId: null,
154
+ state: null,
155
+ source: null,
156
+ unlockUntilUnixMs: null,
157
+ hasPrimaryStateFile,
158
+ hasBackupStateFile,
159
+ hasUnlockSessionFile: hasUnlockSessionFileNow,
160
+ message: describeLockedWalletMessage({
161
+ accessError: error,
162
+ explicitlyLocked: false,
163
+ hasUnlockSessionFile: hasUnlockSessionFileNow,
164
+ }),
165
+ };
166
+ }
108
167
  return {
109
168
  availability: "local-state-corrupt",
110
169
  walletRootId: null,
@@ -113,23 +172,10 @@ async function inspectWalletLocalState(options = {}) {
113
172
  unlockUntilUnixMs: null,
114
173
  hasPrimaryStateFile,
115
174
  hasBackupStateFile,
116
- hasUnlockSessionFile,
175
+ hasUnlockSessionFile: hasUnlockSessionFileNow,
117
176
  message: error instanceof Error ? error.message : String(error),
118
177
  };
119
178
  }
120
- return {
121
- availability: "locked",
122
- walletRootId: null,
123
- state: null,
124
- source: null,
125
- unlockUntilUnixMs: null,
126
- hasPrimaryStateFile,
127
- hasBackupStateFile,
128
- hasUnlockSessionFile,
129
- message: hasUnlockSessionFile
130
- ? "Wallet state exists but the unlock session is expired, invalid, or belongs to a different wallet root."
131
- : "Wallet state exists but is currently locked.",
132
- };
133
179
  }
134
180
  return {
135
181
  availability: "ready",
@@ -142,7 +188,7 @@ async function inspectWalletLocalState(options = {}) {
142
188
  unlockUntilUnixMs: unlocked.session.unlockUntilUnixMs,
143
189
  hasPrimaryStateFile,
144
190
  hasBackupStateFile,
145
- hasUnlockSessionFile,
191
+ hasUnlockSessionFile: true,
146
192
  message: null,
147
193
  };
148
194
  }
@@ -460,6 +506,7 @@ export async function openWalletReadContext(options) {
460
506
  dataDir: options.dataDir,
461
507
  passphrase: options.walletStatePassphrase,
462
508
  secretProvider: options.secretProvider,
509
+ walletControlLockHeld: options.walletControlLockHeld,
463
510
  now,
464
511
  paths: options.paths,
465
512
  });
@@ -0,0 +1,61 @@
1
+ import { type WalletRuntimePaths } from "./runtime.js";
2
+ import { type WalletSecretProvider } from "./state/provider.js";
3
+ import type { WalletPrompter } from "./lifecycle.js";
4
+ export type WalletResetAction = "not-present" | "kept-unchanged" | "reset-base-entropy" | "deleted";
5
+ export type WalletResetSecretCleanupStatus = "deleted" | "not-found" | "failed" | "unknown";
6
+ export type WalletResetSnapshotResultStatus = "not-present" | "invalid-removed" | "deleted" | "preserved";
7
+ export interface WalletResetResult {
8
+ dataRoot: string;
9
+ factoryResetReady: true;
10
+ stoppedProcesses: {
11
+ managedBitcoind: number;
12
+ indexerDaemon: number;
13
+ backgroundMining: number;
14
+ survivors: number;
15
+ };
16
+ secretCleanupStatus: WalletResetSecretCleanupStatus;
17
+ deletedSecretRefs: string[];
18
+ failedSecretRefs: string[];
19
+ preservedSecretRefs: string[];
20
+ walletAction: WalletResetAction;
21
+ walletOldRootId: string | null;
22
+ walletNewRootId: string | null;
23
+ bootstrapSnapshot: {
24
+ status: WalletResetSnapshotResultStatus;
25
+ path: string;
26
+ };
27
+ removedPaths: string[];
28
+ }
29
+ export interface WalletResetPreview {
30
+ dataRoot: string;
31
+ confirmationPhrase: "permanently reset";
32
+ walletPrompt: null | {
33
+ defaultAction: "reset-base-entropy";
34
+ acceptedInputs: ["", "skip", "delete wallet"];
35
+ entropyRetainingResetAvailable: boolean;
36
+ requiresPassphrase: boolean;
37
+ envelopeSource: "primary" | "backup" | null;
38
+ };
39
+ bootstrapSnapshot: {
40
+ status: "not-present" | "invalid" | "valid";
41
+ path: string;
42
+ defaultAction: "preserve" | "delete";
43
+ };
44
+ trackedProcessKinds: Array<"managed-bitcoind" | "indexer-daemon" | "background-mining">;
45
+ willDeleteOsSecrets: boolean;
46
+ removedPaths: string[];
47
+ }
48
+ export declare function previewResetWallet(options: {
49
+ dataDir: string;
50
+ provider?: WalletSecretProvider;
51
+ paths?: WalletRuntimePaths;
52
+ validateSnapshotFile?: (path: string) => Promise<void>;
53
+ }): Promise<WalletResetPreview>;
54
+ export declare function resetWallet(options: {
55
+ dataDir: string;
56
+ provider?: WalletSecretProvider;
57
+ prompter: WalletPrompter;
58
+ nowUnixMs?: number;
59
+ paths?: WalletRuntimePaths;
60
+ validateSnapshotFile?: (path: string) => Promise<void>;
61
+ }): Promise<WalletResetResult>;