@arkade-os/sdk 0.4.59 → 0.4.61

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