@arkade-os/sdk 0.4.59 → 0.4.61

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. package/README.md +34 -6
  2. package/dist/adapters/expo.cjs +5 -5
  3. package/dist/adapters/expo.d.cts +4 -4
  4. package/dist/adapters/expo.d.ts +4 -4
  5. package/dist/adapters/expo.js +3 -3
  6. package/dist/adapters/indexedDB.cjs +5 -5
  7. package/dist/adapters/indexedDB.js +4 -4
  8. package/dist/{asyncStorageTaskQueue-CYHXFfvn.d.cts → asyncStorageTaskQueue-BWDxp3Dr.d.cts} +2 -2
  9. package/dist/{asyncStorageTaskQueue-BwuWQ1Qc.d.ts → asyncStorageTaskQueue-Cq9FpKv6.d.ts} +2 -2
  10. package/dist/{chunk-V2KENVN6.cjs → chunk-2JHN5RCY.cjs} +22 -15
  11. package/dist/chunk-2JHN5RCY.cjs.map +1 -0
  12. package/dist/{chunk-DYNB6POO.cjs → chunk-3NUDA3MB.cjs} +4423 -2934
  13. package/dist/chunk-3NUDA3MB.cjs.map +1 -0
  14. package/dist/{chunk-PN5RD7ZI.js → chunk-6O5OLYTZ.js} +1685 -1138
  15. package/dist/chunk-6O5OLYTZ.js.map +1 -0
  16. package/dist/{chunk-OVSNY3HC.js → chunk-AUEXB42T.js} +3965 -2503
  17. package/dist/chunk-AUEXB42T.js.map +1 -0
  18. package/dist/{chunk-DSTYXT2D.cjs → chunk-FNCQJLR2.cjs} +44 -6
  19. package/dist/chunk-FNCQJLR2.cjs.map +1 -0
  20. package/dist/{chunk-VSHY5TBY.js → chunk-GDI2A65Z.js} +4 -4
  21. package/dist/{chunk-VSHY5TBY.js.map → chunk-GDI2A65Z.js.map} +1 -1
  22. package/dist/{chunk-FIMOVH25.js → chunk-PVY6DMN5.js} +40 -7
  23. package/dist/chunk-PVY6DMN5.js.map +1 -0
  24. package/dist/{chunk-2XO43UHI.cjs → chunk-SF5NKAUA.cjs} +1731 -1180
  25. package/dist/chunk-SF5NKAUA.cjs.map +1 -0
  26. package/dist/{chunk-7FA36NOA.cjs → chunk-XFYFREPH.cjs} +17 -17
  27. package/dist/{chunk-7FA36NOA.cjs.map → chunk-XFYFREPH.cjs.map} +1 -1
  28. package/dist/{chunk-H342JPPZ.js → chunk-XLHN5L2O.js} +5 -5
  29. package/dist/chunk-XLHN5L2O.js.map +1 -0
  30. package/dist/{chunk-IPFGYVF3.cjs → chunk-XMRB74HA.cjs} +10 -10
  31. package/dist/chunk-XMRB74HA.cjs.map +1 -0
  32. package/dist/{chunk-YEHGCHIL.js → chunk-YVQNHELA.js} +12 -5
  33. package/dist/chunk-YVQNHELA.js.map +1 -0
  34. package/dist/contracts/handlers/index.cjs +12 -8
  35. package/dist/contracts/handlers/index.d.cts +5 -5
  36. package/dist/contracts/handlers/index.d.ts +5 -5
  37. package/dist/contracts/handlers/index.js +2 -2
  38. package/dist/{delegate-D0eGaz3J.d.cts → delegate-BySzD37I.d.ts} +1 -1
  39. package/dist/{delegate-B_oKWm1k.d.ts → delegate-C4Rl6h9l.d.cts} +1 -1
  40. package/dist/{ark-C9DsPSm8.d.cts → index-BcRCHnzN.d.cts} +4887 -4331
  41. package/dist/{ark-C9DsPSm8.d.ts → index-BcRCHnzN.d.ts} +4887 -4331
  42. package/dist/{index-BmK6tEqf.d.ts → index-Butfkm-K.d.cts} +224 -68
  43. package/dist/{index-BG2AL2Ie.d.cts → index-C29VNcxC.d.ts} +224 -68
  44. package/dist/index.cjs +374 -230
  45. package/dist/index.d.cts +522 -82
  46. package/dist/index.d.ts +522 -82
  47. package/dist/index.js +4 -4
  48. package/dist/repositories/realm/index.cjs +43 -23
  49. package/dist/repositories/realm/index.cjs.map +1 -1
  50. package/dist/repositories/realm/index.d.cts +23 -11
  51. package/dist/repositories/realm/index.d.ts +23 -11
  52. package/dist/repositories/realm/index.js +29 -9
  53. package/dist/repositories/realm/index.js.map +1 -1
  54. package/dist/repositories/sqlite/index.cjs +48 -23
  55. package/dist/repositories/sqlite/index.cjs.map +1 -1
  56. package/dist/repositories/sqlite/index.d.cts +11 -5
  57. package/dist/repositories/sqlite/index.d.ts +11 -5
  58. package/dist/repositories/sqlite/index.js +35 -10
  59. package/dist/repositories/sqlite/index.js.map +1 -1
  60. package/dist/{taskRunner-Cy4Gh8n7.d.ts → taskRunner-BTq3rMpI.d.cts} +2 -2
  61. package/dist/{taskRunner-CBvKIttE.d.cts → taskRunner-DxMJYJ7Y.d.ts} +2 -2
  62. package/dist/wallet/expo/background.cjs +14 -14
  63. package/dist/wallet/expo/background.d.cts +5 -5
  64. package/dist/wallet/expo/background.d.ts +5 -5
  65. package/dist/wallet/expo/background.js +6 -6
  66. package/dist/wallet/expo/index.cjs +37 -14
  67. package/dist/wallet/expo/index.cjs.map +1 -1
  68. package/dist/wallet/expo/index.d.cts +13 -5
  69. package/dist/wallet/expo/index.d.ts +13 -5
  70. package/dist/wallet/expo/index.js +28 -5
  71. package/dist/wallet/expo/index.js.map +1 -1
  72. package/dist/{wallet-EyVDhqzP.d.ts → wallet-BVIfg2ic.d.ts} +222 -19
  73. package/dist/{wallet-DlXemAzP.d.cts → wallet-DrSsUden.d.cts} +222 -19
  74. package/dist/worker/expo/index.cjs +9 -9
  75. package/dist/worker/expo/index.d.cts +7 -7
  76. package/dist/worker/expo/index.d.ts +7 -7
  77. package/dist/worker/expo/index.js +5 -5
  78. package/package.json +4 -2
  79. package/dist/chunk-2XO43UHI.cjs.map +0 -1
  80. package/dist/chunk-DSTYXT2D.cjs.map +0 -1
  81. package/dist/chunk-DYNB6POO.cjs.map +0 -1
  82. package/dist/chunk-FIMOVH25.js.map +0 -1
  83. package/dist/chunk-H342JPPZ.js.map +0 -1
  84. package/dist/chunk-IPFGYVF3.cjs.map +0 -1
  85. package/dist/chunk-OVSNY3HC.js.map +0 -1
  86. package/dist/chunk-PN5RD7ZI.js.map +0 -1
  87. package/dist/chunk-V2KENVN6.cjs.map +0 -1
  88. package/dist/chunk-YEHGCHIL.js.map +0 -1
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var chunkDSTYXT2D_cjs = require('./chunk-DSTYXT2D.cjs');
3
+ var chunkFNCQJLR2_cjs = require('./chunk-FNCQJLR2.cjs');
4
4
  var chunk5BLDMQED_cjs = require('./chunk-5BLDMQED.cjs');
5
5
  var base = require('@scure/base');
6
6
  var descriptorsScure = require('@bitcoinerlab/descriptors-scure');
