@cogcoin/client 1.1.6 → 1.1.8

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 (125) hide show
  1. package/README.md +2 -2
  2. package/dist/bitcoind/indexer-daemon.js +29 -79
  3. package/dist/bitcoind/managed-runtime/bitcoind-runtime.d.ts +20 -0
  4. package/dist/bitcoind/managed-runtime/bitcoind-runtime.js +74 -0
  5. package/dist/bitcoind/managed-runtime/bitcoind-status.d.ts +11 -0
  6. package/dist/bitcoind/managed-runtime/bitcoind-status.js +44 -0
  7. package/dist/bitcoind/managed-runtime/indexer-runtime.d.ts +15 -0
  8. package/dist/bitcoind/managed-runtime/indexer-runtime.js +82 -0
  9. package/dist/bitcoind/managed-runtime/types.d.ts +40 -0
  10. package/dist/bitcoind/node.d.ts +2 -2
  11. package/dist/bitcoind/node.js +2 -2
  12. package/dist/bitcoind/rpc.d.ts +2 -1
  13. package/dist/bitcoind/rpc.js +53 -3
  14. package/dist/bitcoind/service.js +47 -127
  15. package/dist/cli/command-registry.d.ts +1 -1
  16. package/dist/cli/command-registry.js +2 -64
  17. package/dist/cli/commands/client-admin.js +3 -18
  18. package/dist/cli/commands/mining-runtime.js +4 -60
  19. package/dist/cli/commands/wallet-admin.js +6 -6
  20. package/dist/cli/context.js +1 -3
  21. package/dist/cli/mining-json.d.ts +1 -22
  22. package/dist/cli/mining-json.js +0 -23
  23. package/dist/cli/output.js +16 -2
  24. package/dist/cli/parse.js +0 -2
  25. package/dist/cli/preview-json.d.ts +1 -22
  26. package/dist/cli/preview-json.js +0 -19
  27. package/dist/cli/types.d.ts +1 -3
  28. package/dist/cli/wallet-format.js +1 -1
  29. package/dist/cli/workflow-hints.d.ts +1 -2
  30. package/dist/cli/workflow-hints.js +5 -8
  31. package/dist/wallet/lifecycle/context.js +0 -1
  32. package/dist/wallet/lifecycle/repair-mining.d.ts +1 -5
  33. package/dist/wallet/lifecycle/repair-mining.js +5 -39
  34. package/dist/wallet/lifecycle/repair.js +0 -3
  35. package/dist/wallet/lifecycle/setup.js +10 -8
  36. package/dist/wallet/lifecycle/types.d.ts +1 -4
  37. package/dist/wallet/managed-core-wallet.d.ts +2 -0
  38. package/dist/wallet/managed-core-wallet.js +27 -1
  39. package/dist/wallet/mining/candidate.d.ts +1 -0
  40. package/dist/wallet/mining/candidate.js +38 -6
  41. package/dist/wallet/mining/competitiveness.d.ts +1 -0
  42. package/dist/wallet/mining/competitiveness.js +6 -0
  43. package/dist/wallet/mining/cycle.d.ts +2 -0
  44. package/dist/wallet/mining/cycle.js +14 -4
  45. package/dist/wallet/mining/engine-state.js +10 -0
  46. package/dist/wallet/mining/engine-types.d.ts +1 -0
  47. package/dist/wallet/mining/index.d.ts +1 -1
  48. package/dist/wallet/mining/index.js +1 -1
  49. package/dist/wallet/mining/publish.d.ts +3 -0
  50. package/dist/wallet/mining/publish.js +78 -6
  51. package/dist/wallet/mining/runner.d.ts +0 -32
  52. package/dist/wallet/mining/runner.js +59 -104
  53. package/dist/wallet/mining/stop.d.ts +7 -0
  54. package/dist/wallet/mining/stop.js +23 -0
  55. package/dist/wallet/mining/supervisor.d.ts +2 -36
  56. package/dist/wallet/mining/supervisor.js +139 -246
  57. package/dist/wallet/mining/visualizer-sync.js +79 -15
  58. package/dist/wallet/read/context.d.ts +1 -5
  59. package/dist/wallet/read/context.js +21 -205
  60. package/dist/wallet/read/managed-services.d.ts +33 -0
  61. package/dist/wallet/read/managed-services.js +222 -0
  62. package/dist/wallet/reset/artifacts.d.ts +16 -0
  63. package/dist/wallet/reset/artifacts.js +141 -0
  64. package/dist/wallet/reset/execution.d.ts +38 -0
  65. package/dist/wallet/reset/execution.js +458 -0
  66. package/dist/wallet/reset/preflight.d.ts +7 -0
  67. package/dist/wallet/reset/preflight.js +116 -0
  68. package/dist/wallet/reset/preview.d.ts +2 -0
  69. package/dist/wallet/reset/preview.js +50 -0
  70. package/dist/wallet/reset/process-cleanup.d.ts +12 -0
  71. package/dist/wallet/reset/process-cleanup.js +179 -0
  72. package/dist/wallet/reset/types.d.ts +189 -0
  73. package/dist/wallet/reset/types.js +1 -0
  74. package/dist/wallet/reset.d.ts +4 -119
  75. package/dist/wallet/reset.js +4 -882
  76. package/dist/wallet/state/client-password/bootstrap.d.ts +2 -0
  77. package/dist/wallet/state/client-password/bootstrap.js +3 -0
  78. package/dist/wallet/state/client-password/context.d.ts +10 -0
  79. package/dist/wallet/state/client-password/context.js +46 -0
  80. package/dist/wallet/state/client-password/crypto.d.ts +34 -0
  81. package/dist/wallet/state/client-password/crypto.js +117 -0
  82. package/dist/wallet/state/client-password/files.d.ts +10 -0
  83. package/dist/wallet/state/client-password/files.js +109 -0
  84. package/dist/wallet/state/client-password/legacy-cleanup.d.ts +11 -0
  85. package/dist/wallet/state/client-password/legacy-cleanup.js +338 -0
  86. package/dist/wallet/state/client-password/messages.d.ts +3 -0
  87. package/dist/wallet/state/client-password/messages.js +9 -0
  88. package/dist/wallet/state/client-password/migration.d.ts +4 -0
  89. package/dist/wallet/state/client-password/migration.js +32 -0
  90. package/dist/wallet/state/client-password/prompts.d.ts +12 -0
  91. package/dist/wallet/state/client-password/prompts.js +79 -0
  92. package/dist/wallet/state/client-password/protected-secrets.d.ts +13 -0
  93. package/dist/wallet/state/client-password/protected-secrets.js +90 -0
  94. package/dist/wallet/state/client-password/readiness.d.ts +4 -0
  95. package/dist/wallet/state/client-password/readiness.js +48 -0
  96. package/dist/wallet/state/client-password/references.d.ts +1 -0
  97. package/dist/wallet/state/client-password/references.js +56 -0
  98. package/dist/wallet/state/client-password/rotation.d.ts +6 -0
  99. package/dist/wallet/state/client-password/rotation.js +98 -0
  100. package/dist/wallet/state/client-password/session-policy.d.ts +6 -0
  101. package/dist/wallet/state/client-password/session-policy.js +28 -0
  102. package/dist/wallet/state/client-password/session.d.ts +19 -0
  103. package/dist/wallet/state/client-password/session.js +170 -0
  104. package/dist/wallet/state/client-password/setup.d.ts +8 -0
  105. package/dist/wallet/state/client-password/setup.js +49 -0
  106. package/dist/wallet/state/client-password/types.d.ts +82 -0
  107. package/dist/wallet/state/client-password/types.js +5 -0
  108. package/dist/wallet/state/client-password.d.ts +7 -38
  109. package/dist/wallet/state/client-password.js +52 -937
  110. package/dist/wallet/tx/anchor.js +123 -216
  111. package/dist/wallet/tx/cog.js +294 -489
  112. package/dist/wallet/tx/common.d.ts +2 -0
  113. package/dist/wallet/tx/common.js +2 -0
  114. package/dist/wallet/tx/domain-admin.js +111 -220
  115. package/dist/wallet/tx/domain-market.js +401 -681
  116. package/dist/wallet/tx/executor.d.ts +176 -0
  117. package/dist/wallet/tx/executor.js +302 -0
  118. package/dist/wallet/tx/field.js +109 -215
  119. package/dist/wallet/tx/register.js +158 -269
  120. package/dist/wallet/tx/reputation.js +120 -227
  121. package/package.json +1 -1
  122. package/dist/wallet/mining/worker-main.d.ts +0 -1
  123. package/dist/wallet/mining/worker-main.js +0 -17
  124. package/dist/wallet/state/client-password-agent.d.ts +0 -1
  125. package/dist/wallet/state/client-password-agent.js +0 -211
