@cogcoin/client 0.5.4 → 0.5.5
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.
- package/README.md +1 -1
- package/dist/app-paths.d.ts +2 -0
- package/dist/app-paths.js +4 -0
- package/dist/art/wallet.txt +9 -9
- package/dist/bitcoind/bootstrap/chunk-manifest.d.ts +14 -0
- package/dist/bitcoind/bootstrap/chunk-manifest.js +85 -0
- package/dist/bitcoind/bootstrap/chunk-recovery.d.ts +4 -0
- package/dist/bitcoind/bootstrap/chunk-recovery.js +122 -0
- package/dist/bitcoind/bootstrap/constants.d.ts +3 -1
- package/dist/bitcoind/bootstrap/constants.js +3 -1
- package/dist/bitcoind/bootstrap/controller.d.ts +6 -1
- package/dist/bitcoind/bootstrap/controller.js +14 -7
- package/dist/bitcoind/bootstrap/default-snapshot-chunk-manifest.d.ts +2 -0
- package/dist/bitcoind/bootstrap/default-snapshot-chunk-manifest.js +2309 -0
- package/dist/bitcoind/bootstrap/download.js +177 -83
- package/dist/bitcoind/bootstrap/headers.d.ts +4 -2
- package/dist/bitcoind/bootstrap/headers.js +29 -4
- package/dist/bitcoind/bootstrap/state.d.ts +11 -1
- package/dist/bitcoind/bootstrap/state.js +50 -23
- package/dist/bitcoind/bootstrap/types.d.ts +12 -1
- package/dist/bitcoind/client/internal-types.d.ts +1 -0
- package/dist/bitcoind/client/managed-client.js +27 -13
- package/dist/bitcoind/client/sync-engine.js +42 -5
- package/dist/bitcoind/errors.js +9 -0
- package/dist/bitcoind/types.d.ts +9 -0
- package/dist/cli/output.js +1 -1
- package/dist/wallet/lifecycle.js +64 -5
- package/dist/wallet/runtime.d.ts +2 -0
- package/dist/wallet/runtime.js +2 -0
- package/dist/wallet/state/pending-init.d.ts +24 -0
- package/dist/wallet/state/pending-init.js +59 -0
- package/dist/wallet/state/provider.d.ts +1 -0
- package/dist/wallet/state/provider.js +7 -1
- package/dist/wallet/types.d.ts +8 -0
- package/package.json +4 -2
|
@@ -2,9 +2,39 @@ import { formatManagedSyncErrorMessage } from "../errors.js";
|
|
|
2
2
|
import { normalizeRpcBlock } from "../normalize.js";
|
|
3
3
|
import { estimateEtaSeconds } from "./rate-tracker.js";
|
|
4
4
|
const DEFAULT_SYNC_CATCH_UP_POLL_MS = 2_000;
|
|
5
|
-
function
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
function createAbortError(signal) {
|
|
6
|
+
const reason = signal?.reason;
|
|
7
|
+
if (reason instanceof Error) {
|
|
8
|
+
return reason;
|
|
9
|
+
}
|
|
10
|
+
const error = new Error("managed_sync_aborted");
|
|
11
|
+
error.name = "AbortError";
|
|
12
|
+
return error;
|
|
13
|
+
}
|
|
14
|
+
function isAbortError(error, signal) {
|
|
15
|
+
if (signal?.aborted) {
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
18
|
+
return error instanceof Error
|
|
19
|
+
&& (error.name === "AbortError" || error.message === "managed_sync_aborted");
|
|
20
|
+
}
|
|
21
|
+
function throwIfAborted(signal) {
|
|
22
|
+
if (signal?.aborted) {
|
|
23
|
+
throw createAbortError(signal);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
function sleep(ms, signal) {
|
|
27
|
+
return new Promise((resolve, reject) => {
|
|
28
|
+
const timer = setTimeout(() => {
|
|
29
|
+
signal?.removeEventListener("abort", onAbort);
|
|
30
|
+
resolve();
|
|
31
|
+
}, ms);
|
|
32
|
+
const onAbort = () => {
|
|
33
|
+
clearTimeout(timer);
|
|
34
|
+
signal?.removeEventListener("abort", onAbort);
|
|
35
|
+
reject(createAbortError(signal));
|
|
36
|
+
};
|
|
37
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
8
38
|
});
|
|
9
39
|
}
|
|
10
40
|
async function setBitcoinSyncProgress(dependencies, info) {
|
|
@@ -81,9 +111,12 @@ async function syncAgainstBestHeight(dependencies, bestHeight) {
|
|
|
81
111
|
}
|
|
82
112
|
export async function syncToTip(dependencies) {
|
|
83
113
|
try {
|
|
114
|
+
throwIfAborted(dependencies.abortSignal);
|
|
84
115
|
await dependencies.node.validate();
|
|
85
116
|
const indexedTipBeforeBootstrap = await dependencies.client.getTip();
|
|
86
|
-
await dependencies.bootstrap.ensureReady(indexedTipBeforeBootstrap, dependencies.node.expectedChain
|
|
117
|
+
await dependencies.bootstrap.ensureReady(indexedTipBeforeBootstrap, dependencies.node.expectedChain, {
|
|
118
|
+
signal: dependencies.abortSignal,
|
|
119
|
+
});
|
|
87
120
|
const startTip = await dependencies.client.getTip();
|
|
88
121
|
const aggregate = {
|
|
89
122
|
appliedBlocks: 0,
|
|
@@ -95,6 +128,7 @@ export async function syncToTip(dependencies) {
|
|
|
95
128
|
bestHashHex: "",
|
|
96
129
|
};
|
|
97
130
|
while (true) {
|
|
131
|
+
throwIfAborted(dependencies.abortSignal);
|
|
98
132
|
const startInfo = await dependencies.rpc.getBlockchainInfo();
|
|
99
133
|
await setBitcoinSyncProgress(dependencies, startInfo);
|
|
100
134
|
const pass = await syncAgainstBestHeight(dependencies, startInfo.blocks);
|
|
@@ -129,10 +163,13 @@ export async function syncToTip(dependencies) {
|
|
|
129
163
|
if (endInfo.blocks >= dependencies.startHeight && finalTip?.height !== endInfo.blocks) {
|
|
130
164
|
continue;
|
|
131
165
|
}
|
|
132
|
-
await sleep(DEFAULT_SYNC_CATCH_UP_POLL_MS);
|
|
166
|
+
await sleep(DEFAULT_SYNC_CATCH_UP_POLL_MS, dependencies.abortSignal);
|
|
133
167
|
}
|
|
134
168
|
}
|
|
135
169
|
catch (error) {
|
|
170
|
+
if (isAbortError(error, dependencies.abortSignal)) {
|
|
171
|
+
throw createAbortError(dependencies.abortSignal);
|
|
172
|
+
}
|
|
136
173
|
const message = formatManagedSyncErrorMessage(error instanceof Error ? error.message : String(error));
|
|
137
174
|
await dependencies.progress.setPhase("error", {
|
|
138
175
|
lastError: message,
|
package/dist/bitcoind/errors.js
CHANGED
|
@@ -18,6 +18,15 @@ export function formatManagedSyncErrorMessage(message) {
|
|
|
18
18
|
if (message === "snapshot_response_body_missing") {
|
|
19
19
|
return appendNextStep("Snapshot server returned an empty response body.", "Wait a moment, confirm the snapshot host is reachable, then rerun sync.");
|
|
20
20
|
}
|
|
21
|
+
if (message === "snapshot_resume_requires_partial_content") {
|
|
22
|
+
return appendNextStep("Snapshot server ignored the resume request for a partial download.", "Wait a moment and rerun sync. If this keeps happening, confirm the snapshot host supports HTTP range requests.");
|
|
23
|
+
}
|
|
24
|
+
if (message.startsWith("snapshot_chunk_sha256_mismatch_")) {
|
|
25
|
+
return appendNextStep("A downloaded snapshot chunk was corrupted and was rolled back to the last verified checkpoint.", "Wait a moment and rerun sync. If this keeps happening, check local disk health and the stability of the snapshot connection.");
|
|
26
|
+
}
|
|
27
|
+
if (message.startsWith("snapshot_download_incomplete_")) {
|
|
28
|
+
return appendNextStep("Snapshot download ended before the expected file size was reached.", "Wait a moment and rerun sync. The downloader will resume from the last verified checkpoint.");
|
|
29
|
+
}
|
|
21
30
|
if (message === "bitcoind_cookie_timeout") {
|
|
22
31
|
return appendNextStep("The managed Bitcoin node did not finish starting in time.", "Check the node logs and local permissions for the Bitcoin data directory, then rerun sync.");
|
|
23
32
|
}
|
package/dist/bitcoind/types.d.ts
CHANGED
|
@@ -8,6 +8,15 @@ export interface SnapshotMetadata {
|
|
|
8
8
|
sha256: string;
|
|
9
9
|
sizeBytes: number;
|
|
10
10
|
}
|
|
11
|
+
export interface SnapshotChunkManifest {
|
|
12
|
+
formatVersion: number;
|
|
13
|
+
chunkSizeBytes: number;
|
|
14
|
+
snapshotFilename: string;
|
|
15
|
+
snapshotHeight: number;
|
|
16
|
+
snapshotSizeBytes: number;
|
|
17
|
+
snapshotSha256: string;
|
|
18
|
+
chunkSha256s: string[];
|
|
19
|
+
}
|
|
11
20
|
export interface WritingQuote {
|
|
12
21
|
quote: string;
|
|
13
22
|
author: string;
|
package/dist/cli/output.js
CHANGED
|
@@ -335,7 +335,7 @@ export function createCliErrorPresentation(errorCode, fallbackMessage) {
|
|
|
335
335
|
return {
|
|
336
336
|
what: "Mnemonic confirmation failed.",
|
|
337
337
|
why: "The requested recovery-phrase confirmation word did not match, so wallet initialization was canceled before it could finish.",
|
|
338
|
-
next: "Run `cogcoin init` again and re-enter the requested confirmation words carefully.",
|
|
338
|
+
next: "Run `cogcoin init` again and re-enter the requested confirmation words carefully. The same recovery phrase will be shown until confirmation succeeds.",
|
|
339
339
|
};
|
|
340
340
|
}
|
|
341
341
|
if (errorCode === "wallet_restore_mnemonic_invalid") {
|
package/dist/wallet/lifecycle.js
CHANGED
|
@@ -19,8 +19,9 @@ 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 { createDefaultWalletSecretProvider, createWalletPendingInitSecretReference, createWalletRootId, createWalletSecretReference, } from "./state/provider.js";
|
|
24
25
|
import { 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";
|
|
@@ -62,6 +63,53 @@ function extractWalletRootIdFromEnvelope(envelope) {
|
|
|
62
63
|
}
|
|
63
64
|
return keyId.slice(prefix.length);
|
|
64
65
|
}
|
|
66
|
+
function resolvePendingInitializationStoragePaths(paths) {
|
|
67
|
+
return {
|
|
68
|
+
primaryPath: paths.walletInitPendingPath,
|
|
69
|
+
backupPath: paths.walletInitPendingBackupPath,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
async function clearPendingInitialization(paths, provider) {
|
|
73
|
+
await clearWalletPendingInitializationState(resolvePendingInitializationStoragePaths(paths), {
|
|
74
|
+
provider,
|
|
75
|
+
secretReference: createWalletPendingInitSecretReference(paths.stateRoot),
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
async function loadOrCreatePendingInitializationMaterial(options) {
|
|
79
|
+
try {
|
|
80
|
+
const loaded = await loadWalletPendingInitializationStateOrNull(resolvePendingInitializationStoragePaths(options.paths), {
|
|
81
|
+
provider: options.provider,
|
|
82
|
+
});
|
|
83
|
+
if (loaded !== null) {
|
|
84
|
+
return deriveWalletMaterialFromMnemonic(loaded.state.mnemonic.phrase);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
await clearPendingInitialization(options.paths, options.provider);
|
|
89
|
+
}
|
|
90
|
+
const material = generateWalletMaterial();
|
|
91
|
+
const secretReference = createWalletPendingInitSecretReference(options.paths.stateRoot);
|
|
92
|
+
const pendingState = {
|
|
93
|
+
schemaVersion: 1,
|
|
94
|
+
createdAtUnixMs: options.nowUnixMs,
|
|
95
|
+
mnemonic: {
|
|
96
|
+
phrase: material.mnemonic.phrase,
|
|
97
|
+
language: material.mnemonic.language,
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
await options.provider.storeSecret(secretReference.keyId, randomBytes(32));
|
|
101
|
+
try {
|
|
102
|
+
await saveWalletPendingInitializationState(resolvePendingInitializationStoragePaths(options.paths), pendingState, {
|
|
103
|
+
provider: options.provider,
|
|
104
|
+
secretReference,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
catch (error) {
|
|
108
|
+
await options.provider.deleteSecret(secretReference.keyId).catch(() => undefined);
|
|
109
|
+
throw error;
|
|
110
|
+
}
|
|
111
|
+
return material;
|
|
112
|
+
}
|
|
65
113
|
function createInitialWalletState(options) {
|
|
66
114
|
return {
|
|
67
115
|
schemaVersion: 1,
|
|
@@ -780,8 +828,9 @@ export function parseUnlockDurationToMs(raw) {
|
|
|
780
828
|
}
|
|
781
829
|
return duration;
|
|
782
830
|
}
|
|
783
|
-
async function ensureWalletNotInitialized(paths) {
|
|
831
|
+
async function ensureWalletNotInitialized(paths, provider) {
|
|
784
832
|
if (await pathExists(paths.walletStatePath) || await pathExists(paths.walletStateBackupPath)) {
|
|
833
|
+
await clearPendingInitialization(paths, provider);
|
|
785
834
|
throw new Error("wallet_already_initialized");
|
|
786
835
|
}
|
|
787
836
|
}
|
|
@@ -1079,11 +1128,16 @@ export async function initializeWallet(options) {
|
|
|
1079
1128
|
walletRootId: null,
|
|
1080
1129
|
});
|
|
1081
1130
|
try {
|
|
1082
|
-
await ensureWalletNotInitialized(paths);
|
|
1083
|
-
const material =
|
|
1131
|
+
await ensureWalletNotInitialized(paths, provider);
|
|
1132
|
+
const material = await loadOrCreatePendingInitializationMaterial({
|
|
1133
|
+
provider,
|
|
1134
|
+
paths,
|
|
1135
|
+
nowUnixMs,
|
|
1136
|
+
});
|
|
1084
1137
|
let mnemonicRevealed = false;
|
|
1085
1138
|
options.prompter.writeLine("Cogcoin Wallet Initialization");
|
|
1086
|
-
options.prompter.writeLine("Write down this 24-word recovery phrase.
|
|
1139
|
+
options.prompter.writeLine("Write down this 24-word recovery phrase.");
|
|
1140
|
+
options.prompter.writeLine("The same phrase will be shown again until confirmation succeeds:");
|
|
1087
1141
|
options.prompter.writeLine("");
|
|
1088
1142
|
for (const line of renderWalletMnemonicRevealArt(material.mnemonic.words)) {
|
|
1089
1143
|
options.prompter.writeLine(line);
|
|
@@ -1126,6 +1180,7 @@ export async function initializeWallet(options) {
|
|
|
1126
1180
|
provider,
|
|
1127
1181
|
secretReference,
|
|
1128
1182
|
});
|
|
1183
|
+
await clearPendingInitialization(paths, provider);
|
|
1129
1184
|
return {
|
|
1130
1185
|
walletRootId,
|
|
1131
1186
|
fundingAddress: verifiedState.funding.address,
|
|
@@ -1290,6 +1345,7 @@ export async function importWallet(options) {
|
|
|
1290
1345
|
const replacementStateExists = await pathExists(paths.walletStatePath) || await pathExists(paths.walletStateBackupPath);
|
|
1291
1346
|
const importedWalletDir = join(options.dataDir, "wallets", sanitizeWalletName(payload.walletRootId));
|
|
1292
1347
|
const replacementCoreWalletExists = await pathExists(importedWalletDir);
|
|
1348
|
+
await clearPendingInitialization(paths, provider);
|
|
1293
1349
|
if (replacementStateExists || replacementCoreWalletExists) {
|
|
1294
1350
|
await confirmTypedAcknowledgement(options.prompter, "IMPORT", "Type IMPORT to replace the existing local wallet state and managed Core wallet replica: ");
|
|
1295
1351
|
}
|
|
@@ -1333,6 +1389,7 @@ export async function importWallet(options) {
|
|
|
1333
1389
|
provider,
|
|
1334
1390
|
secretReference,
|
|
1335
1391
|
});
|
|
1392
|
+
await clearPendingInitialization(paths, provider);
|
|
1336
1393
|
if (previousWalletRootId !== null && previousWalletRootId !== payload.walletRootId) {
|
|
1337
1394
|
await provider.deleteSecret(createWalletSecretReference(previousWalletRootId).keyId).catch(() => undefined);
|
|
1338
1395
|
}
|
|
@@ -1372,6 +1429,7 @@ export async function restoreWalletFromMnemonic(options) {
|
|
|
1372
1429
|
|| await pathExists(paths.walletStateBackupPath);
|
|
1373
1430
|
const replacementCoreWalletExists = await detectExistingManagedWalletReplica(options.dataDir);
|
|
1374
1431
|
const mnemonicPhrase = await promptForRestoreMnemonic(options.prompter);
|
|
1432
|
+
await clearPendingInitialization(paths, provider);
|
|
1375
1433
|
if (replacementStateExists || replacementCoreWalletExists) {
|
|
1376
1434
|
await confirmRestoreReplacement(options.prompter);
|
|
1377
1435
|
}
|
|
@@ -1425,6 +1483,7 @@ export async function restoreWalletFromMnemonic(options) {
|
|
|
1425
1483
|
provider,
|
|
1426
1484
|
secretReference,
|
|
1427
1485
|
});
|
|
1486
|
+
await clearPendingInitialization(paths, provider);
|
|
1428
1487
|
if (previousWalletRootId !== null && previousWalletRootId !== walletRootId) {
|
|
1429
1488
|
try {
|
|
1430
1489
|
await clearPreviousManagedWalletRuntime({
|
package/dist/wallet/runtime.d.ts
CHANGED
|
@@ -9,6 +9,8 @@ export interface WalletRuntimePaths {
|
|
|
9
9
|
indexerRoot: string;
|
|
10
10
|
walletStatePath: string;
|
|
11
11
|
walletStateBackupPath: string;
|
|
12
|
+
walletInitPendingPath: string;
|
|
13
|
+
walletInitPendingBackupPath: string;
|
|
12
14
|
walletUnlockSessionPath: string;
|
|
13
15
|
walletExplicitLockPath: string;
|
|
14
16
|
walletControlLockPath: string;
|
package/dist/wallet/runtime.js
CHANGED
|
@@ -11,6 +11,8 @@ export function resolveWalletRuntimePathsForTesting(resolution = {}) {
|
|
|
11
11
|
indexerRoot: paths.indexerRoot,
|
|
12
12
|
walletStatePath: paths.walletStatePath,
|
|
13
13
|
walletStateBackupPath: paths.walletStateBackupPath,
|
|
14
|
+
walletInitPendingPath: paths.walletInitPendingPath,
|
|
15
|
+
walletInitPendingBackupPath: paths.walletInitPendingBackupPath,
|
|
14
16
|
walletUnlockSessionPath: paths.walletUnlockSessionPath,
|
|
15
17
|
walletExplicitLockPath: paths.walletExplicitLockPath,
|
|
16
18
|
walletControlLockPath: paths.walletControlLockPath,
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { WalletPendingInitializationStateV1 } from "../types.js";
|
|
2
|
+
import type { WalletSecretProvider, WalletSecretReference } from "./provider.js";
|
|
3
|
+
export interface WalletPendingInitializationStoragePaths {
|
|
4
|
+
primaryPath: string;
|
|
5
|
+
backupPath: string;
|
|
6
|
+
}
|
|
7
|
+
export interface LoadedWalletPendingInitializationState {
|
|
8
|
+
source: "primary" | "backup";
|
|
9
|
+
state: WalletPendingInitializationStateV1;
|
|
10
|
+
}
|
|
11
|
+
export declare function saveWalletPendingInitializationState(paths: WalletPendingInitializationStoragePaths, state: WalletPendingInitializationStateV1, access: {
|
|
12
|
+
provider: WalletSecretProvider;
|
|
13
|
+
secretReference: WalletSecretReference;
|
|
14
|
+
}): Promise<void>;
|
|
15
|
+
export declare function loadWalletPendingInitializationState(paths: WalletPendingInitializationStoragePaths, access: {
|
|
16
|
+
provider: WalletSecretProvider;
|
|
17
|
+
}): Promise<LoadedWalletPendingInitializationState>;
|
|
18
|
+
export declare function loadWalletPendingInitializationStateOrNull(paths: WalletPendingInitializationStoragePaths, access: {
|
|
19
|
+
provider: WalletSecretProvider;
|
|
20
|
+
}): Promise<LoadedWalletPendingInitializationState | null>;
|
|
21
|
+
export declare function clearWalletPendingInitializationState(paths: WalletPendingInitializationStoragePaths, access?: {
|
|
22
|
+
provider?: WalletSecretProvider;
|
|
23
|
+
secretReference?: WalletSecretReference;
|
|
24
|
+
}): Promise<void>;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { readFile, rm } from "node:fs/promises";
|
|
2
|
+
import { writeJsonFileAtomic } from "../fs/atomic.js";
|
|
3
|
+
import { decryptJsonWithSecretProvider, encryptJsonWithSecretProvider, } from "./crypto.js";
|
|
4
|
+
function isMissingFileError(error) {
|
|
5
|
+
return error instanceof Error
|
|
6
|
+
&& "code" in error
|
|
7
|
+
&& error.code === "ENOENT";
|
|
8
|
+
}
|
|
9
|
+
async function readEnvelope(path) {
|
|
10
|
+
const raw = await readFile(path, "utf8");
|
|
11
|
+
return JSON.parse(raw);
|
|
12
|
+
}
|
|
13
|
+
async function loadFromPath(path, source, provider) {
|
|
14
|
+
return {
|
|
15
|
+
source,
|
|
16
|
+
state: await decryptJsonWithSecretProvider(await readEnvelope(path), provider),
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
export async function saveWalletPendingInitializationState(paths, state, access) {
|
|
20
|
+
const envelope = await encryptJsonWithSecretProvider(state, access.provider, access.secretReference, {
|
|
21
|
+
format: "cogcoin-wallet-init-pending-state",
|
|
22
|
+
});
|
|
23
|
+
await writeJsonFileAtomic(paths.primaryPath, envelope, { mode: 0o600 });
|
|
24
|
+
await writeJsonFileAtomic(paths.backupPath, envelope, { mode: 0o600 });
|
|
25
|
+
}
|
|
26
|
+
export async function loadWalletPendingInitializationState(paths, access) {
|
|
27
|
+
try {
|
|
28
|
+
return await loadFromPath(paths.primaryPath, "primary", access.provider);
|
|
29
|
+
}
|
|
30
|
+
catch (primaryError) {
|
|
31
|
+
try {
|
|
32
|
+
return await loadFromPath(paths.backupPath, "backup", access.provider);
|
|
33
|
+
}
|
|
34
|
+
catch (backupError) {
|
|
35
|
+
if (isMissingFileError(primaryError)) {
|
|
36
|
+
throw backupError;
|
|
37
|
+
}
|
|
38
|
+
throw primaryError;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
export async function loadWalletPendingInitializationStateOrNull(paths, access) {
|
|
43
|
+
try {
|
|
44
|
+
return await loadWalletPendingInitializationState(paths, access);
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
if (isMissingFileError(error)) {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
throw error;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
export async function clearWalletPendingInitializationState(paths, access) {
|
|
54
|
+
await rm(paths.primaryPath, { force: true }).catch(() => undefined);
|
|
55
|
+
await rm(paths.backupPath, { force: true }).catch(() => undefined);
|
|
56
|
+
if (access?.provider != null && access.secretReference != null) {
|
|
57
|
+
await access.provider.deleteSecret(access.secretReference.keyId).catch(() => undefined);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -23,6 +23,7 @@ export interface WalletSecretProvider {
|
|
|
23
23
|
deleteSecret(keyId: string): Promise<void>;
|
|
24
24
|
}
|
|
25
25
|
export declare function createWalletSecretReference(walletRootId: string): WalletSecretReference;
|
|
26
|
+
export declare function createWalletPendingInitSecretReference(stateRoot: string): WalletSecretReference;
|
|
26
27
|
export declare class MemoryWalletSecretProvider implements WalletSecretProvider {
|
|
27
28
|
#private;
|
|
28
29
|
readonly kind = "memory-test";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { randomUUID } from "node:crypto";
|
|
1
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
2
2
|
import { execFile, spawn } from "node:child_process";
|
|
3
3
|
import { mkdir, readFile, rm } from "node:fs/promises";
|
|
4
4
|
import { dirname, join } from "node:path";
|
|
@@ -17,6 +17,12 @@ export function createWalletSecretReference(walletRootId) {
|
|
|
17
17
|
keyId: `wallet-state:${walletRootId}`,
|
|
18
18
|
};
|
|
19
19
|
}
|
|
20
|
+
export function createWalletPendingInitSecretReference(stateRoot) {
|
|
21
|
+
return {
|
|
22
|
+
kind: "wallet-init-pending-key",
|
|
23
|
+
keyId: `wallet-init-pending:${createHash("sha256").update(stateRoot).digest("hex")}`,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
20
26
|
function bytesToBase64(secret) {
|
|
21
27
|
return Buffer.from(secret).toString("base64");
|
|
22
28
|
}
|
package/dist/wallet/types.d.ts
CHANGED
|
@@ -252,3 +252,11 @@ export interface WalletExplicitLockStateV1 {
|
|
|
252
252
|
walletRootId: string;
|
|
253
253
|
lockedAtUnixMs: number;
|
|
254
254
|
}
|
|
255
|
+
export interface WalletPendingInitializationStateV1 {
|
|
256
|
+
schemaVersion: 1;
|
|
257
|
+
createdAtUnixMs: number;
|
|
258
|
+
mnemonic: {
|
|
259
|
+
phrase: string;
|
|
260
|
+
language: WalletMnemonicLanguage;
|
|
261
|
+
};
|
|
262
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cogcoin/client",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.5",
|
|
4
4
|
"description": "Store-backed Cogcoin client with wallet flows, SQLite persistence, and managed Bitcoin Core integration.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -53,10 +53,12 @@
|
|
|
53
53
|
"files": [
|
|
54
54
|
"LICENSE",
|
|
55
55
|
"README.md",
|
|
56
|
-
"dist
|
|
56
|
+
"dist"
|
|
57
57
|
],
|
|
58
58
|
"scripts": {
|
|
59
59
|
"build": "rm -rf dist && node ./node_modules/typescript/bin/tsc -p tsconfig.json && node -e \"import('node:fs/promises').then(async (fs) => { await fs.mkdir('dist/art', { recursive: true }); await Promise.all([fs.copyFile('src/writing_quotes.json', 'dist/writing_quotes.json'), fs.copyFile('src/art/banner.txt', 'dist/art/banner.txt'), fs.copyFile('src/art/scroll.txt', 'dist/art/scroll.txt'), fs.copyFile('src/art/train-smoke.txt', 'dist/art/train-smoke.txt'), fs.copyFile('src/art/train.txt', 'dist/art/train.txt'), fs.copyFile('src/art/train-car.txt', 'dist/art/train-car.txt'), fs.copyFile('src/art/wallet.txt', 'dist/art/wallet.txt')]); })\"",
|
|
60
|
+
"generate:default-snapshot-chunk-manifest": "node scripts/generate-default-snapshot-chunk-manifest.mjs",
|
|
61
|
+
"verify:default-snapshot-chunk-manifest": "node scripts/generate-default-snapshot-chunk-manifest.mjs --check",
|
|
60
62
|
"test": "rm -rf .test-dist && node ./node_modules/typescript/bin/tsc -p tsconfig.test.json && node -e \"import('node:fs/promises').then(async (fs) => { await fs.mkdir('.test-dist/src/art', { recursive: true }); await Promise.all([fs.copyFile('src/writing_quotes.json', '.test-dist/src/writing_quotes.json'), fs.copyFile('src/art/banner.txt', '.test-dist/src/art/banner.txt'), fs.copyFile('src/art/scroll.txt', '.test-dist/src/art/scroll.txt'), fs.copyFile('src/art/train-smoke.txt', '.test-dist/src/art/train-smoke.txt'), fs.copyFile('src/art/train.txt', '.test-dist/src/art/train.txt'), fs.copyFile('src/art/train-car.txt', '.test-dist/src/art/train-car.txt'), fs.copyFile('src/art/wallet.txt', '.test-dist/src/art/wallet.txt')]); })\" && node --test .test-dist/test/*.test.js"
|
|
61
63
|
},
|
|
62
64
|
"dependencies": {
|