@@ -76,14 +76,14 @@ var ContractHandlerRegistry = class {
76
76
  var contractHandlers = new ContractHandlerRegistry();
77
77
  exports.DefaultVtxo = void 0;
78
78
  ((DefaultVtxo2) => {
79
- class Script3 extends chunkDSTYXT2D_cjs.VtxoScript {
79
+ class Script3 extends chunkFNCQJLR2_cjs.VtxoScript {
80
80
  /** Create the default virtual output script with one forfeit path and one exit path. */
81
81
  constructor(options) {
82
82
  const { pubKey, serverPubKey, csvTimelock } = options;
83
- const forfeitScript = chunkDSTYXT2D_cjs.MultisigTapscript.encode({
83
+ const forfeitScript = chunkFNCQJLR2_cjs.MultisigTapscript.encode({
84
84
  pubkeys: [pubKey, serverPubKey]
85
85
  }).script;
86
- const exitScript = chunkDSTYXT2D_cjs.CSVMultisigTapscript.encode({
86
+ const exitScript = chunkFNCQJLR2_cjs.CSVMultisigTapscript.encode({
87
87
  timelock: csvTimelock,
88
88
  pubkeys: [pubKey]
89
89
  }).script;
@@ -212,6 +212,31 @@ function deriveDescriptorLeafCompressedPubKey(descriptor) {
212
212
  }
213
213
  return key.keyPath ? key.bip32.derivePath(key.keyPath.replace(/^\//, "")).publicKey : key.bip32.publicKey;
214
214
  }
215
+ function parseHDDescriptor(descriptor) {
216
+ if (!isDescriptor(descriptor)) {
217
+ return null;
218
+ }
219
+ let expansion;
220
+ try {
221
+ const network = isMainnetDescriptor(descriptor) ? descriptorsScure.networks.bitcoin : descriptorsScure.networks.testnet;
222
+ expansion = descriptorsScure.expand({ descriptor, network });
223
+ } catch {
224
+ return null;
225
+ }
226
+ const key = expansion.expansionMap?.["@0"];
227
+ if (!key?.masterFingerprint || !key.originPath || !key.keyPath || !key.bip32) {
228
+ return null;
229
+ }
230
+ return {
231
+ fingerprint: base.hex.encode(key.masterFingerprint),
232
+ basePath: key.originPath.replace(/^\//, ""),
233
+ xpub: key.bip32.toBase58(),
234
+ derivationPath: key.keyPath.replace(/^\//, "")
235
+ };
236
+ }
237
+ async function identityDescriptor(identity) {
238
+ return normalizeToDescriptor(base.hex.encode(await identity.xOnlyPublicKey()));
239
+ }
215
240
 
216
241
  // src/contracts/metadata.ts
217
242
  var WALLET_RECEIVE_SOURCE = "wallet-receive";
@@ -263,21 +288,49 @@ function extractPubKeyBytes(value) {
263
288
  return base.hex.decode(extractPubKey(normalizeToDescriptor(value)));
264
289
  }
265
290
  function deserializeCsvTimelock(value) {
266
- return value !== void 0 && value !== "" ? chunkDSTYXT2D_cjs.sequenceToTimelock(Number(value)) : exports.DefaultVtxo.Script.DEFAULT_TIMELOCK;
291
+ return value !== void 0 && value !== "" ? chunkFNCQJLR2_cjs.sequenceToTimelock(Number(value)) : exports.DefaultVtxo.Script.DEFAULT_TIMELOCK;
267
292
  }
268
293
  var CLTV_HEIGHT_THRESHOLD = 500000000n;
269
294
  function isCltvSatisfied(context, locktime) {
295
+ return cltvMaturity(context, locktime) === "satisfied";
296
+ }
297
+ function cltvMaturity(context, locktime) {
270
298
  if (locktime < CLTV_HEIGHT_THRESHOLD) {
271
- if (context.blockHeight === void 0) return false;
272
- return BigInt(context.blockHeight) >= locktime;
299
+ if (context.blockHeight === void 0) return "unknown";
300
+ return BigInt(context.blockHeight) >= locktime ? "satisfied" : "pending";
301
+ }
302
+ return nowSeconds(context) >= locktime ? "satisfied" : "pending";
303
+ }
304
+ function nowSeconds(context) {
305
+ return BigInt(Math.floor(context.chainTime ?? context.currentTime / 1e3));
306
+ }
307
+ function isHeightLocktime(locktime) {
308
+ return locktime < CLTV_HEIGHT_THRESHOLD;
309
+ }
310
+ function assertVhtlcSpendableNow(contract, context) {
311
+ if (!context.collaborative) return;
312
+ if (resolveRole(contract, context) !== "sender") return;
313
+ const raw = contract.params?.refundLocktime;
314
+ if (raw === void 0) return;
315
+ let refundLocktime;
316
+ try {
317
+ refundLocktime = BigInt(raw);
318
+ } catch {
319
+ return;
273
320
  }
274
- const currentTimeSec = BigInt(Math.floor(context.currentTime / 1e3));
275
- return currentTimeSec >= locktime;
321
+ if (cltvMaturity(context, refundLocktime) !== "pending") return;
322
+ const [matures, now] = isHeightLocktime(refundLocktime) ? [`block ${refundLocktime}`, `${context.blockHeight}`] : [
323
+ `${new Date(Number(refundLocktime) * 1e3).toISOString()}`,
324
+ new Date(context.currentTime).toISOString()
325
+ ];
326
+ throw new Error(
327
+ `vhtlc ${contract.script} cannot be spent yet: its refund path opens at ${matures}, now ${now}. Until then the lockup is the receiver's to claim, and the server rejects a spend of it. Retry after ${matures}, or wait for the receiver to claim it with the preimage.`
328
+ );
276
329
  }
277
330
  function isCsvSpendable(context, sequence) {
278
331
  if (sequence === void 0) return true;
279
332
  if (!context.vtxo) return false;
280
- const timelock = chunkDSTYXT2D_cjs.sequenceToTimelock(sequence);
333
+ const timelock = chunkFNCQJLR2_cjs.sequenceToTimelock(sequence);
281
334
  if (timelock.type === "blocks") {
282
335
  if (context.blockHeight === void 0 || context.vtxo.status.block_height === void 0) {
283
336
  return false;
@@ -287,7 +340,7 @@ function isCsvSpendable(context, sequence) {
287
340
  if (timelock.type === "seconds") {
288
341
  const blockTime = context.vtxo.status.block_time;
289
342
  if (blockTime === void 0) return false;
290
- return context.currentTime / 1e3 - blockTime >= Number(timelock.value);
343
+ return Number(nowSeconds(context)) - blockTime >= Number(timelock.value);
291
344
  }
292
345
  return false;
293
346
  }
@@ -335,21 +388,21 @@ function forfeitExitSpendablePaths(script, contract, context) {
335
388
  async function detectUsedScripts(indexerProvider, scriptHexes) {
336
389
  const used = /* @__PURE__ */ new Set();
337
390
  const unique = [...new Set(scriptHexes)];
338
- for (let offset = 0; offset < unique.length; offset += chunkDSTYXT2D_cjs.SCRIPT_QUERY_CHUNK_SIZE) {
339
- const scripts = unique.slice(offset, offset + chunkDSTYXT2D_cjs.SCRIPT_QUERY_CHUNK_SIZE);
391
+ for (let offset = 0; offset < unique.length; offset += chunkFNCQJLR2_cjs.SCRIPT_QUERY_CHUNK_SIZE) {
392
+ const scripts = unique.slice(offset, offset + chunkFNCQJLR2_cjs.SCRIPT_QUERY_CHUNK_SIZE);
340
393
  const remaining = new Set(scripts);
341
394
  let pageIndex = 0;
342
395
  let hasMore = true;
343
396
  while (hasMore && remaining.size > 0) {
344
- const { vtxos, page } = await chunkDSTYXT2D_cjs.getNormalizedVtxos(indexerProvider, {
397
+ const { vtxos, page } = await chunkFNCQJLR2_cjs.getNormalizedVtxos(indexerProvider, {
345
398
  scripts,
346
399
  pageIndex,
347
- pageSize: chunkDSTYXT2D_cjs.DEFAULT_PAGE_SIZE
400
+ pageSize: chunkFNCQJLR2_cjs.DEFAULT_PAGE_SIZE
348
401
  });
349
402
  for (const vtxo of vtxos) {
350
403
  if (remaining.delete(vtxo.script)) used.add(vtxo.script);
351
404
  }
352
- hasMore = page ? vtxos.length === chunkDSTYXT2D_cjs.DEFAULT_PAGE_SIZE : false;
405
+ hasMore = page ? vtxos.length === chunkFNCQJLR2_cjs.DEFAULT_PAGE_SIZE : false;
353
406
  pageIndex++;
354
407
  }
355
408
  }
@@ -410,7 +463,7 @@ var DefaultContractHandler = {
410
463
  return {
411
464
  pubKey: base.hex.encode(params.pubKey),
412
465
  serverPubKey: base.hex.encode(params.serverPubKey),
413
- csvTimelock: chunkDSTYXT2D_cjs.timelockToSequence(params.csvTimelock).toString()
466
+ csvTimelock: chunkFNCQJLR2_cjs.timelockToSequence(params.csvTimelock).toString()
414
467
  };
415
468
  },
416
469
  deserializeParams(params) {
@@ -429,6 +482,8 @@ var DefaultContractHandler = {
429
482
  getSpendablePaths(script, contract, context) {
430
483
  return forfeitExitSpendablePaths(script, contract, context);
431
484
  },
485
+ /** The wallet's own money. */
486
+ isGenericallySpendable: () => true,
432
487
  discoverAt: discoverAtViaRange(discoverDefaultRange),
433
488
  discoverRange: discoverDefaultRange,
434
489
  candidatesAt: defaultCandidatesAt
@@ -443,7 +498,7 @@ function defaultCandidatesAt(_index, descriptor, deps) {
443
498
  params: {
444
499
  pubKey: base.hex.encode(c.pubKey),
445
500
  serverPubKey: base.hex.encode(c.serverPubKey),
446
- csvTimelock: chunkDSTYXT2D_cjs.timelockToSequence(c.csvTimelock).toString()
501
+ csvTimelock: chunkFNCQJLR2_cjs.timelockToSequence(c.csvTimelock).toString()
447
502
  },
448
503
  script: c.scriptHex,
449
504
  address: c.script.address(deps.network.hrp, c.serverPubKey).encode()
@@ -459,12 +514,12 @@ function discoverDefaultRange(entries, deps) {
459
514
  }
460
515
  exports.DelegateVtxo = void 0;
461
516
  ((DelegateVtxo2) => {
462
- class Script3 extends chunkDSTYXT2D_cjs.VtxoScript {
517
+ class Script3 extends chunkFNCQJLR2_cjs.VtxoScript {
463
518
  /** Create a delegated virtual output script with forfeit, exit, and delegate paths. */
464
519
  constructor(options) {
465
520
  const defaultVtxo = new exports.DefaultVtxo.Script(options);
466
521
  const { delegatePubKey, pubKey, serverPubKey } = options;
467
- const delegateScript = chunkDSTYXT2D_cjs.MultisigTapscript.encode({
522
+ const delegateScript = chunkFNCQJLR2_cjs.MultisigTapscript.encode({
468
523
  pubkeys: [pubKey, delegatePubKey, serverPubKey]
469
524
  }).script;
470
525
  super([...defaultVtxo.scripts, delegateScript]);
@@ -503,7 +558,7 @@ var DelegateContractHandler = {
503
558
  pubKey: base.hex.encode(params.pubKey),
504
559
  serverPubKey: base.hex.encode(params.serverPubKey),
505
560
  delegatePubKey: base.hex.encode(params.delegatePubKey),
506
- csvTimelock: chunkDSTYXT2D_cjs.timelockToSequence(params.csvTimelock).toString()
561
+ csvTimelock: chunkFNCQJLR2_cjs.timelockToSequence(params.csvTimelock).toString()
507
562
  };
508
563
  },
509
564
  deserializeParams(params) {
@@ -528,6 +583,8 @@ var DelegateContractHandler = {
528
583
  getSpendablePaths(script, contract, context) {
529
584
  return forfeitExitSpendablePaths(script, contract, context);
530
585
  },
586
+ /** The wallet's own money, delegated signing. */
587
+ isGenericallySpendable: () => true,
531
588
  discoverAt: discoverAtViaRange(discoverDelegateRange),
532
589
  discoverRange: discoverDelegateRange,
533
590
  candidatesAt: delegateCandidatesAt
@@ -545,7 +602,7 @@ function delegateCandidatesAt(_index, descriptor, deps) {
545
602
  pubKey: base.hex.encode(c.pubKey),
546
603
  serverPubKey: base.hex.encode(c.serverPubKey),
547
604
  delegatePubKey: base.hex.encode(delegatePubKey),
548
- csvTimelock: chunkDSTYXT2D_cjs.timelockToSequence(c.csvTimelock).toString()
605
+ csvTimelock: chunkFNCQJLR2_cjs.timelockToSequence(c.csvTimelock).toString()
549
606
  },
550
607
  script: c.scriptHex,
551
608
  address: c.script.address(deps.network.hrp, c.serverPubKey).encode()
@@ -562,1225 +619,1702 @@ function discoverDelegateRange(entries, deps) {
562
619
  (c, index, descriptor) => ({ ...c, ...rotatedReceiveMetadata(index, descriptor) })
563
620
  );
564
621
  }
565
- exports.VHTLC = void 0;
566
- ((VHTLC2) => {
567
- class Script3 extends chunkDSTYXT2D_cjs.VtxoScript {
568
- /** Create a VHTLC script from the supplied participant keys, hash, and timelocks. */
569
- constructor(options) {
570
- validateOptions(options);
571
- const {
572
- sender,
573
- receiver,
574
- server,
575
- preimageHash,
576
- refundLocktime,
577
- unilateralClaimDelay,
578
- unilateralRefundDelay,
579
- unilateralRefundWithoutReceiverDelay
580
- } = options;
581
- const conditionScript = preimageConditionScript(preimageHash);
582
- const claimScript = chunkDSTYXT2D_cjs.ConditionMultisigTapscript.encode({
583
- conditionScript,
584
- pubkeys: [receiver, server]
585
- }).script;
586
- const refundScript = chunkDSTYXT2D_cjs.MultisigTapscript.encode({
587
- pubkeys: [sender, receiver, server]
588
- }).script;
589
- const refundWithoutReceiverScript = chunkDSTYXT2D_cjs.CLTVMultisigTapscript.encode({
590
- absoluteTimelock: refundLocktime,
591
- pubkeys: [sender, server]
592
- }).script;
593
- const unilateralClaimScript = chunkDSTYXT2D_cjs.ConditionCSVMultisigTapscript.encode({
594
- conditionScript,
595
- timelock: unilateralClaimDelay,
596
- pubkeys: [receiver]
597
- }).script;
598
- const unilateralRefundScript = chunkDSTYXT2D_cjs.CSVMultisigTapscript.encode({
599
- timelock: unilateralRefundDelay,
600
- pubkeys: [sender, receiver]
601
- }).script;
602
- const unilateralRefundWithoutReceiverScript = chunkDSTYXT2D_cjs.CSVMultisigTapscript.encode({
603
- timelock: unilateralRefundWithoutReceiverDelay,
604
- pubkeys: [sender]
605
- }).script;
606
- super([
607
- claimScript,
608
- refundScript,
609
- refundWithoutReceiverScript,
610
- unilateralClaimScript,
611
- unilateralRefundScript,
612
- unilateralRefundWithoutReceiverScript
613
- ]);
614
- this.options = options;
615
- this.claimScript = base.hex.encode(claimScript);
616
- this.refundScript = base.hex.encode(refundScript);
617
- this.refundWithoutReceiverScript = base.hex.encode(refundWithoutReceiverScript);
618
- this.unilateralClaimScript = base.hex.encode(unilateralClaimScript);
619
- this.unilateralRefundScript = base.hex.encode(unilateralRefundScript);
620
- this.unilateralRefundWithoutReceiverScript = base.hex.encode(
621
- unilateralRefundWithoutReceiverScript
622
- );
623
- }
624
- options;
625
- claimScript;
626
- refundScript;
627
- refundWithoutReceiverScript;
628
- unilateralClaimScript;
629
- unilateralRefundScript;
630
- unilateralRefundWithoutReceiverScript;
631
- /** Return the collaborative claim tapleaf script. */
632
- claim() {
633
- return this.findLeaf(this.claimScript);
622
+ function equalBytes(a, b) {
623
+ if (a.length !== b.length)
624
+ return false;
625
+ for (let i = 0; i < a.length; i++)
626
+ if (a[i] !== b[i])
627
+ return false;
628
+ return true;
629
+ }
630
+ function isBytes(a) {
631
+ return a instanceof Uint8Array || ArrayBuffer.isView(a) && a.constructor.name === "Uint8Array";
632
+ }
633
+ var createView = (arr) => new DataView(arr.buffer, arr.byteOffset, arr.byteLength);
634
+ function isPlainObject(obj) {
635
+ return Object.prototype.toString.call(obj) === "[object Object]";
636
+ }
637
+ function isNum(num) {
638
+ return Number.isSafeInteger(num);
639
+ }
640
+ var Bitset = {
641
+ BITS: 32,
642
+ FULL_MASK: -1 >>> 0,
643
+ // 1<<32 will overflow
644
+ len: (len) => Math.ceil(len / 32),
645
+ create: (len) => new Uint32Array(Bitset.len(len)),
646
+ clean: (bs) => bs.fill(0),
647
+ debug: (bs) => Array.from(bs).map((i) => (i >>> 0).toString(2).padStart(32, "0")),
648
+ checkLen: (bs, len) => {
649
+ if (Bitset.len(len) === bs.length)
650
+ return;
651
+ throw new Error(`wrong length=${bs.length}. Expected: ${Bitset.len(len)}`);
652
+ },
653
+ chunkLen: (bsLen, pos, len) => {
654
+ if (pos < 0)
655
+ throw new Error(`wrong pos=${pos}`);
656
+ if (pos + len > bsLen)
657
+ throw new Error(`wrong range=${pos}/${len} of ${bsLen}`);
658
+ },
659
+ set: (bs, chunk, value, allowRewrite = true) => {
660
+ if (!allowRewrite && (bs[chunk] & value) !== 0)
661
+ return false;
662
+ bs[chunk] |= value;
663
+ return true;
664
+ },
665
+ pos: (pos, i) => ({
666
+ chunk: Math.floor((pos + i) / 32),
667
+ mask: 1 << 32 - (pos + i) % 32 - 1
668
+ }),
669
+ indices: (bs, len, invert = false) => {
670
+ Bitset.checkLen(bs, len);
671
+ const { FULL_MASK, BITS } = Bitset;
672
+ const left = BITS - len % BITS;
673
+ const lastMask = left ? FULL_MASK >>> left << left : FULL_MASK;
674
+ const res = [];
675
+ for (let i = 0; i < bs.length; i++) {
676
+ let c = bs[i];
677
+ if (invert)
678
+ c = ~c;
679
+ if (i === bs.length - 1)
680
+ c &= lastMask;
681
+ if (c === 0)
682
+ continue;
683
+ for (let j = 0; j < BITS; j++) {
684
+ const m = 1 << BITS - j - 1;
685
+ if (c & m)
686
+ res.push(i * BITS + j);
687
+ }
634
688
  }
635
- /** Return the collaborative refund tapleaf script. */
636
- refund() {
637
- return this.findLeaf(this.refundScript);
689
+ return res;
690
+ },
691
+ range: (arr) => {
692
+ const res = [];
693
+ let cur;
694
+ for (const i of arr) {
695
+ if (cur === void 0 || i !== cur.pos + cur.length)
696
+ res.push(cur = { pos: i, length: 1 });
697
+ else
698
+ cur.length += 1;
638
699
  }
639
- /** Return the refund-without-receiver tapleaf script. */
640
- refundWithoutReceiver() {
641
- return this.findLeaf(this.refundWithoutReceiverScript);
700
+ return res;
701
+ },
702
+ rangeDebug: (bs, len, invert = false) => `[${Bitset.range(Bitset.indices(bs, len, invert)).map((i) => `(${i.pos}/${i.length})`).join(", ")}]`,
703
+ setRange: (bs, bsLen, pos, len, allowRewrite = true) => {
704
+ Bitset.chunkLen(bsLen, pos, len);
705
+ const { FULL_MASK, BITS } = Bitset;
706
+ const first = pos % BITS ? Math.floor(pos / BITS) : void 0;
707
+ const lastPos = pos + len;
708
+ const last = lastPos % BITS ? Math.floor(lastPos / BITS) : void 0;
709
+ if (first !== void 0 && first === last)
710
+ return Bitset.set(bs, first, FULL_MASK >>> BITS - len << BITS - len - pos, allowRewrite);
711
+ if (first !== void 0) {
712
+ if (!Bitset.set(bs, first, FULL_MASK >>> pos % BITS, allowRewrite))
713
+ return false;
642
714
  }
643
- /** Return the unilateral claim tapleaf script. */
644
- unilateralClaim() {
645
- return this.findLeaf(this.unilateralClaimScript);
715
+ const start = first !== void 0 ? first + 1 : pos / BITS;
716
+ const end = last !== void 0 ? last : lastPos / BITS;
717
+ for (let i = start; i < end; i++)
718
+ if (!Bitset.set(bs, i, FULL_MASK, allowRewrite))
719
+ return false;
720
+ if (last !== void 0 && first !== last) {
721
+ if (!Bitset.set(bs, last, FULL_MASK << BITS - lastPos % BITS, allowRewrite))
722
+ return false;
646
723
  }
647
- /** Return the unilateral refund tapleaf script. */
648
- unilateralRefund() {
649
- return this.findLeaf(this.unilateralRefundScript);
724
+ return true;
725
+ }
726
+ };
727
+ var Path = {
728
+ /**
729
+ * Internal method for handling stack of paths (debug, errors, dynamic fields via path)
730
+ * This is looks ugly (callback), but allows us to force stack cleaning by construction (.pop always after function).
731
+ * Also, this makes impossible:
732
+ * - pushing field when stack is empty
733
+ * - pushing field inside of field (real bug)
734
+ * NOTE: we don't want to do '.pop' on error!
735
+ */
736
+ pushObj: (stack, obj, objFn) => {
737
+ const last = { obj };
738
+ stack.push(last);
739
+ objFn((field, fieldFn) => {
740
+ last.field = field;
741
+ fieldFn();
742
+ last.field = void 0;
743
+ });
744
+ stack.pop();
745
+ },
746
+ path: (stack) => {
747
+ const res = [];
748
+ for (const i of stack)
749
+ if (i.field !== void 0)
750
+ res.push(i.field);
751
+ return res.join("/");
752
+ },
753
+ err: (name, stack, msg) => {
754
+ const err = new Error(`${name}(${Path.path(stack)}): ${typeof msg === "string" ? msg : msg.message}`);
755
+ if (msg instanceof Error && msg.stack)
756
+ err.stack = msg.stack;
757
+ return err;
758
+ },
759
+ resolve: (stack, path) => {
760
+ const parts = path.split("/");
761
+ const objPath = stack.map((i2) => i2.obj);
762
+ let i = 0;
763
+ for (; i < parts.length; i++) {
764
+ if (parts[i] === "..")
765
+ objPath.pop();
766
+ else
767
+ break;
650
768
  }
651
- /** Return the unilateral refund-without-receiver tapleaf script. */
652
- unilateralRefundWithoutReceiver() {
653
- return this.findLeaf(this.unilateralRefundWithoutReceiverScript);
769
+ let cur = objPath.pop();
770
+ for (; i < parts.length; i++) {
771
+ if (!cur || cur[parts[i]] === void 0)
772
+ return void 0;
773
+ cur = cur[parts[i]];
654
774
  }
775
+ return cur;
655
776
  }
656
- VHTLC2.Script = Script3;
657
- function validateOptions(options) {
658
- const {
659
- sender,
660
- receiver,
661
- server,
662
- preimageHash,
663
- refundLocktime,
664
- unilateralClaimDelay,
665
- unilateralRefundDelay,
666
- unilateralRefundWithoutReceiverDelay
667
- } = options;
668
- if (!preimageHash || preimageHash.length !== 20) {
669
- throw new Error("preimage hash must be 20 bytes");
670
- }
671
- if (!receiver || receiver.length !== 32) {
672
- throw new Error("Invalid public key length (receiver)");
673
- }
674
- if (!sender || sender.length !== 32) {
675
- throw new Error("Invalid public key length (sender)");
777
+ };
778
+ var _Reader = class __Reader {
779
+ pos = 0;
780
+ data;
781
+ opts;
782
+ stack;
783
+ parent;
784
+ parentOffset;
785
+ bitBuf = 0;
786
+ bitPos = 0;
787
+ bs;
788
+ // bitset
789
+ view;
790
+ constructor(data, opts = {}, stack = [], parent = void 0, parentOffset = 0) {
791
+ this.data = data;
792
+ this.opts = opts;
793
+ this.stack = stack;
794
+ this.parent = parent;
795
+ this.parentOffset = parentOffset;
796
+ this.view = createView(data);
797
+ }
798
+ /** Internal method for pointers. */
799
+ _enablePointers() {
800
+ if (this.parent)
801
+ return this.parent._enablePointers();
802
+ if (this.bs)
803
+ return;
804
+ this.bs = Bitset.create(this.data.length);
805
+ Bitset.setRange(this.bs, this.data.length, 0, this.pos, this.opts.allowMultipleReads);
806
+ }
807
+ markBytesBS(pos, len) {
808
+ if (this.parent)
809
+ return this.parent.markBytesBS(this.parentOffset + pos, len);
810
+ if (!len)
811
+ return true;
812
+ if (!this.bs)
813
+ return true;
814
+ return Bitset.setRange(this.bs, this.data.length, pos, len, false);
815
+ }
816
+ markBytes(len) {
817
+ const pos = this.pos;
818
+ this.pos += len;
819
+ const res = this.markBytesBS(pos, len);
820
+ if (!this.opts.allowMultipleReads && !res)
821
+ throw this.err(`multiple read pos=${this.pos} len=${len}`);
822
+ return res;
823
+ }
824
+ pushObj(obj, objFn) {
825
+ return Path.pushObj(this.stack, obj, objFn);
826
+ }
827
+ readView(n, fn) {
828
+ if (!Number.isFinite(n))
829
+ throw this.err(`readView: wrong length=${n}`);
830
+ if (this.pos + n > this.data.length)
831
+ throw this.err("readView: Unexpected end of buffer");
832
+ const res = fn(this.view, this.pos);
833
+ this.markBytes(n);
834
+ return res;
835
+ }
836
+ // read bytes by absolute offset
837
+ absBytes(n) {
838
+ if (n > this.data.length)
839
+ throw new Error("Unexpected end of buffer");
840
+ return this.data.subarray(n);
841
+ }
842
+ finish() {
843
+ if (this.opts.allowUnreadBytes)
844
+ return;
845
+ if (this.bitPos) {
846
+ throw this.err(`${this.bitPos} bits left after unpack: ${base.hex.encode(this.data.slice(this.pos))}`);
676
847
  }
677
- if (!server || server.length !== 32) {
678
- throw new Error("Invalid public key length (server)");
848
+ if (this.bs && !this.parent) {
849
+ const notRead = Bitset.indices(this.bs, this.data.length, true);
850
+ if (notRead.length) {
851
+ const formatted = Bitset.range(notRead).map(({ pos, length }) => `(${pos}/${length})[${base.hex.encode(this.data.subarray(pos, pos + length))}]`).join(", ");
852
+ throw this.err(`unread byte ranges: ${formatted} (total=${this.data.length})`);
853
+ } else
854
+ return;
679
855
  }
680
- if (typeof refundLocktime !== "bigint" || refundLocktime <= 0n) {
681
- throw new Error("refund locktime must be greater than 0");
856
+ if (!this.isEnd()) {
857
+ throw this.err(`${this.leftBytes} bytes ${this.bitPos} bits left after unpack: ${base.hex.encode(this.data.slice(this.pos))}`);
682
858
  }
683
- if (!unilateralClaimDelay || typeof unilateralClaimDelay.value !== "bigint" || unilateralClaimDelay.value <= 0n) {
684
- throw new Error("unilateral claim delay must greater than 0");
859
+ }
860
+ // User methods
861
+ err(msg) {
862
+ return Path.err("Reader", this.stack, msg);
863
+ }
864
+ offsetReader(n) {
865
+ if (n > this.data.length)
866
+ throw this.err("offsetReader: Unexpected end of buffer");
867
+ return new __Reader(this.absBytes(n), this.opts, this.stack, this, n);
868
+ }
869
+ bytes(n, peek = false) {
870
+ if (this.bitPos)
871
+ throw this.err("readBytes: bitPos not empty");
872
+ if (!Number.isFinite(n))
873
+ throw this.err(`readBytes: wrong length=${n}`);
874
+ if (this.pos + n > this.data.length)
875
+ throw this.err("readBytes: Unexpected end of buffer");
876
+ const slice = this.data.subarray(this.pos, this.pos + n);
877
+ if (!peek)
878
+ this.markBytes(n);
879
+ return slice;
880
+ }
881
+ byte(peek = false) {
882
+ if (this.bitPos)
883
+ throw this.err("readByte: bitPos not empty");
884
+ if (this.pos + 1 > this.data.length)
885
+ throw this.err("readBytes: Unexpected end of buffer");
886
+ const data = this.data[this.pos];
887
+ if (!peek)
888
+ this.markBytes(1);
889
+ return data;
890
+ }
891
+ get leftBytes() {
892
+ return this.data.length - this.pos;
893
+ }
894
+ get totalBytes() {
895
+ return this.data.length;
896
+ }
897
+ isEnd() {
898
+ return this.pos >= this.data.length && !this.bitPos;
899
+ }
900
+ // bits are read in BE mode (left to right): (0b1000_0000).readBits(1) == 1
901
+ bits(bits) {
902
+ if (bits > 32)
903
+ throw this.err("BitReader: cannot read more than 32 bits in single call");
904
+ let out = 0;
905
+ while (bits) {
906
+ if (!this.bitPos) {
907
+ this.bitBuf = this.byte();
908
+ this.bitPos = 8;
909
+ }
910
+ const take = Math.min(bits, this.bitPos);
911
+ this.bitPos -= take;
912
+ out = out << take | this.bitBuf >> this.bitPos & 2 ** take - 1;
913
+ this.bitBuf &= 2 ** this.bitPos - 1;
914
+ bits -= take;
685
915
  }
686
- if (unilateralClaimDelay.type === "seconds" && unilateralClaimDelay.value % 512n !== 0n) {
687
- throw new Error("seconds timelock must be multiple of 512");
916
+ return out >>> 0;
917
+ }
918
+ find(needle, pos = this.pos) {
919
+ if (!isBytes(needle))
920
+ throw this.err(`find: needle is not bytes! ${needle}`);
921
+ if (this.bitPos)
922
+ throw this.err("findByte: bitPos not empty");
923
+ if (!needle.length)
924
+ throw this.err(`find: needle is empty`);
925
+ for (let idx = pos; (idx = this.data.indexOf(needle[0], idx)) !== -1; idx++) {
926
+ if (idx === -1)
927
+ return;
928
+ const leftBytes = this.data.length - idx;
929
+ if (leftBytes < needle.length)
930
+ return;
931
+ if (equalBytes(needle, this.data.subarray(idx, idx + needle.length)))
932
+ return idx;
688
933
  }
689
- if (unilateralClaimDelay.type === "seconds" && unilateralClaimDelay.value < 512n) {
690
- throw new Error("seconds timelock must be greater or equal to 512");
934
+ return;
935
+ }
936
+ };
937
+ var _Writer = class {
938
+ pos = 0;
939
+ stack;
940
+ // We could have a single buffer here and re-alloc it with
941
+ // x1.5-2 size each time it full, but it will be slower:
942
+ // basic/encode bench: 395ns -> 560ns
943
+ buffers = [];
944
+ ptrs = [];
945
+ bitBuf = 0;
946
+ bitPos = 0;
947
+ viewBuf = new Uint8Array(8);
948
+ view;
949
+ finished = false;
950
+ constructor(stack = []) {
951
+ this.stack = stack;
952
+ this.view = createView(this.viewBuf);
953
+ }
954
+ pushObj(obj, objFn) {
955
+ return Path.pushObj(this.stack, obj, objFn);
956
+ }
957
+ writeView(len, fn) {
958
+ if (this.finished)
959
+ throw this.err("buffer: finished");
960
+ if (!isNum(len) || len > 8)
961
+ throw new Error(`wrong writeView length=${len}`);
962
+ fn(this.view);
963
+ this.bytes(this.viewBuf.slice(0, len));
964
+ this.viewBuf.fill(0);
965
+ }
966
+ // User methods
967
+ err(msg) {
968
+ if (this.finished)
969
+ throw this.err("buffer: finished");
970
+ return Path.err("Reader", this.stack, msg);
971
+ }
972
+ bytes(b) {
973
+ if (this.finished)
974
+ throw this.err("buffer: finished");
975
+ if (this.bitPos)
976
+ throw this.err("writeBytes: ends with non-empty bit buffer");
977
+ this.buffers.push(b);
978
+ this.pos += b.length;
979
+ }
980
+ byte(b) {
981
+ if (this.finished)
982
+ throw this.err("buffer: finished");
983
+ if (this.bitPos)
984
+ throw this.err("writeByte: ends with non-empty bit buffer");
985
+ this.buffers.push(new Uint8Array([b]));
986
+ this.pos++;
987
+ }
988
+ finish(clean = true) {
989
+ if (this.finished)
990
+ throw this.err("buffer: finished");
991
+ if (this.bitPos)
992
+ throw this.err("buffer: ends with non-empty bit buffer");
993
+ const buffers = this.buffers.concat(this.ptrs.map((i) => i.buffer));
994
+ const sum = buffers.map((b) => b.length).reduce((a, b) => a + b, 0);
995
+ const buf = new Uint8Array(sum);
996
+ for (let i = 0, pad = 0; i < buffers.length; i++) {
997
+ const a = buffers[i];
998
+ buf.set(a, pad);
999
+ pad += a.length;
691
1000
  }
692
- if (!unilateralRefundDelay || typeof unilateralRefundDelay.value !== "bigint" || unilateralRefundDelay.value <= 0n) {
693
- throw new Error("unilateral refund delay must greater than 0");
1001
+ for (let pos = this.pos, i = 0; i < this.ptrs.length; i++) {
1002
+ const ptr = this.ptrs[i];
1003
+ buf.set(ptr.ptr.encode(pos), ptr.pos);
1004
+ pos += ptr.buffer.length;
694
1005
  }
695
- if (unilateralRefundDelay.type === "seconds" && unilateralRefundDelay.value % 512n !== 0n) {
696
- throw new Error("seconds timelock must be multiple of 512");
697
- }
698
- if (unilateralRefundDelay.type === "seconds" && unilateralRefundDelay.value < 512n) {
699
- throw new Error("seconds timelock must be greater or equal to 512");
700
- }
701
- if (!unilateralRefundWithoutReceiverDelay || typeof unilateralRefundWithoutReceiverDelay.value !== "bigint" || unilateralRefundWithoutReceiverDelay.value <= 0n) {
702
- throw new Error("unilateral refund without receiver delay must greater than 0");
703
- }
704
- if (unilateralRefundWithoutReceiverDelay.type === "seconds" && unilateralRefundWithoutReceiverDelay.value % 512n !== 0n) {
705
- throw new Error("seconds timelock must be multiple of 512");
706
- }
707
- if (unilateralRefundWithoutReceiverDelay.type === "seconds" && unilateralRefundWithoutReceiverDelay.value < 512n) {
708
- throw new Error("seconds timelock must be greater or equal to 512");
1006
+ if (clean) {
1007
+ this.buffers = [];
1008
+ for (const p of this.ptrs)
1009
+ p.buffer.fill(0);
1010
+ this.ptrs = [];
1011
+ this.finished = true;
1012
+ this.bitBuf = 0;
709
1013
  }
1014
+ return buf;
710
1015
  }
711
- })(exports.VHTLC || (exports.VHTLC = {}));
712
- function preimageConditionScript(preimageHash) {
713
- return btcSigner.Script.encode(["HASH160", preimageHash, "EQUAL"]);
714
- }
715
-
716
- // src/contracts/handlers/vhtlc.ts
717
- var VHTLCContractHandler = {
718
- type: "vhtlc",
719
- createScript(params) {
720
- const typed = this.deserializeParams(params);
721
- return new exports.VHTLC.Script(typed);
722
- },
723
- serializeParams(params) {
724
- return {
725
- sender: base.hex.encode(params.sender),
726
- receiver: base.hex.encode(params.receiver),
727
- server: base.hex.encode(params.server),
728
- hash: base.hex.encode(params.preimageHash),
729
- refundLocktime: params.refundLocktime.toString(),
730
- claimDelay: chunkDSTYXT2D_cjs.timelockToSequence(params.unilateralClaimDelay).toString(),
731
- refundDelay: chunkDSTYXT2D_cjs.timelockToSequence(params.unilateralRefundDelay).toString(),
732
- refundNoReceiverDelay: chunkDSTYXT2D_cjs.timelockToSequence(
733
- params.unilateralRefundWithoutReceiverDelay
734
- ).toString()
735
- };
736
- },
737
- deserializeParams(params) {
738
- return {
739
- sender: base.hex.decode(params.sender),
740
- receiver: base.hex.decode(params.receiver),
741
- server: base.hex.decode(params.server),
742
- preimageHash: base.hex.decode(params.hash),
743
- refundLocktime: BigInt(params.refundLocktime),
744
- unilateralClaimDelay: chunkDSTYXT2D_cjs.sequenceToTimelock(Number(params.claimDelay)),
745
- unilateralRefundDelay: chunkDSTYXT2D_cjs.sequenceToTimelock(Number(params.refundDelay)),
746
- unilateralRefundWithoutReceiverDelay: chunkDSTYXT2D_cjs.sequenceToTimelock(
747
- Number(params.refundNoReceiverDelay)
748
- )
749
- };
750
- },
751
- /**
752
- * Select spending path based on context.
753
- *
754
- * Role is determined from `context.role` or by matching
755
- * `context.walletDescriptor` (preferred) / `context.walletPubKey`
756
- * against sender/receiver in contract params.
757
- */
758
- selectPath(script, contract, context) {
759
- const role = resolveRole(contract, context);
760
- const preimage = contract.params?.preimage;
761
- const refundLocktime = BigInt(contract.params.refundLocktime);
762
- if (!role) {
763
- return null;
764
- }
765
- if (context.collaborative) {
766
- if (role === "receiver" && preimage) {
767
- return {
768
- leaf: script.claim(),
769
- extraWitness: [base.hex.decode(preimage)]
770
- };
771
- }
772
- if (role === "sender" && isCltvSatisfied(context, refundLocktime)) {
773
- return {
774
- leaf: script.refundWithoutReceiver()
775
- };
776
- }
777
- return null;
778
- }
779
- if (role === "receiver" && preimage) {
780
- const sequence = Number(contract.params.claimDelay);
781
- if (!isCsvSpendable(context, sequence)) return null;
782
- return {
783
- leaf: script.unilateralClaim(),
784
- extraWitness: [base.hex.decode(preimage)],
785
- sequence
786
- };
787
- }
788
- if (role === "sender") {
789
- const sequence = Number(contract.params.refundNoReceiverDelay);
790
- if (!isCsvSpendable(context, sequence)) return null;
791
- return {
792
- leaf: script.unilateralRefundWithoutReceiver(),
793
- sequence
794
- };
795
- }
796
- return null;
797
- },
798
- /**
799
- * Get all possible spending paths (no timelock checks).
800
- *
801
- * Role is determined from `context.role` or by matching
802
- * `context.walletDescriptor` (preferred) / `context.walletPubKey`
803
- * against sender/receiver in contract params.
804
- */
805
- getAllSpendingPaths(script, contract, context) {
806
- const role = resolveRole(contract, context);
807
- const paths = [];
808
- if (!role) {
809
- return paths;
810
- }
811
- const preimage = contract.params?.preimage;
812
- if (context.collaborative) {
813
- if (role === "receiver" && preimage) {
814
- paths.push({
815
- leaf: script.claim(),
816
- extraWitness: [base.hex.decode(preimage)]
817
- });
818
- }
819
- if (role === "sender") {
820
- paths.push({
821
- leaf: script.refundWithoutReceiver()
822
- });
823
- }
824
- } else {
825
- if (role === "receiver" && preimage) {
826
- const sequence = Number(contract.params.claimDelay);
827
- paths.push({
828
- leaf: script.unilateralClaim(),
829
- extraWitness: [base.hex.decode(preimage)],
830
- sequence
831
- });
832
- }
833
- if (role === "sender") {
834
- const sequence = Number(contract.params.refundNoReceiverDelay);
835
- paths.push({
836
- leaf: script.unilateralRefundWithoutReceiver(),
837
- sequence
838
- });
1016
+ bits(value, bits) {
1017
+ if (bits > 32)
1018
+ throw this.err("writeBits: cannot write more than 32 bits in single call");
1019
+ if (value >= 2 ** bits)
1020
+ throw this.err(`writeBits: value (${value}) >= 2**bits (${bits})`);
1021
+ while (bits) {
1022
+ const take = Math.min(bits, 8 - this.bitPos);
1023
+ this.bitBuf = this.bitBuf << take | value >> bits - take;
1024
+ this.bitPos += take;
1025
+ bits -= take;
1026
+ value &= 2 ** bits - 1;
1027
+ if (this.bitPos === 8) {
1028
+ this.bitPos = 0;
1029
+ this.buffers.push(new Uint8Array([this.bitBuf]));
1030
+ this.pos++;
839
1031
  }
840
1032
  }
841
- return paths;
842
- },
843
- getSpendablePaths(script, contract, context) {
844
- const role = resolveRole(contract, context);
845
- const paths = [];
846
- if (!role) {
847
- return paths;
848
- }
849
- const preimage = contract.params?.preimage;
850
- const refundLocktime = BigInt(contract.params.refundLocktime);
851
- if (context.collaborative) {
852
- if (role === "receiver" && preimage) {
853
- paths.push({
854
- leaf: script.claim(),
855
- extraWitness: [base.hex.decode(preimage)]
856
- });
857
- }
858
- if (role === "sender" && isCltvSatisfied(context, refundLocktime)) {
859
- paths.push({
860
- leaf: script.refundWithoutReceiver()
861
- });
862
- }
863
- return paths;
1033
+ }
1034
+ };
1035
+ function _wrap(inner) {
1036
+ return {
1037
+ // NOTE: we cannot export validate here, since it is likely mistake.
1038
+ encodeStream: inner.encodeStream,
1039
+ decodeStream: inner.decodeStream,
1040
+ size: inner.size,
1041
+ encode: (value) => {
1042
+ const w = new _Writer();
1043
+ inner.encodeStream(w, value);
1044
+ return w.finish();
1045
+ },
1046
+ decode: (data, opts = {}) => {
1047
+ const r = new _Reader(data, opts);
1048
+ const res = inner.decodeStream(r);
1049
+ r.finish();
1050
+ return res;
864
1051
  }
865
- if (role === "receiver" && preimage) {
866
- const sequence = Number(contract.params.claimDelay);
867
- if (isCsvSpendable(context, sequence)) {
868
- paths.push({
869
- leaf: script.unilateralClaim(),
870
- extraWitness: [base.hex.decode(preimage)],
871
- sequence
872
- });
1052
+ };
1053
+ }
1054
+ function validate(inner, fn) {
1055
+ if (!isCoder(inner))
1056
+ throw new Error(`validate: invalid inner value ${inner}`);
1057
+ if (typeof fn !== "function")
1058
+ throw new Error("validate: fn should be function");
1059
+ return _wrap({
1060
+ size: inner.size,
1061
+ encodeStream: (w, value) => {
1062
+ let res;
1063
+ try {
1064
+ res = fn(value);
1065
+ } catch (e) {
1066
+ throw w.err(e);
873
1067
  }
874
- }
875
- if (role === "sender") {
876
- const sequence = Number(contract.params.refundNoReceiverDelay);
877
- if (isCsvSpendable(context, sequence)) {
878
- paths.push({
879
- leaf: script.unilateralRefundWithoutReceiver(),
880
- sequence
881
- });
1068
+ inner.encodeStream(w, res);
1069
+ },
1070
+ decodeStream: (r) => {
1071
+ const res = inner.decodeStream(r);
1072
+ try {
1073
+ return fn(res);
1074
+ } catch (e) {
1075
+ throw r.err(e);
882
1076
  }
883
1077
  }
884
- return paths;
885
- }
1078
+ });
1079
+ }
1080
+ var wrap = (inner) => {
1081
+ const res = _wrap(inner);
1082
+ return inner.validate ? validate(res, inner.validate) : res;
886
1083
  };
887
- var BoardingContractHandler = {
888
- type: "boarding",
889
- createScript(params) {
890
- return DefaultContractHandler.createScript(params);
891
- },
892
- serializeParams(params) {
893
- return DefaultContractHandler.serializeParams(params);
894
- },
895
- deserializeParams(params) {
896
- return DefaultContractHandler.deserializeParams(params);
897
- },
898
- selectPath(script, contract, context) {
899
- return DefaultContractHandler.selectPath(script, contract, context);
900
- },
901
- getAllSpendingPaths(script, contract, context) {
902
- return DefaultContractHandler.getAllSpendingPaths(script, contract, context);
903
- },
904
- getSpendablePaths(script, contract, context) {
905
- return DefaultContractHandler.getSpendablePaths(script, contract, context);
906
- },
907
- /**
908
- * Probe the on-chain UTXO set for a boarding output at this HD index.
909
- *
910
- * Boarding's source of truth is the **current** on-chain coin set
911
- * (`OnchainProvider.getCoins`), not the Ark indexer: a boarded (spent)
912
- * boarding output becomes an L2 VTXO at the receive index, so the
913
- * indexer probe already keeps the gap window open for it; only an
914
- * *unspent* boarding output needs the on-chain probe (see plan §2).
915
- *
916
- * No-ops (returns `[]`) when boarding discovery is not plumbed — i.e.
917
- * `deps.boardingTimelock` or `deps.onchainNetwork` is absent — so the
918
- * scanner unit harness (which sets neither) is unaffected.
919
- *
920
- * Always emits `type: "boarding"`, even in the degenerate equal-delay case
921
- * where the boarding script is byte-identical to a `default` script at this
922
- * index (`boardingExitDelay === unilateralExitDelay`). The collision is no
923
- * longer pre-judged in discovery; it is tolerated FIRST-WINS at the
924
- * persistence layer ({@link ContractManager.upsertContract}). `deps.csvTimelocks`
925
- * is intentionally not read here.
926
- */
927
- async discoverAt(index, descriptor, deps) {
928
- if (!deps.boardingTimelock || !deps.onchainNetwork) return [];
929
- const pubKey = deriveDescriptorLeafPubKey(descriptor);
930
- const script = new exports.DefaultVtxo.Script({
931
- pubKey,
932
- serverPubKey: deps.serverPubKey,
933
- csvTimelock: deps.boardingTimelock
934
- });
935
- const onchainAddress = script.onchainAddress(deps.onchainNetwork);
936
- const coins = await deps.onchainProvider.getCoins(onchainAddress);
937
- if (coins.length === 0) return [];
938
- const scriptHex = base.hex.encode(script.pkScript);
939
- const boardingSeq = chunkDSTYXT2D_cjs.timelockToSequence(deps.boardingTimelock);
940
- return [
941
- {
942
- type: "boarding",
943
- params: {
944
- pubKey: base.hex.encode(pubKey),
945
- serverPubKey: base.hex.encode(deps.serverPubKey),
946
- csvTimelock: boardingSeq.toString()
947
- },
948
- script: scriptHex,
949
- // The persisted row's `address` is the *Ark* address (not the
950
- // on-chain P2TR), matching the row registered at init so the
951
- // ContractWatcher keeps monitoring the same L2 script and the
952
- // VTXO repository bucket lines up (plan §6-I.4). The P2TR is
953
- // recomputed from params only for the `getCoins` probe.
954
- address: script.address(deps.network.hrp, deps.serverPubKey).encode(),
955
- // Tag rotated rows (index > 0) so boot resolution can find the
956
- // newest boarding address and descriptor-aware signing can
957
- // recover the per-index key (plan §6-II/§6-III.3). The index-0
958
- // baseline stays untagged, matching `default`/`delegate`.
959
- ...rotatedReceiveMetadata(index, descriptor)
1084
+ var isBaseCoder = (elm) => isPlainObject(elm) && typeof elm.decode === "function" && typeof elm.encode === "function";
1085
+ function isCoder(elm) {
1086
+ return isPlainObject(elm) && isBaseCoder(elm) && typeof elm.encodeStream === "function" && typeof elm.decodeStream === "function" && (elm.size === void 0 || isNum(elm.size));
1087
+ }
1088
+ var view = (len, opts) => wrap({
1089
+ size: len,
1090
+ encodeStream: (w, value) => w.writeView(len, (view2) => opts.write(view2, value)),
1091
+ decodeStream: (r) => r.readView(len, opts.read),
1092
+ validate: (value) => {
1093
+ if (typeof value !== "number")
1094
+ throw new Error(`viewCoder: expected number, got ${typeof value}`);
1095
+ if (opts.validate)
1096
+ opts.validate(value);
1097
+ return value;
1098
+ }
1099
+ });
1100
+ var intView = (len, signed, opts) => {
1101
+ const bits = len * 8;
1102
+ const maxVal = 2 ** bits;
1103
+ const validateUnsigned = (value) => {
1104
+ if (!isNum(value))
1105
+ throw new Error(`uintView: value is not safe integer: ${value}`);
1106
+ if (0 > value || value >= maxVal) {
1107
+ throw new Error(`uintView: value out of bounds. Expected 0 <= ${value} < ${maxVal}`);
1108
+ }
1109
+ };
1110
+ return view(len, {
1111
+ write: opts.write,
1112
+ read: opts.read,
1113
+ validate: validateUnsigned
1114
+ });
1115
+ };
1116
+ var U32LE = /* @__PURE__ */ intView(4, false, {
1117
+ read: (view2, pos) => view2.getUint32(pos, true),
1118
+ write: (view2, value) => view2.setUint32(0, value, true)
1119
+ });
1120
+ var U16LE = /* @__PURE__ */ intView(2, false, {
1121
+ read: (view2, pos) => view2.getUint16(pos, true),
1122
+ write: (view2, value) => view2.setUint16(0, value, true)
1123
+ });
1124
+ var U8 = /* @__PURE__ */ intView(1, false, {
1125
+ read: (view2, pos) => view2.getUint8(pos),
1126
+ write: (view2, value) => view2.setUint8(0, value)
1127
+ });
1128
+ var ARKADE_OP = {
1129
+ // Merkle Branch Verification (0xb3 — repurposed NOP4 slot)
1130
+ MERKLEBRANCHVERIFY: 179,
1131
+ // Digest (0xc3)
1132
+ DIGEST: 195,
1133
+ // SHA256 Streaming (0xc4-0xc6)
1134
+ SHA256INITIALIZE: 196,
1135
+ SHA256UPDATE: 197,
1136
+ SHA256FINALIZE: 198,
1137
+ // Input Introspection (0xc7-0xcb)
1138
+ INSPECTINPUTOUTPOINT: 199,
1139
+ INSPECTINPUTARKADESCRIPTHASH: 200,
1140
+ INSPECTINPUTVALUE: 201,
1141
+ INSPECTINPUTSCRIPTPUBKEY: 202,
1142
+ INSPECTINPUTSEQUENCE: 203,
1143
+ // Signatures (0xcc-0xcd)
1144
+ CHECKSIGFROMSTACK: 204,
1145
+ PUSHCURRENTINPUTINDEX: 205,
1146
+ // Input Arkade Witness Introspection (0xce)
1147
+ INSPECTINPUTARKADEWITNESSHASH: 206,
1148
+ // Output Introspection (0xcf, 0xd1)
1149
+ INSPECTOUTPUTVALUE: 207,
1150
+ INSPECTOUTPUTSCRIPTPUBKEY: 209,
1151
+ // Transaction Introspection (0xd2-0xd6)
1152
+ INSPECTVERSION: 210,
1153
+ INSPECTLOCKTIME: 211,
1154
+ INSPECTNUMINPUTS: 212,
1155
+ INSPECTNUMOUTPUTS: 213,
1156
+ TXWEIGHT: 214,
1157
+ // Byte/Number Conversion & Arithmetic (0xd7-0xda)
1158
+ NUM2BIN: 215,
1159
+ BIN2NUM: 216,
1160
+ REVERSEBYTES: 217,
1161
+ MODEXP: 218,
1162
+ // 0xdb-0xdf are unassigned (OP_UNKNOWN219-223)
1163
+ // EC Operations (0xe0-0xe2)
1164
+ ECADD: 224,
1165
+ ECMUL: 225,
1166
+ ECPAIRING: 226,
1167
+ // EC Operations (0xe3-0xe4)
1168
+ ECMULSCALARVERIFY: 227,
1169
+ TWEAKVERIFY: 228,
1170
+ // Asset Groups (0xe5-0xf2)
1171
+ INSPECTNUMASSETGROUPS: 229,
1172
+ INSPECTASSETGROUPASSETID: 230,
1173
+ INSPECTASSETGROUPCTRL: 231,
1174
+ FINDASSETGROUPBYASSETID: 232,
1175
+ INSPECTASSETGROUPMETADATAHASH: 233,
1176
+ INSPECTASSETGROUPNUM: 234,
1177
+ INSPECTASSETGROUP: 235,
1178
+ INSPECTASSETGROUPSUM: 236,
1179
+ INSPECTOUTASSETCOUNT: 237,
1180
+ INSPECTOUTASSETAT: 238,
1181
+ INSPECTOUTASSETLOOKUP: 239,
1182
+ INSPECTINASSETCOUNT: 240,
1183
+ INSPECTINASSETAT: 241,
1184
+ INSPECTINASSETLOOKUP: 242,
1185
+ // Transaction ID (0xf3)
1186
+ TXID: 243,
1187
+ // Packet Introspection (0xf4-0xf5) — added in emulator v0.0.1
1188
+ INSPECTPACKET: 244,
1189
+ INSPECTINPUTPACKET: 245,
1190
+ // Signature Hash (0xf6)
1191
+ SIGHASH: 246
1192
+ };
1193
+ var ARKADE_OPCODES = Object.values(ARKADE_OP);
1194
+ var ARKADE_OPCODE_NAMES = Object.fromEntries(
1195
+ Object.entries(ARKADE_OP).map(([name, value]) => [value, name])
1196
+ );
1197
+ var ARKADE_OPCODE_VALUES = Object.fromEntries(
1198
+ Object.entries(ARKADE_OPCODE_NAMES).map(([value, name]) => [name, Number(value)])
1199
+ );
1200
+ function buildBitcoinOpcodeNames() {
1201
+ const names = {};
1202
+ for (const [key, value] of Object.entries(btcSigner.OP)) {
1203
+ if (typeof value === "number") {
1204
+ const name = key.startsWith("OP_") ? key : `OP_${key}`;
1205
+ names[value] = name;
1206
+ }
1207
+ }
1208
+ names[0] = "OP_0";
1209
+ return names;
1210
+ }
1211
+ function buildBitcoinOpcodeValues() {
1212
+ const values = {};
1213
+ for (const [key, value] of Object.entries(btcSigner.OP)) {
1214
+ if (typeof value === "number") {
1215
+ const name = key.startsWith("OP_") ? key : `OP_${key}`;
1216
+ values[name] = value;
1217
+ values[key] = value;
1218
+ }
1219
+ }
1220
+ return values;
1221
+ }
1222
+ var BITCOIN_OPCODE_NAMES = buildBitcoinOpcodeNames();
1223
+ var BITCOIN_OPCODE_VALUES = buildBitcoinOpcodeValues();
1224
+ var OPCODE_NAMES = {
1225
+ ...BITCOIN_OPCODE_NAMES,
1226
+ // Add Arkade opcodes with OP_ prefix
1227
+ ...Object.fromEntries(
1228
+ Object.entries(ARKADE_OPCODE_NAMES).map(([value, name]) => [Number(value), `OP_${name}`])
1229
+ )
1230
+ };
1231
+ var OPCODE_VALUES = {
1232
+ ...BITCOIN_OPCODE_VALUES,
1233
+ // Add Arkade opcodes with and without OP_ prefix
1234
+ ...ARKADE_OPCODE_VALUES,
1235
+ ...Object.fromEntries(
1236
+ Object.entries(ARKADE_OPCODE_VALUES).map(([name, value]) => [`OP_${name}`, value])
1237
+ )
1238
+ };
1239
+ function getOpcodeName(value) {
1240
+ if (value >= 1 && value <= 75) {
1241
+ return `OP_DATA_${value}`;
1242
+ }
1243
+ return OPCODE_NAMES[value];
1244
+ }
1245
+ function getOpcodeValue(name) {
1246
+ const dataMatch = name.match(/^OP_DATA_(\d+)$/);
1247
+ if (dataMatch) {
1248
+ const n = parseInt(dataMatch[1], 10);
1249
+ if (n >= 1 && n <= 75) {
1250
+ return n;
1251
+ }
1252
+ return void 0;
1253
+ }
1254
+ return OPCODE_VALUES[name];
1255
+ }
1256
+
1257
+ // src/arkade/bignum.ts
1258
+ var bignum_exports = {};
1259
+ chunk5BLDMQED_cjs.__export(bignum_exports, {
1260
+ BIGNUM_MAX_BYTES: () => BIGNUM_MAX_BYTES,
1261
+ decode: () => decode,
1262
+ encode: () => encode
1263
+ });
1264
+ var BIGNUM_MAX_BYTES = 520;
1265
+ var codec = btcSigner.ScriptNum(
1266
+ BIGNUM_MAX_BYTES,
1267
+ /* forceMinimal */
1268
+ true
1269
+ );
1270
+ function encode(value) {
1271
+ const result = codec.encode(value);
1272
+ if (result.length > BIGNUM_MAX_BYTES) {
1273
+ throw new Error(`BigNum value exceeds 520 bytes (encoded to ${result.length} bytes)`);
1274
+ }
1275
+ return result;
1276
+ }
1277
+ function decode(value) {
1278
+ return codec.decode(value);
1279
+ }
1280
+
1281
+ // src/arkade/script.ts
1282
+ var ARKADE_OPS = { ...btcSigner.OP, ...ARKADE_OP };
1283
+ var ArkadeOPNames = {};
1284
+ for (const [k, v] of Object.entries(ARKADE_OPS)) {
1285
+ if (typeof v === "number") ArkadeOPNames[v] = k;
1286
+ }
1287
+ var ArkadeScript = wrap({
1288
+ encodeStream: (w, value) => {
1289
+ for (let o of value) {
1290
+ if (typeof o === "string") {
1291
+ const v = ARKADE_OPS[o];
1292
+ if (v === void 0) throw new Error(`Unknown opcode=${o}`);
1293
+ w.byte(v);
1294
+ continue;
1295
+ } else if (typeof o === "number" || typeof o === "bigint") {
1296
+ const big = typeof o === "number" ? BigInt(o) : o;
1297
+ o = encode(big);
1298
+ }
1299
+ if (!(o instanceof Uint8Array)) throw new Error(`Wrong Script OP=${o} (${typeof o})`);
1300
+ const len = o.length;
1301
+ if (len === 1 && o[0] >= 1 && o[0] <= 16) {
1302
+ w.byte(btcSigner.OP.OP_1 - 1 + o[0]);
1303
+ continue;
1304
+ } else if (len === 1 && o[0] === 129) {
1305
+ w.byte(btcSigner.OP["1NEGATE"]);
1306
+ continue;
1307
+ } else if (len < btcSigner.OP.PUSHDATA1) w.byte(len);
1308
+ else if (len <= 255) {
1309
+ w.byte(btcSigner.OP.PUSHDATA1);
1310
+ w.byte(len);
1311
+ } else if (len <= 65535) {
1312
+ w.byte(btcSigner.OP.PUSHDATA2);
1313
+ w.bytes(U16LE.encode(len));
1314
+ } else {
1315
+ w.byte(btcSigner.OP.PUSHDATA4);
1316
+ w.bytes(U32LE.encode(len));
1317
+ }
1318
+ w.bytes(o);
1319
+ }
1320
+ },
1321
+ decodeStream: (r) => {
1322
+ const out = [];
1323
+ while (!r.isEnd()) {
1324
+ const cur = r.byte();
1325
+ if (btcSigner.OP.OP_0 < cur && cur <= btcSigner.OP.PUSHDATA4) {
1326
+ let len;
1327
+ if (cur < btcSigner.OP.PUSHDATA1) len = cur;
1328
+ else if (cur === btcSigner.OP.PUSHDATA1) len = U8.decodeStream(r);
1329
+ else if (cur === btcSigner.OP.PUSHDATA2) len = U16LE.decodeStream(r);
1330
+ else if (cur === btcSigner.OP.PUSHDATA4) len = U32LE.decodeStream(r);
1331
+ else throw new Error("Should be not possible");
1332
+ out.push(r.bytes(len));
1333
+ } else if (cur === 0) {
1334
+ out.push(0);
1335
+ } else if (btcSigner.OP.OP_1 <= cur && cur <= btcSigner.OP.OP_16) {
1336
+ out.push(cur - (btcSigner.OP.OP_1 - 1));
1337
+ } else if (cur === btcSigner.OP["1NEGATE"]) {
1338
+ out.push(-1);
1339
+ } else {
1340
+ const op = ArkadeOPNames[cur];
1341
+ if (op === void 0) throw new Error(`Unknown opcode=${cur.toString(16)}`);
1342
+ out.push(op);
1343
+ }
1344
+ }
1345
+ return out;
1346
+ }
1347
+ });
1348
+ function toASM(script) {
1349
+ const parts = [];
1350
+ for (const op of script) {
1351
+ if (typeof op === "string") {
1352
+ const name = op.startsWith("OP_") ? op : `OP_${op}`;
1353
+ parts.push(name);
1354
+ } else if (typeof op === "number" || typeof op === "bigint") {
1355
+ if (op === 0 || op === 0n) {
1356
+ parts.push("OP_0");
1357
+ } else if (op >= 1 && op <= 16) {
1358
+ parts.push(`OP_${op}`);
1359
+ } else if (op === -1 || op === -1n) {
1360
+ parts.push("OP_1NEGATE");
1361
+ } else {
1362
+ parts.push(base.hex.encode(encode(BigInt(op))));
1363
+ }
1364
+ } else {
1365
+ parts.push(base.hex.encode(op));
1366
+ }
1367
+ }
1368
+ return parts.join(" ");
1369
+ }
1370
+ function fromASM(asm) {
1371
+ const tokens = asm.trim().split(/\s+/).filter(Boolean);
1372
+ const out = [];
1373
+ for (const token of tokens) {
1374
+ if (token === "OP_0" || token === "OP_FALSE") {
1375
+ out.push(0);
1376
+ continue;
1377
+ }
1378
+ if (token === "OP_TRUE") {
1379
+ out.push(1);
1380
+ continue;
1381
+ }
1382
+ const numMatch = token.match(/^OP_(\d+)$/);
1383
+ if (numMatch) {
1384
+ const n = parseInt(numMatch[1], 10);
1385
+ if (n >= 1 && n <= 16) {
1386
+ out.push(n);
1387
+ continue;
1388
+ }
1389
+ }
1390
+ let key;
1391
+ if (token.startsWith("OP_")) {
1392
+ key = token.slice(3);
1393
+ } else {
1394
+ key = token;
1395
+ }
1396
+ if (key in ARKADE_OPS) {
1397
+ out.push(key);
1398
+ continue;
1399
+ }
1400
+ if (/^[0-9a-fA-F]+$/.test(token) && token.length % 2 === 0) {
1401
+ try {
1402
+ out.push(base.hex.decode(token));
1403
+ continue;
1404
+ } catch {
1405
+ }
1406
+ }
1407
+ throw new Error(`Invalid ASM token: ${token}`);
1408
+ }
1409
+ return out;
1410
+ }
1411
+ function asmToBytes(asm) {
1412
+ return ArkadeScript.encode(fromASM(asm));
1413
+ }
1414
+ function bytesToASM(script) {
1415
+ return toASM(ArkadeScript.decode(script));
1416
+ }
1417
+ var TAG_SCRIPT = "ArkScriptHash";
1418
+ var TAG_WITNESS = "ArkWitnessHash";
1419
+ function arkadeScriptHash(script) {
1420
+ return secp256k1_js.schnorr.utils.taggedHash(TAG_SCRIPT, script);
1421
+ }
1422
+ function arkadeWitnessHash(witness) {
1423
+ if (witness.length === 0) {
1424
+ return new Uint8Array(32);
1425
+ }
1426
+ return secp256k1_js.schnorr.utils.taggedHash(TAG_WITNESS, witness);
1427
+ }
1428
+ function computeArkadeScriptPublicKey(pubKey, script) {
1429
+ const hash = arkadeScriptHash(script);
1430
+ const xOnly = pubKey.length === 33 ? pubKey.subarray(1) : pubKey;
1431
+ const point = secp256k1_js.schnorr.utils.lift_x(utils_js.bytesToNumberBE(xOnly));
1432
+ const scalar = utils_js.bytesToNumberBE(hash) % secp256k1_js.secp256k1.Point.CURVE().n;
1433
+ const tweak = secp256k1_js.secp256k1.Point.BASE.multiply(scalar);
1434
+ return secp256k1_js.schnorr.utils.pointToBytes(point.add(tweak));
1435
+ }
1436
+
1437
+ // src/script/vhtlc.ts
1438
+ exports.VHTLC = void 0;
1439
+ ((VHTLC2) => {
1440
+ class BaseScript extends chunkFNCQJLR2_cjs.VtxoScript {
1441
+ options;
1442
+ claimScript;
1443
+ refundScript;
1444
+ refundWithoutReceiverScript;
1445
+ unilateralClaimScript;
1446
+ unilateralRefundScript;
1447
+ unilateralRefundWithoutReceiverScript;
1448
+ nonInteractiveClaimScript;
1449
+ nonInteractiveClaimArkadeScript;
1450
+ nonInteractiveRefundScript;
1451
+ nonInteractiveRefundArkadeScript;
1452
+ constructor(options, preimageCondition) {
1453
+ validateOptions(options);
1454
+ const {
1455
+ sender,
1456
+ receiver,
1457
+ server,
1458
+ preimageHash,
1459
+ refundLocktime,
1460
+ unilateralClaimDelay,
1461
+ unilateralRefundDelay,
1462
+ unilateralRefundWithoutReceiverDelay
1463
+ } = options;
1464
+ const conditionScript = preimageCondition(preimageHash);
1465
+ const claimScript = chunkFNCQJLR2_cjs.ConditionMultisigTapscript.encode({
1466
+ conditionScript,
1467
+ pubkeys: [receiver, server]
1468
+ }).script;
1469
+ const refundScript = chunkFNCQJLR2_cjs.MultisigTapscript.encode({
1470
+ pubkeys: [sender, receiver, server]
1471
+ }).script;
1472
+ const refundWithoutReceiverScript = chunkFNCQJLR2_cjs.CLTVMultisigTapscript.encode({
1473
+ absoluteTimelock: refundLocktime,
1474
+ pubkeys: [sender, server]
1475
+ }).script;
1476
+ const unilateralClaimScript = chunkFNCQJLR2_cjs.ConditionCSVMultisigTapscript.encode({
1477
+ conditionScript,
1478
+ timelock: unilateralClaimDelay,
1479
+ pubkeys: [receiver]
1480
+ }).script;
1481
+ const unilateralRefundScript = chunkFNCQJLR2_cjs.CSVMultisigTapscript.encode({
1482
+ timelock: unilateralRefundDelay,
1483
+ pubkeys: [sender, receiver]
1484
+ }).script;
1485
+ const unilateralRefundWithoutReceiverScript = chunkFNCQJLR2_cjs.CSVMultisigTapscript.encode({
1486
+ timelock: unilateralRefundWithoutReceiverDelay,
1487
+ pubkeys: [sender]
1488
+ }).script;
1489
+ const scripts = [
1490
+ claimScript,
1491
+ refundScript,
1492
+ refundWithoutReceiverScript,
1493
+ unilateralClaimScript,
1494
+ unilateralRefundScript,
1495
+ unilateralRefundWithoutReceiverScript
1496
+ ];
1497
+ let arkadeScriptNic;
1498
+ let nonInteractiveClaimScript;
1499
+ if (options.nonInteractiveClaim) {
1500
+ arkadeScriptNic = enforcePayTo(options.nonInteractiveClaim.receiverPkScript);
1501
+ nonInteractiveClaimScript = chunkFNCQJLR2_cjs.ConditionMultisigTapscript.encode({
1502
+ conditionScript,
1503
+ pubkeys: [
1504
+ server,
1505
+ computeArkadeScriptPublicKey(
1506
+ options.nonInteractiveClaim.emulatorPubkey,
1507
+ arkadeScriptNic
1508
+ )
1509
+ ]
1510
+ }).script;
1511
+ scripts.push(nonInteractiveClaimScript);
1512
+ }
1513
+ let arkadeScriptNir;
1514
+ let nonInteractiveRefundScript;
1515
+ if (options.nonInteractiveRefund) {
1516
+ arkadeScriptNir = enforcePayTo(options.nonInteractiveRefund.senderPkScript);
1517
+ nonInteractiveRefundScript = chunkFNCQJLR2_cjs.MultisigTapscript.encode({
1518
+ pubkeys: [
1519
+ server,
1520
+ receiver,
1521
+ computeArkadeScriptPublicKey(
1522
+ options.nonInteractiveRefund.emulatorPubkey,
1523
+ arkadeScriptNir
1524
+ )
1525
+ ]
1526
+ }).script;
1527
+ scripts.push(nonInteractiveRefundScript);
1528
+ }
1529
+ super(scripts);
1530
+ this.options = options;
1531
+ this.claimScript = base.hex.encode(claimScript);
1532
+ this.refundScript = base.hex.encode(refundScript);
1533
+ this.refundWithoutReceiverScript = base.hex.encode(refundWithoutReceiverScript);
1534
+ this.unilateralClaimScript = base.hex.encode(unilateralClaimScript);
1535
+ this.unilateralRefundScript = base.hex.encode(unilateralRefundScript);
1536
+ this.unilateralRefundWithoutReceiverScript = base.hex.encode(
1537
+ unilateralRefundWithoutReceiverScript
1538
+ );
1539
+ if (nonInteractiveClaimScript) {
1540
+ this.nonInteractiveClaimScript = base.hex.encode(nonInteractiveClaimScript);
1541
+ this.nonInteractiveClaimArkadeScript = arkadeScriptNic;
960
1542
  }
961
- ];
962
- }
963
- };
964
- function equalBytes(a, b) {
965
- if (a.length !== b.length)
966
- return false;
967
- for (let i = 0; i < a.length; i++)
968
- if (a[i] !== b[i])
969
- return false;
970
- return true;
971
- }
972
- function isBytes(a) {
973
- return a instanceof Uint8Array || ArrayBuffer.isView(a) && a.constructor.name === "Uint8Array";
974
- }
975
- var createView = (arr) => new DataView(arr.buffer, arr.byteOffset, arr.byteLength);
976
- function isPlainObject(obj) {
977
- return Object.prototype.toString.call(obj) === "[object Object]";
978
- }
979
- function isNum(num) {
980
- return Number.isSafeInteger(num);
981
- }
982
- var Bitset = {
983
- BITS: 32,
984
- FULL_MASK: -1 >>> 0,
985
- // 1<<32 will overflow
986
- len: (len) => Math.ceil(len / 32),
987
- create: (len) => new Uint32Array(Bitset.len(len)),
988
- clean: (bs) => bs.fill(0),
989
- debug: (bs) => Array.from(bs).map((i) => (i >>> 0).toString(2).padStart(32, "0")),
990
- checkLen: (bs, len) => {
991
- if (Bitset.len(len) === bs.length)
992
- return;
993
- throw new Error(`wrong length=${bs.length}. Expected: ${Bitset.len(len)}`);
994
- },
995
- chunkLen: (bsLen, pos, len) => {
996
- if (pos < 0)
997
- throw new Error(`wrong pos=${pos}`);
998
- if (pos + len > bsLen)
999
- throw new Error(`wrong range=${pos}/${len} of ${bsLen}`);
1000
- },
1001
- set: (bs, chunk, value, allowRewrite = true) => {
1002
- if (!allowRewrite && (bs[chunk] & value) !== 0)
1003
- return false;
1004
- bs[chunk] |= value;
1005
- return true;
1006
- },
1007
- pos: (pos, i) => ({
1008
- chunk: Math.floor((pos + i) / 32),
1009
- mask: 1 << 32 - (pos + i) % 32 - 1
1010
- }),
1011
- indices: (bs, len, invert = false) => {
1012
- Bitset.checkLen(bs, len);
1013
- const { FULL_MASK, BITS } = Bitset;
1014
- const left = BITS - len % BITS;
1015
- const lastMask = left ? FULL_MASK >>> left << left : FULL_MASK;
1016
- const res = [];
1017
- for (let i = 0; i < bs.length; i++) {
1018
- let c = bs[i];
1019
- if (invert)
1020
- c = ~c;
1021
- if (i === bs.length - 1)
1022
- c &= lastMask;
1023
- if (c === 0)
1024
- continue;
1025
- for (let j = 0; j < BITS; j++) {
1026
- const m = 1 << BITS - j - 1;
1027
- if (c & m)
1028
- res.push(i * BITS + j);
1543
+ if (nonInteractiveRefundScript) {
1544
+ this.nonInteractiveRefundScript = base.hex.encode(nonInteractiveRefundScript);
1545
+ this.nonInteractiveRefundArkadeScript = arkadeScriptNir;
1029
1546
  }
1030
1547
  }
1031
- return res;
1032
- },
1033
- range: (arr) => {
1034
- const res = [];
1035
- let cur;
1036
- for (const i of arr) {
1037
- if (cur === void 0 || i !== cur.pos + cur.length)
1038
- res.push(cur = { pos: i, length: 1 });
1039
- else
1040
- cur.length += 1;
1041
- }
1042
- return res;
1043
- },
1044
- rangeDebug: (bs, len, invert = false) => `[${Bitset.range(Bitset.indices(bs, len, invert)).map((i) => `(${i.pos}/${i.length})`).join(", ")}]`,
1045
- setRange: (bs, bsLen, pos, len, allowRewrite = true) => {
1046
- Bitset.chunkLen(bsLen, pos, len);
1047
- const { FULL_MASK, BITS } = Bitset;
1048
- const first = pos % BITS ? Math.floor(pos / BITS) : void 0;
1049
- const lastPos = pos + len;
1050
- const last = lastPos % BITS ? Math.floor(lastPos / BITS) : void 0;
1051
- if (first !== void 0 && first === last)
1052
- return Bitset.set(bs, first, FULL_MASK >>> BITS - len << BITS - len - pos, allowRewrite);
1053
- if (first !== void 0) {
1054
- if (!Bitset.set(bs, first, FULL_MASK >>> pos % BITS, allowRewrite))
1055
- return false;
1548
+ /** Return the collaborative claim tapleaf script. */
1549
+ claim() {
1550
+ return this.findLeaf(this.claimScript);
1056
1551
  }
1057
- const start = first !== void 0 ? first + 1 : pos / BITS;
1058
- const end = last !== void 0 ? last : lastPos / BITS;
1059
- for (let i = start; i < end; i++)
1060
- if (!Bitset.set(bs, i, FULL_MASK, allowRewrite))
1061
- return false;
1062
- if (last !== void 0 && first !== last) {
1063
- if (!Bitset.set(bs, last, FULL_MASK << BITS - lastPos % BITS, allowRewrite))
1064
- return false;
1552
+ /** Return the collaborative refund tapleaf script. */
1553
+ refund() {
1554
+ return this.findLeaf(this.refundScript);
1065
1555
  }
1066
- return true;
1067
- }
1068
- };
1069
- var Path = {
1070
- /**
1071
- * Internal method for handling stack of paths (debug, errors, dynamic fields via path)
1072
- * This is looks ugly (callback), but allows us to force stack cleaning by construction (.pop always after function).
1073
- * Also, this makes impossible:
1074
- * - pushing field when stack is empty
1075
- * - pushing field inside of field (real bug)
1076
- * NOTE: we don't want to do '.pop' on error!
1077
- */
1078
- pushObj: (stack, obj, objFn) => {
1079
- const last = { obj };
1080
- stack.push(last);
1081
- objFn((field, fieldFn) => {
1082
- last.field = field;
1083
- fieldFn();
1084
- last.field = void 0;
1085
- });
1086
- stack.pop();
1087
- },
1088
- path: (stack) => {
1089
- const res = [];
1090
- for (const i of stack)
1091
- if (i.field !== void 0)
1092
- res.push(i.field);
1093
- return res.join("/");
1094
- },
1095
- err: (name, stack, msg) => {
1096
- const err = new Error(`${name}(${Path.path(stack)}): ${typeof msg === "string" ? msg : msg.message}`);
1097
- if (msg instanceof Error && msg.stack)
1098
- err.stack = msg.stack;
1099
- return err;
1100
- },
1101
- resolve: (stack, path) => {
1102
- const parts = path.split("/");
1103
- const objPath = stack.map((i2) => i2.obj);
1104
- let i = 0;
1105
- for (; i < parts.length; i++) {
1106
- if (parts[i] === "..")
1107
- objPath.pop();
1108
- else
1109
- break;
1556
+ /** Return the refund-without-receiver tapleaf script. */
1557
+ refundWithoutReceiver() {
1558
+ return this.findLeaf(this.refundWithoutReceiverScript);
1110
1559
  }
1111
- let cur = objPath.pop();
1112
- for (; i < parts.length; i++) {
1113
- if (!cur || cur[parts[i]] === void 0)
1114
- return void 0;
1115
- cur = cur[parts[i]];
1560
+ /** Return the unilateral claim tapleaf script. */
1561
+ unilateralClaim() {
1562
+ return this.findLeaf(this.unilateralClaimScript);
1116
1563
  }
1117
- return cur;
1118
- }
1119
- };
1120
- var _Reader = class __Reader {
1121
- pos = 0;
1122
- data;
1123
- opts;
1124
- stack;
1125
- parent;
1126
- parentOffset;
1127
- bitBuf = 0;
1128
- bitPos = 0;
1129
- bs;
1130
- // bitset
1131
- view;
1132
- constructor(data, opts = {}, stack = [], parent = void 0, parentOffset = 0) {
1133
- this.data = data;
1134
- this.opts = opts;
1135
- this.stack = stack;
1136
- this.parent = parent;
1137
- this.parentOffset = parentOffset;
1138
- this.view = createView(data);
1139
- }
1140
- /** Internal method for pointers. */
1141
- _enablePointers() {
1142
- if (this.parent)
1143
- return this.parent._enablePointers();
1144
- if (this.bs)
1145
- return;
1146
- this.bs = Bitset.create(this.data.length);
1147
- Bitset.setRange(this.bs, this.data.length, 0, this.pos, this.opts.allowMultipleReads);
1148
- }
1149
- markBytesBS(pos, len) {
1150
- if (this.parent)
1151
- return this.parent.markBytesBS(this.parentOffset + pos, len);
1152
- if (!len)
1153
- return true;
1154
- if (!this.bs)
1155
- return true;
1156
- return Bitset.setRange(this.bs, this.data.length, pos, len, false);
1157
- }
1158
- markBytes(len) {
1159
- const pos = this.pos;
1160
- this.pos += len;
1161
- const res = this.markBytesBS(pos, len);
1162
- if (!this.opts.allowMultipleReads && !res)
1163
- throw this.err(`multiple read pos=${this.pos} len=${len}`);
1164
- return res;
1165
- }
1166
- pushObj(obj, objFn) {
1167
- return Path.pushObj(this.stack, obj, objFn);
1168
- }
1169
- readView(n, fn) {
1170
- if (!Number.isFinite(n))
1171
- throw this.err(`readView: wrong length=${n}`);
1172
- if (this.pos + n > this.data.length)
1173
- throw this.err("readView: Unexpected end of buffer");
1174
- const res = fn(this.view, this.pos);
1175
- this.markBytes(n);
1176
- return res;
1177
- }
1178
- // read bytes by absolute offset
1179
- absBytes(n) {
1180
- if (n > this.data.length)
1181
- throw new Error("Unexpected end of buffer");
1182
- return this.data.subarray(n);
1183
- }
1184
- finish() {
1185
- if (this.opts.allowUnreadBytes)
1186
- return;
1187
- if (this.bitPos) {
1188
- throw this.err(`${this.bitPos} bits left after unpack: ${base.hex.encode(this.data.slice(this.pos))}`);
1564
+ /** Return the unilateral refund tapleaf script. */
1565
+ unilateralRefund() {
1566
+ return this.findLeaf(this.unilateralRefundScript);
1189
1567
  }
1190
- if (this.bs && !this.parent) {
1191
- const notRead = Bitset.indices(this.bs, this.data.length, true);
1192
- if (notRead.length) {
1193
- const formatted = Bitset.range(notRead).map(({ pos, length }) => `(${pos}/${length})[${base.hex.encode(this.data.subarray(pos, pos + length))}]`).join(", ");
1194
- throw this.err(`unread byte ranges: ${formatted} (total=${this.data.length})`);
1195
- } else
1196
- return;
1568
+ /** Return the unilateral refund-without-receiver tapleaf script. */
1569
+ unilateralRefundWithoutReceiver() {
1570
+ return this.findLeaf(this.unilateralRefundWithoutReceiverScript);
1197
1571
  }
1198
- if (!this.isEnd()) {
1199
- throw this.err(`${this.leftBytes} bytes ${this.bitPos} bits left after unpack: ${base.hex.encode(this.data.slice(this.pos))}`);
1572
+ /** Return the non-interactive claim tapleaf script as well as the ArkadeScript. */
1573
+ nonInteractiveClaim() {
1574
+ if (!this.nonInteractiveClaimScript || !this.nonInteractiveClaimArkadeScript) {
1575
+ throw new Error("VHTLC has no non-interactive claim leaf");
1576
+ }
1577
+ return [
1578
+ this.findLeaf(this.nonInteractiveClaimScript),
1579
+ this.nonInteractiveClaimArkadeScript
1580
+ ];
1581
+ }
1582
+ /** Return the non-interactive refund tapleaf script as well as the ArkadeScript. */
1583
+ nonInteractiveRefund() {
1584
+ if (!this.nonInteractiveRefundScript || !this.nonInteractiveRefundArkadeScript) {
1585
+ throw new Error("VHTLC has no non-interactive refund leaf");
1586
+ }
1587
+ return [
1588
+ this.findLeaf(this.nonInteractiveRefundScript),
1589
+ this.nonInteractiveRefundArkadeScript
1590
+ ];
1200
1591
  }
1201
1592
  }
1202
- // User methods
1203
- err(msg) {
1204
- return Path.err("Reader", this.stack, msg);
1205
- }
1206
- offsetReader(n) {
1207
- if (n > this.data.length)
1208
- throw this.err("offsetReader: Unexpected end of buffer");
1209
- return new __Reader(this.absBytes(n), this.opts, this.stack, this, n);
1210
- }
1211
- bytes(n, peek = false) {
1212
- if (this.bitPos)
1213
- throw this.err("readBytes: bitPos not empty");
1214
- if (!Number.isFinite(n))
1215
- throw this.err(`readBytes: wrong length=${n}`);
1216
- if (this.pos + n > this.data.length)
1217
- throw this.err("readBytes: Unexpected end of buffer");
1218
- const slice = this.data.subarray(this.pos, this.pos + n);
1219
- if (!peek)
1220
- this.markBytes(n);
1221
- return slice;
1222
- }
1223
- byte(peek = false) {
1224
- if (this.bitPos)
1225
- throw this.err("readByte: bitPos not empty");
1226
- if (this.pos + 1 > this.data.length)
1227
- throw this.err("readBytes: Unexpected end of buffer");
1228
- const data = this.data[this.pos];
1229
- if (!peek)
1230
- this.markBytes(1);
1231
- return data;
1232
- }
1233
- get leftBytes() {
1234
- return this.data.length - this.pos;
1235
- }
1236
- get totalBytes() {
1237
- return this.data.length;
1593
+ class Script3 extends BaseScript {
1594
+ constructor(options) {
1595
+ super(options, preimageConditionScript);
1596
+ }
1238
1597
  }
1239
- isEnd() {
1240
- return this.pos >= this.data.length && !this.bitPos;
1598
+ VHTLC2.Script = Script3;
1599
+ class ScriptV2 extends BaseScript {
1600
+ constructor(options) {
1601
+ super(options, preimageConditionScriptV2);
1602
+ }
1241
1603
  }
1242
- // bits are read in BE mode (left to right): (0b1000_0000).readBits(1) == 1
1243
- bits(bits) {
1244
- if (bits > 32)
1245
- throw this.err("BitReader: cannot read more than 32 bits in single call");
1246
- let out = 0;
1247
- while (bits) {
1248
- if (!this.bitPos) {
1249
- this.bitBuf = this.byte();
1250
- this.bitPos = 8;
1604
+ VHTLC2.ScriptV2 = ScriptV2;
1605
+ function validateOptions(options) {
1606
+ const {
1607
+ sender,
1608
+ receiver,
1609
+ server,
1610
+ preimageHash,
1611
+ refundLocktime,
1612
+ unilateralClaimDelay,
1613
+ unilateralRefundDelay,
1614
+ unilateralRefundWithoutReceiverDelay
1615
+ } = options;
1616
+ if (!preimageHash || preimageHash.length !== 20) {
1617
+ throw new Error("preimage hash must be 20 bytes");
1618
+ }
1619
+ if (options.nonInteractiveClaim) {
1620
+ const { emulatorPubkey, receiverPkScript } = options.nonInteractiveClaim;
1621
+ if (!emulatorPubkey || emulatorPubkey.length !== 32 && emulatorPubkey.length !== 33) {
1622
+ throw new Error("Invalid public key length (emulator)");
1623
+ }
1624
+ if (!receiverPkScript || !isP2trPkScript(receiverPkScript)) {
1625
+ throw new Error("Invalid P2TR script");
1251
1626
  }
1252
- const take = Math.min(bits, this.bitPos);
1253
- this.bitPos -= take;
1254
- out = out << take | this.bitBuf >> this.bitPos & 2 ** take - 1;
1255
- this.bitBuf &= 2 ** this.bitPos - 1;
1256
- bits -= take;
1257
1627
  }
1258
- return out >>> 0;
1259
- }
1260
- find(needle, pos = this.pos) {
1261
- if (!isBytes(needle))
1262
- throw this.err(`find: needle is not bytes! ${needle}`);
1263
- if (this.bitPos)
1264
- throw this.err("findByte: bitPos not empty");
1265
- if (!needle.length)
1266
- throw this.err(`find: needle is empty`);
1267
- for (let idx = pos; (idx = this.data.indexOf(needle[0], idx)) !== -1; idx++) {
1268
- if (idx === -1)
1269
- return;
1270
- const leftBytes = this.data.length - idx;
1271
- if (leftBytes < needle.length)
1272
- return;
1273
- if (equalBytes(needle, this.data.subarray(idx, idx + needle.length)))
1274
- return idx;
1628
+ if (options.nonInteractiveRefund) {
1629
+ const { emulatorPubkey, senderPkScript } = options.nonInteractiveRefund;
1630
+ if (!emulatorPubkey || emulatorPubkey.length !== 32 && emulatorPubkey.length !== 33) {
1631
+ throw new Error("Invalid public key length (emulator)");
1632
+ }
1633
+ if (!senderPkScript || !isP2trPkScript(senderPkScript)) {
1634
+ throw new Error("Invalid P2TR script");
1635
+ }
1636
+ }
1637
+ if (!receiver || receiver.length !== 32) {
1638
+ throw new Error("Invalid public key length (receiver)");
1639
+ }
1640
+ if (!sender || sender.length !== 32) {
1641
+ throw new Error("Invalid public key length (sender)");
1642
+ }
1643
+ if (!server || server.length !== 32) {
1644
+ throw new Error("Invalid public key length (server)");
1645
+ }
1646
+ if (typeof refundLocktime !== "bigint" || refundLocktime <= 0n) {
1647
+ throw new Error("refund locktime must be greater than 0");
1648
+ }
1649
+ if (!unilateralClaimDelay || typeof unilateralClaimDelay.value !== "bigint" || unilateralClaimDelay.value <= 0n) {
1650
+ throw new Error("unilateral claim delay must greater than 0");
1651
+ }
1652
+ if (unilateralClaimDelay.type === "seconds" && unilateralClaimDelay.value % 512n !== 0n) {
1653
+ throw new Error("seconds timelock must be multiple of 512");
1654
+ }
1655
+ if (unilateralClaimDelay.type === "seconds" && unilateralClaimDelay.value < 512n) {
1656
+ throw new Error("seconds timelock must be greater or equal to 512");
1657
+ }
1658
+ if (!unilateralRefundDelay || typeof unilateralRefundDelay.value !== "bigint" || unilateralRefundDelay.value <= 0n) {
1659
+ throw new Error("unilateral refund delay must greater than 0");
1660
+ }
1661
+ if (unilateralRefundDelay.type === "seconds" && unilateralRefundDelay.value % 512n !== 0n) {
1662
+ throw new Error("seconds timelock must be multiple of 512");
1663
+ }
1664
+ if (unilateralRefundDelay.type === "seconds" && unilateralRefundDelay.value < 512n) {
1665
+ throw new Error("seconds timelock must be greater or equal to 512");
1666
+ }
1667
+ if (!unilateralRefundWithoutReceiverDelay || typeof unilateralRefundWithoutReceiverDelay.value !== "bigint" || unilateralRefundWithoutReceiverDelay.value <= 0n) {
1668
+ throw new Error("unilateral refund without receiver delay must greater than 0");
1669
+ }
1670
+ if (unilateralRefundWithoutReceiverDelay.type === "seconds" && unilateralRefundWithoutReceiverDelay.value % 512n !== 0n) {
1671
+ throw new Error("seconds timelock must be multiple of 512");
1672
+ }
1673
+ if (unilateralRefundWithoutReceiverDelay.type === "seconds" && unilateralRefundWithoutReceiverDelay.value < 512n) {
1674
+ throw new Error("seconds timelock must be greater or equal to 512");
1275
1675
  }
1276
- return;
1277
- }
1278
- };
1279
- var _Writer = class {
1280
- pos = 0;
1281
- stack;
1282
- // We could have a single buffer here and re-alloc it with
1283
- // x1.5-2 size each time it full, but it will be slower:
1284
- // basic/encode bench: 395ns -> 560ns
1285
- buffers = [];
1286
- ptrs = [];
1287
- bitBuf = 0;
1288
- bitPos = 0;
1289
- viewBuf = new Uint8Array(8);
1290
- view;
1291
- finished = false;
1292
- constructor(stack = []) {
1293
- this.stack = stack;
1294
- this.view = createView(this.viewBuf);
1295
- }
1296
- pushObj(obj, objFn) {
1297
- return Path.pushObj(this.stack, obj, objFn);
1298
- }
1299
- writeView(len, fn) {
1300
- if (this.finished)
1301
- throw this.err("buffer: finished");
1302
- if (!isNum(len) || len > 8)
1303
- throw new Error(`wrong writeView length=${len}`);
1304
- fn(this.view);
1305
- this.bytes(this.viewBuf.slice(0, len));
1306
- this.viewBuf.fill(0);
1307
- }
1308
- // User methods
1309
- err(msg) {
1310
- if (this.finished)
1311
- throw this.err("buffer: finished");
1312
- return Path.err("Reader", this.stack, msg);
1313
- }
1314
- bytes(b) {
1315
- if (this.finished)
1316
- throw this.err("buffer: finished");
1317
- if (this.bitPos)
1318
- throw this.err("writeBytes: ends with non-empty bit buffer");
1319
- this.buffers.push(b);
1320
- this.pos += b.length;
1321
- }
1322
- byte(b) {
1323
- if (this.finished)
1324
- throw this.err("buffer: finished");
1325
- if (this.bitPos)
1326
- throw this.err("writeByte: ends with non-empty bit buffer");
1327
- this.buffers.push(new Uint8Array([b]));
1328
- this.pos++;
1329
1676
  }
1330
- finish(clean = true) {
1331
- if (this.finished)
1332
- throw this.err("buffer: finished");
1333
- if (this.bitPos)
1334
- throw this.err("buffer: ends with non-empty bit buffer");
1335
- const buffers = this.buffers.concat(this.ptrs.map((i) => i.buffer));
1336
- const sum = buffers.map((b) => b.length).reduce((a, b) => a + b, 0);
1337
- const buf = new Uint8Array(sum);
1338
- for (let i = 0, pad = 0; i < buffers.length; i++) {
1339
- const a = buffers[i];
1340
- buf.set(a, pad);
1341
- pad += a.length;
1677
+ })(exports.VHTLC || (exports.VHTLC = {}));
1678
+ function preimageConditionScript(preimageHash) {
1679
+ return btcSigner.Script.encode(["HASH160", preimageHash, "EQUAL"]);
1680
+ }
1681
+ function preimageConditionScriptV2(preimageHash) {
1682
+ return btcSigner.Script.encode(["SIZE", 32, "EQUALVERIFY", "HASH160", preimageHash, "EQUAL"]);
1683
+ }
1684
+ function isP2trPkScript(pkScript) {
1685
+ return pkScript.length === 34 && pkScript[0] === 81 && pkScript[1] === 32;
1686
+ }
1687
+ function enforcePayTo(destinationPkScript) {
1688
+ if (!isP2trPkScript(destinationPkScript)) {
1689
+ throw new Error("invalid P2TR script");
1690
+ }
1691
+ return ArkadeScript.encode([
1692
+ "PUSHCURRENTINPUTINDEX",
1693
+ "DUP",
1694
+ "INSPECTOUTPUTSCRIPTPUBKEY",
1695
+ 1,
1696
+ "EQUALVERIFY",
1697
+ destinationPkScript.subarray(2),
1698
+ "EQUALVERIFY",
1699
+ "INSPECTOUTPUTVALUE",
1700
+ "PUSHCURRENTINPUTINDEX",
1701
+ "INSPECTINPUTVALUE",
1702
+ "GREATERTHANOREQUAL"
1703
+ ]);
1704
+ }
1705
+
1706
+ // src/contracts/handlers/vhtlc.ts
1707
+ var VHTLCContractHandler = {
1708
+ type: "vhtlc",
1709
+ createScript(params) {
1710
+ const typed = this.deserializeParams(params);
1711
+ return new exports.VHTLC.Script(typed);
1712
+ },
1713
+ serializeParams(params) {
1714
+ return {
1715
+ sender: base.hex.encode(params.sender),
1716
+ receiver: base.hex.encode(params.receiver),
1717
+ server: base.hex.encode(params.server),
1718
+ hash: base.hex.encode(params.preimageHash),
1719
+ refundLocktime: params.refundLocktime.toString(),
1720
+ claimDelay: chunkFNCQJLR2_cjs.timelockToSequence(params.unilateralClaimDelay).toString(),
1721
+ refundDelay: chunkFNCQJLR2_cjs.timelockToSequence(params.unilateralRefundDelay).toString(),
1722
+ refundNoReceiverDelay: chunkFNCQJLR2_cjs.timelockToSequence(
1723
+ params.unilateralRefundWithoutReceiverDelay
1724
+ ).toString()
1725
+ };
1726
+ },
1727
+ deserializeParams(params) {
1728
+ return {
1729
+ sender: base.hex.decode(params.sender),
1730
+ receiver: base.hex.decode(params.receiver),
1731
+ server: base.hex.decode(params.server),
1732
+ preimageHash: base.hex.decode(params.hash),
1733
+ refundLocktime: BigInt(params.refundLocktime),
1734
+ unilateralClaimDelay: chunkFNCQJLR2_cjs.sequenceToTimelock(Number(params.claimDelay)),
1735
+ unilateralRefundDelay: chunkFNCQJLR2_cjs.sequenceToTimelock(Number(params.refundDelay)),
1736
+ unilateralRefundWithoutReceiverDelay: chunkFNCQJLR2_cjs.sequenceToTimelock(
1737
+ Number(params.refundNoReceiverDelay)
1738
+ )
1739
+ };
1740
+ },
1741
+ /**
1742
+ * Select spending path based on context.
1743
+ *
1744
+ * Role is determined from `context.role` or by matching
1745
+ * `context.walletDescriptor` (preferred) / `context.walletPubKey`
1746
+ * against sender/receiver in contract params.
1747
+ */
1748
+ selectPath(script, contract, context) {
1749
+ const role = resolveRole(contract, context);
1750
+ const preimage = contract.params?.preimage;
1751
+ const refundLocktime = BigInt(contract.params.refundLocktime);
1752
+ if (!role) {
1753
+ return null;
1342
1754
  }
1343
- for (let pos = this.pos, i = 0; i < this.ptrs.length; i++) {
1344
- const ptr = this.ptrs[i];
1345
- buf.set(ptr.ptr.encode(pos), ptr.pos);
1346
- pos += ptr.buffer.length;
1755
+ if (context.collaborative) {
1756
+ if (role === "receiver" && preimage) {
1757
+ return {
1758
+ leaf: script.claim(),
1759
+ extraWitness: [base.hex.decode(preimage)]
1760
+ };
1761
+ }
1762
+ if (role === "sender" && isCltvSatisfied(context, refundLocktime)) {
1763
+ return {
1764
+ leaf: script.refundWithoutReceiver()
1765
+ };
1766
+ }
1767
+ return null;
1347
1768
  }
1348
- if (clean) {
1349
- this.buffers = [];
1350
- for (const p of this.ptrs)
1351
- p.buffer.fill(0);
1352
- this.ptrs = [];
1353
- this.finished = true;
1354
- this.bitBuf = 0;
1769
+ if (role === "receiver" && preimage) {
1770
+ const sequence = Number(contract.params.claimDelay);
1771
+ if (!isCsvSpendable(context, sequence)) return null;
1772
+ return {
1773
+ leaf: script.unilateralClaim(),
1774
+ extraWitness: [base.hex.decode(preimage)],
1775
+ sequence
1776
+ };
1355
1777
  }
1356
- return buf;
1357
- }
1358
- bits(value, bits) {
1359
- if (bits > 32)
1360
- throw this.err("writeBits: cannot write more than 32 bits in single call");
1361
- if (value >= 2 ** bits)
1362
- throw this.err(`writeBits: value (${value}) >= 2**bits (${bits})`);
1363
- while (bits) {
1364
- const take = Math.min(bits, 8 - this.bitPos);
1365
- this.bitBuf = this.bitBuf << take | value >> bits - take;
1366
- this.bitPos += take;
1367
- bits -= take;
1368
- value &= 2 ** bits - 1;
1369
- if (this.bitPos === 8) {
1370
- this.bitPos = 0;
1371
- this.buffers.push(new Uint8Array([this.bitBuf]));
1372
- this.pos++;
1373
- }
1778
+ if (role === "sender") {
1779
+ const sequence = Number(contract.params.refundNoReceiverDelay);
1780
+ if (!isCsvSpendable(context, sequence)) return null;
1781
+ return {
1782
+ leaf: script.unilateralRefundWithoutReceiver(),
1783
+ sequence
1784
+ };
1374
1785
  }
1375
- }
1376
- };
1377
- function _wrap(inner) {
1378
- return {
1379
- // NOTE: we cannot export validate here, since it is likely mistake.
1380
- encodeStream: inner.encodeStream,
1381
- decodeStream: inner.decodeStream,
1382
- size: inner.size,
1383
- encode: (value) => {
1384
- const w = new _Writer();
1385
- inner.encodeStream(w, value);
1386
- return w.finish();
1387
- },
1388
- decode: (data, opts = {}) => {
1389
- const r = new _Reader(data, opts);
1390
- const res = inner.decodeStream(r);
1391
- r.finish();
1392
- return res;
1786
+ return null;
1787
+ },
1788
+ /**
1789
+ * Get all possible spending paths (no timelock checks).
1790
+ *
1791
+ * Role is determined from `context.role` or by matching
1792
+ * `context.walletDescriptor` (preferred) / `context.walletPubKey`
1793
+ * against sender/receiver in contract params.
1794
+ */
1795
+ getAllSpendingPaths(script, contract, context) {
1796
+ const role = resolveRole(contract, context);
1797
+ const paths = [];
1798
+ if (!role) {
1799
+ return paths;
1393
1800
  }
1394
- };
1395
- }
1396
- function validate(inner, fn) {
1397
- if (!isCoder(inner))
1398
- throw new Error(`validate: invalid inner value ${inner}`);
1399
- if (typeof fn !== "function")
1400
- throw new Error("validate: fn should be function");
1401
- return _wrap({
1402
- size: inner.size,
1403
- encodeStream: (w, value) => {
1404
- let res;
1405
- try {
1406
- res = fn(value);
1407
- } catch (e) {
1408
- throw w.err(e);
1801
+ const preimage = contract.params?.preimage;
1802
+ if (context.collaborative) {
1803
+ if (role === "receiver" && preimage) {
1804
+ paths.push({
1805
+ leaf: script.claim(),
1806
+ extraWitness: [base.hex.decode(preimage)]
1807
+ });
1409
1808
  }
1410
- inner.encodeStream(w, res);
1411
- },
1412
- decodeStream: (r) => {
1413
- const res = inner.decodeStream(r);
1414
- try {
1415
- return fn(res);
1416
- } catch (e) {
1417
- throw r.err(e);
1809
+ if (role === "sender") {
1810
+ paths.push({
1811
+ leaf: script.refundWithoutReceiver()
1812
+ });
1813
+ }
1814
+ } else {
1815
+ if (role === "receiver" && preimage) {
1816
+ const sequence = Number(contract.params.claimDelay);
1817
+ paths.push({
1818
+ leaf: script.unilateralClaim(),
1819
+ extraWitness: [base.hex.decode(preimage)],
1820
+ sequence
1821
+ });
1822
+ }
1823
+ if (role === "sender") {
1824
+ const sequence = Number(contract.params.refundNoReceiverDelay);
1825
+ paths.push({
1826
+ leaf: script.unilateralRefundWithoutReceiver(),
1827
+ sequence
1828
+ });
1418
1829
  }
1419
1830
  }
1420
- });
1421
- }
1422
- var wrap = (inner) => {
1423
- const res = _wrap(inner);
1424
- return inner.validate ? validate(res, inner.validate) : res;
1425
- };
1426
- var isBaseCoder = (elm) => isPlainObject(elm) && typeof elm.decode === "function" && typeof elm.encode === "function";
1427
- function isCoder(elm) {
1428
- return isPlainObject(elm) && isBaseCoder(elm) && typeof elm.encodeStream === "function" && typeof elm.decodeStream === "function" && (elm.size === void 0 || isNum(elm.size));
1429
- }
1430
- var view = (len, opts) => wrap({
1431
- size: len,
1432
- encodeStream: (w, value) => w.writeView(len, (view2) => opts.write(view2, value)),
1433
- decodeStream: (r) => r.readView(len, opts.read),
1434
- validate: (value) => {
1435
- if (typeof value !== "number")
1436
- throw new Error(`viewCoder: expected number, got ${typeof value}`);
1437
- if (opts.validate)
1438
- opts.validate(value);
1439
- return value;
1440
- }
1441
- });
1442
- var intView = (len, signed, opts) => {
1443
- const bits = len * 8;
1444
- const maxVal = 2 ** bits;
1445
- const validateUnsigned = (value) => {
1446
- if (!isNum(value))
1447
- throw new Error(`uintView: value is not safe integer: ${value}`);
1448
- if (0 > value || value >= maxVal) {
1449
- throw new Error(`uintView: value out of bounds. Expected 0 <= ${value} < ${maxVal}`);
1450
- }
1451
- };
1452
- return view(len, {
1453
- write: opts.write,
1454
- read: opts.read,
1455
- validate: validateUnsigned
1456
- });
1457
- };
1458
- var U32LE = /* @__PURE__ */ intView(4, false, {
1459
- read: (view2, pos) => view2.getUint32(pos, true),
1460
- write: (view2, value) => view2.setUint32(0, value, true)
1461
- });
1462
- var U16LE = /* @__PURE__ */ intView(2, false, {
1463
- read: (view2, pos) => view2.getUint16(pos, true),
1464
- write: (view2, value) => view2.setUint16(0, value, true)
1465
- });
1466
- var U8 = /* @__PURE__ */ intView(1, false, {
1467
- read: (view2, pos) => view2.getUint8(pos),
1468
- write: (view2, value) => view2.setUint8(0, value)
1469
- });
1470
- var ARKADE_OP = {
1471
- // Merkle Branch Verification (0xb3 — repurposed NOP4 slot)
1472
- MERKLEBRANCHVERIFY: 179,
1473
- // Digest (0xc3)
1474
- DIGEST: 195,
1475
- // SHA256 Streaming (0xc4-0xc6)
1476
- SHA256INITIALIZE: 196,
1477
- SHA256UPDATE: 197,
1478
- SHA256FINALIZE: 198,
1479
- // Input Introspection (0xc7-0xcb)
1480
- INSPECTINPUTOUTPOINT: 199,
1481
- INSPECTINPUTARKADESCRIPTHASH: 200,
1482
- INSPECTINPUTVALUE: 201,
1483
- INSPECTINPUTSCRIPTPUBKEY: 202,
1484
- INSPECTINPUTSEQUENCE: 203,
1485
- // Signatures (0xcc-0xcd)
1486
- CHECKSIGFROMSTACK: 204,
1487
- PUSHCURRENTINPUTINDEX: 205,
1488
- // Input Arkade Witness Introspection (0xce)
1489
- INSPECTINPUTARKADEWITNESSHASH: 206,
1490
- // Output Introspection (0xcf, 0xd1)
1491
- INSPECTOUTPUTVALUE: 207,
1492
- INSPECTOUTPUTSCRIPTPUBKEY: 209,
1493
- // Transaction Introspection (0xd2-0xd6)
1494
- INSPECTVERSION: 210,
1495
- INSPECTLOCKTIME: 211,
1496
- INSPECTNUMINPUTS: 212,
1497
- INSPECTNUMOUTPUTS: 213,
1498
- TXWEIGHT: 214,
1499
- // Byte/Number Conversion & Arithmetic (0xd7-0xda)
1500
- NUM2BIN: 215,
1501
- BIN2NUM: 216,
1502
- REVERSEBYTES: 217,
1503
- MODEXP: 218,
1504
- // 0xdb-0xdf are unassigned (OP_UNKNOWN219-223)
1505
- // EC Operations (0xe0-0xe2)
1506
- ECADD: 224,
1507
- ECMUL: 225,
1508
- ECPAIRING: 226,
1509
- // EC Operations (0xe3-0xe4)
1510
- ECMULSCALARVERIFY: 227,
1511
- TWEAKVERIFY: 228,
1512
- // Asset Groups (0xe5-0xf2)
1513
- INSPECTNUMASSETGROUPS: 229,
1514
- INSPECTASSETGROUPASSETID: 230,
1515
- INSPECTASSETGROUPCTRL: 231,
1516
- FINDASSETGROUPBYASSETID: 232,
1517
- INSPECTASSETGROUPMETADATAHASH: 233,
1518
- INSPECTASSETGROUPNUM: 234,
1519
- INSPECTASSETGROUP: 235,
1520
- INSPECTASSETGROUPSUM: 236,
1521
- INSPECTOUTASSETCOUNT: 237,
1522
- INSPECTOUTASSETAT: 238,
1523
- INSPECTOUTASSETLOOKUP: 239,
1524
- INSPECTINASSETCOUNT: 240,
1525
- INSPECTINASSETAT: 241,
1526
- INSPECTINASSETLOOKUP: 242,
1527
- // Transaction ID (0xf3)
1528
- TXID: 243,
1529
- // Packet Introspection (0xf4-0xf5) — added in emulator v0.0.1
1530
- INSPECTPACKET: 244,
1531
- INSPECTINPUTPACKET: 245,
1532
- // Signature Hash (0xf6)
1533
- SIGHASH: 246
1534
- };
1535
- var ARKADE_OPCODES = Object.values(ARKADE_OP);
1536
- var ARKADE_OPCODE_NAMES = Object.fromEntries(
1537
- Object.entries(ARKADE_OP).map(([name, value]) => [value, name])
1538
- );
1539
- var ARKADE_OPCODE_VALUES = Object.fromEntries(
1540
- Object.entries(ARKADE_OPCODE_NAMES).map(([value, name]) => [name, Number(value)])
1541
- );
1542
- function buildBitcoinOpcodeNames() {
1543
- const names = {};
1544
- for (const [key, value] of Object.entries(btcSigner.OP)) {
1545
- if (typeof value === "number") {
1546
- const name = key.startsWith("OP_") ? key : `OP_${key}`;
1547
- names[value] = name;
1831
+ return paths;
1832
+ },
1833
+ getSpendablePaths(script, contract, context) {
1834
+ const role = resolveRole(contract, context);
1835
+ const paths = [];
1836
+ if (!role) {
1837
+ return paths;
1548
1838
  }
1549
- }
1550
- names[0] = "OP_0";
1551
- return names;
1552
- }
1553
- function buildBitcoinOpcodeValues() {
1554
- const values = {};
1555
- for (const [key, value] of Object.entries(btcSigner.OP)) {
1556
- if (typeof value === "number") {
1557
- const name = key.startsWith("OP_") ? key : `OP_${key}`;
1558
- values[name] = value;
1559
- values[key] = value;
1839
+ const preimage = contract.params?.preimage;
1840
+ const refundLocktime = BigInt(contract.params.refundLocktime);
1841
+ if (context.collaborative) {
1842
+ if (role === "receiver" && preimage) {
1843
+ paths.push({
1844
+ leaf: script.claim(),
1845
+ extraWitness: [base.hex.decode(preimage)]
1846
+ });
1847
+ }
1848
+ if (role === "sender" && isCltvSatisfied(context, refundLocktime)) {
1849
+ paths.push({
1850
+ leaf: script.refundWithoutReceiver()
1851
+ });
1852
+ }
1853
+ return paths;
1560
1854
  }
1561
- }
1562
- return values;
1563
- }
1564
- var BITCOIN_OPCODE_NAMES = buildBitcoinOpcodeNames();
1565
- var BITCOIN_OPCODE_VALUES = buildBitcoinOpcodeValues();
1566
- var OPCODE_NAMES = {
1567
- ...BITCOIN_OPCODE_NAMES,
1568
- // Add Arkade opcodes with OP_ prefix
1569
- ...Object.fromEntries(
1570
- Object.entries(ARKADE_OPCODE_NAMES).map(([value, name]) => [Number(value), `OP_${name}`])
1571
- )
1572
- };
1573
- var OPCODE_VALUES = {
1574
- ...BITCOIN_OPCODE_VALUES,
1575
- // Add Arkade opcodes with and without OP_ prefix
1576
- ...ARKADE_OPCODE_VALUES,
1577
- ...Object.fromEntries(
1578
- Object.entries(ARKADE_OPCODE_VALUES).map(([name, value]) => [`OP_${name}`, value])
1579
- )
1580
- };
1581
- function getOpcodeName(value) {
1582
- if (value >= 1 && value <= 75) {
1583
- return `OP_DATA_${value}`;
1584
- }
1585
- return OPCODE_NAMES[value];
1586
- }
1587
- function getOpcodeValue(name) {
1588
- const dataMatch = name.match(/^OP_DATA_(\d+)$/);
1589
- if (dataMatch) {
1590
- const n = parseInt(dataMatch[1], 10);
1591
- if (n >= 1 && n <= 75) {
1592
- return n;
1855
+ if (role === "receiver" && preimage) {
1856
+ const sequence = Number(contract.params.claimDelay);
1857
+ if (isCsvSpendable(context, sequence)) {
1858
+ paths.push({
1859
+ leaf: script.unilateralClaim(),
1860
+ extraWitness: [base.hex.decode(preimage)],
1861
+ sequence
1862
+ });
1863
+ }
1593
1864
  }
1594
- return void 0;
1865
+ if (role === "sender") {
1866
+ const sequence = Number(contract.params.refundNoReceiverDelay);
1867
+ if (isCsvSpendable(context, sequence)) {
1868
+ paths.push({
1869
+ leaf: script.unilateralRefundWithoutReceiver(),
1870
+ sequence
1871
+ });
1872
+ }
1873
+ }
1874
+ return paths;
1875
+ },
1876
+ /**
1877
+ * Refuse an explicit spend of a lockup whose refund path has not opened.
1878
+ * Shared with the v2 handler so the two cannot drift.
1879
+ * @see assertVhtlcSpendableNow for why this is the one certain case.
1880
+ */
1881
+ assertSpendableNow(_script, contract, context) {
1882
+ assertVhtlcSpendableNow(contract, context);
1883
+ },
1884
+ /**
1885
+ * Never. A live VHTLC is escrow: the counterparty's claim leaf is armed,
1886
+ * and generic selection — send, settle, renewal, offboard — would move the
1887
+ * lockup out from under a swap the counterparty is still entitled to
1888
+ * complete, or race a claim already in flight.
1889
+ *
1890
+ * Two narrow reasons, and neither is "spending this would destroy the
1891
+ * VTXO". It would not: the leaf {@link deriveTapscripts} stamps is
1892
+ * `refundWithoutReceiver` — `CLTV[sender, server]`, the sender's OWN
1893
+ * refund, which the server rejects until `refundLocktime` matures. The
1894
+ * reasons are that an escrowed lockup must not count toward the
1895
+ * `available` balance bucket, and that generic RENEWAL must not silently
1896
+ * execute a refund the caller never asked for — `runPeriodicSettle` selects
1897
+ * through `getSpendableVtxos`, which this gate filters, and it runs
1898
+ * unprompted whenever a wallet is built without an explicit
1899
+ * `settlementConfig`.
1900
+ *
1901
+ * **Inseparable from {@link deriveTapscripts}.** Deriving the leaf without
1902
+ * closing the gate hands generic renewal an escrow it had never been
1903
+ * offered before; before that method existed the answer here was
1904
+ * unobservable, so the two only ever made sense together.
1905
+ *
1906
+ * Recovery is filtered on {@link assertSpendableNow}, not on this: the gate
1907
+ * is permanent, and applying it there would strand a matured lockup.
1908
+ */
1909
+ isGenericallySpendable: () => false,
1910
+ /**
1911
+ * The annotation leaf stamped onto every VTXO locked to this contract.
1912
+ *
1913
+ * **Without this the contract is unusable, not merely unoptimized.**
1914
+ * `deriveContractTapscripts` falls back to `script.forfeit()` for any
1915
+ * handler that does not implement {@link TapscriptDeriving}, and no VHTLC
1916
+ * script version has a `forfeit()` — `VtxoScript` does not define one and
1917
+ * `VHTLC.BaseScript` does not add one. The fallback therefore threw
1918
+ * `legacy.forfeit is not a function`, `annotatableIn` caught it, and
1919
+ * `fetchContractVtxosBulk` dropped this contract's VTXOs before they were
1920
+ * persisted. The row existed and was watched while its balance stayed
1921
+ * permanently invisible, and `getSyncState()` reported `degraded` forever.
1922
+ *
1923
+ * `refundWithoutReceiver` is the leaf, for both roles the annotation
1924
+ * serves, because it is the only collaborative path this wallet can
1925
+ * actually satisfy as the sender — the same conclusion `selectPath`
1926
+ * reaches, kept in step with it deliberately. The claim leaves belong to
1927
+ * the receiver and need a preimage the sender does not have; `refund` and
1928
+ * `unilateralRefund` both need the counterparty's signature, which this
1929
+ * protocol has no message to ask for. V1 has no covenant leaves at all.
1930
+ *
1931
+ * **It carries a CLTV, and callers must respect it.** A settlement built
1932
+ * on this leaf is only valid once `refundLocktime` has matured, so a
1933
+ * recovery round that sweeps this VTXO early is rejected by the server.
1934
+ * Nothing in this handler can enforce that, because the annotation is
1935
+ * derived per contract and knows no clock; `recoverVtxos` filters on
1936
+ * {@link assertSpendableNow} for it.
1937
+ *
1938
+ * **Role-blind.** A receiver's row gets the same leaf, which names keys that
1939
+ * wallet does not hold, so it is satisfiable only by the wallet holding the
1940
+ * lockup's `sender` key and fails at intent registration for anyone else.
1941
+ */
1942
+ deriveTapscripts(script) {
1943
+ const leaf = script.refundWithoutReceiver();
1944
+ return {
1945
+ forfeitTapLeafScript: leaf,
1946
+ intentTapLeafScript: leaf,
1947
+ tapTree: script.encode()
1948
+ };
1595
1949
  }
1596
- return OPCODE_VALUES[name];
1597
- }
1598
-
1599
- // src/arkade/bignum.ts
1600
- var bignum_exports = {};
1601
- chunk5BLDMQED_cjs.__export(bignum_exports, {
1602
- BIGNUM_MAX_BYTES: () => BIGNUM_MAX_BYTES,
1603
- decode: () => decode,
1604
- encode: () => encode
1605
- });
1606
- var BIGNUM_MAX_BYTES = 520;
1607
- var codec = btcSigner.ScriptNum(
1608
- BIGNUM_MAX_BYTES,
1609
- /* forceMinimal */
1610
- true
1611
- );
1612
- function encode(value) {
1613
- const result = codec.encode(value);
1614
- if (result.length > BIGNUM_MAX_BYTES) {
1615
- throw new Error(`BigNum value exceeds 520 bytes (encoded to ${result.length} bytes)`);
1950
+ };
1951
+ function decodeCovenantLeaf(params, destinationKey, emulatorKey, label) {
1952
+ const destination = params[destinationKey];
1953
+ const emulator = params[emulatorKey];
1954
+ if (!destination && !emulator) return void 0;
1955
+ if (!destination || !emulator) {
1956
+ throw new Error(`${label} needs both '${destinationKey}' and '${emulatorKey}', or neither`);
1616
1957
  }
1617
- return result;
1618
- }
1619
- function decode(value) {
1620
- return codec.decode(value);
1621
- }
1622
-
1623
- // src/arkade/script.ts
1624
- var ARKADE_OPS = { ...btcSigner.OP, ...ARKADE_OP };
1625
- var ArkadeOPNames = {};
1626
- for (const [k, v] of Object.entries(ARKADE_OPS)) {
1627
- if (typeof v === "number") ArkadeOPNames[v] = k;
1958
+ return { destination: base.hex.decode(destination), emulatorPubkey: base.hex.decode(emulator) };
1628
1959
  }
1629
- var ArkadeScript = wrap({
1630
- encodeStream: (w, value) => {
1631
- for (let o of value) {
1632
- if (typeof o === "string") {
1633
- const v = ARKADE_OPS[o];
1634
- if (v === void 0) throw new Error(`Unknown opcode=${o}`);
1635
- w.byte(v);
1636
- continue;
1637
- } else if (typeof o === "number" || typeof o === "bigint") {
1638
- if (o === 0 || o === 0n) {
1639
- w.byte(0);
1640
- continue;
1641
- } else if (o >= 1 && o <= 16) {
1642
- w.byte(btcSigner.OP.OP_1 - 1 + Number(o));
1643
- continue;
1644
- } else if (o === -1 || o === -1n) {
1645
- w.byte(btcSigner.OP["1NEGATE"]);
1646
- continue;
1647
- }
1648
- const big = typeof o === "number" ? BigInt(o) : o;
1649
- o = encode(big);
1960
+ var VHTLCV2ContractHandler = {
1961
+ type: "vhtlc-v2",
1962
+ createScript(params) {
1963
+ const typed = this.deserializeParams(params);
1964
+ return new exports.VHTLC.ScriptV2(typed);
1965
+ },
1966
+ serializeParams(params) {
1967
+ return {
1968
+ sender: base.hex.encode(params.sender),
1969
+ receiver: base.hex.encode(params.receiver),
1970
+ server: base.hex.encode(params.server),
1971
+ hash: base.hex.encode(params.preimageHash),
1972
+ refundLocktime: params.refundLocktime.toString(),
1973
+ claimDelay: chunkFNCQJLR2_cjs.timelockToSequence(params.unilateralClaimDelay).toString(),
1974
+ refundDelay: chunkFNCQJLR2_cjs.timelockToSequence(params.unilateralRefundDelay).toString(),
1975
+ refundNoReceiverDelay: chunkFNCQJLR2_cjs.timelockToSequence(
1976
+ params.unilateralRefundWithoutReceiverDelay
1977
+ ).toString(),
1978
+ // Spread rather than written as `undefined`: a `Record<string,
1979
+ // string>` round-tripped through a repository must not gain keys
1980
+ // whose value is the string "undefined".
1981
+ ...params.nonInteractiveClaim && {
1982
+ nonInteractiveClaimReceiverPkScript: base.hex.encode(
1983
+ params.nonInteractiveClaim.receiverPkScript
1984
+ ),
1985
+ nonInteractiveClaimEmulatorPubkey: base.hex.encode(
1986
+ params.nonInteractiveClaim.emulatorPubkey
1987
+ )
1988
+ },
1989
+ ...params.nonInteractiveRefund && {
1990
+ nonInteractiveRefundSenderPkScript: base.hex.encode(
1991
+ params.nonInteractiveRefund.senderPkScript
1992
+ ),
1993
+ nonInteractiveRefundEmulatorPubkey: base.hex.encode(
1994
+ params.nonInteractiveRefund.emulatorPubkey
1995
+ )
1650
1996
  }
1651
- if (!(o instanceof Uint8Array)) throw new Error(`Wrong Script OP=${o} (${typeof o})`);
1652
- const len = o.length;
1653
- if (len < btcSigner.OP.PUSHDATA1) w.byte(len);
1654
- else if (len <= 255) {
1655
- w.byte(btcSigner.OP.PUSHDATA1);
1656
- w.byte(len);
1657
- } else if (len <= 65535) {
1658
- w.byte(btcSigner.OP.PUSHDATA2);
1659
- w.bytes(U16LE.encode(len));
1660
- } else {
1661
- w.byte(btcSigner.OP.PUSHDATA4);
1662
- w.bytes(U32LE.encode(len));
1997
+ };
1998
+ },
1999
+ deserializeParams(params) {
2000
+ const claim = decodeCovenantLeaf(
2001
+ params,
2002
+ "nonInteractiveClaimReceiverPkScript",
2003
+ "nonInteractiveClaimEmulatorPubkey",
2004
+ "nonInteractiveClaim"
2005
+ );
2006
+ const refund = decodeCovenantLeaf(
2007
+ params,
2008
+ "nonInteractiveRefundSenderPkScript",
2009
+ "nonInteractiveRefundEmulatorPubkey",
2010
+ "nonInteractiveRefund"
2011
+ );
2012
+ return {
2013
+ sender: base.hex.decode(params.sender),
2014
+ receiver: base.hex.decode(params.receiver),
2015
+ server: base.hex.decode(params.server),
2016
+ preimageHash: base.hex.decode(params.hash),
2017
+ refundLocktime: BigInt(params.refundLocktime),
2018
+ unilateralClaimDelay: chunkFNCQJLR2_cjs.sequenceToTimelock(Number(params.claimDelay)),
2019
+ unilateralRefundDelay: chunkFNCQJLR2_cjs.sequenceToTimelock(Number(params.refundDelay)),
2020
+ unilateralRefundWithoutReceiverDelay: chunkFNCQJLR2_cjs.sequenceToTimelock(
2021
+ Number(params.refundNoReceiverDelay)
2022
+ ),
2023
+ ...claim && {
2024
+ nonInteractiveClaim: {
2025
+ receiverPkScript: claim.destination,
2026
+ emulatorPubkey: claim.emulatorPubkey
2027
+ }
2028
+ },
2029
+ ...refund && {
2030
+ nonInteractiveRefund: {
2031
+ senderPkScript: refund.destination,
2032
+ emulatorPubkey: refund.emulatorPubkey
2033
+ }
1663
2034
  }
1664
- w.bytes(o);
1665
- }
2035
+ };
1666
2036
  },
1667
- decodeStream: (r) => {
1668
- const out = [];
1669
- while (!r.isEnd()) {
1670
- const cur = r.byte();
1671
- if (btcSigner.OP.OP_0 < cur && cur <= btcSigner.OP.PUSHDATA4) {
1672
- let len;
1673
- if (cur < btcSigner.OP.PUSHDATA1) len = cur;
1674
- else if (cur === btcSigner.OP.PUSHDATA1) len = U8.decodeStream(r);
1675
- else if (cur === btcSigner.OP.PUSHDATA2) len = U16LE.decodeStream(r);
1676
- else if (cur === btcSigner.OP.PUSHDATA4) len = U32LE.decodeStream(r);
1677
- else throw new Error("Should be not possible");
1678
- out.push(r.bytes(len));
1679
- } else if (cur === 0) {
1680
- out.push(0);
1681
- } else if (btcSigner.OP.OP_1 <= cur && cur <= btcSigner.OP.OP_16) {
1682
- out.push(cur - (btcSigner.OP.OP_1 - 1));
1683
- } else if (cur === btcSigner.OP["1NEGATE"]) {
1684
- out.push(-1);
1685
- } else {
1686
- const op = ArkadeOPNames[cur];
1687
- if (op === void 0) throw new Error(`Unknown opcode=${cur.toString(16)}`);
1688
- out.push(op);
2037
+ /**
2038
+ * Select spending path based on context.
2039
+ *
2040
+ * Role is determined from `context.role` or by matching
2041
+ * `context.walletDescriptor` (preferred) / `context.walletPubKey`
2042
+ * against sender/receiver in contract params.
2043
+ */
2044
+ selectPath(script, contract, context) {
2045
+ const role = resolveRole(contract, context);
2046
+ const preimage = contract.params?.preimage;
2047
+ const refundLocktime = BigInt(contract.params.refundLocktime);
2048
+ if (!role) {
2049
+ return null;
2050
+ }
2051
+ if (context.collaborative) {
2052
+ if (role === "receiver" && preimage) {
2053
+ return {
2054
+ leaf: script.claim(),
2055
+ extraWitness: [base.hex.decode(preimage)]
2056
+ };
2057
+ }
2058
+ if (role === "sender" && isCltvSatisfied(context, refundLocktime)) {
2059
+ return {
2060
+ leaf: script.refundWithoutReceiver()
2061
+ };
1689
2062
  }
2063
+ return null;
2064
+ }
2065
+ if (role === "receiver" && preimage) {
2066
+ const sequence = Number(contract.params.claimDelay);
2067
+ if (!isCsvSpendable(context, sequence)) return null;
2068
+ return {
2069
+ leaf: script.unilateralClaim(),
2070
+ extraWitness: [base.hex.decode(preimage)],
2071
+ sequence
2072
+ };
2073
+ }
2074
+ if (role === "sender") {
2075
+ const sequence = Number(contract.params.refundNoReceiverDelay);
2076
+ if (!isCsvSpendable(context, sequence)) return null;
2077
+ return {
2078
+ leaf: script.unilateralRefundWithoutReceiver(),
2079
+ sequence
2080
+ };
2081
+ }
2082
+ return null;
2083
+ },
2084
+ /**
2085
+ * Get all possible spending paths (no timelock checks).
2086
+ *
2087
+ * Role is determined from `context.role` or by matching
2088
+ * `context.walletDescriptor` (preferred) / `context.walletPubKey`
2089
+ * against sender/receiver in contract params.
2090
+ */
2091
+ getAllSpendingPaths(script, contract, context) {
2092
+ const role = resolveRole(contract, context);
2093
+ const paths = [];
2094
+ if (!role) {
2095
+ return paths;
1690
2096
  }
1691
- return out;
1692
- }
1693
- });
1694
- function toASM(script) {
1695
- const parts = [];
1696
- for (const op of script) {
1697
- if (typeof op === "string") {
1698
- const name = op.startsWith("OP_") ? op : `OP_${op}`;
1699
- parts.push(name);
1700
- } else if (typeof op === "number" || typeof op === "bigint") {
1701
- if (op === 0 || op === 0n) {
1702
- parts.push("OP_0");
1703
- } else if (op >= 1 && op <= 16) {
1704
- parts.push(`OP_${op}`);
1705
- } else if (op === -1 || op === -1n) {
1706
- parts.push("OP_1NEGATE");
1707
- } else {
1708
- parts.push(base.hex.encode(encode(BigInt(op))));
2097
+ const preimage = contract.params?.preimage;
2098
+ if (context.collaborative) {
2099
+ if (role === "receiver" && preimage) {
2100
+ paths.push({
2101
+ leaf: script.claim(),
2102
+ extraWitness: [base.hex.decode(preimage)]
2103
+ });
2104
+ }
2105
+ if (role === "sender") {
2106
+ paths.push({
2107
+ leaf: script.refundWithoutReceiver()
2108
+ });
1709
2109
  }
1710
2110
  } else {
1711
- parts.push(base.hex.encode(op));
1712
- }
1713
- }
1714
- return parts.join(" ");
1715
- }
1716
- function fromASM(asm) {
1717
- const tokens = asm.trim().split(/\s+/).filter(Boolean);
1718
- const out = [];
1719
- for (const token of tokens) {
1720
- if (token === "OP_0" || token === "OP_FALSE") {
1721
- out.push(0);
1722
- continue;
2111
+ if (role === "receiver" && preimage) {
2112
+ const sequence = Number(contract.params.claimDelay);
2113
+ paths.push({
2114
+ leaf: script.unilateralClaim(),
2115
+ extraWitness: [base.hex.decode(preimage)],
2116
+ sequence
2117
+ });
2118
+ }
2119
+ if (role === "sender") {
2120
+ const sequence = Number(contract.params.refundNoReceiverDelay);
2121
+ paths.push({
2122
+ leaf: script.unilateralRefundWithoutReceiver(),
2123
+ sequence
2124
+ });
2125
+ }
1723
2126
  }
1724
- if (token === "OP_TRUE") {
1725
- out.push(1);
1726
- continue;
2127
+ return paths;
2128
+ },
2129
+ getSpendablePaths(script, contract, context) {
2130
+ const role = resolveRole(contract, context);
2131
+ const paths = [];
2132
+ if (!role) {
2133
+ return paths;
1727
2134
  }
1728
- const numMatch = token.match(/^OP_(\d+)$/);
1729
- if (numMatch) {
1730
- const n = parseInt(numMatch[1], 10);
1731
- if (n >= 1 && n <= 16) {
1732
- out.push(n);
1733
- continue;
2135
+ const preimage = contract.params?.preimage;
2136
+ const refundLocktime = BigInt(contract.params.refundLocktime);
2137
+ if (context.collaborative) {
2138
+ if (role === "receiver" && preimage) {
2139
+ paths.push({
2140
+ leaf: script.claim(),
2141
+ extraWitness: [base.hex.decode(preimage)]
2142
+ });
1734
2143
  }
2144
+ if (role === "sender" && isCltvSatisfied(context, refundLocktime)) {
2145
+ paths.push({
2146
+ leaf: script.refundWithoutReceiver()
2147
+ });
2148
+ }
2149
+ return paths;
1735
2150
  }
1736
- let key;
1737
- if (token.startsWith("OP_")) {
1738
- key = token.slice(3);
1739
- } else {
1740
- key = token;
1741
- }
1742
- if (key in ARKADE_OPS) {
1743
- out.push(key);
1744
- continue;
2151
+ if (role === "receiver" && preimage) {
2152
+ const sequence = Number(contract.params.claimDelay);
2153
+ if (isCsvSpendable(context, sequence)) {
2154
+ paths.push({
2155
+ leaf: script.unilateralClaim(),
2156
+ extraWitness: [base.hex.decode(preimage)],
2157
+ sequence
2158
+ });
2159
+ }
1745
2160
  }
1746
- if (/^[0-9a-fA-F]+$/.test(token) && token.length % 2 === 0) {
1747
- try {
1748
- out.push(base.hex.decode(token));
1749
- continue;
1750
- } catch {
2161
+ if (role === "sender") {
2162
+ const sequence = Number(contract.params.refundNoReceiverDelay);
2163
+ if (isCsvSpendable(context, sequence)) {
2164
+ paths.push({
2165
+ leaf: script.unilateralRefundWithoutReceiver(),
2166
+ sequence
2167
+ });
1751
2168
  }
1752
2169
  }
1753
- throw new Error(`Invalid ASM token: ${token}`);
2170
+ return paths;
2171
+ },
2172
+ /**
2173
+ * Refuse an explicit spend of a lockup whose refund path has not opened.
2174
+ * @see assertVhtlcSpendableNow for why this is the one certain case.
2175
+ */
2176
+ assertSpendableNow(_script, contract, context) {
2177
+ assertVhtlcSpendableNow(contract, context);
2178
+ },
2179
+ /**
2180
+ * Never. A live VHTLC is escrow: the counterparty's claim leaf is armed,
2181
+ * and generic selection — send, settle, RENEWAL, offboard — would move the
2182
+ * lockup out from under a swap the counterparty is still entitled to
2183
+ * complete, or race a claim already in flight. The `vhtlc` handler now
2184
+ * answers the same, for the same reason.
2185
+ *
2186
+ * The gate covers `available` and renewal. Recovery is generic and names
2187
+ * nothing, so it is not covered here: `recoverVtxos` filters on
2188
+ * {@link assertSpendableNow} instead, which drops a lockup only while its
2189
+ * refund path is shut rather than forever. The explicit routes out —
2190
+ * `settle({ inputs })`, or `@arkade-os/swap`'s `pushRefundWithoutReceiver`
2191
+ * over `findLockupVtxos`' outpoints — are unaffected either way.
2192
+ */
2193
+ isGenericallySpendable: () => false,
2194
+ /**
2195
+ * The annotation leaf stamped onto every VTXO locked to this contract.
2196
+ *
2197
+ * **Without this the contract is unusable, not merely unoptimized.**
2198
+ * `deriveContractTapscripts` falls back to `script.forfeit()` for any
2199
+ * handler that does not implement {@link TapscriptDeriving}, and no VHTLC
2200
+ * script version has a `forfeit()` — `VtxoScript` does not define one and
2201
+ * `VHTLC.BaseScript` does not add one. The fallback therefore throws
2202
+ * `legacy.forfeit is not a function`, `annotatableIn` catches it, and
2203
+ * `fetchContractVtxosBulk` drops this contract's VTXOs before they are
2204
+ * persisted. The row would exist and be watched while its balance stayed
2205
+ * permanently invisible, and `getSyncState()` would report `degraded`
2206
+ * forever. (The `vhtlc` handler had the same gap and now closes it the same
2207
+ * way; nothing registers V1 rows through `ContractManager` in this repo, so
2208
+ * it was never hit here.)
2209
+ *
2210
+ * `refundWithoutReceiver` is the leaf, for both roles the annotation
2211
+ * serves, because it is the only collaborative path this wallet can
2212
+ * actually satisfy as the sender — the same conclusion `selectPath`
2213
+ * reaches, kept in step with it deliberately. The claim leaves belong to
2214
+ * the receiver and need a preimage the sender does not have; `refund` and
2215
+ * `unilateralRefund` need the counterparty; the covenant leaves are the
2216
+ * emulator's.
2217
+ *
2218
+ * **It carries a CLTV, and callers must respect it.** A settlement built on
2219
+ * this leaf is only valid once `refundLocktime` has matured, so a recovery
2220
+ * round that sweeps this VTXO early is rejected by the server. That is the
2221
+ * same constraint `packages/boltz-swap` encodes as "pre-CLTV recoverable →
2222
+ * skipped"; nothing in this handler can enforce it, because the annotation
2223
+ * is derived per contract and knows no clock. `recoverVtxos` filters on
2224
+ * {@link assertSpendableNow} for it.
2225
+ *
2226
+ * **Role-blind.** A receiver's row gets the same leaf, which names keys that
2227
+ * wallet does not hold, so it is satisfiable only by the wallet holding the
2228
+ * lockup's `sender` key and fails at intent registration for anyone else.
2229
+ */
2230
+ deriveTapscripts(script) {
2231
+ const leaf = script.refundWithoutReceiver();
2232
+ return {
2233
+ forfeitTapLeafScript: leaf,
2234
+ intentTapLeafScript: leaf,
2235
+ tapTree: script.encode()
2236
+ };
1754
2237
  }
1755
- return out;
1756
- }
1757
- function asmToBytes(asm) {
1758
- return ArkadeScript.encode(fromASM(asm));
1759
- }
1760
- function bytesToASM(script) {
1761
- return toASM(ArkadeScript.decode(script));
1762
- }
1763
- var TAG_SCRIPT = "ArkScriptHash";
1764
- var TAG_WITNESS = "ArkWitnessHash";
1765
- function arkadeScriptHash(script) {
1766
- return secp256k1_js.schnorr.utils.taggedHash(TAG_SCRIPT, script);
1767
- }
1768
- function arkadeWitnessHash(witness) {
1769
- if (witness.length === 0) {
1770
- return new Uint8Array(32);
2238
+ };
2239
+ var BoardingContractHandler = {
2240
+ type: "boarding",
2241
+ createScript(params) {
2242
+ return DefaultContractHandler.createScript(params);
2243
+ },
2244
+ serializeParams(params) {
2245
+ return DefaultContractHandler.serializeParams(params);
2246
+ },
2247
+ deserializeParams(params) {
2248
+ return DefaultContractHandler.deserializeParams(params);
2249
+ },
2250
+ selectPath(script, contract, context) {
2251
+ return DefaultContractHandler.selectPath(script, contract, context);
2252
+ },
2253
+ getAllSpendingPaths(script, contract, context) {
2254
+ return DefaultContractHandler.getAllSpendingPaths(script, contract, context);
2255
+ },
2256
+ getSpendablePaths(script, contract, context) {
2257
+ return DefaultContractHandler.getSpendablePaths(script, contract, context);
2258
+ },
2259
+ /** Preserves today's settle/board behaviour. */
2260
+ isGenericallySpendable: () => true,
2261
+ /**
2262
+ * Probe the on-chain UTXO set for a boarding output at this HD index.
2263
+ *
2264
+ * Boarding's source of truth is the **current** on-chain coin set
2265
+ * (`OnchainProvider.getCoins`), not the Ark indexer: a boarded (spent)
2266
+ * boarding output becomes an L2 VTXO at the receive index, so the
2267
+ * indexer probe already keeps the gap window open for it; only an
2268
+ * *unspent* boarding output needs the on-chain probe (see plan §2).
2269
+ *
2270
+ * No-ops (returns `[]`) when boarding discovery is not plumbed — i.e.
2271
+ * `deps.boardingTimelock` or `deps.onchainNetwork` is absent — so the
2272
+ * scanner unit harness (which sets neither) is unaffected.
2273
+ *
2274
+ * Always emits `type: "boarding"`, even in the degenerate equal-delay case
2275
+ * where the boarding script is byte-identical to a `default` script at this
2276
+ * index (`boardingExitDelay === unilateralExitDelay`). The collision is no
2277
+ * longer pre-judged in discovery; it is tolerated FIRST-WINS at the
2278
+ * persistence layer ({@link ContractManager.upsertContract}). `deps.csvTimelocks`
2279
+ * is intentionally not read here.
2280
+ */
2281
+ async discoverAt(index, descriptor, deps) {
2282
+ if (!deps.boardingTimelock || !deps.onchainNetwork) return [];
2283
+ const pubKey = deriveDescriptorLeafPubKey(descriptor);
2284
+ const script = new exports.DefaultVtxo.Script({
2285
+ pubKey,
2286
+ serverPubKey: deps.serverPubKey,
2287
+ csvTimelock: deps.boardingTimelock
2288
+ });
2289
+ const onchainAddress = script.onchainAddress(deps.onchainNetwork);
2290
+ const coins = await deps.onchainProvider.getCoins(onchainAddress);
2291
+ if (coins.length === 0) return [];
2292
+ const scriptHex = base.hex.encode(script.pkScript);
2293
+ const boardingSeq = chunkFNCQJLR2_cjs.timelockToSequence(deps.boardingTimelock);
2294
+ return [
2295
+ {
2296
+ type: "boarding",
2297
+ params: {
2298
+ pubKey: base.hex.encode(pubKey),
2299
+ serverPubKey: base.hex.encode(deps.serverPubKey),
2300
+ csvTimelock: boardingSeq.toString()
2301
+ },
2302
+ script: scriptHex,
2303
+ // The persisted row's `address` is the *Ark* address (not the
2304
+ // on-chain P2TR), matching the row registered at init so the
2305
+ // ContractWatcher keeps monitoring the same L2 script and the
2306
+ // VTXO repository bucket lines up (plan §6-I.4). The P2TR is
2307
+ // recomputed from params only for the `getCoins` probe.
2308
+ address: script.address(deps.network.hrp, deps.serverPubKey).encode(),
2309
+ // Tag rotated rows (index > 0) so boot resolution can find the
2310
+ // newest boarding address and descriptor-aware signing can
2311
+ // recover the per-index key (plan §6-II/§6-III.3). The index-0
2312
+ // baseline stays untagged, matching `default`/`delegate`.
2313
+ ...rotatedReceiveMetadata(index, descriptor)
2314
+ }
2315
+ ];
1771
2316
  }
1772
- return secp256k1_js.schnorr.utils.taggedHash(TAG_WITNESS, witness);
1773
- }
1774
- function computeArkadeScriptPublicKey(pubKey, script) {
1775
- const hash = arkadeScriptHash(script);
1776
- const xOnly = pubKey.length === 33 ? pubKey.subarray(1) : pubKey;
1777
- const point = secp256k1_js.schnorr.utils.lift_x(utils_js.bytesToNumberBE(xOnly));
1778
- const scalar = utils_js.bytesToNumberBE(hash) % secp256k1_js.secp256k1.Point.CURVE().n;
1779
- const tweak = secp256k1_js.secp256k1.Point.BASE.multiply(scalar);
1780
- return secp256k1_js.schnorr.utils.pointToBytes(point.add(tweak));
1781
- }
1782
-
1783
- // src/arkade/program.ts
2317
+ };
1784
2318
  var MinimalScriptNum = btcSigner.ScriptNum(void 0, true);
1785
2319
  var SUPPORTED_PROGRAM_VERSION = 0;
1786
2320
  function inputName(ref) {
@@ -1917,27 +2451,27 @@ function encodeTapscriptSegment(seg, pubkeys, args) {
1917
2451
  if (seg.csv) {
1918
2452
  const timelock = { type: seg.csv.type, value: resolveTimelockValue(seg.csv.value, args) };
1919
2453
  if (seg.asm) {
1920
- return chunkDSTYXT2D_cjs.ConditionCSVMultisigTapscript.encode({
2454
+ return chunkFNCQJLR2_cjs.ConditionCSVMultisigTapscript.encode({
1921
2455
  conditionScript: resolveAsm(seg.asm, args),
1922
2456
  timelock,
1923
2457
  pubkeys
1924
2458
  });
1925
2459
  }
1926
- return chunkDSTYXT2D_cjs.CSVMultisigTapscript.encode({ timelock, pubkeys });
2460
+ return chunkFNCQJLR2_cjs.CSVMultisigTapscript.encode({ timelock, pubkeys });
1927
2461
  }
1928
2462
  if (seg.cltv !== void 0) {
1929
- return chunkDSTYXT2D_cjs.CLTVMultisigTapscript.encode({
2463
+ return chunkFNCQJLR2_cjs.CLTVMultisigTapscript.encode({
1930
2464
  absoluteTimelock: resolveTimelockValue(seg.cltv, args),
1931
2465
  pubkeys
1932
2466
  });
1933
2467
  }
1934
2468
  if (seg.asm) {
1935
- return chunkDSTYXT2D_cjs.ConditionMultisigTapscript.encode({
2469
+ return chunkFNCQJLR2_cjs.ConditionMultisigTapscript.encode({
1936
2470
  conditionScript: resolveAsm(seg.asm, args),
1937
2471
  pubkeys
1938
2472
  });
1939
2473
  }
1940
- return chunkDSTYXT2D_cjs.MultisigTapscript.encode({ pubkeys });
2474
+ return chunkFNCQJLR2_cjs.MultisigTapscript.encode({ pubkeys });
1941
2475
  }
1942
2476
  function compileFunctions(program, args, keys) {
1943
2477
  if (program.version !== SUPPORTED_PROGRAM_VERSION) {
@@ -1967,7 +2501,7 @@ function compileFunctions(program, args, keys) {
1967
2501
  return { name: names[i], def, leafScript, arkadeScript, signerKeys: pubkeys };
1968
2502
  });
1969
2503
  }
1970
- var ArkadeProgramScript = class extends chunkDSTYXT2D_cjs.VtxoScript {
2504
+ var ArkadeProgramScript = class extends chunkFNCQJLR2_cjs.VtxoScript {
1971
2505
  constructor(program, args, keys) {
1972
2506
  const partial = compileFunctions(program, args, keys);
1973
2507
  super(partial.map((p) => p.leafScript));
@@ -2180,7 +2714,7 @@ function pathsFor(script, context, opts) {
2180
2714
  continue;
2181
2715
  }
2182
2716
  if (requiresServer) continue;
2183
- const sequence = csv ? Number(chunkDSTYXT2D_cjs.timelockToSequence(csv)) : void 0;
2717
+ const sequence = csv ? Number(chunkFNCQJLR2_cjs.timelockToSequence(csv)) : void 0;
2184
2718
  if (opts.checkTimelocks && csv && !isCsvSpendable(context, sequence)) continue;
2185
2719
  if (opts.checkTimelocks && cltv !== void 0 && !isCltvSatisfied(context, cltv)) {
2186
2720
  continue;
@@ -2219,6 +2753,18 @@ var ArkadeContractHandler = {
2219
2753
  getSpendablePaths(script, _contract, context) {
2220
2754
  return pathsFor(script, context, { checkTimelocks: true });
2221
2755
  },
2756
+ /**
2757
+ * Opt-in, never opt-out. A program artifact cannot say whether its funds are
2758
+ * escrowed — an escrow's collaborative cancel leaf is indistinguishable from
2759
+ * an ordinary spend — so the answer is read from the row the registering code
2760
+ * wrote. Anything but an explicit `true` (absent, malformed, or a row written
2761
+ * before this gate existed) is not generically spendable: `createContract` is
2762
+ * first-writer-wins, so a fail-open marker would let one unmarked
2763
+ * registration permanently open the gate for every contract sharing the script.
2764
+ */
2765
+ isGenericallySpendable(contract) {
2766
+ return contract.metadata?.genericallySpendable === true;
2767
+ },
2222
2768
  /**
2223
2769
  * Annotation tapscripts for VTXOs locked to this contract: prefer the
2224
2770
  * collaborative tapscript path (the multisig the Arkade Service co-signs,
@@ -2246,6 +2792,7 @@ var ArkadeContractHandler = {
2246
2792
  contractHandlers.register(DefaultContractHandler);
2247
2793
  contractHandlers.register(DelegateContractHandler);
2248
2794
  contractHandlers.register(VHTLCContractHandler);
2795
+ contractHandlers.register(VHTLCV2ContractHandler);
2249
2796
  contractHandlers.register(BoardingContractHandler);
2250
2797
  contractHandlers.register(ArkadeContractHandler);
2251
2798
 
@@ -2264,6 +2811,7 @@ exports.OPCODE_NAMES = OPCODE_NAMES;
2264
2811
  exports.OPCODE_VALUES = OPCODE_VALUES;
2265
2812
  exports.SUPPORTED_PROGRAM_VERSION = SUPPORTED_PROGRAM_VERSION;
2266
2813
  exports.VHTLCContractHandler = VHTLCContractHandler;
2814
+ exports.VHTLCV2ContractHandler = VHTLCV2ContractHandler;
2267
2815
  exports.WALLET_RECEIVE_SOURCE = WALLET_RECEIVE_SOURCE;
2268
2816
  exports.arkadeScriptHash = arkadeScriptHash;
2269
2817
  exports.arkadeWitnessHash = arkadeWitnessHash;
@@ -2279,11 +2827,14 @@ exports.deserializeArkadeContractParams = deserializeArkadeContractParams;
2279
2827
  exports.fromASM = fromASM;
2280
2828
  exports.getOpcodeName = getOpcodeName;
2281
2829
  exports.getOpcodeValue = getOpcodeValue;
2830
+ exports.identityDescriptor = identityDescriptor;
2282
2831
  exports.inputName = inputName;
2283
2832
  exports.isCltvSatisfied = isCltvSatisfied;
2284
2833
  exports.isCsvSpendable = isCsvSpendable;
2285
2834
  exports.isMainnetDescriptor = isMainnetDescriptor;
2835
+ exports.normalizeToDescriptor = normalizeToDescriptor;
2286
2836
  exports.parseArtifact = parseArtifact;
2837
+ exports.parseHDDescriptor = parseHDDescriptor;
2287
2838
  exports.resolveAsm = resolveAsm;
2288
2839
  exports.serializeArkadeContractParams = serializeArkadeContractParams;
2289
2840
  exports.speculativeReceiveMetadata = speculativeReceiveMetadata;
@@ -2291,5 +2842,5 @@ exports.stringifyArtifact = stringifyArtifact;
2291
2842
  exports.toASM = toASM;
2292
2843
  exports.validateProgram = validateProgram;
2293
2844
  exports.witnessRefToBytes = witnessRefToBytes;
2294
- //# sourceMappingURL=chunk-2XO43UHI.cjs.map
2295
- //# sourceMappingURL=chunk-2XO43UHI.cjs.map
2845
+ //# sourceMappingURL=chunk-SF5NKAUA.cjs.map
2846
+ //# sourceMappingURL=chunk-SF5NKAUA.cjs.map