@did-btcr2/method 0.37.0 → 0.38.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/dist/browser.js CHANGED
@@ -93172,6 +93172,7 @@ a=end-of-candidates
93172
93172
  BeaconUtils: () => BeaconUtils,
93173
93173
  CASBeacon: () => CASBeacon,
93174
93174
  CASBeaconError: () => CASBeaconError,
93175
+ CHANGE_OUTPUT_VBYTES: () => CHANGE_OUTPUT_VBYTES,
93175
93176
  COHORT_ADVERT: () => COHORT_ADVERT,
93176
93177
  COHORT_OPT_IN: () => COHORT_OPT_IN,
93177
93178
  COHORT_OPT_IN_ACCEPT: () => COHORT_OPT_IN_ACCEPT,
@@ -93180,6 +93181,7 @@ a=end-of-candidates
93180
93181
  DEFAULT_ADVERT_REPEAT_INTERVAL_MS: () => DEFAULT_ADVERT_REPEAT_INTERVAL_MS,
93181
93182
  DEFAULT_BROADCAST_LOOKBACK_MS: () => DEFAULT_BROADCAST_LOOKBACK_MS,
93182
93183
  DEFAULT_CLOCK_SKEW_SEC: () => DEFAULT_CLOCK_SKEW_SEC,
93184
+ DEFAULT_FEE_ESTIMATOR: () => DEFAULT_FEE_ESTIMATOR,
93183
93185
  DEFAULT_FUNDING_MODEL: () => DEFAULT_FUNDING_MODEL,
93184
93186
  DEFAULT_MAX_UPDATE_SIZE_BYTES: () => DEFAULT_MAX_UPDATE_SIZE_BYTES,
93185
93187
  DEFAULT_NONCE_LEN_BYTES: () => DEFAULT_NONCE_LEN_BYTES,
@@ -93187,6 +93189,7 @@ a=end-of-candidates
93187
93189
  DEFAULT_RECOVERY_SEQUENCE: () => DEFAULT_RECOVERY_SEQUENCE,
93188
93190
  DID_REGEX: () => DID_REGEX,
93189
93191
  DISTRIBUTE_AGGREGATED_DATA: () => DISTRIBUTE_AGGREGATED_DATA,
93192
+ DUST_LIMIT_SATS: () => DUST_LIMIT_SATS2,
93190
93193
  DidBtcr2: () => DidBtcr2,
93191
93194
  DidCommTransport: () => DidCommTransport,
93192
93195
  DidDocument: () => DidDocument,
@@ -93243,6 +93246,7 @@ a=end-of-candidates
93243
93246
  TypedEventEmitter: () => TypedEventEmitter,
93244
93247
  Updater: () => Updater,
93245
93248
  VALIDATION_ACK: () => VALIDATION_ACK,
93249
+ beaconTxVsize: () => beaconTxVsize,
93246
93250
  buildAggregationBeaconTx: () => buildAggregationBeaconTx,
93247
93251
  buildFallbackLeaf: () => buildFallbackLeaf,
93248
93252
  buildFallbackSpend: () => buildFallbackSpend,
@@ -93294,6 +93298,7 @@ a=end-of-candidates
93294
93298
  parseRequestAuth: () => parseRequestAuth,
93295
93299
  parseSseStream: () => parseSseStream,
93296
93300
  registerBeaconStrategy: () => registerBeaconStrategy,
93301
+ resolveChangeAddress: () => resolveChangeAddress,
93297
93302
  resolveFallbackThreshold: () => resolveFallbackThreshold,
93298
93303
  reviveFromWire: () => reviveFromWire,
93299
93304
  signEnvelope: () => signEnvelope,
