@did-btcr2/cli 0.13.0 → 0.14.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.
@@ -1,3 +1,4 @@
1
+ import type { PublishToCasMode } from '@did-btcr2/api';
1
2
  import { KeyManagerSigner } from '@did-btcr2/key-manager';
2
3
  import type { Command } from 'commander';
3
4
  import { deriveNetwork, type ApiFactory } from '../config.js';
@@ -37,12 +38,20 @@ export function registerUpdateCommand(
37
38
  'Beacon ID as a JSON string',
38
39
  parseJsonArg('--beacon-id'),
39
40
  )
41
+ .option(
42
+ '--publish-to-cas <mode>',
43
+ 'Publish update artifacts to a writable CAS before broadcast: auto|always|never. '
44
+ + 'CAS publication is optional; the default distributes the returned artifacts via sidecar.',
45
+ parsePublishToCasMode,
46
+ 'never',
47
+ )
40
48
  .action(async (options: {
41
49
  sourceDocument : unknown;
42
50
  sourceVersionId : string;
43
51
  patches : unknown;
44
52
  verificationMethodId : string;
45
53
  beaconId : unknown;
54
+ publishToCas : PublishToCasMode;
46
55
  }) => {
47
56
  if (!/^\d+$/.test(options.sourceVersionId)) {
48
57
  throw new CLIError(
@@ -70,9 +79,12 @@ export function registerUpdateCommand(
70
79
  const api = factory(network, globals());
71
80
  const keyId = resolveKeyRef(api.kms.kms, globals().signingKey);
72
81
  const signer = new KeyManagerSigner(api.kms.kms, keyId);
73
- // The CLI's CAS is a read-only gateway (no writable CAS is configurable
74
- // yet), so CAS publication is disabled explicitly. The returned artifacts
75
- // (txid, announcement, proof) are printed for manual sidecar distribution.
82
+ // CAS publication is optional and never required: every beacon update can
83
+ // be completed and shared via sidecar alone. It is opt-in and defaults to
84
+ // 'never'; pass --publish-to-cas auto|always to publish the signed update
85
+ // (and, for CAS beacons, the announcement) to a writable CAS configured
86
+ // via --cas-rpc-url. The returned artifacts (txid, announcement, proof)
87
+ // are always printed for sidecar distribution regardless.
76
88
  const data = await api.btcr2.update({
77
89
  sourceDocument : parsed.sourceDocument,
78
90
  patches : parsed.patches,
@@ -80,12 +92,27 @@ export function registerUpdateCommand(
80
92
  verificationMethodId : parsed.verificationMethodId,
81
93
  beaconId : parsed.beaconId,
82
94
  signer,
83
- publishToCas : 'never',
95
+ publishToCas : options.publishToCas,
84
96
  });
85
97
  console.log(formatResult({ action: 'update', data }, globals()));
86
98
  });
87
99
  }
88
100
 
101
+ /**
102
+ * Commander argParser for `--publish-to-cas`. Validates the value is one of the
103
+ * three {@link PublishToCasMode} policies, erroring at parse time otherwise.
104
+ */
105
+ function parsePublishToCasMode(value: string): PublishToCasMode {
106
+ if (value !== 'auto' && value !== 'always' && value !== 'never') {
107
+ throw new CLIError(
108
+ '--publish-to-cas must be one of "auto", "always", or "never".',
109
+ 'INVALID_ARGUMENT_ERROR',
110
+ { value },
111
+ );
112
+ }
113
+ return value;
114
+ }
115
+
89
116
  /**
90
117
  * Returns a commander argParser that validates JSON.
91
118
  * Errors at parse time with a clear flag reference.
package/src/config.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { createApi, Identifier, type BitcoinApiConfig, type DidBtcr2Api } from '@did-btcr2/api';
1
+ import { createApi, Identifier, type BitcoinApiConfig, type CasConfig, type DidBtcr2Api } from '@did-btcr2/api';
2
2
  import type { KeyManager } from '@did-btcr2/key-manager';
3
3
  import { readFileSync } from 'node:fs';
4
4
  import { homedir } from 'node:os';
@@ -23,7 +23,10 @@ export type ConnectionOverrides = {
23
23
  btcRpcUrl? : string;
24
24
  btcRpcUser? : string;
25
25
  btcRpcPass? : string;
26
+ /** IPFS HTTP gateway for CAS reads (read-only). */
26
27
  casGateway? : string;
28
+ /** IPFS HTTP RPC endpoint for a writable CAS (reads + writes). */
29
+ casRpcUrl? : string;
27
30
  config? : string;
28
31
  profile? : string;
29
32
  /** Keystore file path. Overrides the default `$XDG_DATA_HOME/btcr2/keystore.json`. */
@@ -49,7 +52,7 @@ export type ConnectionOverrides = {
49
52
  * },
50
53
  * "bitcoin": {
51
54
  * "btc": { "rest": "https://my-mempool/api" },
52
- * "cas": { "gateway": "https://ipfs.io" }
55
+ * "cas": { "gateway": "https://ipfs.io", "rpcUrl": "http://127.0.0.1:5001" }
53
56
  * }
54
57
  * }
55
58
  * }
@@ -72,7 +75,10 @@ export type ConfigFile = {
72
75
  rpcPass? : string;
73
76
  };
74
77
  cas?: {
78
+ /** IPFS HTTP gateway for CAS reads (read-only). */
75
79
  gateway?: string;
80
+ /** IPFS HTTP RPC endpoint for a writable CAS (reads + writes). */
81
+ rpcUrl?: string;
76
82
  };
77
83
  /** Signing identity references. Never embeds key material; the secret lives in the keystore. */
78
84
  identity?: {
@@ -153,6 +159,7 @@ export type ApiFactory = (network?: NetworkOption, overrides?: ConnectionOverrid
153
159
  * | `BTCR2_BTC_RPC_USER` | `--btc-rpc-user` |
154
160
  * | `BTCR2_BTC_RPC_PASS` | `--btc-rpc-pass` |
155
161
  * | `BTCR2_CAS_GATEWAY` | `--cas-gateway` |
162
+ * | `BTCR2_CAS_RPC_URL` | `--cas-rpc-url` |
156
163
  */
157
164
  export const ENV_VARS = {
158
165
  BTC_REST : 'BTCR2_BTC_REST',
@@ -160,6 +167,7 @@ export const ENV_VARS = {
160
167
  BTC_RPC_USER : 'BTCR2_BTC_RPC_USER',
161
168
  BTC_RPC_PASS : 'BTCR2_BTC_RPC_PASS',
162
169
  CAS_GATEWAY : 'BTCR2_CAS_GATEWAY',
170
+ CAS_RPC_URL : 'BTCR2_CAS_RPC_URL',
163
171
  } as const;
164
172
 
165
173
  /**
@@ -174,6 +182,7 @@ export function readEnvOverrides(): ConnectionOverrides {
174
182
  btcRpcUser : env(ENV_VARS.BTC_RPC_USER),
175
183
  btcRpcPass : env(ENV_VARS.BTC_RPC_PASS),
176
184
  casGateway : env(ENV_VARS.CAS_GATEWAY),
185
+ casRpcUrl : env(ENV_VARS.CAS_RPC_URL),
177
186
  };
178
187
  }
179
188
 
@@ -221,6 +230,7 @@ export function profileToOverrides(
221
230
  btcRpcUser : profile.btc?.rpcUser,
222
231
  btcRpcPass : profile.btc?.rpcPass,
223
232
  casGateway : profile.cas?.gateway,
233
+ casRpcUrl : profile.cas?.rpcUrl,
224
234
  };
225
235
  }
226
236
 
@@ -261,7 +271,7 @@ export function resolveDefaultNetwork(overrides?: ConnectionOverrides): NetworkO
261
271
  function resolveConnectionConfig(
262
272
  network? : NetworkOption,
263
273
  overrides?: ConnectionOverrides,
264
- ): { btc?: BitcoinApiConfig; cas?: { gateway: string } } {
274
+ ): { btc?: BitcoinApiConfig; cas?: CasConfig } {
265
275
  if (!network) return {};
266
276
 
267
277
  // Layer 1: Config file profile (lowest precedence of the three override layers)
@@ -280,6 +290,7 @@ function resolveConnectionConfig(
280
290
  btcRpcUser : overrides?.btcRpcUser ?? env.btcRpcUser ?? fileOverrides.btcRpcUser,
281
291
  btcRpcPass : overrides?.btcRpcPass ?? env.btcRpcPass ?? fileOverrides.btcRpcPass,
282
292
  casGateway : overrides?.casGateway ?? env.casGateway ?? fileOverrides.casGateway,
293
+ casRpcUrl : overrides?.casRpcUrl ?? env.casRpcUrl ?? fileOverrides.casRpcUrl,
283
294
  };
284
295
 
285
296
  const btc: BitcoinApiConfig = { network };
@@ -296,9 +307,15 @@ function resolveConnectionConfig(
296
307
  };
297
308
  }
298
309
 
299
- const cas = merged.casGateway ? { gateway: merged.casGateway } : undefined;
310
+ // A configured RPC endpoint is writable and takes precedence over the
311
+ // read-only gateway (matching the api's CasConfig priority: rpcUrl > gateway).
312
+ // Both may be set; the api selects one executor from them.
313
+ const cas: CasConfig = {};
314
+ if (merged.casGateway) cas.gateway = merged.casGateway;
315
+ if (merged.casRpcUrl) cas.rpcUrl = merged.casRpcUrl;
316
+ const hasCas = merged.casGateway || merged.casRpcUrl;
300
317
 
301
- return { btc, ...(cas && { cas }) };
318
+ return { btc, ...(hasCas && { cas }) };
302
319
  }
303
320
 
304
321
  /**
package/src/types.ts CHANGED
@@ -56,6 +56,7 @@ export interface GlobalOptions {
56
56
  btcRpcUser? : string;
57
57
  btcRpcPass? : string;
58
58
  casGateway? : string;
59
+ casRpcUrl? : string;
59
60
  keystore? : string;
60
61
  passphraseFile?: string;
61
62
  signingKey? : string;