@@ -0,0 +1,141 @@
1
+ import { access, constants, copyFile, mkdir, readFile, rename, rm } from "node:fs/promises";
2
+ import { dirname, join, relative } from "node:path";
3
+ import { DEFAULT_SNAPSHOT_METADATA } from "../../bitcoind/bootstrap/constants.js";
4
+ import { resolveBootstrapPathsForTesting } from "../../bitcoind/bootstrap/paths.js";
5
+ import { resolveLegacyHooksRootPath } from "../../app-paths.js";
6
+ function resolveArtifactDependencies(overrides = {}) {
7
+ return {
8
+ access: overrides.access ?? access,
9
+ copyFile: overrides.copyFile ?? copyFile,
10
+ mkdir: overrides.mkdir ?? mkdir,
11
+ readFile: overrides.readFile ?? readFile,
12
+ rename: overrides.rename ?? rename,
13
+ remove: overrides.remove ?? rm,
14
+ };
15
+ }
16
+ export async function pathExists(path, deps = {}) {
17
+ const resolved = resolveArtifactDependencies(deps);
18
+ try {
19
+ await resolved.access(path, constants.F_OK);
20
+ return true;
21
+ }
22
+ catch (error) {
23
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") {
24
+ return false;
25
+ }
26
+ throw error;
27
+ }
28
+ }
29
+ export async function readJsonFileOrNull(path, deps = {}) {
30
+ const resolved = resolveArtifactDependencies(deps);
31
+ try {
32
+ return JSON.parse(await resolved.readFile(path, "utf8"));
33
+ }
34
+ catch (error) {
35
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") {
36
+ return null;
37
+ }
38
+ return null;
39
+ }
40
+ }
41
+ export function isPathWithin(root, target) {
42
+ const rel = relative(root, target);
43
+ return rel === "" || (!rel.startsWith("..") && rel !== ".");
44
+ }
45
+ export function dedupeSortedPaths(candidates) {
46
+ return [...new Set(candidates)].sort((left, right) => right.length - left.length);
47
+ }
48
+ export function resolveDefaultRemovedRoots(paths) {
49
+ const configRoot = dirname(paths.clientConfigPath);
50
+ return dedupeSortedPaths([
51
+ paths.dataRoot,
52
+ paths.stateRoot,
53
+ paths.runtimeRoot,
54
+ configRoot,
55
+ ]);
56
+ }
57
+ export function resolveBitcoindPreservingRemovedRoots(paths) {
58
+ const configRoot = dirname(paths.clientConfigPath);
59
+ return dedupeSortedPaths([
60
+ paths.clientDataDir,
61
+ paths.indexerRoot,
62
+ paths.stateRoot,
63
+ paths.runtimeRoot,
64
+ configRoot,
65
+ resolveLegacyHooksRootPath({
66
+ dataRoot: paths.dataRoot,
67
+ clientConfigPath: paths.clientConfigPath,
68
+ }),
69
+ ]);
70
+ }
71
+ export function resolveRemovedRoots(paths, options = {
72
+ preserveBitcoinDataDir: false,
73
+ }) {
74
+ return options.preserveBitcoinDataDir
75
+ ? resolveBitcoindPreservingRemovedRoots(paths)
76
+ : resolveDefaultRemovedRoots(paths);
77
+ }
78
+ export function isDeletedByRemovalPlan(removedRoots, targetPath) {
79
+ return removedRoots.some((root) => isPathWithin(root, targetPath));
80
+ }
81
+ async function moveFile(sourcePath, destinationPath, deps = {}) {
82
+ const resolved = resolveArtifactDependencies(deps);
83
+ await resolved.mkdir(dirname(destinationPath), { recursive: true });
84
+ try {
85
+ await resolved.rename(sourcePath, destinationPath);
86
+ }
87
+ catch (error) {
88
+ if (!(error instanceof Error) || !("code" in error) || error.code !== "EXDEV") {
89
+ throw error;
90
+ }
91
+ await resolved.copyFile(sourcePath, destinationPath);
92
+ await resolved.remove(sourcePath, { force: true });
93
+ }
94
+ }
95
+ export async function stageArtifact(sourcePath, stagingRoot, label, deps = {}) {
96
+ if (!await pathExists(sourcePath, deps)) {
97
+ return null;
98
+ }
99
+ const stagedPath = join(stagingRoot, label);
100
+ await moveFile(sourcePath, stagedPath, deps);
101
+ return {
102
+ originalPath: sourcePath,
103
+ stagedPath,
104
+ restorePath: sourcePath,
105
+ };
106
+ }
107
+ export async function restoreStagedArtifacts(artifacts, deps = {}) {
108
+ for (const artifact of artifacts) {
109
+ if (!await pathExists(artifact.stagedPath, deps)) {
110
+ continue;
111
+ }
112
+ await moveFile(artifact.stagedPath, artifact.restorePath, deps);
113
+ }
114
+ }
115
+ export async function deleteRemovedRoots(roots, deps = {}) {
116
+ const resolved = resolveArtifactDependencies(deps);
117
+ try {
118
+ for (const root of roots) {
119
+ await resolved.remove(root, {
120
+ recursive: true,
121
+ force: true,
122
+ });
123
+ }
124
+ }
125
+ catch {
126
+ throw new Error("reset_data_root_delete_failed");
127
+ }
128
+ }
129
+ export async function deleteBootstrapSnapshotArtifacts(dataDir, deps = {}) {
130
+ const resolved = resolveArtifactDependencies(deps);
131
+ const snapshotPaths = resolveBootstrapPathsForTesting(dataDir, DEFAULT_SNAPSHOT_METADATA);
132
+ await Promise.all([
133
+ snapshotPaths.snapshotPath,
134
+ snapshotPaths.partialSnapshotPath,
135
+ snapshotPaths.statePath,
136
+ snapshotPaths.quoteStatePath,
137
+ ].map(async (path) => resolved.remove(path, {
138
+ recursive: false,
139
+ force: true,
140
+ })));
141
+ }
@@ -0,0 +1,38 @@
1
+ import { type WalletRuntimePaths } from "../runtime.js";
2
+ import { type WalletSecretProvider } from "../state/provider.js";
3
+ import { type WalletStateSaveAccess } from "../state/storage.js";
4
+ import type { WalletStateV1 } from "../types.js";
5
+ import { preflightReset } from "./preflight.js";
6
+ import type { ResetExecutionDecision, WalletAccessForReset, WalletResetAction, WalletResetBitcoinDataDirResultStatus, WalletResetExecutionOptions, WalletResetPreflight, WalletResetResult, WalletResetSnapshotResultStatus } from "./types.js";
7
+ export declare function loadWalletForEntropyReset(options: {
8
+ wallet: WalletResetPreflight["wallet"];
9
+ paths: WalletRuntimePaths;
10
+ provider: WalletSecretProvider;
11
+ }): Promise<WalletAccessForReset>;
12
+ export declare function createEntropyRetainedWalletState(previousState: WalletStateV1, nowUnixMs: number): WalletStateV1;
13
+ export declare function recreateManagedCoreWalletReplicaForReset(options: {
14
+ state: WalletStateV1;
15
+ access: WalletStateSaveAccess;
16
+ paths: NonNullable<WalletResetExecutionOptions["paths"]>;
17
+ dataDir: string;
18
+ nowUnixMs: number;
19
+ attachService?: WalletResetExecutionOptions["attachService"];
20
+ rpcFactory?: WalletResetExecutionOptions["rpcFactory"];
21
+ }): Promise<WalletStateV1>;
22
+ export declare function resolveResetExecutionDecision(options: {
23
+ preflight: Awaited<ReturnType<typeof preflightReset>>;
24
+ provider: NonNullable<WalletResetExecutionOptions["provider"]>;
25
+ prompter: WalletResetExecutionOptions["prompter"];
26
+ paths: NonNullable<WalletResetExecutionOptions["paths"]>;
27
+ }): Promise<ResetExecutionDecision>;
28
+ export declare function determineWalletAction(walletPresent: boolean, walletChoice: ResetExecutionDecision["walletChoice"]): WalletResetAction;
29
+ export declare function determineSnapshotResultStatus(options: {
30
+ snapshotStatus: Awaited<ReturnType<typeof preflightReset>>["snapshot"]["status"];
31
+ deleteSnapshot: boolean;
32
+ }): WalletResetSnapshotResultStatus;
33
+ export declare function determineBitcoinDataDirResultStatus(options: {
34
+ bitcoinDataDirStatus: Awaited<ReturnType<typeof preflightReset>>["bitcoinDataDir"]["status"];
35
+ deleteSnapshot: boolean;
36
+ deleteBitcoinDataDir: boolean;
37
+ }): WalletResetBitcoinDataDirResultStatus;
38
+ export declare function resetWallet(options: WalletResetExecutionOptions): Promise<WalletResetResult>;
@@ -0,0 +1,458 @@
1
+ import { randomBytes } from "node:crypto";
2
+ import { dirname, join } from "node:path";
3
+ import { mkdir, mkdtemp, rm } from "node:fs/promises";
4
+ import { attachOrStartManagedBitcoindService, createManagedWalletReplica, } from "../../bitcoind/service.js";
5
+ import { createRpcClient } from "../../bitcoind/node.js";
6
+ import { resolveNormalizedWalletDescriptorState } from "../descriptor-normalization.js";
7
+ import { createInternalCoreWalletPassphrase, deriveWalletMaterialFromMnemonic, } from "../material.js";
8
+ import { withUnlockedManagedCoreWallet } from "../managed-core-wallet.js";
9
+ import { resolveWalletRuntimePathsForTesting } from "../runtime.js";
10
+ import { createDefaultWalletSecretProvider, createWalletRootId, createWalletSecretReference, } from "../state/provider.js";
11
+ import { extractWalletRootIdHintFromWalletStateEnvelope, loadWalletState, saveWalletState, } from "../state/storage.js";
12
+ import { confirmTypedAcknowledgement } from "../tx/confirm.js";
13
+ import { deleteBootstrapSnapshotArtifacts, deleteRemovedRoots, isDeletedByRemovalPlan, restoreStagedArtifacts, resolveRemovedRoots, stageArtifact, } from "./artifacts.js";
14
+ import { preflightReset } from "./preflight.js";
15
+ import { acquireResetLocks, terminateTrackedProcesses, } from "./process-cleanup.js";
16
+ function sanitizeWalletName(walletRootId) {
17
+ return `cogcoin-${walletRootId}`.replace(/[^a-zA-Z0-9._-]+/g, "-").slice(0, 63);
18
+ }
19
+ export async function loadWalletForEntropyReset(options) {
20
+ if (options.wallet.rawEnvelope === null) {
21
+ throw new Error("reset_wallet_entropy_reset_unavailable");
22
+ }
23
+ if (options.wallet.mode === "provider-backed") {
24
+ try {
25
+ const loaded = await loadWalletState({
26
+ primaryPath: options.paths.walletStatePath,
27
+ backupPath: options.paths.walletStateBackupPath,
28
+ }, {
29
+ provider: options.provider,
30
+ });
31
+ return {
32
+ loaded,
33
+ access: {
34
+ kind: "provider",
35
+ provider: options.provider,
36
+ },
37
+ };
38
+ }
39
+ catch {
40
+ throw new Error("reset_wallet_entropy_reset_unavailable");
41
+ }
42
+ }
43
+ throw new Error("reset_wallet_entropy_reset_unavailable");
44
+ }
45
+ export function createEntropyRetainedWalletState(previousState, nowUnixMs) {
46
+ const material = deriveWalletMaterialFromMnemonic(previousState.mnemonic.phrase);
47
+ const walletRootId = createWalletRootId();
48
+ return {
49
+ schemaVersion: 5,
50
+ stateRevision: 1,
51
+ lastWrittenAtUnixMs: nowUnixMs,
52
+ walletRootId,
53
+ network: previousState.network,
54
+ localScriptPubKeyHexes: [material.funding.scriptPubKeyHex],
55
+ mnemonic: {
56
+ phrase: previousState.mnemonic.phrase,
57
+ language: previousState.mnemonic.language,
58
+ },
59
+ keys: {
60
+ masterFingerprintHex: material.keys.masterFingerprintHex,
61
+ accountPath: material.keys.accountPath,
62
+ accountXprv: material.keys.accountXprv,
63
+ accountXpub: material.keys.accountXpub,
64
+ },
65
+ descriptor: {
66
+ privateExternal: material.descriptor.privateExternal,
67
+ publicExternal: material.descriptor.publicExternal,
68
+ checksum: null,
69
+ rangeEnd: previousState.descriptor.rangeEnd,
70
+ safetyMargin: previousState.descriptor.safetyMargin,
71
+ },
72
+ funding: {
73
+ address: material.funding.address,
74
+ scriptPubKeyHex: material.funding.scriptPubKeyHex,
75
+ },
76
+ walletBirthTime: previousState.walletBirthTime,
77
+ managedCoreWallet: {
78
+ walletName: sanitizeWalletName(walletRootId),
79
+ internalPassphrase: createInternalCoreWalletPassphrase(),
80
+ descriptorChecksum: null,
81
+ walletAddress: null,
82
+ walletScriptPubKeyHex: null,
83
+ proofStatus: "not-proven",
84
+ lastImportedAtUnixMs: null,
85
+ lastVerifiedAtUnixMs: null,
86
+ },
87
+ domains: [],
88
+ miningState: {
89
+ runMode: "stopped",
90
+ state: "idle",
91
+ pauseReason: null,
92
+ currentPublishState: "none",
93
+ currentDomain: null,
94
+ currentDomainId: null,
95
+ currentDomainIndex: null,
96
+ currentSenderScriptPubKeyHex: null,
97
+ currentTxid: null,
98
+ currentWtxid: null,
99
+ currentFeeRateSatVb: null,
100
+ currentAbsoluteFeeSats: null,
101
+ currentScore: null,
102
+ currentSentence: null,
103
+ currentEncodedSentenceBytesHex: null,
104
+ currentBip39WordIndices: null,
105
+ currentBlendSeedHex: null,
106
+ currentBlockTargetHeight: null,
107
+ currentReferencedBlockHashDisplay: null,
108
+ currentIntentFingerprintHex: null,
109
+ livePublishInMempool: null,
110
+ currentPublishDecision: null,
111
+ replacementCount: 0,
112
+ currentBlockFeeSpentSats: "0",
113
+ sessionFeeSpentSats: "0",
114
+ lifetimeFeeSpentSats: "0",
115
+ sharedMiningConflictOutpoint: null,
116
+ },
117
+ pendingMutations: [],
118
+ };
119
+ }
120
+ export async function recreateManagedCoreWalletReplicaForReset(options) {
121
+ const node = await (options.attachService ?? attachOrStartManagedBitcoindService)({
122
+ dataDir: options.dataDir,
123
+ chain: "main",
124
+ startHeight: 0,
125
+ walletRootId: options.state.walletRootId,
126
+ managedWalletPassphrase: options.state.managedCoreWallet.internalPassphrase,
127
+ });
128
+ const rpc = (options.rpcFactory ?? createRpcClient)(node.rpc);
129
+ await createManagedWalletReplica(rpc, options.state.walletRootId, {
130
+ managedWalletPassphrase: options.state.managedCoreWallet.internalPassphrase,
131
+ });
132
+ const normalizedDescriptors = await resolveNormalizedWalletDescriptorState(options.state, rpc);
133
+ const walletName = sanitizeWalletName(options.state.walletRootId);
134
+ await withUnlockedManagedCoreWallet({
135
+ rpc,
136
+ walletName,
137
+ internalPassphrase: options.state.managedCoreWallet.internalPassphrase,
138
+ run: async () => {
139
+ const importResults = await rpc.importDescriptors(walletName, [{
140
+ desc: normalizedDescriptors.privateExternal,
141
+ timestamp: options.state.walletBirthTime,
142
+ active: false,
143
+ internal: false,
144
+ range: [0, options.state.descriptor.rangeEnd],
145
+ }]);
146
+ if (!importResults.every((result) => result.success)) {
147
+ throw new Error(`wallet_descriptor_import_failed_${JSON.stringify(importResults)}`);
148
+ }
149
+ },
150
+ });
151
+ const derivedFunding = await rpc.deriveAddresses(normalizedDescriptors.publicExternal, [0, 0]);
152
+ if (derivedFunding[0] !== options.state.funding.address) {
153
+ throw new Error("wallet_funding_address_verification_failed");
154
+ }
155
+ const descriptors = await rpc.listDescriptors(walletName);
156
+ const importedDescriptor = descriptors.descriptors.find((entry) => entry.desc === normalizedDescriptors.publicExternal);
157
+ if (importedDescriptor == null) {
158
+ throw new Error("wallet_descriptor_not_present_after_import");
159
+ }
160
+ const nextState = {
161
+ ...options.state,
162
+ stateRevision: options.state.stateRevision + 1,
163
+ lastWrittenAtUnixMs: options.nowUnixMs,
164
+ descriptor: {
165
+ ...options.state.descriptor,
166
+ privateExternal: normalizedDescriptors.privateExternal,
167
+ publicExternal: normalizedDescriptors.publicExternal,
168
+ checksum: normalizedDescriptors.checksum,
169
+ },
170
+ managedCoreWallet: {
171
+ ...options.state.managedCoreWallet,
172
+ walletName,
173
+ descriptorChecksum: normalizedDescriptors.checksum,
174
+ walletAddress: options.state.funding.address,
175
+ walletScriptPubKeyHex: options.state.funding.scriptPubKeyHex,
176
+ proofStatus: "ready",
177
+ lastImportedAtUnixMs: options.nowUnixMs,
178
+ lastVerifiedAtUnixMs: options.nowUnixMs,
179
+ },
180
+ };
181
+ await saveWalletState({
182
+ primaryPath: options.paths.walletStatePath,
183
+ backupPath: options.paths.walletStateBackupPath,
184
+ }, nextState, options.access);
185
+ return nextState;
186
+ }
187
+ export async function resolveResetExecutionDecision(options) {
188
+ if (!options.prompter.isInteractive) {
189
+ throw new Error("reset_requires_tty");
190
+ }
191
+ await confirmTypedAcknowledgement(options.prompter, {
192
+ expected: "permanently reset",
193
+ prompt: "Type \"permanently reset\" to continue: ",
194
+ errorCode: "reset_typed_ack_required",
195
+ requiresTtyErrorCode: "reset_requires_tty",
196
+ typedAckRequiredErrorCode: "reset_typed_ack_required",
197
+ });
198
+ let walletChoice = "";
199
+ let loadedWalletForEntropyReset = null;
200
+ if (options.preflight.wallet.present) {
201
+ const answer = (await options.prompter.prompt("Wallet reset choice ([Enter] retain base entropy, \"skip\", or \"clear wallet entropy\"): ")).trim();
202
+ if (answer !== "" && answer !== "skip" && answer !== "clear wallet entropy") {
203
+ throw new Error("reset_wallet_choice_invalid");
204
+ }
205
+ walletChoice = answer;
206
+ if (walletChoice === "") {
207
+ loadedWalletForEntropyReset = await loadWalletForEntropyReset({
208
+ wallet: options.preflight.wallet,
209
+ paths: options.paths,
210
+ provider: options.provider,
211
+ });
212
+ }
213
+ }
214
+ let deleteSnapshot = false;
215
+ let deleteBitcoinDataDir = false;
216
+ if (options.preflight.snapshot.shouldPrompt) {
217
+ const answer = (await options.prompter.prompt("Delete downloaded 910000 UTXO snapshot too? [y/N]: ")).trim().toLowerCase();
218
+ deleteSnapshot = answer === "y" || answer === "yes";
219
+ if (!deleteSnapshot && options.preflight.bitcoinDataDir.shouldPrompt) {
220
+ const bitcoindAnswer = (await options.prompter.prompt("Delete managed Bitcoin datadir too? [y/N]: ")).trim().toLowerCase();
221
+ deleteBitcoinDataDir = bitcoindAnswer === "y" || bitcoindAnswer === "yes";
222
+ }
223
+ }
224
+ return {
225
+ walletChoice,
226
+ deleteSnapshot,
227
+ deleteBitcoinDataDir,
228
+ loadedWalletForEntropyReset,
229
+ };
230
+ }
231
+ export function determineWalletAction(walletPresent, walletChoice) {
232
+ if (!walletPresent) {
233
+ return "not-present";
234
+ }
235
+ if (walletChoice === "skip") {
236
+ return "kept-unchanged";
237
+ }
238
+ if (walletChoice === "clear wallet entropy") {
239
+ return "deleted";
240
+ }
241
+ return "retain-mnemonic";
242
+ }
243
+ export function determineSnapshotResultStatus(options) {
244
+ if (options.snapshotStatus === "not-present") {
245
+ return "not-present";
246
+ }
247
+ if (options.snapshotStatus === "invalid") {
248
+ return "invalid-removed";
249
+ }
250
+ return options.deleteSnapshot ? "deleted" : "preserved";
251
+ }
252
+ export function determineBitcoinDataDirResultStatus(options) {
253
+ if (options.bitcoinDataDirStatus === "not-present") {
254
+ return "not-present";
255
+ }
256
+ if (options.bitcoinDataDirStatus === "outside-reset-scope") {
257
+ return "outside-reset-scope";
258
+ }
259
+ if (options.deleteSnapshot || options.deleteBitcoinDataDir) {
260
+ return "deleted";
261
+ }
262
+ return "preserved";
263
+ }
264
+ export async function resetWallet(options) {
265
+ const provider = options.provider ?? createDefaultWalletSecretProvider();
266
+ const nowUnixMs = options.nowUnixMs ?? Date.now();
267
+ const paths = options.paths ?? resolveWalletRuntimePathsForTesting();
268
+ const preflight = await preflightReset({
269
+ ...options,
270
+ provider,
271
+ paths,
272
+ });
273
+ const decision = await resolveResetExecutionDecision({
274
+ preflight,
275
+ provider,
276
+ prompter: options.prompter,
277
+ paths,
278
+ });
279
+ const walletAction = determineWalletAction(preflight.wallet.present, decision.walletChoice);
280
+ const snapshotResultStatus = determineSnapshotResultStatus({
281
+ snapshotStatus: preflight.snapshot.status,
282
+ deleteSnapshot: decision.deleteSnapshot,
283
+ });
284
+ const bitcoinDataDirResultStatus = determineBitcoinDataDirResultStatus({
285
+ bitcoinDataDirStatus: preflight.bitcoinDataDir.status,
286
+ deleteSnapshot: decision.deleteSnapshot,
287
+ deleteBitcoinDataDir: decision.deleteBitcoinDataDir,
288
+ });
289
+ const removedPaths = resolveRemovedRoots(paths, {
290
+ preserveBitcoinDataDir: bitcoinDataDirResultStatus === "preserved",
291
+ });
292
+ const locks = await acquireResetLocks(paths, preflight.serviceLockPaths, options.processCleanupDeps);
293
+ await mkdir(dirname(paths.dataRoot), { recursive: true });
294
+ const stagingRoot = await mkdtemp(join(dirname(paths.dataRoot), ".cogcoin-reset-"));
295
+ const stagedWalletArtifacts = [];
296
+ const stagedSnapshotArtifacts = [];
297
+ let stoppedProcesses = {
298
+ managedBitcoind: 0,
299
+ indexerDaemon: 0,
300
+ backgroundMining: 0,
301
+ survivors: 0,
302
+ };
303
+ let rootsDeleted = false;
304
+ let committed = false;
305
+ let newProviderKeyId = null;
306
+ let secretCleanupStatus = "not-found";
307
+ const deletedSecretRefs = [];
308
+ const failedSecretRefs = [];
309
+ const preservedSecretRefs = [];
310
+ let walletOldRootId = extractWalletRootIdHintFromWalletStateEnvelope(preflight.wallet.rawEnvelope?.envelope ?? null)
311
+ ?? null;
312
+ let walletNewRootId = null;
313
+ try {
314
+ stoppedProcesses = await terminateTrackedProcesses(preflight.trackedProcesses, options.processCleanupDeps);
315
+ if (walletAction === "kept-unchanged" || walletAction === "retain-mnemonic") {
316
+ const stagedPrimary = await stageArtifact(paths.walletStatePath, stagingRoot, "wallet/wallet-state.enc", options.artifactDeps);
317
+ const stagedBackup = await stageArtifact(paths.walletStateBackupPath, stagingRoot, "wallet/wallet-state.enc.bak", options.artifactDeps);
318
+ if (stagedPrimary !== null) {
319
+ stagedWalletArtifacts.push(stagedPrimary);
320
+ }
321
+ if (stagedBackup !== null) {
322
+ stagedWalletArtifacts.push(stagedBackup);
323
+ }
324
+ }
325
+ if (snapshotResultStatus === "preserved" && isDeletedByRemovalPlan(removedPaths, preflight.snapshot.path)) {
326
+ const stagedSnapshot = await stageArtifact(preflight.snapshot.path, stagingRoot, "snapshot/utxo-910000.dat", options.artifactDeps);
327
+ if (stagedSnapshot !== null) {
328
+ stagedSnapshotArtifacts.push(stagedSnapshot);
329
+ }
330
+ }
331
+ await deleteRemovedRoots(removedPaths, options.artifactDeps);
332
+ rootsDeleted = true;
333
+ if ((snapshotResultStatus === "deleted" || snapshotResultStatus === "invalid-removed")
334
+ && !isDeletedByRemovalPlan(removedPaths, preflight.snapshot.path)) {
335
+ await deleteBootstrapSnapshotArtifacts(options.dataDir, options.artifactDeps);
336
+ }
337
+ if (walletAction === "kept-unchanged") {
338
+ await restoreStagedArtifacts(stagedWalletArtifacts, options.artifactDeps);
339
+ }
340
+ else if (walletAction === "retain-mnemonic") {
341
+ if (decision.loadedWalletForEntropyReset === null) {
342
+ throw new Error("reset_wallet_entropy_reset_unavailable");
343
+ }
344
+ let nextState = createEntropyRetainedWalletState(decision.loadedWalletForEntropyReset.loaded.state, nowUnixMs);
345
+ walletOldRootId = decision.loadedWalletForEntropyReset.loaded.state.walletRootId;
346
+ walletNewRootId = nextState.walletRootId;
347
+ const secretReference = createWalletSecretReference(nextState.walletRootId);
348
+ newProviderKeyId = secretReference.keyId;
349
+ await provider.storeSecret(secretReference.keyId, randomBytes(32));
350
+ const nextAccess = {
351
+ provider,
352
+ secretReference,
353
+ };
354
+ await saveWalletState({
355
+ primaryPath: paths.walletStatePath,
356
+ backupPath: paths.walletStateBackupPath,
357
+ }, nextState, nextAccess);
358
+ preservedSecretRefs.push(secretReference.keyId);
359
+ nextState = await recreateManagedCoreWalletReplicaForReset({
360
+ state: nextState,
361
+ access: nextAccess,
362
+ paths,
363
+ dataDir: options.dataDir,
364
+ nowUnixMs,
365
+ attachService: options.attachService,
366
+ rpcFactory: options.rpcFactory,
367
+ });
368
+ }
369
+ if (snapshotResultStatus === "preserved") {
370
+ await restoreStagedArtifacts(stagedSnapshotArtifacts, options.artifactDeps);
371
+ }
372
+ committed = true;
373
+ const deleteTrackedSecretReference = async (keyId) => {
374
+ try {
375
+ await provider.deleteSecret(keyId);
376
+ deletedSecretRefs.push(keyId);
377
+ }
378
+ catch {
379
+ failedSecretRefs.push(keyId);
380
+ secretCleanupStatus = "failed";
381
+ throw new Error("reset_secret_cleanup_failed");
382
+ }
383
+ };
384
+ for (const importedSecretKeyId of preflight.wallet.importedSeedSecretProviderKeyIds) {
385
+ await deleteTrackedSecretReference(importedSecretKeyId);
386
+ }
387
+ if (walletAction === "deleted") {
388
+ if (preflight.wallet.secretProviderKeyId !== null) {
389
+ await deleteTrackedSecretReference(preflight.wallet.secretProviderKeyId);
390
+ }
391
+ }
392
+ else if (walletAction === "retain-mnemonic" && preflight.wallet.secretProviderKeyId !== null) {
393
+ if (preflight.wallet.secretProviderKeyId !== newProviderKeyId) {
394
+ await deleteTrackedSecretReference(preflight.wallet.secretProviderKeyId);
395
+ }
396
+ }
397
+ else if (preflight.wallet.secretProviderKeyId !== null) {
398
+ preservedSecretRefs.push(preflight.wallet.secretProviderKeyId);
399
+ }
400
+ if (failedSecretRefs.length > 0) {
401
+ secretCleanupStatus = "failed";
402
+ }
403
+ else if (deletedSecretRefs.length > 0) {
404
+ secretCleanupStatus = "deleted";
405
+ }
406
+ else if (provider.kind === "macos-keychain"
407
+ && preflight.wallet.secretProviderKeyId === null
408
+ && preflight.wallet.importedSeedSecretProviderKeyIds.length === 0
409
+ && preflight.wallet.present
410
+ && preflight.wallet.rawEnvelope === null) {
411
+ secretCleanupStatus = "unknown";
412
+ }
413
+ else if (preflight.wallet.secretProviderKeyId === null
414
+ && preflight.wallet.importedSeedSecretProviderKeyIds.length === 0
415
+ && preflight.wallet.present
416
+ && preflight.wallet.rawEnvelope === null) {
417
+ secretCleanupStatus = "not-found";
418
+ }
419
+ else if (deletedSecretRefs.length === 0) {
420
+ secretCleanupStatus = "not-found";
421
+ }
422
+ return {
423
+ dataRoot: preflight.dataRoot,
424
+ factoryResetReady: true,
425
+ stoppedProcesses,
426
+ secretCleanupStatus,
427
+ deletedSecretRefs,
428
+ failedSecretRefs,
429
+ preservedSecretRefs,
430
+ walletAction,
431
+ walletOldRootId,
432
+ walletNewRootId,
433
+ bootstrapSnapshot: {
434
+ status: snapshotResultStatus,
435
+ path: preflight.snapshot.path,
436
+ },
437
+ bitcoinDataDir: {
438
+ status: bitcoinDataDirResultStatus,
439
+ path: preflight.bitcoinDataDir.path,
440
+ },
441
+ removedPaths,
442
+ };
443
+ }
444
+ catch (error) {
445
+ if (!committed && rootsDeleted) {
446
+ await restoreStagedArtifacts(stagedWalletArtifacts, options.artifactDeps).catch(() => undefined);
447
+ await restoreStagedArtifacts(stagedSnapshotArtifacts, options.artifactDeps).catch(() => undefined);
448
+ if (newProviderKeyId !== null) {
449
+ await provider.deleteSecret(newProviderKeyId).catch(() => undefined);
450
+ }
451
+ }
452
+ throw error;
453
+ }
454
+ finally {
455
+ await rm(stagingRoot, { recursive: true, force: true }).catch(() => undefined);
456
+ await Promise.all(locks.reverse().map(async (lock) => lock.release().catch(() => undefined)));
457
+ }
458
+ }
@@ -0,0 +1,7 @@
1
+ import type { WalletSecretProvider } from "../state/provider.js";
2
+ import type { WalletResetPreflight, WalletResetPreflightOptions } from "./types.js";
3
+ export declare function resetDeletesOsSecrets(options: {
4
+ provider: WalletSecretProvider;
5
+ preflight: WalletResetPreflight;
6
+ }): boolean;
7
+ export declare function preflightReset(options: WalletResetPreflightOptions): Promise<WalletResetPreflight>;