@@ -112455,6 +112460,30 @@ ${value2}`;
112455
112460
 
112456
112461
  // src/core/aggregation/runner/service-runner.ts
112457
112462
  init_shim();
112463
+
112464
+ // src/core/beacon/fee-estimator.ts
112465
+ init_shim();
112466
+ var StaticFeeEstimator = class {
112467
+ satsPerVbyte;
112468
+ /**
112469
+ * @param satsPerVbyte Fee rate in satoshis per virtual byte. Default: 5 sat/vB.
112470
+ */
112471
+ constructor(satsPerVbyte = 5) {
112472
+ if (satsPerVbyte < 0 || !Number.isFinite(satsPerVbyte)) {
112473
+ throw new Error(`Invalid satsPerVbyte: ${satsPerVbyte}`);
112474
+ }
112475
+ this.satsPerVbyte = satsPerVbyte;
112476
+ }
112477
+ async estimateFee(vsize) {
112478
+ if (vsize < 0 || !Number.isFinite(vsize)) {
112479
+ throw new Error(`Invalid vsize: ${vsize}`);
112480
+ }
112481
+ return BigInt(Math.ceil(vsize * this.satsPerVbyte));
112482
+ }
112483
+ };
112484
+ var DEFAULT_FEE_ESTIMATOR = new StaticFeeEstimator(5);
112485
+
112486
+ // src/core/aggregation/runner/service-runner.ts
112458
112487
  var DEFAULT_ADVERT_REPEAT_INTERVAL_MS = 6e4;
112459
112488
  var AggregationServiceRunner = class _AggregationServiceRunner extends TypedEventEmitter {
112460
112489
  /** Direct access to the underlying state machine for advanced use. */
@@ -112465,6 +112494,7 @@ ${value2}`;
112465
112494
  #onOptInReceived;
112466
112495
  #onReadyToFinalize;
112467
112496
  #onProvideTxData;
112497
+ #feeEstimator;
112468
112498
  #cohortTtlMs;
112469
112499
  #phaseTimeoutMs;
112470
112500
  #advertRepeatIntervalMs;
@@ -112489,6 +112519,7 @@ ${value2}`;
112489
112519
  finalize: acceptedCount >= minRequired
112490
112520
  }));
112491
112521
  this.#onProvideTxData = options2.onProvideTxData;
112522
+ this.#feeEstimator = options2.feeEstimator ?? DEFAULT_FEE_ESTIMATOR;
112492
112523
  this.#cohortTtlMs = options2.cohortTtlMs;
112493
112524
  this.#phaseTimeoutMs = options2.phaseTimeoutMs;
112494
112525
  this.#advertRepeatIntervalMs = options2.advertRepeatIntervalMs ?? DEFAULT_ADVERT_REPEAT_INTERVAL_MS;
@@ -112919,7 +112950,8 @@ ${value2}`;
112919
112950
  const txData = await this.#onProvideTxData({
112920
112951
  cohortId: ctx.cohortId,
112921
112952
  beaconAddress: cohort.beaconAddress,
112922
- signalBytes: cohort.signalBytes
112953
+ signalBytes: cohort.signalBytes,
112954
+ feeEstimator: this.#feeEstimator
112923
112955
  });
112924
112956
  const authMsgs = this.session.startSigning(ctx.cohortId, txData);
112925
112957
  const sessionId = this.session.getSigningSessionId(ctx.cohortId) ?? "";
@@ -113375,6 +113407,7 @@ ${value2}`;
113375
113407
  keys: options2.service.keys,
113376
113408
  config: { minParticipants: 1, network: options2.config.network, beaconType: options2.config.beaconType, recoveryKey, recoverySequence, fundingModel: DEFAULT_FUNDING_MODEL },
113377
113409
  onProvideTxData: options2.onProvideTxData,
113410
+ feeEstimator: options2.feeEstimator,
113378
113411
  cohortTtlMs: options2.cohortTtlMs,
113379
113412
  phaseTimeoutMs: options2.phaseTimeoutMs,
113380
113413
  // In-process bus with the participant already listening: a single advert
@@ -113430,29 +113463,7 @@ ${value2}`;
113430
113463
  }
113431
113464
  };
113432
113465
 
113433
- // src/core/beacon/fee-estimator.ts
113434
- init_shim();
113435
- var StaticFeeEstimator = class {
113436
- satsPerVbyte;
113437
- /**
113438
- * @param satsPerVbyte Fee rate in satoshis per virtual byte. Default: 5 sat/vB.
113439
- */
113440
- constructor(satsPerVbyte = 5) {
113441
- if (satsPerVbyte < 0 || !Number.isFinite(satsPerVbyte)) {
113442
- throw new Error(`Invalid satsPerVbyte: ${satsPerVbyte}`);
113443
- }
113444
- this.satsPerVbyte = satsPerVbyte;
113445
- }
113446
- async estimateFee(vsize) {
113447
- if (vsize < 0 || !Number.isFinite(vsize)) {
113448
- throw new Error(`Invalid vsize: ${vsize}`);
113449
- }
113450
- return BigInt(Math.ceil(vsize * this.satsPerVbyte));
113451
- }
113452
- };
113453
-
113454
113466
  // src/core/beacon/beacon.ts
