@did-btcr2/cli 0.18.0 → 0.19.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.
@@ -19,16 +19,17 @@ const CONFIG_SCHEMA: SchemaNode = {
19
19
  '*' : {
20
20
  network : 'enum:network',
21
21
  btc : {
22
- rest : 'string',
23
- rpcUrl : 'string',
24
- rpcUser : 'string',
25
- rpcPass : 'string',
26
- feeRate : 'number',
27
- changeAddress : 'string',
28
- timeoutMs : 'number',
29
- headers : 'object',
30
- wallet : 'string',
31
- rpcHeaders : 'object',
22
+ rest : 'string',
23
+ rpcUrl : 'string',
24
+ rpcUser : 'string',
25
+ rpcPass : 'string',
26
+ feeRate : 'number',
27
+ changeAddress : 'string',
28
+ timeoutMs : 'number',
29
+ headers : 'object',
30
+ wallet : 'string',
31
+ rpcHeaders : 'object',
32
+ signalDiscovery : 'enum:signalDiscovery',
32
33
  },
33
34
  cas : {
34
35
  gateway : 'string',
@@ -100,6 +101,13 @@ function assertLeafValue(kind: SchemaLeaf, dotted: string, value: unknown): void
100
101
  { path: dotted, value },
101
102
  );
102
103
  }
104
+ if (kind === 'enum:signalDiscovery' && value !== 'indexer' && value !== 'fullnode') {
105
+ throw new CLIError(
106
+ `Invalid value for ${dotted}: "${String(value)}". Expected "indexer" or "fullnode".`,
107
+ 'INVALID_ARGUMENT_ERROR',
108
+ { path: dotted, value },
109
+ );
110
+ }
103
111
  if (kind === 'number' && typeof value !== 'number') {
104
112
  throw new CLIError(
105
113
  `Invalid value for ${dotted}: expected a number, got ${typeof value}. `
package/src/config.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { createApi, DEFAULT_BITCOIN_NETWORK_CONFIG, DEFAULT_CAS_GATEWAY, Identifier, type BitcoinApiConfig, type CasConfig, type DidBtcr2Api } from '@did-btcr2/api';
1
+ import { createApi, DEFAULT_BITCOIN_NETWORK_CONFIG, DEFAULT_CAS_GATEWAY, Identifier, type BitcoinApiConfig, type CasConfig, type DidBtcr2Api, type SignalDiscoveryMode } from '@did-btcr2/api';
2
2
  import type { KeyManager } from '@did-btcr2/key-manager';
3
3
  import { StaticFeeEstimator } from '@did-btcr2/method';
4
4
  import type { BroadcastOptions } from '@did-btcr2/method';
@@ -43,6 +43,8 @@ export type ConnectionOverrides = {
43
43
  btcRpcWallet? : string;
44
44
  /** Extra Bitcoin Core RPC headers as raw `Key: Value` flag values (repeatable). */
45
45
  btcRpcHeader? : string[];
46
+ /** Where beacon signals are read from (raw flag/env/profile string). */
47
+ btcSignalDiscovery? : string;
46
48
  /** CLI home root from `--home`. Colocates config.json + keystore.json (ADR 079). */
47
49
  home? : string;
48
50
  config? : string;
@@ -112,6 +114,12 @@ export type ConfigFile = {
112
114
  wallet? : string;
113
115
  /** Extra headers sent on Bitcoin Core RPC requests. */
114
116
  rpcHeaders? : Record<string, string>;
117
+ /**
118
+ * Where beacon signals are read from: `indexer` (Esplora, the default) or
119
+ * `fullnode` (scan blocks over Bitcoin Core RPC). `fullnode` needs an
120
+ * RPC-capable connection.
121
+ */
122
+ signalDiscovery? : SignalDiscoveryMode;
115
123
  };
116
124
  cas?: {
117
125
  /** IPFS HTTP gateway for CAS reads (read-only). */
@@ -231,23 +239,25 @@ export type ApiFactory = (network?: NetworkOption, overrides?: ConnectionOverrid
231
239
  * | `BTCR2_BTC_REST` | `--btc-rest` |
232
240
  * | `BTCR2_BTC_RPC_URL` | `--btc-rpc-url` |
233
241
  * | `BTCR2_BTC_RPC_USER` | `--btc-rpc-user` |
234
- * | `BTCR2_BTC_RPC_PASS` | `--btc-rpc-pass` |
242
+ * | `BTCR2_BTC_RPC_PASS` | (no flag: never argv) |
235
243
  * | `BTCR2_CAS_GATEWAY` | `--cas-gateway` |
236
244
  * | `BTCR2_CAS_RPC_URL` | `--cas-rpc-url` |
237
245
  * | `BTCR2_BTC_TIMEOUT` | `--btc-timeout` |
238
246
  * | `BTCR2_CAS_TIMEOUT` | `--cas-timeout` |
239
247
  * | `BTCR2_FEE_RATE` | `--fee-rate` |
248
+ * | `BTCR2_BTC_SIGNAL_DISCOVERY` | `--btc-signal-discovery` |
240
249
  */
241
250
  export const ENV_VARS = {
242
- BTC_REST : 'BTCR2_BTC_REST',
243
- BTC_RPC_URL : 'BTCR2_BTC_RPC_URL',
244
- BTC_RPC_USER : 'BTCR2_BTC_RPC_USER',
245
- BTC_RPC_PASS : 'BTCR2_BTC_RPC_PASS',
246
- CAS_GATEWAY : 'BTCR2_CAS_GATEWAY',
247
- CAS_RPC_URL : 'BTCR2_CAS_RPC_URL',
248
- BTC_TIMEOUT : 'BTCR2_BTC_TIMEOUT',
249
- CAS_TIMEOUT : 'BTCR2_CAS_TIMEOUT',
250
- FEE_RATE : 'BTCR2_FEE_RATE',
251
+ BTC_REST : 'BTCR2_BTC_REST',
252
+ BTC_RPC_URL : 'BTCR2_BTC_RPC_URL',
253
+ BTC_RPC_USER : 'BTCR2_BTC_RPC_USER',
254
+ BTC_RPC_PASS : 'BTCR2_BTC_RPC_PASS',
255
+ CAS_GATEWAY : 'BTCR2_CAS_GATEWAY',
256
+ CAS_RPC_URL : 'BTCR2_CAS_RPC_URL',
257
+ BTC_TIMEOUT : 'BTCR2_BTC_TIMEOUT',
258
+ CAS_TIMEOUT : 'BTCR2_CAS_TIMEOUT',
259
+ FEE_RATE : 'BTCR2_FEE_RATE',
260
+ BTC_SIGNAL_DISCOVERY : 'BTCR2_BTC_SIGNAL_DISCOVERY',
251
261
  } as const;
252
262
 
253
263
  /**
@@ -257,12 +267,13 @@ export const ENV_VARS = {
257
267
  export function readEnvOverrides(): ConnectionOverrides {
258
268
  const env = (key: string): string | undefined => process.env[key] || undefined;
259
269
  return {
260
- btcRest : env(ENV_VARS.BTC_REST),
261
- btcRpcUrl : env(ENV_VARS.BTC_RPC_URL),
262
- btcRpcUser : env(ENV_VARS.BTC_RPC_USER),
263
- btcRpcPass : env(ENV_VARS.BTC_RPC_PASS),
264
- casGateway : env(ENV_VARS.CAS_GATEWAY),
265
- casRpcUrl : env(ENV_VARS.CAS_RPC_URL),
270
+ btcRest : env(ENV_VARS.BTC_REST),
271
+ btcRpcUrl : env(ENV_VARS.BTC_RPC_URL),
272
+ btcRpcUser : env(ENV_VARS.BTC_RPC_USER),
273
+ btcRpcPass : env(ENV_VARS.BTC_RPC_PASS),
274
+ casGateway : env(ENV_VARS.CAS_GATEWAY),
275
+ casRpcUrl : env(ENV_VARS.CAS_RPC_URL),
276
+ btcSignalDiscovery : env(ENV_VARS.BTC_SIGNAL_DISCOVERY),
266
277
  };
267
278
  }
268
279
 
@@ -343,12 +354,13 @@ export function profileToOverrides(
343
354
  const profile = config.profiles?.[profileName];
344
355
  if (!profile) return {};
345
356
  return {
346
- btcRest : profile.btc?.rest,
347
- btcRpcUrl : profile.btc?.rpcUrl,
348
- btcRpcUser : profile.btc?.rpcUser,
349
- btcRpcPass : profile.btc?.rpcPass,
350
- casGateway : profile.cas?.gateway,
351
- casRpcUrl : profile.cas?.rpcUrl,
357
+ btcRest : profile.btc?.rest,
358
+ btcRpcUrl : profile.btc?.rpcUrl,
359
+ btcRpcUser : profile.btc?.rpcUser,
360
+ btcRpcPass : profile.btc?.rpcPass,
361
+ casGateway : profile.cas?.gateway,
362
+ casRpcUrl : profile.cas?.rpcUrl,
363
+ btcSignalDiscovery : profile.btc?.signalDiscovery,
352
364
  };
353
365
  }
354
366
 
@@ -563,6 +575,27 @@ function resolveRpcUnit(
563
575
  return undefined;
564
576
  }
565
577
 
578
+ /** The accepted `--btc-signal-discovery` values, in help/error order. */
579
+ const SIGNAL_DISCOVERY_MODES: readonly SignalDiscoveryMode[] = ['indexer', 'fullnode'];
580
+
581
+ /**
582
+ * Validates a beacon signal-discovery mode drawn from any precedence layer.
583
+ * A bad value is rejected here rather than passed to the SDK, so a typo in an
584
+ * env var or a profile fails with the same named-flag message as a bad flag.
585
+ */
586
+ function resolveSignalDiscovery(value?: string): SignalDiscoveryMode | undefined {
587
+ const mode = blankToUndef(value);
588
+ if (mode === undefined) return undefined;
589
+ if (!SIGNAL_DISCOVERY_MODES.includes(mode as SignalDiscoveryMode)) {
590
+ throw new CLIError(
591
+ `Invalid --btc-signal-discovery value "${mode}". Expected ${SIGNAL_DISCOVERY_MODES.join(' or ')}.`,
592
+ 'INVALID_ARGUMENT_ERROR',
593
+ { value: mode },
594
+ );
595
+ }
596
+ return mode as SignalDiscoveryMode;
597
+ }
598
+
566
599
  /**
567
600
  * Resolves the Bitcoin and CAS connection config for a network by merging,
568
601
  * in precedence order, CLI flags, environment variables, and the config-file
@@ -641,6 +674,14 @@ export function resolveConnectionConfig(
641
674
  };
642
675
  }
643
676
 
677
+ // Where beacon signals are read from. `fullnode` scans blocks over Bitcoin
678
+ // Core RPC and is rejected by BitcoinApi when the resolved connection has no
679
+ // RPC client, which includes the networks that carry no default RPC host.
680
+ const signalDiscovery = resolveSignalDiscovery(
681
+ pick(overrides?.btcSignalDiscovery, env.btcSignalDiscovery, fileOverrides.btcSignalDiscovery),
682
+ );
683
+ if (signalDiscovery) btc.signalDiscovery = signalDiscovery;
684
+
644
685
  // Bitcoin request timeout. No default: honored only when explicitly set, so
645
686
  // callers that rely on unbounded waits are unaffected (ADR 076).
646
687
  const btcTimeout = resolveTimeout(overrides?.btcTimeout, process.env[ENV_VARS.BTC_TIMEOUT], profileBtc?.timeoutMs, '--btc-timeout', 1);
@@ -839,12 +880,13 @@ export interface EffectiveConfig {
839
880
  network : NetworkOption;
840
881
  profile : string | undefined;
841
882
  btc : {
842
- rest : EffectiveEntry;
843
- rpcUrl : EffectiveEntry;
844
- rpcUser : EffectiveEntry;
845
- rpcPass : EffectiveEntry;
846
- rpcWallet : EffectiveEntry;
847
- timeoutMs : EffectiveEntry;
883
+ rest : EffectiveEntry;
884
+ rpcUrl : EffectiveEntry;
885
+ rpcUser : EffectiveEntry;
886
+ rpcPass : EffectiveEntry;
887
+ rpcWallet : EffectiveEntry;
888
+ signalDiscovery : EffectiveEntry;
889
+ timeoutMs : EffectiveEntry;
848
890
  };
849
891
  cas : {
850
892
  gateway : EffectiveEntry;
@@ -897,12 +939,16 @@ export function resolveEffectiveConfig(network: NetworkOption, overrides?: Conne
897
939
  network,
898
940
  profile : activeProfile,
899
941
  btc : {
900
- rest : { value: restCfg.host, source: src(overrides?.btcRest, env.btcRest, fileOv.btcRest) },
901
- rpcUrl : { value: rpcCfg?.host, source: rpcUnit?.url !== undefined ? rpcSrc : 'default' },
902
- rpcUser : { value: rpcCfg?.username, source: rpcUnit?.user !== undefined ? rpcSrc : 'default' },
903
- rpcPass : { value: rpcCfg?.password, source: rpcUnit?.pass !== undefined ? rpcSrc : (passFromFile ? 'env' : 'default') },
904
- rpcWallet : { value: rpcCfg?.wallet, source: src(overrides?.btcRpcWallet, undefined, profileBtc?.wallet) },
905
- timeoutMs : { value: conn.btc?.timeoutMs, source: src(overrides?.btcTimeout, process.env[ENV_VARS.BTC_TIMEOUT], profileBtc?.timeoutMs) },
942
+ rest : { value: restCfg.host, source: src(overrides?.btcRest, env.btcRest, fileOv.btcRest) },
943
+ rpcUrl : { value: rpcCfg?.host, source: rpcUnit?.url !== undefined ? rpcSrc : 'default' },
944
+ rpcUser : { value: rpcCfg?.username, source: rpcUnit?.user !== undefined ? rpcSrc : 'default' },
945
+ rpcPass : { value: rpcCfg?.password, source: rpcUnit?.pass !== undefined ? rpcSrc : (passFromFile ? 'env' : 'default') },
946
+ rpcWallet : { value: rpcCfg?.wallet, source: src(overrides?.btcRpcWallet, undefined, profileBtc?.wallet) },
947
+ // Read back from the constructed api, like the endpoints above, so the
948
+ // reported value is the mode a live command would actually resolve with
949
+ // (the SDK's `indexer` default included) rather than only an explicit setting.
950
+ signalDiscovery : { value: api.btc.signalDiscovery, source: src(overrides?.btcSignalDiscovery, env.btcSignalDiscovery, fileOv.btcSignalDiscovery) },
951
+ timeoutMs : { value: conn.btc?.timeoutMs, source: src(overrides?.btcTimeout, process.env[ENV_VARS.BTC_TIMEOUT], profileBtc?.timeoutMs) },
906
952
  },
907
953
  cas : {
908
954
  gateway : { value: casGatewayVal, source: src(overrides?.casGateway, env.casGateway, fileOv.casGateway) },
package/src/types.ts CHANGED
@@ -88,24 +88,27 @@ export type CommandResult =
88
88
  | { action: 'profile-remove'; data: { profile: string } };
89
89
 
90
90
  export interface GlobalOptions {
91
- output : OutputFormat;
92
- verbose : boolean;
93
- quiet : boolean;
94
- home? : string;
95
- config? : string;
96
- profile? : string;
97
- btcRest? : string;
98
- btcRpcUrl? : string;
99
- btcRpcUser? : string;
100
- btcRpcPass? : string;
101
- casGateway? : string;
102
- casRpcUrl? : string;
103
- btcTimeout? : string;
104
- casTimeout? : string;
105
- btcRestHeader? : string[];
106
- btcRpcWallet? : string;
107
- btcRpcHeader? : string[];
108
- keystore? : string;
109
- passphraseFile?: string;
110
- signingKey? : string;
91
+ output : OutputFormat;
92
+ verbose : boolean;
93
+ quiet : boolean;
94
+ home? : string;
95
+ config? : string;
96
+ profile? : string;
97
+ btcRest? : string;
98
+ btcRpcUrl? : string;
99
+ btcRpcUser? : string;
100
+ // No btcRpcPass: the RPC password is never accepted as a flag, so it cannot be
101
+ // parsed off argv. It reaches the connection from the environment, a pass file,
102
+ // or a profile secret reference instead (see the flag list in `cli.ts`).
103
+ casGateway? : string;
104
+ casRpcUrl? : string;
105
+ btcTimeout? : string;
106
+ casTimeout? : string;
107
+ btcRestHeader? : string[];
108
+ btcRpcWallet? : string;
109
+ btcRpcHeader? : string[];
110
+ btcSignalDiscovery? : string;
111
+ keystore? : string;
112
+ passphraseFile? : string;
113
+ signingKey? : string;
111
114
  }