@arkade-os/sdk 0.4.59 → 0.4.60

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