113455
- var DEFAULT_FEE_ESTIMATOR = new StaticFeeEstimator(5);
113456
113467
  var P2TR_BEACON_TX_VSIZE = 160;
113457
113468
  var P2WPKH_BEACON_TX_VSIZE = 155;
113458
113469
  var P2PKH_BEACON_TX_VSIZE = 240;
@@ -113461,6 +113472,20 @@ ${value2}`;
113461
113472
  p2wpkh: P2WPKH_BEACON_TX_VSIZE,
113462
113473
  p2tr: P2TR_BEACON_TX_VSIZE
113463
113474
  };
113475
+ var CHANGE_OUTPUT_VBYTES = {
113476
+ p2pkh: 34,
113477
+ p2wpkh: 31,
113478
+ p2tr: 43
113479
+ };
113480
+ var DUST_LIMIT_SATS2 = {
113481
+ p2pkh: 546,
113482
+ p2wpkh: 294,
113483
+ p2tr: 330
113484
+ };
113485
+ function beaconTxVsize(beaconKind, changeKind) {
113486
+ const base5 = SINGLETON_BEACON_TX_VSIZE[beaconKind] - CHANGE_OUTPUT_VBYTES[beaconKind];
113487
+ return base5 + CHANGE_OUTPUT_VBYTES[changeKind];
113488
+ }
113464
113489
  function detectSingletonScriptKind(bitcoinAddress, network) {
113465
113490
  const decoded = Address(network).decode(bitcoinAddress);
113466
113491
  if (decoded.type === "pkh") return "p2pkh";
@@ -113477,6 +113502,26 @@ ${value2}`;
113477
113502
  if (kind === "p2wpkh") return p2wpkh(pubkey, network).address;
113478
113503
  return p2tr(pubkey.slice(1, 33), void 0, network).address;
113479
113504
  }
113505
+ function resolveChangeAddress(beaconAddress, network, changeAddress) {
113506
+ if (!changeAddress || changeAddress === beaconAddress) return beaconAddress;
113507
+ try {
113508
+ Address(network).decode(changeAddress);
113509
+ } catch {
113510
+ throw new BeaconError(
113511
+ `Invalid change address "${changeAddress}" for network "${network}".`,
113512
+ "INVALID_CHANGE_ADDRESS",
113513
+ { changeAddress, network }
113514
+ );
113515
+ }
113516
+ return changeAddress;
113517
+ }
113518
+ function changeOutputKind(changeAddress, network) {
113519
+ try {
113520
+ return detectSingletonScriptKind(changeAddress, network);
113521
+ } catch {
113522
+ return "p2tr";
113523
+ }
113524
+ }
113480
113525
  function opReturnScript(signalBytes) {
113481
113526
  return Script.encode(["RETURN", signalBytes]);
113482
113527
  }
@@ -113503,8 +113548,10 @@ ${value2}`;
113503
113548
  async function buildAggregationBeaconTx(opts) {
113504
113549
  const feeEstimator = opts.feeEstimator ?? DEFAULT_FEE_ESTIMATOR;
113505
113550
  const { utxo, prevTxBytes } = await fetchSpendableUtxo(opts.beaconAddress, opts.bitcoin);
113551
+ const changeAddress = resolveChangeAddress(opts.beaconAddress, opts.network, opts.changeAddress);
113506
113552
  const witnessScript = OutScript.encode(Address(opts.network).decode(opts.beaconAddress));
113507
- const feeSats = await feeEstimator.estimateFee(P2TR_BEACON_TX_VSIZE);
113553
+ const changeKind = changeOutputKind(changeAddress, opts.network);
113554
+ const feeSats = await feeEstimator.estimateFee(beaconTxVsize("p2tr", changeKind));
113508
113555
  if (BigInt(utxo.value) <= feeSats) {
113509
113556
  throw new BeaconError(
113510
113557
  `UTXO value (${utxo.value}) insufficient to cover fee (${feeSats}).`,
@@ -113520,13 +113567,17 @@ ${value2}`;
113520
113567
  witnessUtxo: { amount: BigInt(utxo.value), script: witnessScript },
