@did-btcr2/cli 0.14.0 → 0.16.0
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 +106 -17
- package/dist/.tsbuildinfo +1 -1
- package/dist/cjs/index.js +1160 -131
- package/dist/esm/src/cli.js +29 -5
- package/dist/esm/src/cli.js.map +1 -1
- package/dist/esm/src/commands/config.js +130 -18
- package/dist/esm/src/commands/config.js.map +1 -1
- package/dist/esm/src/commands/create.js +13 -1
- package/dist/esm/src/commands/create.js.map +1 -1
- package/dist/esm/src/commands/deactivate.js +16 -2
- package/dist/esm/src/commands/deactivate.js.map +1 -1
- package/dist/esm/src/commands/index.js +2 -0
- package/dist/esm/src/commands/index.js.map +1 -1
- package/dist/esm/src/commands/init.js +63 -0
- package/dist/esm/src/commands/init.js.map +1 -0
- package/dist/esm/src/commands/keystore.js +81 -0
- package/dist/esm/src/commands/keystore.js.map +1 -0
- package/dist/esm/src/commands/profile.js +6 -4
- package/dist/esm/src/commands/profile.js.map +1 -1
- package/dist/esm/src/commands/update.js +16 -2
- package/dist/esm/src/commands/update.js.map +1 -1
- package/dist/esm/src/config-schema.js +149 -0
- package/dist/esm/src/config-schema.js.map +1 -0
- package/dist/esm/src/config.js +579 -55
- package/dist/esm/src/config.js.map +1 -1
- package/dist/esm/src/keystore/file-key-store.js +340 -32
- package/dist/esm/src/keystore/file-key-store.js.map +1 -1
- package/dist/esm/src/keystore/passphrase.js +40 -10
- package/dist/esm/src/keystore/passphrase.js.map +1 -1
- package/dist/esm/src/keystore/paths.js +6 -17
- package/dist/esm/src/keystore/paths.js.map +1 -1
- package/dist/esm/src/output.js +49 -0
- package/dist/esm/src/output.js.map +1 -1
- package/dist/esm/src/paths.js +59 -0
- package/dist/esm/src/paths.js.map +1 -0
- package/dist/esm/src/types.js +11 -0
- package/dist/esm/src/types.js.map +1 -1
- package/dist/types/src/cli.d.ts.map +1 -1
- package/dist/types/src/commands/config.d.ts.map +1 -1
- package/dist/types/src/commands/create.d.ts.map +1 -1
- package/dist/types/src/commands/deactivate.d.ts.map +1 -1
- package/dist/types/src/commands/index.d.ts +2 -0
- package/dist/types/src/commands/index.d.ts.map +1 -1
- package/dist/types/src/commands/init.d.ts +13 -0
- package/dist/types/src/commands/init.d.ts.map +1 -0
- package/dist/types/src/commands/keystore.d.ts +10 -0
- package/dist/types/src/commands/keystore.d.ts.map +1 -0
- package/dist/types/src/commands/profile.d.ts.map +1 -1
- package/dist/types/src/commands/update.d.ts.map +1 -1
- package/dist/types/src/config-schema.d.ts +24 -0
- package/dist/types/src/config-schema.d.ts.map +1 -0
- package/dist/types/src/config.d.ts +252 -14
- package/dist/types/src/config.d.ts.map +1 -1
- package/dist/types/src/keystore/file-key-store.d.ts +86 -10
- package/dist/types/src/keystore/file-key-store.d.ts.map +1 -1
- package/dist/types/src/keystore/passphrase.d.ts +16 -1
- package/dist/types/src/keystore/passphrase.d.ts.map +1 -1
- package/dist/types/src/keystore/paths.d.ts +6 -10
- package/dist/types/src/keystore/paths.d.ts.map +1 -1
- package/dist/types/src/output.d.ts +22 -0
- package/dist/types/src/output.d.ts.map +1 -1
- package/dist/types/src/paths.d.ts +54 -0
- package/dist/types/src/paths.d.ts.map +1 -0
- package/dist/types/src/types.d.ts +70 -0
- package/dist/types/src/types.d.ts.map +1 -1
- package/package.json +5 -5
- package/src/cli.ts +32 -4
- package/src/commands/config.ts +143 -18
- package/src/commands/create.ts +16 -1
- package/src/commands/deactivate.ts +24 -2
- package/src/commands/index.ts +2 -0
- package/src/commands/init.ts +74 -0
- package/src/commands/keystore.ts +98 -0
- package/src/commands/profile.ts +6 -4
- package/src/commands/update.ts +24 -2
- package/src/config-schema.ts +178 -0
- package/src/config.ts +752 -58
- package/src/keystore/file-key-store.ts +455 -43
- package/src/keystore/passphrase.ts +48 -8
- package/src/keystore/paths.ts +6 -18
- package/src/output.ts +53 -0
- package/src/paths.ts +79 -0
- package/src/types.ts +34 -0
package/dist/cjs/index.js
CHANGED
|
@@ -23,30 +23,53 @@ __export(index_exports, {
|
|
|
23
23
|
CLIError: () => CLIError,
|
|
24
24
|
CONFIG_SCHEMA_VERSION: () => CONFIG_SCHEMA_VERSION,
|
|
25
25
|
DidBtcr2Cli: () => DidBtcr2Cli,
|
|
26
|
+
ENV_RPC_PASS_FILE: () => ENV_RPC_PASS_FILE,
|
|
26
27
|
ENV_VARS: () => ENV_VARS,
|
|
28
|
+
REDACTED: () => REDACTED,
|
|
27
29
|
SUPPORTED_NETWORKS: () => SUPPORTED_NETWORKS,
|
|
28
30
|
VERSION: () => VERSION,
|
|
31
|
+
assertKeystoreAllowedForNetwork: () => assertKeystoreAllowedForNetwork,
|
|
32
|
+
blankToUndef: () => blankToUndef,
|
|
29
33
|
defaultApiFactory: () => defaultApiFactory,
|
|
30
34
|
defaultConfigPath: () => defaultConfigPath,
|
|
31
35
|
deriveNetwork: () => deriveNetwork,
|
|
32
36
|
formatResult: () => formatResult,
|
|
33
37
|
getConfigPath: () => getConfigPath,
|
|
38
|
+
isSecretKey: () => isSecretKey,
|
|
34
39
|
keystoreApiFactory: () => keystoreApiFactory,
|
|
40
|
+
parseConfigFileRaw: () => parseConfigFileRaw,
|
|
41
|
+
parseHeaderList: () => parseHeaderList,
|
|
42
|
+
profileNetworkMismatch: () => profileNetworkMismatch,
|
|
35
43
|
profileToOverrides: () => profileToOverrides,
|
|
36
44
|
readConfigFile: () => readConfigFile,
|
|
37
45
|
readEnvOverrides: () => readEnvOverrides,
|
|
46
|
+
redactSecrets: () => redactSecrets,
|
|
38
47
|
registerCompletionCommand: () => registerCompletionCommand,
|
|
39
48
|
registerConfigCommand: () => registerConfigCommand,
|
|
40
49
|
registerCreateCommand: () => registerCreateCommand,
|
|
41
50
|
registerDeactivateCommand: () => registerDeactivateCommand,
|
|
51
|
+
registerInitCommand: () => registerInitCommand,
|
|
42
52
|
registerKeyCommand: () => registerKeyCommand,
|
|
53
|
+
registerKeystoreCommand: () => registerKeystoreCommand,
|
|
43
54
|
registerProfileCommand: () => registerProfileCommand,
|
|
44
55
|
registerResolveCommand: () => registerResolveCommand,
|
|
45
56
|
registerUpdateCommand: () => registerUpdateCommand,
|
|
57
|
+
resolveActiveProfile: () => resolveActiveProfile,
|
|
58
|
+
resolveBroadcastOptions: () => resolveBroadcastOptions,
|
|
59
|
+
resolveConnectionConfig: () => resolveConnectionConfig,
|
|
46
60
|
resolveDefaultNetwork: () => resolveDefaultNetwork,
|
|
61
|
+
resolveEffectiveConfig: () => resolveEffectiveConfig,
|
|
62
|
+
resolveKeystorePath: () => resolveKeystorePath,
|
|
63
|
+
resolveKeystoreProtection: () => resolveKeystoreProtection,
|
|
64
|
+
resolveOutputFormat: () => resolveOutputFormat,
|
|
65
|
+
resolveSecretRef: () => resolveSecretRef,
|
|
66
|
+
resolveSigningKeyRef: () => resolveSigningKeyRef,
|
|
67
|
+
runDoctor: () => runDoctor,
|
|
68
|
+
scrubUrlUserinfo: () => scrubUrlUserinfo,
|
|
47
69
|
setConfigPath: () => setConfigPath,
|
|
48
70
|
unsetConfigPath: () => unsetConfigPath,
|
|
49
|
-
writeConfigFile: () => writeConfigFile
|
|
71
|
+
writeConfigFile: () => writeConfigFile,
|
|
72
|
+
writeDefaultConfigFile: () => writeDefaultConfigFile
|
|
50
73
|
});
|
|
51
74
|
module.exports = __toCommonJS(index_exports);
|
|
52
75
|
|
|
@@ -58,13 +81,13 @@ var importMetaUrl = /* @__PURE__ */ getImportMetaUrl();
|
|
|
58
81
|
var import_common3 = require("@did-btcr2/common");
|
|
59
82
|
var import_commander = require("commander");
|
|
60
83
|
|
|
61
|
-
// src/commands/
|
|
62
|
-
var
|
|
84
|
+
// src/commands/init.ts
|
|
85
|
+
var import_node_fs6 = require("fs");
|
|
63
86
|
|
|
64
87
|
// src/config.ts
|
|
65
88
|
var import_api = require("@did-btcr2/api");
|
|
89
|
+
var import_method = require("@did-btcr2/method");
|
|
66
90
|
var import_node_fs5 = require("fs");
|
|
67
|
-
var import_node_os2 = require("os");
|
|
68
91
|
var import_node_path4 = require("path");
|
|
69
92
|
|
|
70
93
|
// src/error.ts
|
|
@@ -144,6 +167,7 @@ var import_key_manager = require("@did-btcr2/key-manager");
|
|
|
144
167
|
// src/keystore/file-key-store.ts
|
|
145
168
|
var import_node_fs3 = require("fs");
|
|
146
169
|
var import_node_path3 = require("path");
|
|
170
|
+
var import_utils2 = require("@noble/hashes/utils.js");
|
|
147
171
|
var import_base2 = require("@scure/base");
|
|
148
172
|
|
|
149
173
|
// src/keystore/envelope.ts
|
|
@@ -314,16 +338,57 @@ function withFileLock(lockPath, fn, options = {}) {
|
|
|
314
338
|
}
|
|
315
339
|
}
|
|
316
340
|
|
|
317
|
-
// src/
|
|
341
|
+
// src/paths.ts
|
|
318
342
|
var import_node_os = require("os");
|
|
319
343
|
var import_node_path2 = require("path");
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
344
|
+
|
|
345
|
+
// src/types.ts
|
|
346
|
+
var SUPPORTED_NETWORKS = [
|
|
347
|
+
"bitcoin",
|
|
348
|
+
"testnet3",
|
|
349
|
+
"testnet4",
|
|
350
|
+
"signet",
|
|
351
|
+
"mutinynet",
|
|
352
|
+
"regtest"
|
|
353
|
+
];
|
|
354
|
+
function blankToUndef(value) {
|
|
355
|
+
if (value === void 0) return void 0;
|
|
356
|
+
return value.trim() === "" ? void 0 : value;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// src/paths.ts
|
|
360
|
+
var ENV_HOME = "BTCR2_HOME";
|
|
361
|
+
var CONFIG_FILENAME = "config.json";
|
|
362
|
+
var KEYSTORE_FILENAME = "keystore.json";
|
|
363
|
+
function resolveHome(overrides) {
|
|
364
|
+
return blankToUndef(overrides?.home) ?? blankToUndef(process.env[ENV_HOME]) ?? platformDefaultHome();
|
|
365
|
+
}
|
|
366
|
+
function platformDefaultHome() {
|
|
367
|
+
if (process.platform === "win32") {
|
|
368
|
+
const base = blankToUndef(process.env.LOCALAPPDATA) ?? blankToUndef(process.env.APPDATA) ?? (0, import_node_os.homedir)();
|
|
369
|
+
return (0, import_node_path2.join)(base, "btcr2");
|
|
370
|
+
}
|
|
371
|
+
return (0, import_node_path2.join)((0, import_node_os.homedir)(), ".btcr2");
|
|
372
|
+
}
|
|
373
|
+
function defaultConfigPath(overrides) {
|
|
374
|
+
return (0, import_node_path2.join)(resolveHome(overrides), CONFIG_FILENAME);
|
|
375
|
+
}
|
|
376
|
+
function defaultKeystorePath(overrides) {
|
|
377
|
+
return (0, import_node_path2.join)(resolveHome(overrides), KEYSTORE_FILENAME);
|
|
323
378
|
}
|
|
324
379
|
|
|
325
380
|
// src/keystore/file-key-store.ts
|
|
326
381
|
var KEYSTORE_VERSION = 1;
|
|
382
|
+
var VERIFIER_PLAINTEXT = (0, import_utils2.utf8ToBytes)("did-btcr2-keystore-verifier-v1");
|
|
383
|
+
function bytesEqual(a, b) {
|
|
384
|
+
if (a.length !== b.length) return false;
|
|
385
|
+
let diff = 0;
|
|
386
|
+
for (let i = 0; i < a.length; i++) diff |= a[i] ^ b[i];
|
|
387
|
+
return diff === 0;
|
|
388
|
+
}
|
|
389
|
+
function sameEnvelope(a, b) {
|
|
390
|
+
return JSON.stringify(a) === JSON.stringify(b);
|
|
391
|
+
}
|
|
327
392
|
var FileKeyStore = class {
|
|
328
393
|
#path;
|
|
329
394
|
#lockPath;
|
|
@@ -332,12 +397,15 @@ var FileKeyStore = class {
|
|
|
332
397
|
#argonParams;
|
|
333
398
|
#cache = /* @__PURE__ */ new Map();
|
|
334
399
|
#active;
|
|
400
|
+
#protection;
|
|
401
|
+
#verifier;
|
|
335
402
|
constructor(options) {
|
|
336
403
|
this.#path = options.path ?? defaultKeystorePath();
|
|
337
404
|
this.#lockPath = `${this.#path}.lock`;
|
|
338
405
|
this.#lockOptions = options.lock ?? {};
|
|
339
406
|
this.#getPassphrase = options.getPassphrase;
|
|
340
407
|
this.#argonParams = options.argonParams ?? DEFAULT_ARGON_PARAMS;
|
|
408
|
+
this.#protection = options.protection ?? "passphrase";
|
|
341
409
|
ensureDir((0, import_node_path3.dirname)(this.#path), 448);
|
|
342
410
|
this.#loadFromDisk();
|
|
343
411
|
}
|
|
@@ -361,7 +429,26 @@ var FileKeyStore = class {
|
|
|
361
429
|
{ version: parsed.v }
|
|
362
430
|
);
|
|
363
431
|
}
|
|
432
|
+
if (parsed.protection !== "none" && parsed.protection !== "passphrase") {
|
|
433
|
+
throw new KeyStoreError(
|
|
434
|
+
`Keystore at ${this.#path} has no recognized protection header; it was not written by this CLI.`,
|
|
435
|
+
"KEYSTORE_CORRUPT_ERROR",
|
|
436
|
+
{ path: this.#path }
|
|
437
|
+
);
|
|
438
|
+
}
|
|
439
|
+
this.#protection = parsed.protection;
|
|
440
|
+
this.#verifier = parsed.verifier;
|
|
364
441
|
this.#active = parsed.active;
|
|
442
|
+
const sealedPresent = Object.values(parsed.keys ?? {}).some(
|
|
443
|
+
(k) => k && typeof k === "object" && k.secret !== void 0
|
|
444
|
+
);
|
|
445
|
+
if (this.#protection === "passphrase" && sealedPresent && this.#verifier === void 0) {
|
|
446
|
+
throw new KeyStoreError(
|
|
447
|
+
`Keystore at ${this.#path} holds sealed keys but no passphrase verifier; it is corrupt or was tampered with.`,
|
|
448
|
+
"KEYSTORE_CORRUPT_ERROR",
|
|
449
|
+
{ path: this.#path }
|
|
450
|
+
);
|
|
451
|
+
}
|
|
365
452
|
for (const [id, stored] of Object.entries(parsed.keys ?? {})) {
|
|
366
453
|
let publicKey;
|
|
367
454
|
try {
|
|
@@ -381,24 +468,67 @@ var FileKeyStore = class {
|
|
|
381
468
|
{ path: this.#path, keyId: id }
|
|
382
469
|
);
|
|
383
470
|
}
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
471
|
+
const entry = { publicKey, ...stored.tags && { tags: stored.tags } };
|
|
472
|
+
if (stored.plainSecret !== void 0) {
|
|
473
|
+
if (this.#protection !== "none") {
|
|
474
|
+
throw new KeyStoreError(
|
|
475
|
+
`Keystore entry ${id} holds a plaintext secret in an encrypted keystore at ${this.#path}.`,
|
|
476
|
+
"KEYSTORE_CORRUPT_ERROR",
|
|
477
|
+
{ path: this.#path, keyId: id }
|
|
478
|
+
);
|
|
479
|
+
}
|
|
480
|
+
entry.plaintext = true;
|
|
481
|
+
entry.decrypted = this.#decodePlainSecret(stored.plainSecret, id);
|
|
482
|
+
} else if (stored.secret) {
|
|
483
|
+
if (this.#protection !== "passphrase") {
|
|
484
|
+
throw new KeyStoreError(
|
|
485
|
+
`Keystore entry ${id} holds a sealed secret in a dev keystore at ${this.#path}.`,
|
|
486
|
+
"KEYSTORE_CORRUPT_ERROR",
|
|
487
|
+
{ path: this.#path, keyId: id }
|
|
488
|
+
);
|
|
489
|
+
}
|
|
490
|
+
entry.secret = stored.secret;
|
|
491
|
+
}
|
|
492
|
+
this.#cache.set(id, entry);
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
/** Decodes and length-checks a dev-keystore plaintext secret. */
|
|
496
|
+
#decodePlainSecret(encoded, id) {
|
|
497
|
+
let secret;
|
|
498
|
+
try {
|
|
499
|
+
secret = import_base2.base64urlnopad.decode(encoded);
|
|
500
|
+
} catch {
|
|
501
|
+
throw new KeyStoreError(
|
|
502
|
+
`Keystore entry ${id} has a malformed plaintext secret.`,
|
|
503
|
+
"KEYSTORE_CORRUPT_ERROR",
|
|
504
|
+
{ path: this.#path, keyId: id }
|
|
505
|
+
);
|
|
506
|
+
}
|
|
507
|
+
if (secret.length !== 32) {
|
|
508
|
+
throw new KeyStoreError(
|
|
509
|
+
`Keystore entry ${id} has a ${secret.length}-byte secret; expected 32.`,
|
|
510
|
+
"KEYSTORE_CORRUPT_ERROR",
|
|
511
|
+
{ path: this.#path, keyId: id }
|
|
512
|
+
);
|
|
389
513
|
}
|
|
514
|
+
return secret;
|
|
390
515
|
}
|
|
391
516
|
#flush() {
|
|
392
517
|
const keys = {};
|
|
393
518
|
for (const [id, entry] of this.#cache) {
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
}
|
|
519
|
+
const stored = { publicKey: import_base2.base64urlnopad.encode(entry.publicKey) };
|
|
520
|
+
if (entry.tags) stored.tags = entry.tags;
|
|
521
|
+
if (entry.plaintext && entry.decrypted) {
|
|
522
|
+
stored.plainSecret = import_base2.base64urlnopad.encode(entry.decrypted);
|
|
523
|
+
} else if (entry.secret) {
|
|
524
|
+
stored.secret = entry.secret;
|
|
525
|
+
}
|
|
526
|
+
keys[id] = stored;
|
|
399
527
|
}
|
|
400
528
|
const file = {
|
|
401
529
|
v: KEYSTORE_VERSION,
|
|
530
|
+
protection: this.#protection,
|
|
531
|
+
...this.#verifier && { verifier: this.#verifier },
|
|
402
532
|
...this.#active && { active: this.#active },
|
|
403
533
|
keys
|
|
404
534
|
};
|
|
@@ -410,7 +540,8 @@ var FileKeyStore = class {
|
|
|
410
540
|
* mutation applies on top of whatever other processes have written. Secrets
|
|
411
541
|
* already decrypted this session are carried over for entries whose sealed
|
|
412
542
|
* envelope is byte-identical on disk, so a mid-session write does not force a
|
|
413
|
-
* re-prompt for keys it did not touch.
|
|
543
|
+
* re-prompt for keys it did not touch. (Dev-keystore plaintext secrets are
|
|
544
|
+
* reloaded from disk, so they need no carry.)
|
|
414
545
|
*/
|
|
415
546
|
#reload() {
|
|
416
547
|
const carried = /* @__PURE__ */ new Map();
|
|
@@ -441,6 +572,53 @@ var FileKeyStore = class {
|
|
|
441
572
|
this.#flush();
|
|
442
573
|
}, this.#lockOptions);
|
|
443
574
|
}
|
|
575
|
+
/**
|
|
576
|
+
* Verifies a candidate passphrase against the keystore verifier, throwing when
|
|
577
|
+
* it does not open the sentinel. A no-op only on a fresh keystore whose
|
|
578
|
+
* passphrase has not been established yet (no verifier to check against).
|
|
579
|
+
*/
|
|
580
|
+
#assertPassphrase(passphrase) {
|
|
581
|
+
if (!this.#verifier) return;
|
|
582
|
+
let plain;
|
|
583
|
+
try {
|
|
584
|
+
plain = decryptSecret(this.#verifier, passphrase);
|
|
585
|
+
} catch {
|
|
586
|
+
throw new KeyStoreError(
|
|
587
|
+
`Incorrect passphrase for the keystore at ${this.#path}.`,
|
|
588
|
+
"DECRYPT_ERROR",
|
|
589
|
+
{ path: this.#path }
|
|
590
|
+
);
|
|
591
|
+
}
|
|
592
|
+
if (!bytesEqual(plain, VERIFIER_PLAINTEXT)) {
|
|
593
|
+
throw new KeyStoreError(
|
|
594
|
+
`Keystore verifier at ${this.#path} did not match; the file may be corrupt.`,
|
|
595
|
+
"KEYSTORE_CORRUPT_ERROR",
|
|
596
|
+
{ path: this.#path }
|
|
597
|
+
);
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
/** Passphrase for opening an existing secret: verified when a verifier exists. */
|
|
601
|
+
#openPassphrase() {
|
|
602
|
+
const passphrase = this.#getPassphrase();
|
|
603
|
+
this.#assertPassphrase(passphrase);
|
|
604
|
+
return passphrase;
|
|
605
|
+
}
|
|
606
|
+
/**
|
|
607
|
+
* Passphrase for sealing a secret. When the keystore's passphrase is already
|
|
608
|
+
* established (a verifier exists), prompt once and verify it. Otherwise this is
|
|
609
|
+
* establishment on a fresh keystore: prompt with confirm and mint a verifier for
|
|
610
|
+
* the caller to persist alongside the first sealed key.
|
|
611
|
+
*/
|
|
612
|
+
#sealPassphrase() {
|
|
613
|
+
if (this.#verifier) {
|
|
614
|
+
const passphrase2 = this.#getPassphrase();
|
|
615
|
+
this.#assertPassphrase(passphrase2);
|
|
616
|
+
return { passphrase: passphrase2 };
|
|
617
|
+
}
|
|
618
|
+
const passphrase = this.#getPassphrase({ confirm: true });
|
|
619
|
+
const newVerifier = encryptSecret(VERIFIER_PLAINTEXT, passphrase, this.#argonParams);
|
|
620
|
+
return { passphrase, newVerifier };
|
|
621
|
+
}
|
|
444
622
|
get(id) {
|
|
445
623
|
const entry = this.#cache.get(id);
|
|
446
624
|
if (!entry) return void 0;
|
|
@@ -448,13 +626,20 @@ var FileKeyStore = class {
|
|
|
448
626
|
publicKey: entry.publicKey,
|
|
449
627
|
...entry.tags && { tags: entry.tags }
|
|
450
628
|
};
|
|
451
|
-
if (entry.
|
|
629
|
+
if (entry.plaintext && entry.decrypted) {
|
|
630
|
+
const secret = entry.decrypted;
|
|
631
|
+
Object.defineProperty(result, "secretKey", {
|
|
632
|
+
configurable: true,
|
|
633
|
+
enumerable: false,
|
|
634
|
+
get: () => secret
|
|
635
|
+
});
|
|
636
|
+
} else if (entry.secret) {
|
|
452
637
|
const sealed = entry.secret;
|
|
453
638
|
Object.defineProperty(result, "secretKey", {
|
|
454
639
|
configurable: true,
|
|
455
640
|
enumerable: false,
|
|
456
641
|
get: () => {
|
|
457
|
-
entry.decrypted ??= decryptSecret(sealed, this.#
|
|
642
|
+
entry.decrypted ??= decryptSecret(sealed, this.#openPassphrase());
|
|
458
643
|
return entry.decrypted;
|
|
459
644
|
}
|
|
460
645
|
});
|
|
@@ -465,12 +650,45 @@ var FileKeyStore = class {
|
|
|
465
650
|
return this.#cache.has(id);
|
|
466
651
|
}
|
|
467
652
|
set(id, value) {
|
|
468
|
-
|
|
653
|
+
if (this.#protection === "none") {
|
|
654
|
+
this.#mutate(() => {
|
|
655
|
+
this.#cache.set(id, {
|
|
656
|
+
publicKey: value.publicKey,
|
|
657
|
+
...value.tags && { tags: value.tags },
|
|
658
|
+
...value.secretKey && { plaintext: true, decrypted: value.secretKey }
|
|
659
|
+
});
|
|
660
|
+
});
|
|
661
|
+
return;
|
|
662
|
+
}
|
|
663
|
+
const verifierAtSeal = this.#verifier;
|
|
664
|
+
let sealed;
|
|
665
|
+
let newVerifier;
|
|
666
|
+
let passphrase;
|
|
667
|
+
if (value.secretKey) {
|
|
668
|
+
const resolved = this.#sealPassphrase();
|
|
669
|
+
passphrase = resolved.passphrase;
|
|
670
|
+
newVerifier = resolved.newVerifier;
|
|
671
|
+
sealed = encryptSecret(value.secretKey, passphrase, this.#argonParams);
|
|
672
|
+
}
|
|
469
673
|
this.#mutate(() => {
|
|
674
|
+
if (value.secretKey) {
|
|
675
|
+
if (this.#verifier === void 0 && newVerifier !== void 0) {
|
|
676
|
+
this.#verifier = newVerifier;
|
|
677
|
+
} else if (this.#verifier !== void 0 && passphrase !== void 0) {
|
|
678
|
+
if (verifierAtSeal !== void 0 && !sameEnvelope(this.#verifier, verifierAtSeal)) {
|
|
679
|
+
throw new KeyStoreError(
|
|
680
|
+
`The keystore passphrase at ${this.#path} changed concurrently; re-run the command.`,
|
|
681
|
+
"KEYSTORE_CONCURRENT_CHANGE_ERROR",
|
|
682
|
+
{ path: this.#path }
|
|
683
|
+
);
|
|
684
|
+
}
|
|
685
|
+
this.#assertPassphrase(passphrase);
|
|
686
|
+
}
|
|
687
|
+
}
|
|
470
688
|
this.#cache.set(id, {
|
|
471
689
|
publicKey: value.publicKey,
|
|
472
690
|
...value.tags && { tags: value.tags },
|
|
473
|
-
...
|
|
691
|
+
...sealed && { secret: sealed },
|
|
474
692
|
...value.secretKey && { decrypted: value.secretKey }
|
|
475
693
|
});
|
|
476
694
|
});
|
|
@@ -533,7 +751,112 @@ var FileKeyStore = class {
|
|
|
533
751
|
this.#active = id;
|
|
534
752
|
});
|
|
535
753
|
}
|
|
754
|
+
/** The keystore's protection mode. */
|
|
755
|
+
get protection() {
|
|
756
|
+
return this.#protection;
|
|
757
|
+
}
|
|
758
|
+
/**
|
|
759
|
+
* Re-seals every sealed secret and the verifier under a new passphrase (ADR
|
|
760
|
+
* 080), returning the number of secrets re-sealed. Verifies the current
|
|
761
|
+
* passphrase against the verifier first. Refused on a dev keystore, which has
|
|
762
|
+
* no passphrase.
|
|
763
|
+
*/
|
|
764
|
+
changePassphrase(oldPassphrase, newPassphrase) {
|
|
765
|
+
if (this.#protection === "none") {
|
|
766
|
+
throw new KeyStoreError(
|
|
767
|
+
`The keystore at ${this.#path} is an unencrypted dev keystore; it has no passphrase to change.`,
|
|
768
|
+
"KEYSTORE_PROTECTION_ERROR",
|
|
769
|
+
{ path: this.#path }
|
|
770
|
+
);
|
|
771
|
+
}
|
|
772
|
+
if (this.#verifier) this.#assertPassphrase(oldPassphrase);
|
|
773
|
+
const resealed = /* @__PURE__ */ new Map();
|
|
774
|
+
for (const [id, entry] of this.#cache) {
|
|
775
|
+
if (!entry.secret) continue;
|
|
776
|
+
let plain;
|
|
777
|
+
try {
|
|
778
|
+
plain = decryptSecret(entry.secret, oldPassphrase);
|
|
779
|
+
} catch {
|
|
780
|
+
throw new KeyStoreError(
|
|
781
|
+
`Incorrect current passphrase for the keystore at ${this.#path}.`,
|
|
782
|
+
"DECRYPT_ERROR",
|
|
783
|
+
{ path: this.#path }
|
|
784
|
+
);
|
|
785
|
+
}
|
|
786
|
+
resealed.set(id, { from: entry.secret, to: encryptSecret(plain, newPassphrase, this.#argonParams), plain });
|
|
787
|
+
}
|
|
788
|
+
const newVerifier = encryptSecret(VERIFIER_PLAINTEXT, newPassphrase, this.#argonParams);
|
|
789
|
+
let rekeyed = 0;
|
|
790
|
+
this.#mutate(() => {
|
|
791
|
+
for (const [id, entry] of this.#cache) {
|
|
792
|
+
if (!entry.secret) continue;
|
|
793
|
+
const pre = resealed.get(id);
|
|
794
|
+
if (!pre || !sameEnvelope(pre.from, entry.secret)) {
|
|
795
|
+
throw new KeyStoreError(
|
|
796
|
+
`The keystore at ${this.#path} changed while its passphrase was being changed; re-run the command.`,
|
|
797
|
+
"KEYSTORE_CONCURRENT_CHANGE_ERROR",
|
|
798
|
+
{ path: this.#path }
|
|
799
|
+
);
|
|
800
|
+
}
|
|
801
|
+
entry.secret = pre.to;
|
|
802
|
+
entry.decrypted = pre.plain;
|
|
803
|
+
rekeyed++;
|
|
804
|
+
}
|
|
805
|
+
this.#verifier = newVerifier;
|
|
806
|
+
});
|
|
807
|
+
return rekeyed;
|
|
808
|
+
}
|
|
536
809
|
};
|
|
810
|
+
function keystoreSummary(path) {
|
|
811
|
+
const absent = { protection: "absent", established: false, keyCount: 0, active: void 0 };
|
|
812
|
+
if (!(0, import_node_fs3.existsSync)(path)) return absent;
|
|
813
|
+
let parsed;
|
|
814
|
+
try {
|
|
815
|
+
parsed = JSON.parse((0, import_node_fs3.readFileSync)(path, "utf-8"));
|
|
816
|
+
} catch {
|
|
817
|
+
return absent;
|
|
818
|
+
}
|
|
819
|
+
const keys = parsed.keys && typeof parsed.keys === "object" ? parsed.keys : {};
|
|
820
|
+
const keyCount = Object.keys(keys).length;
|
|
821
|
+
const active = typeof parsed.active === "string" ? parsed.active : void 0;
|
|
822
|
+
if (parsed.protection === "none") {
|
|
823
|
+
return { protection: "dev", established: true, keyCount, active };
|
|
824
|
+
}
|
|
825
|
+
if (parsed.protection === "passphrase") {
|
|
826
|
+
return { protection: "encrypted", established: parsed.verifier !== void 0, keyCount, active };
|
|
827
|
+
}
|
|
828
|
+
return absent;
|
|
829
|
+
}
|
|
830
|
+
function keystoreProtection(path) {
|
|
831
|
+
return keystoreSummary(path).protection;
|
|
832
|
+
}
|
|
833
|
+
function initKeystore(path, options) {
|
|
834
|
+
ensureDir((0, import_node_path3.dirname)(path), 448);
|
|
835
|
+
let file;
|
|
836
|
+
if (options.protection === "none") {
|
|
837
|
+
file = { v: KEYSTORE_VERSION, protection: "none", keys: {} };
|
|
838
|
+
} else {
|
|
839
|
+
const passphrase = options.getPassphrase({ confirm: true });
|
|
840
|
+
const verifier = encryptSecret(VERIFIER_PLAINTEXT, passphrase, options.argonParams ?? DEFAULT_ARGON_PARAMS);
|
|
841
|
+
file = { v: KEYSTORE_VERSION, protection: "passphrase", verifier, keys: {} };
|
|
842
|
+
}
|
|
843
|
+
writeFileAtomic(path, `${JSON.stringify(file, null, 2)}
|
|
844
|
+
`, 384);
|
|
845
|
+
}
|
|
846
|
+
function changeKeystorePassphrase(path, oldPassphrase, newPassphrase, argonParams) {
|
|
847
|
+
const store = new FileKeyStore({
|
|
848
|
+
path,
|
|
849
|
+
...argonParams && { argonParams },
|
|
850
|
+
getPassphrase: () => {
|
|
851
|
+
throw new KeyStoreError(
|
|
852
|
+
`Unexpected passphrase prompt while changing the passphrase for ${path}.`,
|
|
853
|
+
"KEYSTORE_INTERNAL_ERROR",
|
|
854
|
+
{ path }
|
|
855
|
+
);
|
|
856
|
+
}
|
|
857
|
+
});
|
|
858
|
+
return store.changePassphrase(oldPassphrase, newPassphrase);
|
|
859
|
+
}
|
|
537
860
|
|
|
538
861
|
// src/keystore/file-backed-key-manager.ts
|
|
539
862
|
var FileBackedKeyManager = class {
|
|
@@ -594,10 +917,12 @@ var FileBackedKeyManager = class {
|
|
|
594
917
|
var import_node_fs4 = require("fs");
|
|
595
918
|
var ENV_KEYSTORE_PASSPHRASE = "BTCR2_KEYSTORE_PASSPHRASE";
|
|
596
919
|
function acquirePassphrase(options = {}) {
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
920
|
+
if (!options.forcePrompt) {
|
|
921
|
+
const fromEnv = process.env[ENV_KEYSTORE_PASSPHRASE];
|
|
922
|
+
if (fromEnv) return assertNonEmpty(fromEnv.replace(/\r?\n$/, ""));
|
|
923
|
+
if (options.passphraseFile) {
|
|
924
|
+
return assertNonEmpty((0, import_node_fs4.readFileSync)(options.passphraseFile, "utf-8").replace(/\r?\n$/, ""));
|
|
925
|
+
}
|
|
601
926
|
}
|
|
602
927
|
if (!process.stdin.isTTY) {
|
|
603
928
|
throw new KeyStoreError(
|
|
@@ -620,6 +945,12 @@ function assertNonEmpty(passphrase) {
|
|
|
620
945
|
}
|
|
621
946
|
return passphrase;
|
|
622
947
|
}
|
|
948
|
+
var IDLE_WAIT = new Int32Array(new SharedArrayBuffer(4));
|
|
949
|
+
var IDLE_POLL_MS = 20;
|
|
950
|
+
function dropLastUtf8Char(bytes) {
|
|
951
|
+
while (bytes.length > 0 && (bytes[bytes.length - 1] & 192) === 128) bytes.pop();
|
|
952
|
+
bytes.pop();
|
|
953
|
+
}
|
|
623
954
|
function promptHidden(label) {
|
|
624
955
|
process.stderr.write(label);
|
|
625
956
|
const stdin = process.stdin;
|
|
@@ -634,7 +965,10 @@ function promptHidden(label) {
|
|
|
634
965
|
read = (0, import_node_fs4.readSync)(stdin.fd, byte, 0, 1, null);
|
|
635
966
|
} catch (error) {
|
|
636
967
|
const code = error.code;
|
|
637
|
-
if (code === "EAGAIN")
|
|
968
|
+
if (code === "EAGAIN") {
|
|
969
|
+
Atomics.wait(IDLE_WAIT, 0, 0, IDLE_POLL_MS);
|
|
970
|
+
continue;
|
|
971
|
+
}
|
|
638
972
|
if (code === "EOF") break;
|
|
639
973
|
throw error;
|
|
640
974
|
}
|
|
@@ -645,7 +979,7 @@ function promptHidden(label) {
|
|
|
645
979
|
throw new KeyStoreError("Passphrase entry aborted.", "PASSPHRASE_REQUIRED_ERROR");
|
|
646
980
|
}
|
|
647
981
|
if (ch === 127 || ch === 8) {
|
|
648
|
-
bytes
|
|
982
|
+
dropLastUtf8Char(bytes);
|
|
649
983
|
continue;
|
|
650
984
|
}
|
|
651
985
|
bytes.push(ch);
|
|
@@ -657,16 +991,6 @@ function promptHidden(label) {
|
|
|
657
991
|
return Buffer.from(bytes).toString("utf-8");
|
|
658
992
|
}
|
|
659
993
|
|
|
660
|
-
// src/types.ts
|
|
661
|
-
var SUPPORTED_NETWORKS = [
|
|
662
|
-
"bitcoin",
|
|
663
|
-
"testnet3",
|
|
664
|
-
"testnet4",
|
|
665
|
-
"signet",
|
|
666
|
-
"mutinynet",
|
|
667
|
-
"regtest"
|
|
668
|
-
];
|
|
669
|
-
|
|
670
994
|
// src/config.ts
|
|
671
995
|
var CONFIG_SCHEMA_VERSION = 1;
|
|
672
996
|
function writeConfigFile(path, mutate) {
|
|
@@ -677,14 +1001,31 @@ function writeConfigFile(path, mutate) {
|
|
|
677
1001
|
writeFileAtomic(path, `${JSON.stringify(raw, null, 2)}
|
|
678
1002
|
`, 384);
|
|
679
1003
|
}
|
|
1004
|
+
function writeDefaultConfigFile(path) {
|
|
1005
|
+
const scaffold = {
|
|
1006
|
+
schemaVersion: CONFIG_SCHEMA_VERSION,
|
|
1007
|
+
defaults: { output: "text" },
|
|
1008
|
+
profiles: Object.fromEntries(SUPPORTED_NETWORKS.map((n) => [n, {}]))
|
|
1009
|
+
};
|
|
1010
|
+
ensureDir((0, import_node_path4.dirname)(path), 448);
|
|
1011
|
+
writeFileAtomic(path, `${JSON.stringify(scaffold, null, 2)}
|
|
1012
|
+
`, 384);
|
|
1013
|
+
}
|
|
680
1014
|
function getConfigPath(config, path) {
|
|
681
1015
|
return path.split(".").reduce(
|
|
682
1016
|
(node, key) => node?.[key],
|
|
683
1017
|
config
|
|
684
1018
|
);
|
|
685
1019
|
}
|
|
1020
|
+
var UNSAFE_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
|
|
1021
|
+
function assertSafeKey(key, path) {
|
|
1022
|
+
if (UNSAFE_KEYS.has(key)) {
|
|
1023
|
+
throw new CLIError(`Illegal config path segment "${key}" in "${path}".`, "INVALID_ARGUMENT_ERROR", { path, key });
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
686
1026
|
function setConfigPath(config, path, value) {
|
|
687
1027
|
const keys = path.split(".");
|
|
1028
|
+
keys.forEach((key) => assertSafeKey(key, path));
|
|
688
1029
|
const last = keys.pop();
|
|
689
1030
|
if (!last) throw new CLIError("Config path must be non-empty.", "INVALID_ARGUMENT_ERROR");
|
|
690
1031
|
let node = config;
|
|
@@ -696,6 +1037,7 @@ function setConfigPath(config, path, value) {
|
|
|
696
1037
|
}
|
|
697
1038
|
function unsetConfigPath(config, path) {
|
|
698
1039
|
const keys = path.split(".");
|
|
1040
|
+
keys.forEach((key) => assertSafeKey(key, path));
|
|
699
1041
|
const last = keys.pop();
|
|
700
1042
|
if (!last) return;
|
|
701
1043
|
let node = config;
|
|
@@ -711,7 +1053,10 @@ var ENV_VARS = {
|
|
|
711
1053
|
BTC_RPC_USER: "BTCR2_BTC_RPC_USER",
|
|
712
1054
|
BTC_RPC_PASS: "BTCR2_BTC_RPC_PASS",
|
|
713
1055
|
CAS_GATEWAY: "BTCR2_CAS_GATEWAY",
|
|
714
|
-
CAS_RPC_URL: "BTCR2_CAS_RPC_URL"
|
|
1056
|
+
CAS_RPC_URL: "BTCR2_CAS_RPC_URL",
|
|
1057
|
+
BTC_TIMEOUT: "BTCR2_BTC_TIMEOUT",
|
|
1058
|
+
CAS_TIMEOUT: "BTCR2_CAS_TIMEOUT",
|
|
1059
|
+
FEE_RATE: "BTCR2_FEE_RATE"
|
|
715
1060
|
};
|
|
716
1061
|
function readEnvOverrides() {
|
|
717
1062
|
const env = (key) => process.env[key] || void 0;
|
|
@@ -724,16 +1069,42 @@ function readEnvOverrides() {
|
|
|
724
1069
|
casRpcUrl: env(ENV_VARS.CAS_RPC_URL)
|
|
725
1070
|
};
|
|
726
1071
|
}
|
|
727
|
-
function
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
1072
|
+
function parseConfigFileRaw(path) {
|
|
1073
|
+
let content;
|
|
1074
|
+
try {
|
|
1075
|
+
content = (0, import_node_fs5.readFileSync)(path, "utf-8");
|
|
1076
|
+
} catch (error) {
|
|
1077
|
+
if (error.code === "ENOENT") return void 0;
|
|
1078
|
+
throw new CLIError(
|
|
1079
|
+
`Failed to read config file at ${path}: ${error.message}`,
|
|
1080
|
+
"CONFIG_READ_ERROR",
|
|
1081
|
+
{ path }
|
|
1082
|
+
);
|
|
1083
|
+
}
|
|
732
1084
|
try {
|
|
733
|
-
const content = (0, import_node_fs5.readFileSync)(path, "utf-8");
|
|
734
1085
|
return JSON.parse(content);
|
|
735
|
-
} catch {
|
|
736
|
-
|
|
1086
|
+
} catch (error) {
|
|
1087
|
+
throw new CLIError(
|
|
1088
|
+
`Config file at ${path} is not valid JSON: ${error.message}. Fix the file by hand; the CLI will not overwrite it while it is unparseable.`,
|
|
1089
|
+
"CONFIG_PARSE_ERROR",
|
|
1090
|
+
{ path }
|
|
1091
|
+
);
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
function readConfigFile(path) {
|
|
1095
|
+
const parsed = parseConfigFileRaw(path);
|
|
1096
|
+
if (parsed === void 0) return void 0;
|
|
1097
|
+
migrateConfigShape(parsed, path);
|
|
1098
|
+
return parsed;
|
|
1099
|
+
}
|
|
1100
|
+
function migrateConfigShape(raw, path) {
|
|
1101
|
+
const version = typeof raw.schemaVersion === "number" ? raw.schemaVersion : 0;
|
|
1102
|
+
if (version > CONFIG_SCHEMA_VERSION) {
|
|
1103
|
+
throw new CLIError(
|
|
1104
|
+
`Config file at ${path} has schemaVersion ${version}, but this CLI supports up to ${CONFIG_SCHEMA_VERSION}. Upgrade the btcr2 CLI to read it.`,
|
|
1105
|
+
"CONFIG_SCHEMA_VERSION_ERROR",
|
|
1106
|
+
{ path, fileVersion: version, supported: CONFIG_SCHEMA_VERSION }
|
|
1107
|
+
);
|
|
737
1108
|
}
|
|
738
1109
|
}
|
|
739
1110
|
function profileToOverrides(config, profileName) {
|
|
@@ -748,58 +1119,326 @@ function profileToOverrides(config, profileName) {
|
|
|
748
1119
|
casRpcUrl: profile.cas?.rpcUrl
|
|
749
1120
|
};
|
|
750
1121
|
}
|
|
1122
|
+
function resolveActiveProfile(file, overrides) {
|
|
1123
|
+
const name = blankToUndef(overrides?.profile) ?? file?.defaults?.profile;
|
|
1124
|
+
if (!name) return { name: void 0, network: void 0 };
|
|
1125
|
+
const declared = file?.profiles?.[name]?.network;
|
|
1126
|
+
const network = declared && SUPPORTED_NETWORKS.includes(declared) ? declared : SUPPORTED_NETWORKS.includes(name) ? name : void 0;
|
|
1127
|
+
return { name, network };
|
|
1128
|
+
}
|
|
751
1129
|
function resolveDefaultNetwork(overrides) {
|
|
752
|
-
const configPath = overrides?.config ?? defaultConfigPath();
|
|
1130
|
+
const configPath = overrides?.config ?? defaultConfigPath(overrides);
|
|
753
1131
|
const file = readConfigFile(configPath);
|
|
754
1132
|
const explicit = file?.defaults?.network;
|
|
755
1133
|
if (explicit && SUPPORTED_NETWORKS.includes(explicit)) return explicit;
|
|
756
|
-
const
|
|
757
|
-
if (
|
|
758
|
-
return profile;
|
|
759
|
-
}
|
|
1134
|
+
const { network } = resolveActiveProfile(file, overrides);
|
|
1135
|
+
if (network) return network;
|
|
760
1136
|
return "regtest";
|
|
761
1137
|
}
|
|
1138
|
+
function profileNetworkMismatch(network, overrides) {
|
|
1139
|
+
let file;
|
|
1140
|
+
try {
|
|
1141
|
+
file = readConfigFile(overrides?.config ?? defaultConfigPath(overrides));
|
|
1142
|
+
} catch {
|
|
1143
|
+
return void 0;
|
|
1144
|
+
}
|
|
1145
|
+
const { name, network: declared } = resolveActiveProfile(file, overrides);
|
|
1146
|
+
if (name && declared && declared !== network) return { profile: name, declared };
|
|
1147
|
+
return void 0;
|
|
1148
|
+
}
|
|
1149
|
+
function resolveOutputFormat(options) {
|
|
1150
|
+
const candidates = [
|
|
1151
|
+
blankToUndef(options.output),
|
|
1152
|
+
process.env.BTCR2_OUTPUT || void 0
|
|
1153
|
+
];
|
|
1154
|
+
try {
|
|
1155
|
+
candidates.push(readConfigFile(options.config ?? defaultConfigPath(options))?.defaults?.output);
|
|
1156
|
+
} catch {
|
|
1157
|
+
}
|
|
1158
|
+
for (const candidate of candidates) {
|
|
1159
|
+
if (candidate === "json" || candidate === "text") return candidate;
|
|
1160
|
+
}
|
|
1161
|
+
return "text";
|
|
1162
|
+
}
|
|
1163
|
+
function resolveRpcUnit(overrides, env, fileOverrides) {
|
|
1164
|
+
const layers = [
|
|
1165
|
+
{ src: "flag", url: overrides?.btcRpcUrl, user: overrides?.btcRpcUser, pass: overrides?.btcRpcPass },
|
|
1166
|
+
{ src: "env", url: env?.btcRpcUrl, user: env?.btcRpcUser, pass: env?.btcRpcPass },
|
|
1167
|
+
{ src: "file", url: fileOverrides?.btcRpcUrl, user: fileOverrides?.btcRpcUser, pass: fileOverrides?.btcRpcPass }
|
|
1168
|
+
];
|
|
1169
|
+
const withUrl = layers.find((l) => blankToUndef(l.url) !== void 0);
|
|
1170
|
+
if (withUrl) {
|
|
1171
|
+
return { src: withUrl.src, url: blankToUndef(withUrl.url), user: blankToUndef(withUrl.user), pass: blankToUndef(withUrl.pass) };
|
|
1172
|
+
}
|
|
1173
|
+
const withCreds = layers.find((l) => blankToUndef(l.user) !== void 0 || blankToUndef(l.pass) !== void 0);
|
|
1174
|
+
if (withCreds) {
|
|
1175
|
+
return { src: withCreds.src, url: void 0, user: blankToUndef(withCreds.user), pass: blankToUndef(withCreds.pass) };
|
|
1176
|
+
}
|
|
1177
|
+
return void 0;
|
|
1178
|
+
}
|
|
762
1179
|
function resolveConnectionConfig(network, overrides) {
|
|
763
1180
|
if (!network) return {};
|
|
764
|
-
const configPath = overrides?.config ?? defaultConfigPath();
|
|
1181
|
+
const configPath = overrides?.config ?? defaultConfigPath(overrides);
|
|
765
1182
|
const file = readConfigFile(configPath);
|
|
766
|
-
const
|
|
1183
|
+
const { name: activeProfile } = resolveActiveProfile(file, overrides);
|
|
1184
|
+
const profileName = activeProfile ?? network;
|
|
767
1185
|
const fileOverrides = file ? profileToOverrides(file, profileName) : {};
|
|
768
1186
|
const env = readEnvOverrides();
|
|
769
|
-
const
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
btcRpcUser: overrides?.btcRpcUser ?? env.btcRpcUser ?? fileOverrides.btcRpcUser,
|
|
773
|
-
btcRpcPass: overrides?.btcRpcPass ?? env.btcRpcPass ?? fileOverrides.btcRpcPass,
|
|
774
|
-
casGateway: overrides?.casGateway ?? env.casGateway ?? fileOverrides.casGateway,
|
|
775
|
-
casRpcUrl: overrides?.casRpcUrl ?? env.casRpcUrl ?? fileOverrides.casRpcUrl
|
|
776
|
-
};
|
|
1187
|
+
const pick = (flag, envVal, fileVal) => blankToUndef(flag) ?? blankToUndef(envVal) ?? blankToUndef(fileVal);
|
|
1188
|
+
const profileBtc = file?.profiles?.[profileName]?.btc;
|
|
1189
|
+
const profileCas = file?.profiles?.[profileName]?.cas;
|
|
777
1190
|
const btc = { network };
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
1191
|
+
const btcRest = pick(overrides?.btcRest, env.btcRest, fileOverrides.btcRest);
|
|
1192
|
+
const restHeaders = mergeHeaders(profileBtc?.headers, parseHeaderList(overrides?.btcRestHeader, "--btc-rest-header"));
|
|
1193
|
+
if (btcRest || restHeaders) {
|
|
1194
|
+
btc.rest = { ...btcRest ? { host: btcRest } : {}, ...restHeaders ? { headers: restHeaders } : {} };
|
|
1195
|
+
}
|
|
1196
|
+
const rpcUnit = resolveRpcUnit(overrides, env, fileOverrides);
|
|
1197
|
+
const rpcWallet = pick(overrides?.btcRpcWallet, void 0, profileBtc?.wallet);
|
|
1198
|
+
const rpcHeaders = mergeHeaders(profileBtc?.rpcHeaders, parseHeaderList(overrides?.btcRpcHeader, "--btc-rpc-header"));
|
|
1199
|
+
const networkHasDefaultRpc = import_api.DEFAULT_BITCOIN_NETWORK_CONFIG[network].rpc !== void 0;
|
|
1200
|
+
const wantsRpc = rpcUnit !== void 0 || rpcWallet !== void 0 || rpcHeaders !== void 0;
|
|
1201
|
+
const hasRpcHost = rpcUnit?.url !== void 0 || networkHasDefaultRpc;
|
|
1202
|
+
if (wantsRpc && hasRpcHost) {
|
|
1203
|
+
const password = resolveSecretRef(rpcUnit?.pass) ?? readRpcPassFile();
|
|
782
1204
|
btc.rpc = {
|
|
783
|
-
host:
|
|
784
|
-
username:
|
|
785
|
-
password:
|
|
1205
|
+
...rpcUnit?.url !== void 0 ? { host: rpcUnit.url } : {},
|
|
1206
|
+
...rpcUnit?.user !== void 0 ? { username: rpcUnit.user } : {},
|
|
1207
|
+
...password !== void 0 ? { password } : {},
|
|
1208
|
+
...rpcWallet ? { wallet: rpcWallet } : {},
|
|
1209
|
+
...rpcHeaders ? { headers: rpcHeaders } : {}
|
|
786
1210
|
};
|
|
787
1211
|
}
|
|
1212
|
+
const btcTimeout = resolveTimeout(overrides?.btcTimeout, process.env[ENV_VARS.BTC_TIMEOUT], profileBtc?.timeoutMs, "--btc-timeout", 1);
|
|
1213
|
+
if (btcTimeout !== void 0) btc.timeoutMs = btcTimeout;
|
|
1214
|
+
const casGateway = pick(overrides?.casGateway, env.casGateway, fileOverrides.casGateway);
|
|
1215
|
+
const casRpcUrl = pick(overrides?.casRpcUrl, env.casRpcUrl, fileOverrides.casRpcUrl);
|
|
1216
|
+
const casTimeout = resolveTimeout(overrides?.casTimeout, process.env[ENV_VARS.CAS_TIMEOUT], profileCas?.timeoutMs, "--cas-timeout");
|
|
788
1217
|
const cas = {};
|
|
789
|
-
if (
|
|
790
|
-
if (
|
|
791
|
-
|
|
1218
|
+
if (casGateway) cas.gateway = casGateway;
|
|
1219
|
+
if (casRpcUrl) cas.rpcUrl = casRpcUrl;
|
|
1220
|
+
if (casTimeout !== void 0) {
|
|
1221
|
+
cas.timeoutMs = casTimeout;
|
|
1222
|
+
if (!casGateway && !casRpcUrl) cas.gateway = import_api.DEFAULT_CAS_GATEWAY;
|
|
1223
|
+
}
|
|
1224
|
+
const hasCas = casGateway || casRpcUrl || casTimeout !== void 0;
|
|
792
1225
|
return { btc, ...hasCas && { cas } };
|
|
793
1226
|
}
|
|
1227
|
+
function resolveTimeout(flag, envVal, fileVal, flagName = "timeout", min = 0) {
|
|
1228
|
+
const raw = blankToUndef(flag) ?? blankToUndef(envVal) ?? (typeof fileVal === "number" ? String(fileVal) : void 0);
|
|
1229
|
+
if (raw === void 0) return void 0;
|
|
1230
|
+
const ms = Number(raw);
|
|
1231
|
+
if (!Number.isFinite(ms) || ms < min) {
|
|
1232
|
+
throw new CLIError(
|
|
1233
|
+
`Invalid ${flagName} value "${raw}": expected a number of milliseconds >= ${min}.`,
|
|
1234
|
+
"INVALID_ARGUMENT_ERROR",
|
|
1235
|
+
{ value: raw }
|
|
1236
|
+
);
|
|
1237
|
+
}
|
|
1238
|
+
return ms;
|
|
1239
|
+
}
|
|
1240
|
+
function parseHeaderList(list, flagName = "--header") {
|
|
1241
|
+
if (!list || list.length === 0) return void 0;
|
|
1242
|
+
const headers = {};
|
|
1243
|
+
for (const entry of list) {
|
|
1244
|
+
const idx = entry.indexOf(":");
|
|
1245
|
+
const key = idx === -1 ? "" : entry.slice(0, idx).trim();
|
|
1246
|
+
if (idx === -1 || key === "") {
|
|
1247
|
+
throw new CLIError(
|
|
1248
|
+
`Invalid ${flagName} "${entry}": expected "Key: Value".`,
|
|
1249
|
+
"INVALID_ARGUMENT_ERROR",
|
|
1250
|
+
{ header: entry }
|
|
1251
|
+
);
|
|
1252
|
+
}
|
|
1253
|
+
headers[key] = entry.slice(idx + 1).trim();
|
|
1254
|
+
}
|
|
1255
|
+
return headers;
|
|
1256
|
+
}
|
|
1257
|
+
function mergeHeaders(base, override) {
|
|
1258
|
+
if (!base && !override) return void 0;
|
|
1259
|
+
return { ...base, ...override };
|
|
1260
|
+
}
|
|
1261
|
+
var ENV_RPC_PASS_FILE = "BTCR2_BTC_RPC_PASS_FILE";
|
|
1262
|
+
function trimTrailingNewline(value) {
|
|
1263
|
+
return value.replace(/\r?\n$/, "");
|
|
1264
|
+
}
|
|
1265
|
+
function readSecretFile(path, source) {
|
|
1266
|
+
try {
|
|
1267
|
+
return trimTrailingNewline((0, import_node_fs5.readFileSync)(path, "utf-8"));
|
|
1268
|
+
} catch (error) {
|
|
1269
|
+
throw new CLIError(
|
|
1270
|
+
`Could not read the RPC password ${source} at ${path}: ${error.message}`,
|
|
1271
|
+
"CONFIG_READ_ERROR",
|
|
1272
|
+
{ path }
|
|
1273
|
+
);
|
|
1274
|
+
}
|
|
1275
|
+
}
|
|
1276
|
+
function resolveSecretRef(value) {
|
|
1277
|
+
if (value === void 0) return void 0;
|
|
1278
|
+
if (value.startsWith("env:")) {
|
|
1279
|
+
const fromEnv = process.env[value.slice(4)];
|
|
1280
|
+
return fromEnv === void 0 ? void 0 : trimTrailingNewline(fromEnv);
|
|
1281
|
+
}
|
|
1282
|
+
if (value.startsWith("file:")) {
|
|
1283
|
+
return readSecretFile(value.slice(5), "file reference");
|
|
1284
|
+
}
|
|
1285
|
+
return value;
|
|
1286
|
+
}
|
|
1287
|
+
function readRpcPassFile() {
|
|
1288
|
+
const path = process.env[ENV_RPC_PASS_FILE];
|
|
1289
|
+
if (!path) return void 0;
|
|
1290
|
+
return readSecretFile(path, `file named by ${ENV_RPC_PASS_FILE}`);
|
|
1291
|
+
}
|
|
1292
|
+
function resolveBroadcastOptions(network, overrides, flags) {
|
|
1293
|
+
const file = readConfigFile(overrides?.config ?? defaultConfigPath(overrides));
|
|
1294
|
+
const { name: activeProfile } = resolveActiveProfile(file, overrides);
|
|
1295
|
+
const profileBtc = file?.profiles?.[activeProfile ?? network]?.btc;
|
|
1296
|
+
const options = {};
|
|
1297
|
+
const feeRateRaw = blankToUndef(flags.feeRate) ?? blankToUndef(process.env[ENV_VARS.FEE_RATE]) ?? (typeof profileBtc?.feeRate === "number" ? String(profileBtc.feeRate) : void 0);
|
|
1298
|
+
if (feeRateRaw !== void 0) options.feeEstimator = new import_method.StaticFeeEstimator(parseFeeRate(feeRateRaw));
|
|
1299
|
+
const changeAddress = blankToUndef(flags.changeAddress) ?? blankToUndef(profileBtc?.changeAddress);
|
|
1300
|
+
if (changeAddress) options.changeAddress = changeAddress;
|
|
1301
|
+
return options.feeEstimator || options.changeAddress ? options : void 0;
|
|
1302
|
+
}
|
|
1303
|
+
function parseFeeRate(raw) {
|
|
1304
|
+
const rate = Number(raw);
|
|
1305
|
+
if (!Number.isFinite(rate) || rate <= 0) {
|
|
1306
|
+
throw new CLIError(
|
|
1307
|
+
`Invalid --fee-rate "${raw}": expected a positive number of sats per vByte.`,
|
|
1308
|
+
"INVALID_ARGUMENT_ERROR",
|
|
1309
|
+
{ value: raw }
|
|
1310
|
+
);
|
|
1311
|
+
}
|
|
1312
|
+
return rate;
|
|
1313
|
+
}
|
|
1314
|
+
function resolveEffectiveConfig(network, overrides) {
|
|
1315
|
+
const file = readConfigFile(overrides?.config ?? defaultConfigPath(overrides));
|
|
1316
|
+
const { name: activeProfile } = resolveActiveProfile(file, overrides);
|
|
1317
|
+
const profileName = activeProfile ?? network;
|
|
1318
|
+
const fileOv = file ? profileToOverrides(file, profileName) : {};
|
|
1319
|
+
const profileBtc = file?.profiles?.[profileName]?.btc;
|
|
1320
|
+
const profileCas = file?.profiles?.[profileName]?.cas;
|
|
1321
|
+
const env = readEnvOverrides();
|
|
1322
|
+
const api = defaultApiFactory(network, overrides);
|
|
1323
|
+
const restCfg = api.btc.connection.rest.config;
|
|
1324
|
+
const rpcCfg = api.btc.connection.rpc?.config;
|
|
1325
|
+
const conn = resolveConnectionConfig(network, overrides);
|
|
1326
|
+
const src = (flag, envVal, fileVal) => blankToUndef(flag) !== void 0 ? "flag" : blankToUndef(envVal) !== void 0 ? "env" : fileVal !== void 0 && fileVal !== null && String(fileVal).trim() !== "" ? "file" : "default";
|
|
1327
|
+
const casGatewayVal = conn.cas?.gateway ?? (conn.cas?.rpcUrl ? void 0 : import_api.DEFAULT_CAS_GATEWAY);
|
|
1328
|
+
const rpcUnit = resolveRpcUnit(overrides, env, fileOv);
|
|
1329
|
+
const rpcSrc = rpcUnit?.src ?? "default";
|
|
1330
|
+
const passFromFile = rpcUnit?.pass === void 0 && process.env[ENV_RPC_PASS_FILE] !== void 0 && rpcCfg?.password !== void 0;
|
|
1331
|
+
return {
|
|
1332
|
+
network,
|
|
1333
|
+
profile: activeProfile,
|
|
1334
|
+
btc: {
|
|
1335
|
+
rest: { value: restCfg.host, source: src(overrides?.btcRest, env.btcRest, fileOv.btcRest) },
|
|
1336
|
+
rpcUrl: { value: rpcCfg?.host, source: rpcUnit?.url !== void 0 ? rpcSrc : "default" },
|
|
1337
|
+
rpcUser: { value: rpcCfg?.username, source: rpcUnit?.user !== void 0 ? rpcSrc : "default" },
|
|
1338
|
+
rpcPass: { value: rpcCfg?.password, source: rpcUnit?.pass !== void 0 ? rpcSrc : passFromFile ? "env" : "default" },
|
|
1339
|
+
rpcWallet: { value: rpcCfg?.wallet, source: src(overrides?.btcRpcWallet, void 0, profileBtc?.wallet) },
|
|
1340
|
+
timeoutMs: { value: conn.btc?.timeoutMs, source: src(overrides?.btcTimeout, process.env[ENV_VARS.BTC_TIMEOUT], profileBtc?.timeoutMs) }
|
|
1341
|
+
},
|
|
1342
|
+
cas: {
|
|
1343
|
+
gateway: { value: casGatewayVal, source: src(overrides?.casGateway, env.casGateway, fileOv.casGateway) },
|
|
1344
|
+
rpcUrl: { value: conn.cas?.rpcUrl, source: src(overrides?.casRpcUrl, env.casRpcUrl, fileOv.casRpcUrl) },
|
|
1345
|
+
timeoutMs: { value: conn.cas?.timeoutMs, source: src(overrides?.casTimeout, process.env[ENV_VARS.CAS_TIMEOUT], profileCas?.timeoutMs) }
|
|
1346
|
+
}
|
|
1347
|
+
};
|
|
1348
|
+
}
|
|
1349
|
+
var DOCTOR_PROBE_TIMEOUT_MS = 5e3;
|
|
1350
|
+
async function probeEndpoint(endpoint, target, url, opts) {
|
|
1351
|
+
try {
|
|
1352
|
+
const res = await fetch(url, {
|
|
1353
|
+
method: opts?.method ?? "GET",
|
|
1354
|
+
headers: opts?.headers,
|
|
1355
|
+
signal: AbortSignal.timeout(DOCTOR_PROBE_TIMEOUT_MS)
|
|
1356
|
+
});
|
|
1357
|
+
return res.ok ? { endpoint, target, ok: true } : { endpoint, target, ok: false, detail: `HTTP ${res.status}` };
|
|
1358
|
+
} catch (error) {
|
|
1359
|
+
return { endpoint, target, ok: false, detail: error.message };
|
|
1360
|
+
}
|
|
1361
|
+
}
|
|
1362
|
+
function withProbeTimeout(promise, ms) {
|
|
1363
|
+
let timer;
|
|
1364
|
+
const timeout = new Promise((_, reject) => {
|
|
1365
|
+
timer = setTimeout(() => reject(new Error(`timed out after ${ms}ms`)), ms);
|
|
1366
|
+
});
|
|
1367
|
+
return Promise.race([promise, timeout]).finally(() => clearTimeout(timer));
|
|
1368
|
+
}
|
|
1369
|
+
async function runDoctor(network, overrides) {
|
|
1370
|
+
const api = defaultApiFactory(network, overrides);
|
|
1371
|
+
const checks = [];
|
|
1372
|
+
const restHost = api.btc.connection.rest.config.host.replace(/\/+$/, "");
|
|
1373
|
+
checks.push(await probeEndpoint("btc-rest", restHost, `${restHost}/blocks/tip/height`, { headers: api.btc.connection.rest.config.headers }));
|
|
1374
|
+
const rpc = api.btc.connection.rpc;
|
|
1375
|
+
if (rpc) {
|
|
1376
|
+
const target = rpc.config.host ?? "(default rpc)";
|
|
1377
|
+
try {
|
|
1378
|
+
await withProbeTimeout(rpc.getBlockchainInfo(), DOCTOR_PROBE_TIMEOUT_MS);
|
|
1379
|
+
checks.push({ endpoint: "btc-rpc", target, ok: true });
|
|
1380
|
+
} catch (error) {
|
|
1381
|
+
checks.push({ endpoint: "btc-rpc", target, ok: false, detail: error.message });
|
|
1382
|
+
}
|
|
1383
|
+
}
|
|
1384
|
+
const conn = resolveConnectionConfig(network, overrides);
|
|
1385
|
+
if (conn.cas?.rpcUrl) {
|
|
1386
|
+
const base = conn.cas.rpcUrl.replace(/\/+$/, "");
|
|
1387
|
+
checks.push(await probeEndpoint("cas", conn.cas.rpcUrl, `${base}/api/v0/version`, { method: "POST" }));
|
|
1388
|
+
} else {
|
|
1389
|
+
const gateway = (conn.cas?.gateway ?? import_api.DEFAULT_CAS_GATEWAY).replace(/\/+$/, "");
|
|
1390
|
+
checks.push(await probeEndpoint("cas", gateway, gateway));
|
|
1391
|
+
}
|
|
1392
|
+
const mismatch = profileNetworkMismatch(network, overrides);
|
|
1393
|
+
return {
|
|
1394
|
+
checks,
|
|
1395
|
+
...mismatch ? { coherence: { profile: mismatch.profile, declared: mismatch.declared, encoding: network } } : {}
|
|
1396
|
+
};
|
|
1397
|
+
}
|
|
794
1398
|
function defaultApiFactory(network, overrides) {
|
|
795
1399
|
return (0, import_api.createApi)(resolveConnectionConfig(network, overrides));
|
|
796
1400
|
}
|
|
797
1401
|
function buildKeystoreKms(overrides) {
|
|
798
1402
|
return new FileBackedKeyManager({
|
|
799
|
-
path: overrides
|
|
800
|
-
|
|
1403
|
+
path: resolveKeystorePath(overrides),
|
|
1404
|
+
// The store decides when to confirm: it passes `confirm: true` only while
|
|
1405
|
+
// establishing a fresh keystore's passphrase, so a first-key typo is caught
|
|
1406
|
+
// by a second entry (ADR 080). confirm is a no-op for env/file sources.
|
|
1407
|
+
getPassphrase: (opts) => acquirePassphrase({ passphraseFile: overrides?.passphraseFile, confirm: opts?.confirm })
|
|
801
1408
|
});
|
|
802
1409
|
}
|
|
1410
|
+
function resolveKeystoreProtection(overrides) {
|
|
1411
|
+
return keystoreProtection(resolveKeystorePath(overrides));
|
|
1412
|
+
}
|
|
1413
|
+
function assertKeystoreAllowedForNetwork(network, overrides) {
|
|
1414
|
+
if (network !== "bitcoin") return;
|
|
1415
|
+
if (resolveKeystoreProtection(overrides) !== "dev") return;
|
|
1416
|
+
const path = resolveKeystorePath(overrides);
|
|
1417
|
+
throw new CLIError(
|
|
1418
|
+
`Refusing a mainnet (bitcoin) operation with the unencrypted dev keystore at ${path}. Dev keystores hold plaintext keys and are for testnet/regtest throwaway material only. Establish an encrypted keystore (btcr2 keystore init) for mainnet keys.`,
|
|
1419
|
+
"DEV_KEYSTORE_MAINNET_ERROR",
|
|
1420
|
+
{ path, network }
|
|
1421
|
+
);
|
|
1422
|
+
}
|
|
1423
|
+
function resolveKeystorePath(overrides, options) {
|
|
1424
|
+
const fromFlag = blankToUndef(overrides?.keystore);
|
|
1425
|
+
if (fromFlag) return fromFlag;
|
|
1426
|
+
let identity;
|
|
1427
|
+
try {
|
|
1428
|
+
identity = activeProfileIdentity(overrides);
|
|
1429
|
+
} catch (error) {
|
|
1430
|
+
if (!options?.lenient) throw error;
|
|
1431
|
+
}
|
|
1432
|
+
return blankToUndef(identity?.keystore) ?? defaultKeystorePath(overrides);
|
|
1433
|
+
}
|
|
1434
|
+
function activeProfileIdentity(overrides) {
|
|
1435
|
+
const file = readConfigFile(overrides?.config ?? defaultConfigPath(overrides));
|
|
1436
|
+
const { name } = resolveActiveProfile(file, overrides);
|
|
1437
|
+
return name ? file?.profiles?.[name]?.identity : void 0;
|
|
1438
|
+
}
|
|
1439
|
+
function resolveSigningKeyRef(overrides) {
|
|
1440
|
+
return blankToUndef(overrides?.signingKey) ?? blankToUndef(activeProfileIdentity(overrides)?.default);
|
|
1441
|
+
}
|
|
803
1442
|
function keystoreApiFactory(network, overrides) {
|
|
804
1443
|
return (0, import_api.createApi)({
|
|
805
1444
|
...resolveConnectionConfig(network, overrides),
|
|
@@ -818,6 +1457,85 @@ function deriveNetwork(did) {
|
|
|
818
1457
|
return network;
|
|
819
1458
|
}
|
|
820
1459
|
|
|
1460
|
+
// src/output.ts
|
|
1461
|
+
var REDACTED = "********";
|
|
1462
|
+
var SECRET_KEY = /(pass|secret|token|auth|api[-_]?key|credential|bearer)/i;
|
|
1463
|
+
function isSecretKey(key) {
|
|
1464
|
+
return SECRET_KEY.test(key);
|
|
1465
|
+
}
|
|
1466
|
+
function scrubUrlUserinfo(value) {
|
|
1467
|
+
if (value === void 0) return void 0;
|
|
1468
|
+
return value.replace(/^([a-z][a-z0-9+.-]*:\/\/[^/@:]+):[^/@]*@/i, `$1:${REDACTED}@`);
|
|
1469
|
+
}
|
|
1470
|
+
function redactSecrets(value, keyName) {
|
|
1471
|
+
if (Array.isArray(value)) {
|
|
1472
|
+
return value.map((item) => redactSecrets(item));
|
|
1473
|
+
}
|
|
1474
|
+
if (value !== null && typeof value === "object") {
|
|
1475
|
+
const out = {};
|
|
1476
|
+
for (const [key, val] of Object.entries(value)) {
|
|
1477
|
+
out[key] = redactSecrets(val, key);
|
|
1478
|
+
}
|
|
1479
|
+
return out;
|
|
1480
|
+
}
|
|
1481
|
+
if (keyName !== void 0 && isSecretKey(keyName)) {
|
|
1482
|
+
return value === void 0 ? void 0 : REDACTED;
|
|
1483
|
+
}
|
|
1484
|
+
return typeof value === "string" ? scrubUrlUserinfo(value) : value;
|
|
1485
|
+
}
|
|
1486
|
+
function formatResult(result, options) {
|
|
1487
|
+
if (options.output === "json") {
|
|
1488
|
+
return JSON.stringify(result, null, 2);
|
|
1489
|
+
}
|
|
1490
|
+
const { data } = result;
|
|
1491
|
+
return typeof data === "string" ? data : JSON.stringify(data, null, 2);
|
|
1492
|
+
}
|
|
1493
|
+
|
|
1494
|
+
// src/commands/init.ts
|
|
1495
|
+
function registerInitCommand(program, globals) {
|
|
1496
|
+
const print = (result) => console.log(formatResult(result, globals()));
|
|
1497
|
+
program.command("init").description("Set up the btcr2 home: create the directory, a default config, and establish the keystore.").option("--dev", "Establish an UNENCRYPTED dev keystore: plaintext keys, no passphrase. Testnet only.", false).option("--force", "Re-create the config and keystore even if they already exist.", false).action((options) => {
|
|
1498
|
+
const g = globals();
|
|
1499
|
+
const home = resolveHome(g);
|
|
1500
|
+
const configPath = g.config ?? defaultConfigPath(g);
|
|
1501
|
+
const keystorePath = resolveKeystorePath(g);
|
|
1502
|
+
ensureDir(home, 448);
|
|
1503
|
+
const created = [];
|
|
1504
|
+
if (!(0, import_node_fs6.existsSync)(configPath) || options.force) {
|
|
1505
|
+
writeDefaultConfigFile(configPath);
|
|
1506
|
+
created.push("config");
|
|
1507
|
+
}
|
|
1508
|
+
const keystoreExists = (0, import_node_fs6.existsSync)(keystorePath);
|
|
1509
|
+
if (keystoreExists && options.force && !g.quiet) {
|
|
1510
|
+
process.stderr.write(
|
|
1511
|
+
`note: a keystore already exists at ${keystorePath} and was left intact. To re-establish it (discarding its keys), run "btcr2 keystore init --force".
|
|
1512
|
+
`
|
|
1513
|
+
);
|
|
1514
|
+
}
|
|
1515
|
+
if (!keystoreExists) {
|
|
1516
|
+
if (options.dev && !g.quiet) {
|
|
1517
|
+
process.stderr.write(
|
|
1518
|
+
"warning: establishing an UNENCRYPTED dev keystore. Keys are stored in plaintext. Use it only for disposable testnet material; mainnet operations will be refused.\n"
|
|
1519
|
+
);
|
|
1520
|
+
}
|
|
1521
|
+
initKeystore(keystorePath, {
|
|
1522
|
+
protection: options.dev ? "none" : "passphrase",
|
|
1523
|
+
getPassphrase: (opts) => acquirePassphrase({ passphraseFile: g.passphraseFile, confirm: opts?.confirm, prompt: "New keystore passphrase: " })
|
|
1524
|
+
});
|
|
1525
|
+
created.push("keystore");
|
|
1526
|
+
}
|
|
1527
|
+
const protection = keystoreSummary(keystorePath).protection;
|
|
1528
|
+
print({ action: "init", data: { home, config: configPath, keystore: keystorePath, created, protection } });
|
|
1529
|
+
if (!g.quiet && g.output !== "json") {
|
|
1530
|
+
process.stderr.write(`btcr2 home ready at ${home}. Next: btcr2 key generate --set-active
|
|
1531
|
+
`);
|
|
1532
|
+
}
|
|
1533
|
+
});
|
|
1534
|
+
}
|
|
1535
|
+
|
|
1536
|
+
// src/commands/create.ts
|
|
1537
|
+
var import_utils3 = require("@noble/hashes/utils.js");
|
|
1538
|
+
|
|
821
1539
|
// src/keystore/resolve-key-ref.ts
|
|
822
1540
|
function fingerprintOf(id) {
|
|
823
1541
|
return /^urn:kms:secp256k1:([0-9a-f]{32})$/.exec(id)?.[1];
|
|
@@ -856,15 +1574,6 @@ function resolveKeyRef(kms, ref) {
|
|
|
856
1574
|
throw new CLIError(`No key matches reference "${ref}".`, "KEY_NOT_FOUND_ERROR", { ref });
|
|
857
1575
|
}
|
|
858
1576
|
|
|
859
|
-
// src/output.ts
|
|
860
|
-
function formatResult(result, options) {
|
|
861
|
-
if (options.output === "json") {
|
|
862
|
-
return JSON.stringify(result, null, 2);
|
|
863
|
-
}
|
|
864
|
-
const { data } = result;
|
|
865
|
-
return typeof data === "string" ? data : JSON.stringify(data, null, 2);
|
|
866
|
-
}
|
|
867
|
-
|
|
868
1577
|
// src/commands/create.ts
|
|
869
1578
|
var EXPECTED_BYTES = {
|
|
870
1579
|
k: { length: 33, label: "secp256k1 compressed public key (33 bytes)" },
|
|
@@ -885,6 +1594,13 @@ function registerCreateCommand(program, factory, keystoreFactory, globals) {
|
|
|
885
1594
|
const overrides = overridesFromGlobals(g);
|
|
886
1595
|
const network = resolveNetwork(options.network, overrides);
|
|
887
1596
|
const signingKey = g.signingKey;
|
|
1597
|
+
const mismatch = profileNetworkMismatch(network, overrides);
|
|
1598
|
+
if (mismatch && !g.quiet) {
|
|
1599
|
+
process.stderr.write(
|
|
1600
|
+
`Warning: creating a "${network}" identifier while the active profile "${mismatch.profile}" declares network "${mismatch.declared}". The identifier's network and the profile's endpoints may not match.
|
|
1601
|
+
`
|
|
1602
|
+
);
|
|
1603
|
+
}
|
|
888
1604
|
const print = (result, note) => {
|
|
889
1605
|
console.log(formatResult(result, g));
|
|
890
1606
|
if (note && g.output !== "json") process.stderr.write(`${note}
|
|
@@ -926,14 +1642,15 @@ function registerCreateCommand(program, factory, keystoreFactory, globals) {
|
|
|
926
1642
|
const publicKey2 = api2.kms.getPublicKey(keyId2);
|
|
927
1643
|
const did2 = api2.createDid("deterministic", publicKey2, { network });
|
|
928
1644
|
print(
|
|
929
|
-
{ action: "create", data: did2, keyId: keyId2, publicKey: (0,
|
|
1645
|
+
{ action: "create", data: did2, keyId: keyId2, publicKey: (0, import_utils3.bytesToHex)(publicKey2) },
|
|
930
1646
|
`Using stored key ${keyId2}.`
|
|
931
1647
|
);
|
|
932
1648
|
return;
|
|
933
1649
|
}
|
|
1650
|
+
assertKeystoreAllowedForNetwork(network, overrides);
|
|
934
1651
|
const api = keystoreFactory(void 0, overrides);
|
|
935
1652
|
const { did, keyId } = api.generateDid({ network, setActive: true });
|
|
936
|
-
const publicKey = (0,
|
|
1653
|
+
const publicKey = (0, import_utils3.bytesToHex)(api.kms.getPublicKey(keyId));
|
|
937
1654
|
print(
|
|
938
1655
|
{ action: "create", data: did, keyId, publicKey },
|
|
939
1656
|
`Generated and stored key ${keyId} (now the active key).`
|
|
@@ -942,6 +1659,7 @@ function registerCreateCommand(program, factory, keystoreFactory, globals) {
|
|
|
942
1659
|
}
|
|
943
1660
|
function overridesFromGlobals(g) {
|
|
944
1661
|
return {
|
|
1662
|
+
home: g.home,
|
|
945
1663
|
config: g.config,
|
|
946
1664
|
profile: g.profile,
|
|
947
1665
|
keystore: g.keystore,
|
|
@@ -963,7 +1681,7 @@ function parseGenesisBytes(hex, type) {
|
|
|
963
1681
|
const expected = EXPECTED_BYTES[type];
|
|
964
1682
|
let bytes;
|
|
965
1683
|
try {
|
|
966
|
-
bytes = (0,
|
|
1684
|
+
bytes = (0, import_utils3.hexToBytes)(hex.trim());
|
|
967
1685
|
} catch {
|
|
968
1686
|
throw new CLIError(
|
|
969
1687
|
`Invalid bytes: not valid hex. Expected ${expected.label}.`,
|
|
@@ -1048,6 +1766,12 @@ function registerUpdateCommand(program, factory, globals) {
|
|
|
1048
1766
|
"Publish update artifacts to a writable CAS before broadcast: auto|always|never. CAS publication is optional; the default distributes the returned artifacts via sidecar.",
|
|
1049
1767
|
parsePublishToCasMode,
|
|
1050
1768
|
"never"
|
|
1769
|
+
).option(
|
|
1770
|
+
"--fee-rate <satsPerVByte>",
|
|
1771
|
+
"Fee rate in sats/vByte for the beacon transaction (default: 5). Raise it under congestion so the transaction confirms."
|
|
1772
|
+
).option(
|
|
1773
|
+
"--change-address <address>",
|
|
1774
|
+
"Send transaction change to this address instead of the beacon address, so a DID's announcements are not linked on-chain (ADR 044)."
|
|
1051
1775
|
).action(async (options) => {
|
|
1052
1776
|
if (!/^\d+$/.test(options.sourceVersionId)) {
|
|
1053
1777
|
throw new CLIError(
|
|
@@ -1072,9 +1796,14 @@ function registerUpdateCommand(program, factory, globals) {
|
|
|
1072
1796
|
);
|
|
1073
1797
|
}
|
|
1074
1798
|
const network = deriveNetwork(did);
|
|
1799
|
+
assertKeystoreAllowedForNetwork(network, globals());
|
|
1075
1800
|
const api = factory(network, globals());
|
|
1076
|
-
const keyId = resolveKeyRef(api.kms.kms, globals()
|
|
1801
|
+
const keyId = resolveKeyRef(api.kms.kms, resolveSigningKeyRef(globals()));
|
|
1077
1802
|
const signer = new import_key_manager2.KeyManagerSigner(api.kms.kms, keyId);
|
|
1803
|
+
const broadcastOptions = resolveBroadcastOptions(network, globals(), {
|
|
1804
|
+
feeRate: options.feeRate,
|
|
1805
|
+
changeAddress: options.changeAddress
|
|
1806
|
+
});
|
|
1078
1807
|
const data = await api.btcr2.update({
|
|
1079
1808
|
sourceDocument: parsed.sourceDocument,
|
|
1080
1809
|
patches: parsed.patches,
|
|
@@ -1082,7 +1811,8 @@ function registerUpdateCommand(program, factory, globals) {
|
|
|
1082
1811
|
verificationMethodId: parsed.verificationMethodId,
|
|
1083
1812
|
beaconId: parsed.beaconId,
|
|
1084
1813
|
signer,
|
|
1085
|
-
publishToCas: options.publishToCas
|
|
1814
|
+
publishToCas: options.publishToCas,
|
|
1815
|
+
...broadcastOptions ? { broadcastOptions } : {}
|
|
1086
1816
|
});
|
|
1087
1817
|
console.log(formatResult({ action: "update", data }, globals()));
|
|
1088
1818
|
});
|
|
@@ -1134,6 +1864,12 @@ function registerDeactivateCommand(program, factory, globals) {
|
|
|
1134
1864
|
"Publish update artifacts to a writable CAS before broadcast: auto|always|never. CAS publication is optional; the default distributes the returned artifacts via sidecar.",
|
|
1135
1865
|
parsePublishToCasMode2,
|
|
1136
1866
|
"never"
|
|
1867
|
+
).option(
|
|
1868
|
+
"--fee-rate <satsPerVByte>",
|
|
1869
|
+
"Fee rate in sats/vByte for the beacon transaction (default: 5). Raise it under congestion so the transaction confirms."
|
|
1870
|
+
).option(
|
|
1871
|
+
"--change-address <address>",
|
|
1872
|
+
"Send transaction change to this address instead of the beacon address, so a DID's announcements are not linked on-chain (ADR 044)."
|
|
1137
1873
|
).action(async (options) => {
|
|
1138
1874
|
if (!/^\d+$/.test(options.sourceVersionId)) {
|
|
1139
1875
|
throw new CLIError(
|
|
@@ -1158,9 +1894,14 @@ function registerDeactivateCommand(program, factory, globals) {
|
|
|
1158
1894
|
);
|
|
1159
1895
|
}
|
|
1160
1896
|
const network = deriveNetwork(did);
|
|
1897
|
+
assertKeystoreAllowedForNetwork(network, globals());
|
|
1161
1898
|
const api = factory(network, globals());
|
|
1162
|
-
const keyId = resolveKeyRef(api.kms.kms, globals()
|
|
1899
|
+
const keyId = resolveKeyRef(api.kms.kms, resolveSigningKeyRef(globals()));
|
|
1163
1900
|
const signer = new import_key_manager3.KeyManagerSigner(api.kms.kms, keyId);
|
|
1901
|
+
const broadcastOptions = resolveBroadcastOptions(network, globals(), {
|
|
1902
|
+
feeRate: options.feeRate,
|
|
1903
|
+
changeAddress: options.changeAddress
|
|
1904
|
+
});
|
|
1164
1905
|
const data = await api.btcr2.update({
|
|
1165
1906
|
sourceDocument: parsed.sourceDocument,
|
|
1166
1907
|
patches: parsed.patches,
|
|
@@ -1168,7 +1909,8 @@ function registerDeactivateCommand(program, factory, globals) {
|
|
|
1168
1909
|
verificationMethodId: parsed.verificationMethodId,
|
|
1169
1910
|
beaconId: parsed.beaconId,
|
|
1170
1911
|
signer,
|
|
1171
|
-
publishToCas: options.publishToCas
|
|
1912
|
+
publishToCas: options.publishToCas,
|
|
1913
|
+
...broadcastOptions ? { broadcastOptions } : {}
|
|
1172
1914
|
});
|
|
1173
1915
|
console.log(formatResult({ action: "deactivate", data }, globals()));
|
|
1174
1916
|
});
|
|
@@ -1199,8 +1941,8 @@ function parseJsonArg2(flagName) {
|
|
|
1199
1941
|
|
|
1200
1942
|
// src/commands/key.ts
|
|
1201
1943
|
var import_keypair = require("@did-btcr2/keypair");
|
|
1202
|
-
var
|
|
1203
|
-
var
|
|
1944
|
+
var import_utils4 = require("@noble/hashes/utils.js");
|
|
1945
|
+
var import_node_fs7 = require("fs");
|
|
1204
1946
|
function registerKeyCommand(program, factory, globals) {
|
|
1205
1947
|
const key = program.command("key").description("Manage keypairs in the encrypted keystore.");
|
|
1206
1948
|
const print = (result) => console.log(formatResult(result, globals()));
|
|
@@ -1209,7 +1951,7 @@ function registerKeyCommand(program, factory, globals) {
|
|
|
1209
1951
|
assertNameAvailable(api.kms.kms, options.name);
|
|
1210
1952
|
const setActive = options.setActive ?? false;
|
|
1211
1953
|
const id = api.kms.generateKey({ ...options.name && { tags: { name: options.name } }, setActive });
|
|
1212
|
-
print({ action: "key-generate", data: { keyId: id, publicKey: (0,
|
|
1954
|
+
print({ action: "key-generate", data: { keyId: id, publicKey: (0, import_utils4.bytesToHex)(api.kms.getPublicKey(id)), active: setActive } });
|
|
1213
1955
|
});
|
|
1214
1956
|
key.command("list").alias("ls").description("List stored keys.").action(() => {
|
|
1215
1957
|
const kms = factory(void 0, globals()).kms.kms;
|
|
@@ -1229,7 +1971,7 @@ function registerKeyCommand(program, factory, globals) {
|
|
|
1229
1971
|
const kms = factory(void 0, globals()).kms.kms;
|
|
1230
1972
|
const id = resolveKeyRef(kms, ref);
|
|
1231
1973
|
const entry = kms.getEntry(id);
|
|
1232
|
-
print({ action: "key-show", data: { keyId: id, publicKey: (0,
|
|
1974
|
+
print({ action: "key-show", data: { keyId: id, publicKey: (0, import_utils4.bytesToHex)(entry.publicKey), ...entry.tags && { tags: entry.tags } } });
|
|
1233
1975
|
});
|
|
1234
1976
|
key.command("import").description("Import a key: a secret from a hex file, or a public key as watch-only.").option("--secret-file <path>", "Path to a file containing a 32-byte secret key as hex.").option("--public <hex>", "A 33-byte compressed public key as hex (imported watch-only).").option("--name <name>", "A human-friendly name, stored as a tag.").option("--set-active", "Make this the active key.", false).action((options) => {
|
|
1235
1977
|
if (Boolean(options.secretFile) === Boolean(options.public)) {
|
|
@@ -1240,13 +1982,13 @@ function registerKeyCommand(program, factory, globals) {
|
|
|
1240
1982
|
const keyPair = options.secretFile ? new import_keypair.SchnorrKeyPair({ secretKey: readHexFile(options.secretFile, 32, "--secret-file") }) : new import_keypair.SchnorrKeyPair({ publicKey: parseHex(options.public ?? "", 33, "--public") });
|
|
1241
1983
|
const setActive = options.setActive ?? false;
|
|
1242
1984
|
const id = api.kms.import(keyPair, { ...options.name && { tags: { name: options.name } }, setActive });
|
|
1243
|
-
print({ action: "key-import", data: { keyId: id, publicKey: (0,
|
|
1985
|
+
print({ action: "key-import", data: { keyId: id, publicKey: (0, import_utils4.bytesToHex)(api.kms.getPublicKey(id)), watchOnly: !options.secretFile, active: setActive } });
|
|
1244
1986
|
});
|
|
1245
1987
|
key.command("export <ref>").description("Export a key. Public material by default; --secret writes the secret to a file.").option("--secret", "Export the secret key. Requires --out.", false).option("--out <path>", "Write the exported secret to this file (created 0600).").action((ref, options) => {
|
|
1246
1988
|
const api = factory(void 0, globals());
|
|
1247
1989
|
const id = resolveKeyRef(api.kms.kms, ref);
|
|
1248
1990
|
if (!options.secret) {
|
|
1249
|
-
print({ action: "key-export", data: { keyId: id, publicKey: (0,
|
|
1991
|
+
print({ action: "key-export", data: { keyId: id, publicKey: (0, import_utils4.bytesToHex)(api.kms.getPublicKey(id)) } });
|
|
1250
1992
|
return;
|
|
1251
1993
|
}
|
|
1252
1994
|
if (!options.out) {
|
|
@@ -1257,7 +1999,7 @@ function registerKeyCommand(program, factory, globals) {
|
|
|
1257
1999
|
throw new CLIError(`Key ${id} is watch-only and has no secret to export.`, "INVALID_ARGUMENT_ERROR", { keyId: id });
|
|
1258
2000
|
}
|
|
1259
2001
|
process.stderr.write("warning: writing an unencrypted secret key to disk. Protect this file and delete it when done.\n");
|
|
1260
|
-
writeSecretFile(options.out, (0,
|
|
2002
|
+
writeSecretFile(options.out, (0, import_utils4.bytesToHex)(keyPair.secretKey.bytes));
|
|
1261
2003
|
print({ action: "key-export", data: { keyId: id, secretWrittenTo: options.out } });
|
|
1262
2004
|
});
|
|
1263
2005
|
key.command("delete <ref>").alias("rm").description("Delete a key from the keystore.").option("--force", "Delete even if it is the active key.", false).action((ref, options) => {
|
|
@@ -1282,7 +2024,7 @@ function assertNameAvailable(kms, name) {
|
|
|
1282
2024
|
function parseHex(hex, expectedBytes, label) {
|
|
1283
2025
|
let bytes;
|
|
1284
2026
|
try {
|
|
1285
|
-
bytes = (0,
|
|
2027
|
+
bytes = (0, import_utils4.hexToBytes)(hex.trim());
|
|
1286
2028
|
} catch {
|
|
1287
2029
|
throw new CLIError(`Invalid hex for ${label}.`, "INVALID_ARGUMENT_ERROR", { label });
|
|
1288
2030
|
}
|
|
@@ -1298,7 +2040,7 @@ function parseHex(hex, expectedBytes, label) {
|
|
|
1298
2040
|
function readHexFile(path, expectedBytes, label) {
|
|
1299
2041
|
let content;
|
|
1300
2042
|
try {
|
|
1301
|
-
content = (0,
|
|
2043
|
+
content = (0, import_node_fs7.readFileSync)(path, "utf-8");
|
|
1302
2044
|
} catch {
|
|
1303
2045
|
throw new CLIError(`Cannot read ${label} at ${path}.`, "INVALID_ARGUMENT_ERROR", { label, path });
|
|
1304
2046
|
}
|
|
@@ -1307,7 +2049,7 @@ function readHexFile(path, expectedBytes, label) {
|
|
|
1307
2049
|
function writeSecretFile(path, contents) {
|
|
1308
2050
|
let fd;
|
|
1309
2051
|
try {
|
|
1310
|
-
fd = (0,
|
|
2052
|
+
fd = (0, import_node_fs7.openSync)(path, "wx", 384);
|
|
1311
2053
|
} catch (error) {
|
|
1312
2054
|
if (error.code === "EEXIST") {
|
|
1313
2055
|
throw new CLIError(`Refusing to overwrite existing file ${path}. Choose a new --out path.`, "INVALID_ARGUMENT_ERROR", { path });
|
|
@@ -1315,62 +2057,316 @@ function writeSecretFile(path, contents) {
|
|
|
1315
2057
|
throw error;
|
|
1316
2058
|
}
|
|
1317
2059
|
try {
|
|
1318
|
-
(0,
|
|
2060
|
+
(0, import_node_fs7.writeFileSync)(fd, contents);
|
|
1319
2061
|
} finally {
|
|
1320
|
-
(0,
|
|
2062
|
+
(0, import_node_fs7.closeSync)(fd);
|
|
2063
|
+
}
|
|
2064
|
+
}
|
|
2065
|
+
|
|
2066
|
+
// src/commands/keystore.ts
|
|
2067
|
+
var import_node_fs8 = require("fs");
|
|
2068
|
+
function registerKeystoreCommand(program, globals) {
|
|
2069
|
+
const keystore = program.command("keystore").description("Establish, inspect, and re-key the keystore.");
|
|
2070
|
+
const print = (result) => console.log(formatResult(result, globals()));
|
|
2071
|
+
keystore.command("init").description("Establish the keystore (encrypted by default). Prompts for a passphrase and confirms it.").option("--dev", "Create an UNENCRYPTED dev keystore: plaintext keys, no passphrase. Testnet only; mainnet operations are refused.", false).option("--force", "Re-establish even if a keystore already exists (discards its keys).", false).action((options) => {
|
|
2072
|
+
const g = globals();
|
|
2073
|
+
const path = resolveKeystorePath(g);
|
|
2074
|
+
if ((0, import_node_fs8.existsSync)(path) && !options.force) {
|
|
2075
|
+
throw new CLIError(
|
|
2076
|
+
`A keystore already exists at ${path}. Use --force to re-establish it (this discards its keys).`,
|
|
2077
|
+
"INVALID_ARGUMENT_ERROR",
|
|
2078
|
+
{ path }
|
|
2079
|
+
);
|
|
2080
|
+
}
|
|
2081
|
+
if ((0, import_node_fs8.existsSync)(path) && options.force && !g.quiet) {
|
|
2082
|
+
const { keyCount } = keystoreSummary(path);
|
|
2083
|
+
if (keyCount > 0) {
|
|
2084
|
+
process.stderr.write(
|
|
2085
|
+
`warning: re-establishing the keystore at ${path} permanently discards its ${keyCount} existing key(s).
|
|
2086
|
+
`
|
|
2087
|
+
);
|
|
2088
|
+
}
|
|
2089
|
+
}
|
|
2090
|
+
if (options.dev && !g.quiet) {
|
|
2091
|
+
process.stderr.write(
|
|
2092
|
+
"warning: creating an UNENCRYPTED dev keystore. Keys are stored in plaintext. Use it only for disposable testnet material; mainnet operations will be refused.\n"
|
|
2093
|
+
);
|
|
2094
|
+
}
|
|
2095
|
+
initKeystore(path, {
|
|
2096
|
+
protection: options.dev ? "none" : "passphrase",
|
|
2097
|
+
getPassphrase: (opts) => acquirePassphrase({ passphraseFile: g.passphraseFile, confirm: opts?.confirm, prompt: "New keystore passphrase: " })
|
|
2098
|
+
});
|
|
2099
|
+
print({ action: "keystore-init", data: { path, protection: options.dev ? "dev" : "encrypted" } });
|
|
2100
|
+
});
|
|
2101
|
+
keystore.command("status").description("Show the keystore path, protection mode, and key count. Never decrypts or prompts.").action(() => {
|
|
2102
|
+
const g = globals();
|
|
2103
|
+
const path = resolveKeystorePath(g, { lenient: true });
|
|
2104
|
+
const summary = keystoreSummary(path);
|
|
2105
|
+
if (summary.protection === "dev" && !g.quiet && g.output !== "json") {
|
|
2106
|
+
process.stderr.write("warning: this is an UNENCRYPTED dev keystore; keys are stored in plaintext.\n");
|
|
2107
|
+
}
|
|
2108
|
+
print({ action: "keystore-status", data: { path, ...summary } });
|
|
2109
|
+
});
|
|
2110
|
+
keystore.command("change-passphrase").alias("passwd").description("Change the keystore passphrase, re-sealing every key under the new one. Encrypted keystores only.").action(() => {
|
|
2111
|
+
const g = globals();
|
|
2112
|
+
const path = resolveKeystorePath(g);
|
|
2113
|
+
const summary = keystoreSummary(path);
|
|
2114
|
+
if (summary.protection === "absent") {
|
|
2115
|
+
throw new CLIError(`No keystore at ${path}. Run "btcr2 keystore init" first.`, "INVALID_ARGUMENT_ERROR", { path });
|
|
2116
|
+
}
|
|
2117
|
+
if (summary.protection === "dev") {
|
|
2118
|
+
throw new CLIError(
|
|
2119
|
+
`The keystore at ${path} is an unencrypted dev keystore; there is no passphrase to change.`,
|
|
2120
|
+
"INVALID_ARGUMENT_ERROR",
|
|
2121
|
+
{ path }
|
|
2122
|
+
);
|
|
2123
|
+
}
|
|
2124
|
+
const oldPassphrase = acquirePassphrase({ passphraseFile: g.passphraseFile, prompt: "Current keystore passphrase: " });
|
|
2125
|
+
const newPassphrase = acquirePassphrase({ forcePrompt: true, confirm: true, prompt: "New keystore passphrase: " });
|
|
2126
|
+
const rekeyed = changeKeystorePassphrase(path, oldPassphrase, newPassphrase);
|
|
2127
|
+
print({ action: "keystore-change-passphrase", data: { path, rekeyed } });
|
|
2128
|
+
});
|
|
2129
|
+
}
|
|
2130
|
+
|
|
2131
|
+
// src/commands/config.ts
|
|
2132
|
+
var import_node_fs9 = require("fs");
|
|
2133
|
+
|
|
2134
|
+
// src/config-schema.ts
|
|
2135
|
+
var CONFIG_SCHEMA = {
|
|
2136
|
+
schemaVersion: "number",
|
|
2137
|
+
defaults: {
|
|
2138
|
+
profile: "string",
|
|
2139
|
+
network: "enum:network",
|
|
2140
|
+
output: "enum:output"
|
|
2141
|
+
},
|
|
2142
|
+
profiles: {
|
|
2143
|
+
"*": {
|
|
2144
|
+
network: "enum:network",
|
|
2145
|
+
btc: {
|
|
2146
|
+
rest: "string",
|
|
2147
|
+
rpcUrl: "string",
|
|
2148
|
+
rpcUser: "string",
|
|
2149
|
+
rpcPass: "string",
|
|
2150
|
+
feeRate: "number",
|
|
2151
|
+
changeAddress: "string",
|
|
2152
|
+
timeoutMs: "number",
|
|
2153
|
+
headers: "object",
|
|
2154
|
+
wallet: "string",
|
|
2155
|
+
rpcHeaders: "object"
|
|
2156
|
+
},
|
|
2157
|
+
cas: {
|
|
2158
|
+
gateway: "string",
|
|
2159
|
+
rpcUrl: "string",
|
|
2160
|
+
timeoutMs: "number"
|
|
2161
|
+
},
|
|
2162
|
+
identity: {
|
|
2163
|
+
keystore: "string",
|
|
2164
|
+
default: "string"
|
|
2165
|
+
}
|
|
2166
|
+
}
|
|
2167
|
+
}
|
|
2168
|
+
};
|
|
2169
|
+
function lookupSchemaNode(dotted) {
|
|
2170
|
+
let node = CONFIG_SCHEMA;
|
|
2171
|
+
for (const segment of dotted.split(".")) {
|
|
2172
|
+
if (typeof node !== "object") return void 0;
|
|
2173
|
+
if (Object.hasOwn(node, segment)) {
|
|
2174
|
+
node = node[segment];
|
|
2175
|
+
} else if (Object.hasOwn(node, "*")) {
|
|
2176
|
+
node = node["*"];
|
|
2177
|
+
} else {
|
|
2178
|
+
return void 0;
|
|
2179
|
+
}
|
|
2180
|
+
}
|
|
2181
|
+
return node;
|
|
2182
|
+
}
|
|
2183
|
+
function assertLeafValue(kind, dotted, value) {
|
|
2184
|
+
if (kind === "enum:network" && !SUPPORTED_NETWORKS.includes(value)) {
|
|
2185
|
+
throw new CLIError(
|
|
2186
|
+
`Invalid value for ${dotted}: "${String(value)}". Expected one of ${SUPPORTED_NETWORKS.join(", ")}.`,
|
|
2187
|
+
"INVALID_ARGUMENT_ERROR",
|
|
2188
|
+
{ path: dotted, value }
|
|
2189
|
+
);
|
|
2190
|
+
}
|
|
2191
|
+
if (kind === "enum:output" && value !== "json" && value !== "text") {
|
|
2192
|
+
throw new CLIError(
|
|
2193
|
+
`Invalid value for ${dotted}: "${String(value)}". Expected "json" or "text".`,
|
|
2194
|
+
"INVALID_ARGUMENT_ERROR",
|
|
2195
|
+
{ path: dotted, value }
|
|
2196
|
+
);
|
|
2197
|
+
}
|
|
2198
|
+
if (kind === "number" && typeof value !== "number") {
|
|
2199
|
+
throw new CLIError(
|
|
2200
|
+
`Invalid value for ${dotted}: expected a number, got ${typeof value}. Pass a bare number (e.g. \`config set ` + dotted + " 5`).",
|
|
2201
|
+
"INVALID_ARGUMENT_ERROR",
|
|
2202
|
+
{ path: dotted, value }
|
|
2203
|
+
);
|
|
2204
|
+
}
|
|
2205
|
+
if (kind === "object" && (typeof value !== "object" || value === null || Array.isArray(value))) {
|
|
2206
|
+
throw new CLIError(
|
|
2207
|
+
`Invalid value for ${dotted}: expected a JSON object (e.g. \`config set ${dotted} '{"Key":"Value"}'\`).`,
|
|
2208
|
+
"INVALID_ARGUMENT_ERROR",
|
|
2209
|
+
{ path: dotted, value }
|
|
2210
|
+
);
|
|
2211
|
+
}
|
|
2212
|
+
}
|
|
2213
|
+
function validateConfigSet(dotted, value) {
|
|
2214
|
+
const node = lookupSchemaNode(dotted);
|
|
2215
|
+
if (node === void 0) return { unknownPath: true };
|
|
2216
|
+
if (typeof node === "string") {
|
|
2217
|
+
assertLeafValue(node, dotted, value);
|
|
2218
|
+
}
|
|
2219
|
+
return { unknownPath: false };
|
|
2220
|
+
}
|
|
2221
|
+
function findConfigIssues(config, supportedSchemaVersion) {
|
|
2222
|
+
const issues = [];
|
|
2223
|
+
const version = config.schemaVersion;
|
|
2224
|
+
if (typeof version === "number" && version > supportedSchemaVersion) {
|
|
2225
|
+
issues.push({
|
|
2226
|
+
path: "schemaVersion",
|
|
2227
|
+
issue: `newer than supported (${version} > ${supportedSchemaVersion}); upgrade the CLI to use this file`
|
|
2228
|
+
});
|
|
2229
|
+
}
|
|
2230
|
+
walk(config, [], issues);
|
|
2231
|
+
return issues;
|
|
2232
|
+
}
|
|
2233
|
+
function walk(obj, prefix, issues) {
|
|
2234
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
2235
|
+
const path = [...prefix, key];
|
|
2236
|
+
const dotted = path.join(".");
|
|
2237
|
+
const node = lookupSchemaNode(dotted);
|
|
2238
|
+
if (node === void 0) {
|
|
2239
|
+
issues.push({ path: dotted, issue: "unknown key" });
|
|
2240
|
+
continue;
|
|
2241
|
+
}
|
|
2242
|
+
if (typeof node === "string") {
|
|
2243
|
+
try {
|
|
2244
|
+
assertLeafValue(node, dotted, value);
|
|
2245
|
+
} catch (error) {
|
|
2246
|
+
issues.push({ path: dotted, issue: error.message });
|
|
2247
|
+
}
|
|
2248
|
+
} else if (value !== null && typeof value === "object" && !Array.isArray(value)) {
|
|
2249
|
+
walk(value, path, issues);
|
|
2250
|
+
}
|
|
1321
2251
|
}
|
|
1322
2252
|
}
|
|
1323
2253
|
|
|
1324
2254
|
// src/commands/config.ts
|
|
1325
|
-
var import_node_fs7 = require("fs");
|
|
1326
|
-
var import_node_path5 = require("path");
|
|
1327
2255
|
function registerConfigCommand(program, globals) {
|
|
1328
2256
|
const config = program.command("config").description("Read and write CLI configuration.");
|
|
1329
|
-
const path = () => globals().config ?? defaultConfigPath();
|
|
2257
|
+
const path = () => globals().config ?? defaultConfigPath(globals());
|
|
1330
2258
|
const print = (result) => console.log(formatResult(result, globals()));
|
|
1331
2259
|
config.command("init").description("Create a default config file with one profile per network.").option("--force", "Overwrite an existing config file.", false).action((options) => {
|
|
1332
2260
|
const p = path();
|
|
1333
|
-
if ((0,
|
|
2261
|
+
if ((0, import_node_fs9.existsSync)(p) && !options.force) {
|
|
1334
2262
|
throw new CLIError(`Config already exists at ${p}. Use --force to overwrite.`, "INVALID_ARGUMENT_ERROR", { path: p });
|
|
1335
2263
|
}
|
|
1336
|
-
|
|
1337
|
-
schemaVersion: CONFIG_SCHEMA_VERSION,
|
|
1338
|
-
defaults: { output: "text" },
|
|
1339
|
-
profiles: Object.fromEntries(SUPPORTED_NETWORKS.map((n) => [n, {}]))
|
|
1340
|
-
};
|
|
1341
|
-
ensureDir((0, import_node_path5.dirname)(p), 448);
|
|
1342
|
-
writeFileAtomic(p, `${JSON.stringify(scaffold, null, 2)}
|
|
1343
|
-
`, 384);
|
|
2264
|
+
writeDefaultConfigFile(p);
|
|
1344
2265
|
print({ action: "config-init", data: { path: p } });
|
|
1345
2266
|
});
|
|
1346
|
-
config.command("get [path]").description("Print a value at a dotted path, or the whole config.").action((dotted) => {
|
|
2267
|
+
config.command("get [path]").description("Print a value at a dotted path, or the whole config.").option("--show-secrets", "Reveal secret values (RPC password, etc.) instead of redacting them.", false).action((dotted, opts) => {
|
|
1347
2268
|
const file = readConfigFile(path()) ?? {};
|
|
1348
|
-
|
|
2269
|
+
const raw = (dotted ? getConfigPath(file, dotted) : file) ?? null;
|
|
2270
|
+
const leaf = dotted ? dotted.split(".").pop() : void 0;
|
|
2271
|
+
print({ action: "config-get", data: opts.showSecrets ? raw : redactSecrets(raw, leaf) });
|
|
1349
2272
|
});
|
|
1350
2273
|
config.command("set <path> <value>").description("Set a value at a dotted path. The value is parsed as JSON when valid, else stored as a string.").action((dotted, value) => {
|
|
1351
|
-
|
|
2274
|
+
const parsed = parseValue(dotted, value);
|
|
2275
|
+
const { unknownPath } = validateConfigSet(dotted, parsed);
|
|
2276
|
+
if (unknownPath && !globals().quiet) {
|
|
2277
|
+
console.error(`Warning: "${dotted}" is not a known config path; writing it anyway.`);
|
|
2278
|
+
}
|
|
2279
|
+
writeConfigFile(path(), (raw) => setConfigPath(raw, dotted, parsed));
|
|
1352
2280
|
print({ action: "config-set", data: { path: dotted } });
|
|
1353
2281
|
});
|
|
1354
2282
|
config.command("unset <path>").description("Delete a value at a dotted path.").action((dotted) => {
|
|
1355
2283
|
writeConfigFile(path(), (raw) => unsetConfigPath(raw, dotted));
|
|
1356
2284
|
print({ action: "config-unset", data: { path: dotted } });
|
|
1357
2285
|
});
|
|
1358
|
-
config.command("list").alias("ls").description("Print the entire config file.").action(() => {
|
|
1359
|
-
|
|
2286
|
+
config.command("list").alias("ls").description("Print the entire config file.").option("--show-secrets", "Reveal secret values (RPC password, etc.) instead of redacting them.", false).action((opts) => {
|
|
2287
|
+
const file = readConfigFile(path()) ?? {};
|
|
2288
|
+
print({ action: "config-list", data: opts.showSecrets ? file : redactSecrets(file) });
|
|
2289
|
+
});
|
|
2290
|
+
config.command("validate").description("Check the config for unknown keys, invalid enum values, and an unsupported schema version.").action(() => {
|
|
2291
|
+
const file = parseConfigFileRaw(path()) ?? {};
|
|
2292
|
+
const issues = findConfigIssues(file, CONFIG_SCHEMA_VERSION);
|
|
2293
|
+
if (issues.length > 0) process.exitCode ??= 1;
|
|
2294
|
+
print({ action: "config-validate", data: { ok: issues.length === 0, issues } });
|
|
2295
|
+
});
|
|
2296
|
+
config.command("effective").description("Print the resolved connection config with per-value provenance (flag|env|file|default).").option("-n, --network <network>", "Network to resolve for (default: config default network)").option("--show-secrets", "Reveal secret values (RPC password) instead of redacting them.", false).action((opts) => {
|
|
2297
|
+
const network = resolveIntrospectionNetwork(opts.network, globals());
|
|
2298
|
+
const data = resolveEffectiveConfig(network, globals());
|
|
2299
|
+
if (!opts.showSecrets) {
|
|
2300
|
+
if (data.btc.rpcPass.value !== void 0) {
|
|
2301
|
+
data.btc.rpcPass = { ...data.btc.rpcPass, value: REDACTED };
|
|
2302
|
+
}
|
|
2303
|
+
for (const entry of [data.btc.rest, data.btc.rpcUrl, data.cas.gateway, data.cas.rpcUrl]) {
|
|
2304
|
+
if (typeof entry.value === "string") entry.value = scrubUrlUserinfo(entry.value);
|
|
2305
|
+
}
|
|
2306
|
+
}
|
|
2307
|
+
print({ action: "config-effective", data });
|
|
2308
|
+
});
|
|
2309
|
+
config.command("path").description("Print the resolved home directory, config-file, and keystore paths.").action(() => {
|
|
2310
|
+
const g = globals();
|
|
2311
|
+
const data = {
|
|
2312
|
+
home: resolveHome(g),
|
|
2313
|
+
config: g.config ?? defaultConfigPath(g),
|
|
2314
|
+
// Diagnostic command: report the path even when the config is malformed
|
|
2315
|
+
// (this is a command you run to find and fix a broken config).
|
|
2316
|
+
keystore: resolveKeystorePath(g, { lenient: true })
|
|
2317
|
+
};
|
|
2318
|
+
print({ action: "config-path", data });
|
|
2319
|
+
});
|
|
2320
|
+
config.command("doctor").description("Probe reachability of the resolved endpoints (read-only; touches the network).").option("-n, --network <network>", "Network to resolve for (default: config default network)").action(async (opts) => {
|
|
2321
|
+
const network = resolveIntrospectionNetwork(opts.network, globals());
|
|
2322
|
+
const report = await runDoctor(network, globals());
|
|
2323
|
+
if (report.checks.some((check) => !check.ok)) process.exitCode ??= 1;
|
|
2324
|
+
print({ action: "config-doctor", data: report });
|
|
1360
2325
|
});
|
|
1361
2326
|
}
|
|
1362
|
-
function
|
|
2327
|
+
function resolveIntrospectionNetwork(explicit, globals) {
|
|
2328
|
+
if (explicit) {
|
|
2329
|
+
if (!SUPPORTED_NETWORKS.includes(explicit)) {
|
|
2330
|
+
throw new CLIError(
|
|
2331
|
+
`Invalid network "${explicit}". Must be one of ${SUPPORTED_NETWORKS.join(", ")}.`,
|
|
2332
|
+
"INVALID_ARGUMENT_ERROR",
|
|
2333
|
+
{ network: explicit }
|
|
2334
|
+
);
|
|
2335
|
+
}
|
|
2336
|
+
return explicit;
|
|
2337
|
+
}
|
|
2338
|
+
return resolveDefaultNetwork(globals);
|
|
2339
|
+
}
|
|
2340
|
+
function parseValue(dotted, value) {
|
|
2341
|
+
if (isStringScalarPath(dotted)) return value;
|
|
1363
2342
|
try {
|
|
1364
2343
|
return JSON.parse(value);
|
|
1365
2344
|
} catch {
|
|
1366
2345
|
return value;
|
|
1367
2346
|
}
|
|
1368
2347
|
}
|
|
2348
|
+
function isStringScalarPath(dotted) {
|
|
2349
|
+
const segments = dotted.split(".");
|
|
2350
|
+
if (segments.length === 2 && segments[0] === "defaults") {
|
|
2351
|
+
return ["profile", "network", "output"].includes(segments[1]);
|
|
2352
|
+
}
|
|
2353
|
+
if (segments.length === 3 && segments[0] === "profiles" && segments[2] === "network") {
|
|
2354
|
+
return true;
|
|
2355
|
+
}
|
|
2356
|
+
if (segments.length === 4 && segments[0] === "profiles") {
|
|
2357
|
+
const group = segments[2];
|
|
2358
|
+
const leaf = segments[3];
|
|
2359
|
+
if (group === "btc") return ["rest", "rpcUrl", "rpcUser", "rpcPass", "changeAddress", "wallet"].includes(leaf);
|
|
2360
|
+
if (group === "cas") return ["gateway", "rpcUrl"].includes(leaf);
|
|
2361
|
+
if (group === "identity") return ["keystore", "default"].includes(leaf);
|
|
2362
|
+
}
|
|
2363
|
+
return false;
|
|
2364
|
+
}
|
|
1369
2365
|
|
|
1370
2366
|
// src/commands/profile.ts
|
|
1371
2367
|
function registerProfileCommand(program, globals) {
|
|
1372
2368
|
const profile = program.command("profile").description("Manage configuration profiles.");
|
|
1373
|
-
const path = () => globals().config ?? defaultConfigPath();
|
|
2369
|
+
const path = () => globals().config ?? defaultConfigPath(globals());
|
|
1374
2370
|
const print = (result) => console.log(formatResult(result, globals()));
|
|
1375
2371
|
profile.command("add <name>").description("Add an empty profile.").action((name) => {
|
|
1376
2372
|
writeConfigFile(path(), (raw) => {
|
|
@@ -1388,7 +2384,7 @@ function registerProfileCommand(program, globals) {
|
|
|
1388
2384
|
});
|
|
1389
2385
|
print({ action: "profile-use", data: { profile: name } });
|
|
1390
2386
|
});
|
|
1391
|
-
profile.command("show [name]").description("Show a profile (defaults to the active profile).").action((name) => {
|
|
2387
|
+
profile.command("show [name]").description("Show a profile (defaults to the active profile).").option("--show-secrets", "Reveal secret values (RPC password, etc.) instead of redacting them.", false).action((name, opts) => {
|
|
1392
2388
|
const file = readConfigFile(path()) ?? {};
|
|
1393
2389
|
const target = name ?? file.defaults?.profile;
|
|
1394
2390
|
if (!target) {
|
|
@@ -1398,7 +2394,8 @@ function registerProfileCommand(program, globals) {
|
|
|
1398
2394
|
if (!data) {
|
|
1399
2395
|
throw new CLIError(`Profile "${target}" not found.`, "INVALID_ARGUMENT_ERROR", { profile: target });
|
|
1400
2396
|
}
|
|
1401
|
-
|
|
2397
|
+
const payload = { profile: target, ...data };
|
|
2398
|
+
print({ action: "profile-show", data: opts.showSecrets ? payload : redactSecrets(payload) });
|
|
1402
2399
|
});
|
|
1403
2400
|
profile.command("remove <name>").alias("rm").description("Remove a profile.").action((name) => {
|
|
1404
2401
|
writeConfigFile(path(), (raw) => {
|
|
@@ -1442,18 +2439,18 @@ function completionScript(shell) {
|
|
|
1442
2439
|
}
|
|
1443
2440
|
|
|
1444
2441
|
// src/version.ts
|
|
1445
|
-
var
|
|
1446
|
-
var
|
|
2442
|
+
var import_node_fs10 = require("fs");
|
|
2443
|
+
var import_node_path5 = require("path");
|
|
1447
2444
|
var import_node_url = require("url");
|
|
1448
2445
|
function readVersion() {
|
|
1449
|
-
let dir = (0,
|
|
2446
|
+
let dir = (0, import_node_path5.dirname)((0, import_node_url.fileURLToPath)(importMetaUrl));
|
|
1450
2447
|
for (let i = 0; i < 5; i++) {
|
|
1451
2448
|
try {
|
|
1452
|
-
const pkg = JSON.parse((0,
|
|
2449
|
+
const pkg = JSON.parse((0, import_node_fs10.readFileSync)((0, import_node_path5.join)(dir, "package.json"), "utf-8"));
|
|
1453
2450
|
if (pkg.name === "@did-btcr2/cli") return pkg.version;
|
|
1454
2451
|
} catch {
|
|
1455
2452
|
}
|
|
1456
|
-
dir = (0,
|
|
2453
|
+
dir = (0, import_node_path5.dirname)(dir);
|
|
1457
2454
|
}
|
|
1458
2455
|
return "0.0.0";
|
|
1459
2456
|
}
|
|
@@ -1477,13 +2474,19 @@ var DidBtcr2Cli = class {
|
|
|
1477
2474
|
* {@link keystoreApiFactory}.
|
|
1478
2475
|
*/
|
|
1479
2476
|
constructor(factory = defaultApiFactory, keystoreFactory = keystoreApiFactory) {
|
|
1480
|
-
this.program = new import_commander.Command("btcr2").version(`btcr2 ${VERSION}`, "-v, --version", "Output the current version").description("CLI tool for the did:btcr2 method").option("-o, --output <format>", "Output format <json|text>
|
|
2477
|
+
this.program = new import_commander.Command("btcr2").version(`btcr2 ${VERSION}`, "-v, --version", "Output the current version").description("CLI tool for the did:btcr2 method").option("-o, --output <format>", "Output format <json|text> (default: config defaults.output, else text)").option("--verbose", "Verbose output", false).option("--quiet", "Suppress non-essential output", false).option("--home <dir>", "btcr2 home directory holding config.json + keystore.json (default: ~/.btcr2, %LOCALAPPDATA%\\btcr2 on Windows; overrides $BTCR2_HOME)").option("-c, --config <path>", "Path to config file (default: <home>/config.json)").option("--profile <name>", "Config profile name (default: auto-detected from network)").option("--btc-rest <url>", "Override Bitcoin REST endpoint (Esplora API)").option("--btc-rpc-url <url>", "Override Bitcoin Core RPC endpoint").option("--btc-rpc-user <user>", "Bitcoin Core RPC username").option("--btc-rpc-pass <pass>", "Bitcoin Core RPC password").option("--cas-gateway <url>", "IPFS HTTP gateway for CAS reads (read-only)").option("--cas-rpc-url <url>", "IPFS HTTP RPC endpoint for a writable CAS (reads + writes; enables --publish-to-cas)").option("--btc-timeout <ms>", "Bitcoin REST/RPC request timeout in milliseconds (default: unbounded)").option("--cas-timeout <ms>", "CAS request timeout in milliseconds (default: 30000; 0 disables)").option("--btc-rest-header <header>", 'Extra Bitcoin REST header "Key: Value" (repeatable)', collectHeader, []).option("--btc-rpc-wallet <name>", "Bitcoin Core wallet name for wallet-scoped RPCs").option("--btc-rpc-header <header>", 'Extra Bitcoin Core RPC header "Key: Value" (repeatable)', collectHeader, []).option("--keystore <path>", "Path to the keystore file (default: <home>/keystore.json)").option("--passphrase-file <path>", "Read the keystore passphrase from a file (unattended use)").option("--signing-key <ref>", "Key for create/update/deactivate signing: a URN, fingerprint prefix, or name");
|
|
1481
2478
|
const globals = () => this.program.opts();
|
|
2479
|
+
this.program.hook("preAction", () => {
|
|
2480
|
+
const opts = this.program.opts();
|
|
2481
|
+
opts.output = resolveOutputFormat({ output: opts.output, config: opts.config, home: opts.home });
|
|
2482
|
+
});
|
|
2483
|
+
registerInitCommand(this.program, globals);
|
|
1482
2484
|
registerCreateCommand(this.program, factory, keystoreFactory, globals);
|
|
1483
2485
|
registerResolveCommand(this.program, factory, globals);
|
|
1484
2486
|
registerUpdateCommand(this.program, keystoreFactory, globals);
|
|
1485
2487
|
registerDeactivateCommand(this.program, keystoreFactory, globals);
|
|
1486
2488
|
registerKeyCommand(this.program, keystoreFactory, globals);
|
|
2489
|
+
registerKeystoreCommand(this.program, globals);
|
|
1487
2490
|
registerConfigCommand(this.program, globals);
|
|
1488
2491
|
registerProfileCommand(this.program, globals);
|
|
1489
2492
|
registerCompletionCommand(this.program, globals);
|
|
@@ -1503,6 +2506,9 @@ var DidBtcr2Cli = class {
|
|
|
1503
2506
|
}
|
|
1504
2507
|
}
|
|
1505
2508
|
};
|
|
2509
|
+
function collectHeader(value, previous) {
|
|
2510
|
+
return [...previous, value];
|
|
2511
|
+
}
|
|
1506
2512
|
function normalizeArgv(argv) {
|
|
1507
2513
|
if (argv.length >= 2) return argv;
|
|
1508
2514
|
if (argv.length === 1) return ["node", argv[0]];
|
|
@@ -1525,28 +2531,51 @@ function handleError(error, verbose) {
|
|
|
1525
2531
|
CLIError,
|
|
1526
2532
|
CONFIG_SCHEMA_VERSION,
|
|
1527
2533
|
DidBtcr2Cli,
|
|
2534
|
+
ENV_RPC_PASS_FILE,
|
|
1528
2535
|
ENV_VARS,
|
|
2536
|
+
REDACTED,
|
|
1529
2537
|
SUPPORTED_NETWORKS,
|
|
1530
2538
|
VERSION,
|
|
2539
|
+
assertKeystoreAllowedForNetwork,
|
|
2540
|
+
blankToUndef,
|
|
1531
2541
|
defaultApiFactory,
|
|
1532
2542
|
defaultConfigPath,
|
|
1533
2543
|
deriveNetwork,
|
|
1534
2544
|
formatResult,
|
|
1535
2545
|
getConfigPath,
|
|
2546
|
+
isSecretKey,
|
|
1536
2547
|
keystoreApiFactory,
|
|
2548
|
+
parseConfigFileRaw,
|
|
2549
|
+
parseHeaderList,
|
|
2550
|
+
profileNetworkMismatch,
|
|
1537
2551
|
profileToOverrides,
|
|
1538
2552
|
readConfigFile,
|
|
1539
2553
|
readEnvOverrides,
|
|
2554
|
+
redactSecrets,
|
|
1540
2555
|
registerCompletionCommand,
|
|
1541
2556
|
registerConfigCommand,
|
|
1542
2557
|
registerCreateCommand,
|
|
1543
2558
|
registerDeactivateCommand,
|
|
2559
|
+
registerInitCommand,
|
|
1544
2560
|
registerKeyCommand,
|
|
2561
|
+
registerKeystoreCommand,
|
|
1545
2562
|
registerProfileCommand,
|
|
1546
2563
|
registerResolveCommand,
|
|
1547
2564
|
registerUpdateCommand,
|
|
2565
|
+
resolveActiveProfile,
|
|
2566
|
+
resolveBroadcastOptions,
|
|
2567
|
+
resolveConnectionConfig,
|
|
1548
2568
|
resolveDefaultNetwork,
|
|
2569
|
+
resolveEffectiveConfig,
|
|
2570
|
+
resolveKeystorePath,
|
|
2571
|
+
resolveKeystoreProtection,
|
|
2572
|
+
resolveOutputFormat,
|
|
2573
|
+
resolveSecretRef,
|
|
2574
|
+
resolveSigningKeyRef,
|
|
2575
|
+
runDoctor,
|
|
2576
|
+
scrubUrlUserinfo,
|
|
1549
2577
|
setConfigPath,
|
|
1550
2578
|
unsetConfigPath,
|
|
1551
|
-
writeConfigFile
|
|
2579
|
+
writeConfigFile,
|
|
2580
|
+
writeDefaultConfigFile
|
|
1552
2581
|
});
|