@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,6 +1,7 @@
1
1
  import type { CogcoinPathResolution } from "../app-paths.js";
2
2
  export interface WalletRuntimePaths {
3
3
  dataRoot: string;
4
+ clientDataDir: string;
4
5
  clientConfigPath: string;
5
6
  runtimeRoot: string;
6
7
  hooksRoot: string;
@@ -9,6 +10,8 @@ export interface WalletRuntimePaths {
9
10
  indexerRoot: string;
10
11
  walletStatePath: string;
11
12
  walletStateBackupPath: string;
13
+ walletInitPendingPath: string;
14
+ walletInitPendingBackupPath: string;
12
15
  walletUnlockSessionPath: string;
13
16
  walletExplicitLockPath: string;
14
17
  walletControlLockPath: string;
@@ -3,6 +3,7 @@ export function resolveWalletRuntimePathsForTesting(resolution = {}) {
3
3
  const paths = resolveCogcoinPathsForTesting(resolution);
4
4
  return {
5
5
  dataRoot: paths.dataRoot,
6
+ clientDataDir: paths.clientDataDir,
6
7
  clientConfigPath: paths.clientConfigPath,
7
8
  runtimeRoot: paths.runtimeRoot,
8
9
  hooksRoot: paths.hooksRoot,
@@ -11,6 +12,8 @@ export function resolveWalletRuntimePathsForTesting(resolution = {}) {
11
12
  indexerRoot: paths.indexerRoot,
12
13
  walletStatePath: paths.walletStatePath,
13
14
  walletStateBackupPath: paths.walletStateBackupPath,
15
+ walletInitPendingPath: paths.walletInitPendingPath,
16
+ walletInitPendingBackupPath: paths.walletInitPendingBackupPath,
14
17
  walletUnlockSessionPath: paths.walletUnlockSessionPath,
15
18
  walletExplicitLockPath: paths.walletExplicitLockPath,
16
19
  walletControlLockPath: paths.walletControlLockPath,
@@ -15,6 +15,7 @@ export declare function rederiveKeyFromEnvelope(passphrase: Uint8Array | string,
15
15
  export declare function encryptBytesWithKey(plaintext: Uint8Array, key: Uint8Array, metadata: {
16
16
  format: string;
17
17
  wrappedBy: string;
18
+ walletRootIdHint?: string | null;
18
19
  argon2id?: Argon2EnvelopeParams | null;
19
20
  secretProvider?: WalletSecretReference | null;
20
21
  }): EncryptedEnvelopeV1;
@@ -22,10 +23,12 @@ export declare function decryptBytesWithKey(envelope: EncryptedEnvelopeV1, key:
22
23
  export declare function encryptJsonWithPassphrase<T>(value: T, passphrase: Uint8Array | string, metadata: {
23
24
  format: string;
24
25
  wrappedBy?: string;
26
+ walletRootIdHint?: string | null;
25
27
  }): Promise<EncryptedEnvelopeV1>;
26
28
  export declare function encryptJsonWithSecretProvider<T>(value: T, provider: WalletSecretProvider, secretReference: WalletSecretReference, metadata: {
27
29
  format: string;
28
30
  wrappedBy?: string;
31
+ walletRootIdHint?: string | null;
29
32
  }): Promise<EncryptedEnvelopeV1>;
30
33
  export declare function decryptJsonWithPassphrase<T>(envelope: EncryptedEnvelopeV1, passphrase: Uint8Array | string): Promise<T>;
31
34
  export declare function decryptJsonWithSecretProvider<T>(envelope: EncryptedEnvelopeV1, provider: WalletSecretProvider): Promise<T>;
@@ -76,6 +76,7 @@ export function encryptBytesWithKey(plaintext, key, metadata) {
76
76
  version: 1,
77
77
  cipher: "aes-256-gcm",
78
78
  wrappedBy: metadata.wrappedBy,
79
+ walletRootIdHint: metadata.walletRootIdHint ?? null,
79
80
  argon2id: metadata.argon2id ?? null,
80
81
  secretProvider: metadata.secretProvider ?? null,
81
82
  nonce: nonce.toString("base64"),
@@ -96,6 +97,7 @@ export async function encryptJsonWithPassphrase(value, passphrase, metadata) {
96
97
  return encryptBytesWithKey(Buffer.from(JSON.stringify(value, jsonReplacer)), derived.key, {
97
98
  format: metadata.format,
98
99
  wrappedBy: metadata.wrappedBy ?? "passphrase",
100
+ walletRootIdHint: metadata.walletRootIdHint ?? null,
99
101
  argon2id: derived.params,
100
102
  });
101
103
  }
@@ -104,6 +106,7 @@ export async function encryptJsonWithSecretProvider(value, provider, secretRefer
104
106
  return encryptBytesWithKey(Buffer.from(JSON.stringify(value, jsonReplacer)), key, {
105
107
  format: metadata.format,
106
108
  wrappedBy: metadata.wrappedBy ?? "secret-provider",
109
+ walletRootIdHint: metadata.walletRootIdHint ?? null,
107
110
  secretProvider: secretReference,
108
111
  });
109
112
  }
@@ -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
  }
@@ -1,4 +1,4 @@
1
- import type { WalletStateV1 } from "../types.js";
1
+ import type { EncryptedEnvelopeV1, WalletStateV1 } from "../types.js";
2
2
  import type { WalletSecretProvider, WalletSecretReference } from "./provider.js";
3
3
  export interface WalletStateStoragePaths {
4
4
  primaryPath: string;
@@ -8,6 +8,10 @@ export interface LoadedWalletState {
8
8
  source: "primary" | "backup";
9
9
  state: WalletStateV1;
10
10
  }
11
+ export interface RawWalletStateEnvelope {
12
+ source: "primary" | "backup";
13
+ envelope: EncryptedEnvelopeV1;
14
+ }
11
15
  export type WalletStateSaveAccess = Uint8Array | string | {
12
16
  provider: WalletSecretProvider;
13
17
  secretReference: WalletSecretReference;
@@ -15,5 +19,7 @@ export type WalletStateSaveAccess = Uint8Array | string | {
15
19
  export type WalletStateLoadAccess = Uint8Array | string | {
16
20
  provider: WalletSecretProvider;
17
21
  };
22
+ export declare function loadRawWalletStateEnvelope(paths: WalletStateStoragePaths): Promise<RawWalletStateEnvelope | null>;
23
+ export declare function extractWalletRootIdHintFromWalletStateEnvelope(envelope: EncryptedEnvelopeV1 | null): string | null;
18
24
  export declare function saveWalletState(paths: WalletStateStoragePaths, state: WalletStateV1, access: WalletStateSaveAccess): Promise<void>;
19
25
  export declare function loadWalletState(paths: WalletStateStoragePaths, access: WalletStateLoadAccess): Promise<LoadedWalletState>;
@@ -5,6 +5,43 @@ async function readEnvelope(path) {
5
5
  const raw = await readFile(path, "utf8");
6
6
  return JSON.parse(raw);
7
7
  }
8
+ export async function loadRawWalletStateEnvelope(paths) {
9
+ try {
10
+ return {
11
+ source: "primary",
12
+ envelope: await readEnvelope(paths.primaryPath),
13
+ };
14
+ }
15
+ catch (primaryError) {
16
+ try {
17
+ return {
18
+ source: "backup",
19
+ envelope: await readEnvelope(paths.backupPath),
20
+ };
21
+ }
22
+ catch {
23
+ if (primaryError instanceof SyntaxError
24
+ || !(primaryError instanceof Error)
25
+ || !("code" in primaryError)
26
+ || primaryError.code !== "ENOENT") {
27
+ throw primaryError;
28
+ }
29
+ return null;
30
+ }
31
+ }
32
+ }
33
+ export function extractWalletRootIdHintFromWalletStateEnvelope(envelope) {
34
+ const hint = envelope?.walletRootIdHint?.trim() ?? "";
35
+ if (hint.length > 0) {
36
+ return hint;
37
+ }
38
+ const keyId = envelope?.secretProvider?.keyId ?? null;
39
+ const prefix = "wallet-state:";
40
+ if (keyId === null || !keyId.startsWith(prefix)) {
41
+ return null;
42
+ }
43
+ return keyId.slice(prefix.length);
44
+ }
8
45
  export async function saveWalletState(paths, state, access) {
9
46
  let previousPrimary = null;
10
47
  try {
@@ -21,9 +58,11 @@ export async function saveWalletState(paths, state, access) {
21
58
  const envelope = typeof access === "string" || access instanceof Uint8Array
22
59
  ? await encryptJsonWithPassphrase(state, access, {
23
60
  format: "cogcoin-local-wallet-state",
61
+ walletRootIdHint: state.walletRootId,
24
62
  })
25
63
  : await encryptJsonWithSecretProvider(state, access.provider, access.secretReference, {
26
64
  format: "cogcoin-local-wallet-state",
65
+ walletRootIdHint: state.walletRootId,
27
66
  });
28
67
  await writeJsonFileAtomic(paths.primaryPath, envelope, { mode: 0o600 });
29
68
  if (previousPrimary !== null) {
@@ -229,6 +229,7 @@ export interface EncryptedEnvelopeV1 {
229
229
  version: 1;
230
230
  cipher: "aes-256-gcm";
231
231
  wrappedBy: string;
232
+ walletRootIdHint?: string | null;
232
233
  argon2id?: Argon2EnvelopeParams | null;
233
234
  secretProvider?: {
234
235
  kind: string;
@@ -252,3 +253,11 @@ export interface WalletExplicitLockStateV1 {
252
253
  walletRootId: string;
253
254
  lockedAtUnixMs: number;
254
255
  }
256
+ export interface WalletPendingInitializationStateV1 {
257
+ schemaVersion: 1;
258
+ createdAtUnixMs: number;
259
+ mnemonic: {
260
+ phrase: string;
261
+ language: WalletMnemonicLanguage;
262
+ };
263
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cogcoin/client",
3
- "version": "0.5.4",
3
+ "version": "0.5.6",
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": {