113521
113568
  tapInternalKey: opts.internalPubkey
113522
113569
  });
113523
- tx.addOutputAddress(opts.beaconAddress, BigInt(utxo.value) - feeSats, opts.network);
113570
+ const changeValue = BigInt(utxo.value) - feeSats;
113571
+ if (changeValue >= BigInt(DUST_LIMIT_SATS2[changeKind])) {
113572
+ tx.addOutputAddress(changeAddress, changeValue, opts.network);
113573
+ }
113524
113574
  tx.addOutput({ script: opReturnScript(opts.signalBytes), amount: 0n });
113525
113575
  return {
113526
113576
  tx,
113527
113577
  prevOutScripts: [witnessScript],
113528
113578
  prevOutValues: [BigInt(utxo.value)],
113529
113579
  beaconAddress: opts.beaconAddress,
113580
+ changeAddress,
113530
113581
  utxo,
113531
113582
  feeSats,
113532
113583
  scriptKind: "p2tr"
@@ -113604,7 +113655,8 @@ ${value2}`;
113604
113655
  prevTxBytes,
113605
113656
  signer,
113606
113657
  bitcoin,
113607
- feeEstimator
113658
+ feeEstimator,
113659
+ changeAddress: options2?.changeAddress
113608
113660
  });
113609
113661
  const signedHex = await this.signSinglePartyTx(plan, signer);
113610
113662
  return this.broadcastRawTx(bitcoin, signedHex);
@@ -113616,13 +113668,15 @@ ${value2}`;
113616
113668
  * the input accordingly. Validates that the signer's pubkey produces the beacon
113617
113669
  * address under that script kind: without this check, a misconfigured caller
113618
113670
  * would burn a real UTXO on a tx that fails at broadcast. Fees are computed from
113619
- * the per-kind {@link SINGLETON_BEACON_TX_VSIZE} constant, avoiding any probe-sign
113620
- * round-trip.
113671
+ * the per-kind {@link SINGLETON_BEACON_TX_VSIZE} constant (via {@link beaconTxVsize}),
113672
+ * avoiding any probe-sign round-trip; a change address of a different kind re-sizes
113673
+ * the fee by the change output's size delta so it stays a valid upper bound.
113621
113674
  */
113622
113675
  async buildSinglePartyTx(opts) {
113623
113676
  const network = opts.bitcoin.data;
113624
113677
  const pubkey = opts.signer.publicKey;
113625
113678
  const kind = detectSingletonScriptKind(opts.beaconAddress, network);
113679
+ const changeAddress = resolveChangeAddress(opts.beaconAddress, network, opts.changeAddress);
113626
113680
  const derivedAddress = deriveSingletonAddress(kind, pubkey, network);
113627
113681
  if (derivedAddress !== opts.beaconAddress) {
113628
113682
  throw new BeaconError(
@@ -113631,7 +113685,8 @@ ${value2}`;
113631
113685
  { kind, address: opts.beaconAddress, derivedAddress }
113632
113686
  );
113633
113687
  }
113634
- const feeSats = await opts.feeEstimator.estimateFee(SINGLETON_BEACON_TX_VSIZE[kind]);
113688
+ const changeKind = changeOutputKind(changeAddress, network);
113689
+ const feeSats = await opts.feeEstimator.estimateFee(beaconTxVsize(kind, changeKind));
113635
113690
  const amount = BigInt(opts.utxo.value);
113636
113691
  if (amount <= feeSats) {
113637
113692
  throw new BeaconError(
@@ -113668,13 +113723,17 @@ ${value2}`;
113668
113723
  tapInternalKey: internalKey
113669
113724
  });
113670
113725
  }
113671
- tx.addOutputAddress(opts.beaconAddress, amount - feeSats, network);
113726
+ const changeValue = amount - feeSats;
113727
+ if (changeValue >= BigInt(DUST_LIMIT_SATS2[changeKind])) {
113728
+ tx.addOutputAddress(changeAddress, changeValue, network);
113729
+ }
113672
113730
  tx.addOutput({ script: opReturnScript(opts.signalBytes), amount: 0n });
113673
113731
  return {
113674
113732
  tx,
113675
113733
  prevOutScripts: [prevOutScript],
113676
113734
  prevOutValues: [amount],
113677
113735
  beaconAddress: opts.beaconAddress,
113736
+ changeAddress,
113678
113737
  utxo: opts.utxo,
113679
113738
  feeSats,
113680
113739
  scriptKind: kind
package/dist/browser.mjs CHANGED
@@ -112295,6 +112295,30 @@ init_shim();
112295
112295
 
112296
112296
  // src/core/aggregation/runner/service-runner.ts
112297
112297
  init_shim();
112298
+
112299
+ // src/core/beacon/fee-estimator.ts
112300
+ init_shim();
112301
+ var StaticFeeEstimator = class {
112302
+ satsPerVbyte;
112303
+ /**
112304
+ * @param satsPerVbyte Fee rate in satoshis per virtual byte. Default: 5 sat/vB.
112305
+ */
112306
+ constructor(satsPerVbyte = 5) {
112307
+ if (satsPerVbyte < 0 || !Number.isFinite(satsPerVbyte)) {
112308
+ throw new Error(`Invalid satsPerVbyte: ${satsPerVbyte}`);
112309
+ }
112310
+ this.satsPerVbyte = satsPerVbyte;
112311
+ }
112312
+ async estimateFee(vsize) {
112313
+ if (vsize < 0 || !Number.isFinite(vsize)) {
112314
+ throw new Error(`Invalid vsize: ${vsize}`);
112315
+ }
112316
+ return BigInt(Math.ceil(vsize * this.satsPerVbyte));
112317
+ }
112318
+ };
112319
+ var DEFAULT_FEE_ESTIMATOR = new StaticFeeEstimator(5);
112320
+
112321
+ // src/core/aggregation/runner/service-runner.ts
112298
112322
  var DEFAULT_ADVERT_REPEAT_INTERVAL_MS = 6e4;
112299
112323
  var AggregationServiceRunner = class _AggregationServiceRunner extends TypedEventEmitter {
112300
112324
  /** Direct access to the underlying state machine for advanced use. */
@@ -112305,6 +112329,7 @@ var AggregationServiceRunner = class _AggregationServiceRunner extends TypedEven
112305
112329
  #onOptInReceived;
112306
112330
  #onReadyToFinalize;
112307
112331
  #onProvideTxData;
112332
+ #feeEstimator;
112308
112333
  #cohortTtlMs;
112309
112334
  #phaseTimeoutMs;
112310
112335
  #advertRepeatIntervalMs;
@@ -112329,6 +112354,7 @@ var AggregationServiceRunner = class _AggregationServiceRunner extends TypedEven
112329
112354
  finalize: acceptedCount >= minRequired
112330
112355
  }));
112331
112356
  this.#onProvideTxData = options2.onProvideTxData;
112357
+ this.#feeEstimator = options2.feeEstimator ?? DEFAULT_FEE_ESTIMATOR;
112332
112358
  this.#cohortTtlMs = options2.cohortTtlMs;
112333
112359
  this.#phaseTimeoutMs = options2.phaseTimeoutMs;
112334
112360
  this.#advertRepeatIntervalMs = options2.advertRepeatIntervalMs ?? DEFAULT_ADVERT_REPEAT_INTERVAL_MS;
@@ -112759,7 +112785,8 @@ var AggregationServiceRunner = class _AggregationServiceRunner extends TypedEven
112759
112785
  const txData = await this.#onProvideTxData({
112760
112786
  cohortId: ctx.cohortId,
112761
112787
  beaconAddress: cohort.beaconAddress,
112762
- signalBytes: cohort.signalBytes
112788
+ signalBytes: cohort.signalBytes,
112789
+ feeEstimator: this.#feeEstimator
112763
112790
  });
112764
112791
  const authMsgs = this.session.startSigning(ctx.cohortId, txData);
112765
112792
  const sessionId = this.session.getSigningSessionId(ctx.cohortId) ?? "";
@@ -113215,6 +113242,7 @@ var AggregationRunner = class {
113215
113242
  keys: options2.service.keys,
113216
113243
  config: { minParticipants: 1, network: options2.config.network, beaconType: options2.config.beaconType, recoveryKey, recoverySequence, fundingModel: DEFAULT_FUNDING_MODEL },
113217
113244
  onProvideTxData: options2.onProvideTxData,
113245
+ feeEstimator: options2.feeEstimator,
113218
113246
  cohortTtlMs: options2.cohortTtlMs,
113219
113247
  phaseTimeoutMs: options2.phaseTimeoutMs,
113220
113248
  // In-process bus with the participant already listening: a single advert
@@ -113270,29 +113298,7 @@ var SMTBeaconError = class extends MethodError {
113270
113298
  }
113271
113299
  };
113272
113300
 
113273
- // src/core/beacon/fee-estimator.ts
113274
- init_shim();
113275
- var StaticFeeEstimator = class {
113276
- satsPerVbyte;
113277
- /**
113278
- * @param satsPerVbyte Fee rate in satoshis per virtual byte. Default: 5 sat/vB.
113279
- */
113280
- constructor(satsPerVbyte = 5) {
113281
- if (satsPerVbyte < 0 || !Number.isFinite(satsPerVbyte)) {
113282
- throw new Error(`Invalid satsPerVbyte: ${satsPerVbyte}`);
113283
- }
113284
- this.satsPerVbyte = satsPerVbyte;
113285
- }
113286
- async estimateFee(vsize) {
113287
- if (vsize < 0 || !Number.isFinite(vsize)) {
113288
- throw new Error(`Invalid vsize: ${vsize}`);
113289
- }
113290
- return BigInt(Math.ceil(vsize * this.satsPerVbyte));
113291
- }
113292
- };
113293
-
113294
113301
  // src/core/beacon/beacon.ts
113295
- var DEFAULT_FEE_ESTIMATOR = new StaticFeeEstimator(5);
113296
113302
  var P2TR_BEACON_TX_VSIZE = 160;
113297
113303
  var P2WPKH_BEACON_TX_VSIZE = 155;
113298
113304
  var P2PKH_BEACON_TX_VSIZE = 240;
@@ -113301,6 +113307,20 @@ var SINGLETON_BEACON_TX_VSIZE = {
113301
113307
  p2wpkh: P2WPKH_BEACON_TX_VSIZE,
113302
113308
  p2tr: P2TR_BEACON_TX_VSIZE
113303
113309
  };
113310
+ var CHANGE_OUTPUT_VBYTES = {
113311
+ p2pkh: 34,
113312
+ p2wpkh: 31,
113313
+ p2tr: 43
113314
+ };
113315
+ var DUST_LIMIT_SATS2 = {
113316
+ p2pkh: 546,
113317
+ p2wpkh: 294,
113318
+ p2tr: 330
113319
+ };
113320
+ function beaconTxVsize(beaconKind, changeKind) {
113321
+ const base5 = SINGLETON_BEACON_TX_VSIZE[beaconKind] - CHANGE_OUTPUT_VBYTES[beaconKind];
113322
+ return base5 + CHANGE_OUTPUT_VBYTES[changeKind];
113323
+ }
113304
113324
  function detectSingletonScriptKind(bitcoinAddress, network) {
113305
113325
  const decoded = Address(network).decode(bitcoinAddress);
113306
113326
  if (decoded.type === "pkh") return "p2pkh";
@@ -113317,6 +113337,26 @@ function deriveSingletonAddress(kind, pubkey, network) {
113317
113337
  if (kind === "p2wpkh") return p2wpkh(pubkey, network).address;
113318
113338
  return p2tr(pubkey.slice(1, 33), void 0, network).address;
113319
113339
  }
113340
+ function resolveChangeAddress(beaconAddress, network, changeAddress) {
113341
+ if (!changeAddress || changeAddress === beaconAddress) return beaconAddress;
113342
+ try {
113343
+ Address(network).decode(changeAddress);
113344
+ } catch {
113345
+ throw new BeaconError(
113346
+ `Invalid change address "${changeAddress}" for network "${network}".`,
113347
+ "INVALID_CHANGE_ADDRESS",
113348
+ { changeAddress, network }
113349
+ );
113350
+ }
113351
+ return changeAddress;
113352
+ }
113353
+ function changeOutputKind(changeAddress, network) {
113354
+ try {
113355
+ return detectSingletonScriptKind(changeAddress, network);
113356
+ } catch {
113357
+ return "p2tr";
113358
+ }
113359
+ }
113320
113360
  function opReturnScript(signalBytes) {
113321
113361
  return Script.encode(["RETURN", signalBytes]);
113322
113362
  }
@@ -113343,8 +113383,10 @@ async function fetchSpendableUtxo(bitcoinAddress, bitcoin) {
113343
113383
  async function buildAggregationBeaconTx(opts) {
113344
113384
  const feeEstimator = opts.feeEstimator ?? DEFAULT_FEE_ESTIMATOR;
113345
113385
  const { utxo, prevTxBytes } = await fetchSpendableUtxo(opts.beaconAddress, opts.bitcoin);
113386
+ const changeAddress = resolveChangeAddress(opts.beaconAddress, opts.network, opts.changeAddress);
113346
113387
  const witnessScript = OutScript.encode(Address(opts.network).decode(opts.beaconAddress));
113347
- const feeSats = await feeEstimator.estimateFee(P2TR_BEACON_TX_VSIZE);
113388
+ const changeKind = changeOutputKind(changeAddress, opts.network);
113389
+ const feeSats = await feeEstimator.estimateFee(beaconTxVsize("p2tr", changeKind));
113348
113390
  if (BigInt(utxo.value) <= feeSats) {
113349
113391
  throw new BeaconError(
113350
113392
  `UTXO value (${utxo.value}) insufficient to cover fee (${feeSats}).`,
@@ -113360,13 +113402,17 @@ async function buildAggregationBeaconTx(opts) {
113360
113402
  witnessUtxo: { amount: BigInt(utxo.value), script: witnessScript },
113361
113403
  tapInternalKey: opts.internalPubkey
113362
113404
  });
113363
- tx.addOutputAddress(opts.beaconAddress, BigInt(utxo.value) - feeSats, opts.network);
113405
+ const changeValue = BigInt(utxo.value) - feeSats;
113406
+ if (changeValue >= BigInt(DUST_LIMIT_SATS2[changeKind])) {
113407
+ tx.addOutputAddress(changeAddress, changeValue, opts.network);
113408
+ }
113364
113409
  tx.addOutput({ script: opReturnScript(opts.signalBytes), amount: 0n });
113365
113410
  return {
113366
113411
  tx,
113367
113412
  prevOutScripts: [witnessScript],
113368
113413
  prevOutValues: [BigInt(utxo.value)],
113369
113414
  beaconAddress: opts.beaconAddress,
113415
+ changeAddress,
113370
113416
  utxo,
113371
113417
  feeSats,
113372
113418
  scriptKind: "p2tr"
@@ -113444,7 +113490,8 @@ var SinglePartyBeacon = class {
113444
113490
  prevTxBytes,
113445
113491
  signer,
113446
113492
  bitcoin,
113447
- feeEstimator
113493
+ feeEstimator,
113494
+ changeAddress: options2?.changeAddress
113448
113495
  });
113449
113496
  const signedHex = await this.signSinglePartyTx(plan, signer);
113450
113497
  return this.broadcastRawTx(bitcoin, signedHex);
@@ -113456,13 +113503,15 @@ var SinglePartyBeacon = class {
113456
113503
  * the input accordingly. Validates that the signer's pubkey produces the beacon
113457
113504
  * address under that script kind: without this check, a misconfigured caller
113458
113505
  * would burn a real UTXO on a tx that fails at broadcast. Fees are computed from
113459
- * the per-kind {@link SINGLETON_BEACON_TX_VSIZE} constant, avoiding any probe-sign
113460
- * round-trip.
113506
+ * the per-kind {@link SINGLETON_BEACON_TX_VSIZE} constant (via {@link beaconTxVsize}),
113507
+ * avoiding any probe-sign round-trip; a change address of a different kind re-sizes
113508
+ * the fee by the change output's size delta so it stays a valid upper bound.
113461
113509
  */
113462
113510
  async buildSinglePartyTx(opts) {
113463
113511
  const network = opts.bitcoin.data;
113464
113512
  const pubkey = opts.signer.publicKey;
113465
113513
  const kind = detectSingletonScriptKind(opts.beaconAddress, network);
113514
+ const changeAddress = resolveChangeAddress(opts.beaconAddress, network, opts.changeAddress);
113466
113515
  const derivedAddress = deriveSingletonAddress(kind, pubkey, network);
113467
113516
  if (derivedAddress !== opts.beaconAddress) {
113468
113517
  throw new BeaconError(
@@ -113471,7 +113520,8 @@ var SinglePartyBeacon = class {
113471
113520
  { kind, address: opts.beaconAddress, derivedAddress }
113472
113521
  );
113473
113522
  }
113474
- const feeSats = await opts.feeEstimator.estimateFee(SINGLETON_BEACON_TX_VSIZE[kind]);
113523
+ const changeKind = changeOutputKind(changeAddress, network);
113524
+ const feeSats = await opts.feeEstimator.estimateFee(beaconTxVsize(kind, changeKind));
113475
113525
  const amount = BigInt(opts.utxo.value);
113476
113526
  if (amount <= feeSats) {
113477
113527
  throw new BeaconError(
@@ -113508,13 +113558,17 @@ var SinglePartyBeacon = class {
113508
113558
  tapInternalKey: internalKey
113509
113559
  });
113510
113560
  }
113511
- tx.addOutputAddress(opts.beaconAddress, amount - feeSats, network);
113561
+ const changeValue = amount - feeSats;
113562
+ if (changeValue >= BigInt(DUST_LIMIT_SATS2[changeKind])) {
113563
+ tx.addOutputAddress(changeAddress, changeValue, network);
113564
+ }
113512
113565
  tx.addOutput({ script: opReturnScript(opts.signalBytes), amount: 0n });
113513
113566
  return {
113514
113567
  tx,
113515
113568
  prevOutScripts: [prevOutScript],
113516
113569
  prevOutValues: [amount],
113517
113570
  beaconAddress: opts.beaconAddress,
113571
+ changeAddress,
113518
113572
  utxo: opts.utxo,
113519
113573
  feeSats,
113520
113574
  scriptKind: kind
@@ -130247,6 +130301,7 @@ export {
130247
130301
  BeaconUtils,
130248
130302
  CASBeacon,
130249
130303
  CASBeaconError,
130304
+ CHANGE_OUTPUT_VBYTES,
130250
130305
  COHORT_ADVERT,
130251
130306
  COHORT_OPT_IN,
130252
130307
  COHORT_OPT_IN_ACCEPT,
@@ -130255,6 +130310,7 @@ export {
130255
130310
  DEFAULT_ADVERT_REPEAT_INTERVAL_MS,
130256
130311
  DEFAULT_BROADCAST_LOOKBACK_MS,
130257
130312
  DEFAULT_CLOCK_SKEW_SEC,
130313
+ DEFAULT_FEE_ESTIMATOR,
130258
130314
  DEFAULT_FUNDING_MODEL,
130259
130315
  DEFAULT_MAX_UPDATE_SIZE_BYTES,
130260
130316
  DEFAULT_NONCE_LEN_BYTES,
@@ -130262,6 +130318,7 @@ export {
130262
130318
  DEFAULT_RECOVERY_SEQUENCE,
130263
130319
  DID_REGEX,
130264
130320
  DISTRIBUTE_AGGREGATED_DATA,
130321
+ DUST_LIMIT_SATS2 as DUST_LIMIT_SATS,
130265
130322
  DidBtcr2,
130266
130323
  DidCommTransport,
130267
130324
  DidDocument,
@@ -130318,6 +130375,7 @@ export {
130318
130375
  TypedEventEmitter,
130319
130376
  Updater,
130320
130377
  VALIDATION_ACK,
130378
+ beaconTxVsize,
130321
130379
  buildAggregationBeaconTx,
130322
130380
  buildFallbackLeaf,
130323
130381
  buildFallbackSpend,
@@ -130369,6 +130427,7 @@ export {
130369
130427
  parseRequestAuth,
130370
130428
  parseSseStream,
130371
130429
  registerBeaconStrategy,
130430
+ resolveChangeAddress,
130372
130431
  resolveFallbackThreshold,
130373
130432
  reviveFromWire,
130374
130433
  signEnvelope,