@arkade-os/sdk 0.5.0-rc.11 → 0.5.0-rc.13

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 (94) hide show
  1. package/README.md +43 -12
  2. package/dist/adapters/expo.cjs +5 -5
  3. package/dist/adapters/expo.d.cts +2 -2
  4. package/dist/adapters/expo.d.ts +2 -2
  5. package/dist/adapters/expo.js +3 -3
  6. package/dist/{asyncStorageTaskQueue-DBaZsltv.d.cts → asyncStorageTaskQueue-0EeSeCAt.d.cts} +1 -1
  7. package/dist/{asyncStorageTaskQueue-uAG3XLyg.d.ts → asyncStorageTaskQueue-DV94jhwq.d.ts} +1 -1
  8. package/dist/{chunk-WSQAWSEH.js → chunk-3JLYJ5KZ.js} +75 -237
  9. package/dist/chunk-3JLYJ5KZ.js.map +1 -0
  10. package/dist/{chunk-VMLLYEYM.js → chunk-4EU2Q6YT.js} +12 -14
  11. package/dist/chunk-4EU2Q6YT.js.map +1 -0
  12. package/dist/{chunk-IA32YGLH.cjs → chunk-7BACS4XK.cjs} +10 -10
  13. package/dist/chunk-7BACS4XK.cjs.map +1 -0
  14. package/dist/{chunk-5OBGYL6U.cjs → chunk-7NK6XFC3.cjs} +120 -194
  15. package/dist/chunk-7NK6XFC3.cjs.map +1 -0
  16. package/dist/{chunk-CQPQ7D4U.cjs → chunk-BFQX2KS3.cjs} +87 -248
  17. package/dist/chunk-BFQX2KS3.cjs.map +1 -0
  18. package/dist/{chunk-DD6MTNAS.js → chunk-D7JLTHSZ.js} +5 -5
  19. package/dist/chunk-D7JLTHSZ.js.map +1 -0
  20. package/dist/{chunk-CMLSTGOC.js → chunk-EJYWGTZ7.js} +119 -194
  21. package/dist/chunk-EJYWGTZ7.js.map +1 -0
  22. package/dist/{chunk-T2ILYGWE.cjs → chunk-FDQQOF3F.cjs} +327 -1080
  23. package/dist/chunk-FDQQOF3F.cjs.map +1 -0
  24. package/dist/{chunk-MGDXN74N.cjs → chunk-I4YBVHAK.cjs} +2357 -3374
  25. package/dist/chunk-I4YBVHAK.cjs.map +1 -0
  26. package/dist/{chunk-M2ZUPLKQ.js → chunk-IV2252XM.js} +1946 -2977
  27. package/dist/chunk-IV2252XM.js.map +1 -0
  28. package/dist/{chunk-OMJRW2D5.js → chunk-JOUPIZ32.js} +263 -1036
  29. package/dist/chunk-JOUPIZ32.js.map +1 -0
  30. package/dist/{chunk-PWEBQNIE.cjs → chunk-RDQSVIXD.cjs} +22 -24
  31. package/dist/chunk-RDQSVIXD.cjs.map +1 -0
  32. package/dist/contracts/handlers/index.cjs +9 -9
  33. package/dist/contracts/handlers/index.d.cts +3 -3
  34. package/dist/contracts/handlers/index.d.ts +3 -3
  35. package/dist/contracts/handlers/index.js +2 -2
  36. package/dist/{delegate-DFwlkVYR.d.cts → delegate-BgpUJW5B.d.cts} +1 -1
  37. package/dist/{delegate-D3YHDERL.d.ts → delegate-BuagcGnG.d.ts} +1 -1
  38. package/dist/{index-DTvUv5lD.d.cts → index-CKmB14Yu.d.cts} +1145 -2263
  39. package/dist/{index-DTvUv5lD.d.ts → index-CKmB14Yu.d.ts} +1145 -2263
  40. package/dist/index-D0BuYwhF.d.cts +637 -0
  41. package/dist/index-DbzyrqK7.d.ts +637 -0
  42. package/dist/index.cjs +341 -297
  43. package/dist/index.d.cts +379 -788
  44. package/dist/index.d.ts +379 -788
  45. package/dist/index.js +4 -4
  46. package/dist/repositories/realm/index.cjs +142 -101
  47. package/dist/repositories/realm/index.cjs.map +1 -1
  48. package/dist/repositories/realm/index.d.cts +13 -11
  49. package/dist/repositories/realm/index.d.ts +13 -11
  50. package/dist/repositories/realm/index.js +130 -89
  51. package/dist/repositories/realm/index.js.map +1 -1
  52. package/dist/repositories/sqlite/index.cjs +158 -118
  53. package/dist/repositories/sqlite/index.cjs.map +1 -1
  54. package/dist/repositories/sqlite/index.d.cts +14 -13
  55. package/dist/repositories/sqlite/index.d.ts +14 -13
  56. package/dist/repositories/sqlite/index.js +147 -107
  57. package/dist/repositories/sqlite/index.js.map +1 -1
  58. package/dist/{taskRunner-B5bl0YRt.d.ts → taskRunner-BX-pB0sv.d.ts} +1 -1
  59. package/dist/{taskRunner--sKy9Inv.d.cts → taskRunner-DJrFFyus.d.cts} +1 -1
  60. package/dist/wallet/expo/background.cjs +14 -14
  61. package/dist/wallet/expo/background.cjs.map +1 -1
  62. package/dist/wallet/expo/background.d.cts +8 -14
  63. package/dist/wallet/expo/background.d.ts +8 -14
  64. package/dist/wallet/expo/background.js +6 -6
  65. package/dist/wallet/expo/background.js.map +1 -1
  66. package/dist/wallet/expo/index.cjs +14 -14
  67. package/dist/wallet/expo/index.cjs.map +1 -1
  68. package/dist/wallet/expo/index.d.cts +5 -5
  69. package/dist/wallet/expo/index.d.ts +5 -5
  70. package/dist/wallet/expo/index.js +5 -5
  71. package/dist/wallet/expo/index.js.map +1 -1
  72. package/dist/wallet-BsL2G3Qb.d.cts +1542 -0
  73. package/dist/wallet-DXNiCmq8.d.ts +1542 -0
  74. package/dist/worker/expo/index.cjs +9 -9
  75. package/dist/worker/expo/index.d.cts +4 -4
  76. package/dist/worker/expo/index.d.ts +4 -4
  77. package/dist/worker/expo/index.js +5 -5
  78. package/package.json +3 -2
  79. package/dist/chunk-5OBGYL6U.cjs.map +0 -1
  80. package/dist/chunk-CMLSTGOC.js.map +0 -1
  81. package/dist/chunk-CQPQ7D4U.cjs.map +0 -1
  82. package/dist/chunk-DD6MTNAS.js.map +0 -1
  83. package/dist/chunk-IA32YGLH.cjs.map +0 -1
  84. package/dist/chunk-M2ZUPLKQ.js.map +0 -1
  85. package/dist/chunk-MGDXN74N.cjs.map +0 -1
  86. package/dist/chunk-OMJRW2D5.js.map +0 -1
  87. package/dist/chunk-PWEBQNIE.cjs.map +0 -1
  88. package/dist/chunk-T2ILYGWE.cjs.map +0 -1
  89. package/dist/chunk-VMLLYEYM.js.map +0 -1
  90. package/dist/chunk-WSQAWSEH.js.map +0 -1
  91. package/dist/hdWalletCapable-C350s3bP.d.ts +0 -2206
  92. package/dist/hdWalletCapable-rrOmXcbT.d.cts +0 -2206
  93. package/dist/index-C0ZyGoyP.d.cts +0 -850
  94. package/dist/index-DASN1cVk.d.ts +0 -850
@@ -1,41 +1,25 @@
1
- import { __export, VtxoScript, MultisigTapscript, CSVMultisigTapscript, timelockToSequence, sequenceToTimelock, ConditionMultisigTapscript, CLTVMultisigTapscript, ConditionCSVMultisigTapscript, SCRIPT_QUERY_CHUNK_SIZE, getNormalizedVtxos, DEFAULT_PAGE_SIZE } from './chunk-CMLSTGOC.js';
1
+ import { __export, VtxoScript, MultisigTapscript, CSVMultisigTapscript, timelockToSequence, sequenceToTimelock, ConditionMultisigTapscript, CLTVMultisigTapscript, ConditionCSVMultisigTapscript, SCRIPT_QUERY_CHUNK_SIZE, getNormalizedVtxos, DEFAULT_PAGE_SIZE } from './chunk-EJYWGTZ7.js';
2
2
  import { hex } from '@scure/base';
3
3
  import { networks, expand } from '@bitcoinerlab/descriptors-scure';
4
4
  import { ScriptNum, OP, Script } from '@scure/btc-signer';
5
+ import * as P from 'micro-packed';
5
6
  import { schnorr, secp256k1 } from '@noble/curves/secp256k1.js';
6
7
  import { bytesToNumberBE } from '@noble/curves/utils.js';
7
8
 
8
9
  // src/contracts/handlers/registry.ts
9
10
  var ContractHandlerRegistry = class {
10
11
  handlers = /* @__PURE__ */ new Map();
11
- /**
12
- * Register a contract handler.
13
- *
14
- * @param handler - The handler to register
15
- * @throws If a handler for this type is already registered
16
- */
12
+ /** @throws If a handler for this type is already registered */
17
13
  register(handler) {
18
14
  if (this.handlers.has(handler.type)) {
19
15
  throw new Error(`Contract handler for type '${handler.type}' is already registered`);
20
16
  }
21
17
  this.handlers.set(handler.type, handler);
22
18
  }
23
- /**
24
- * Get a handler by type.
25
- *
26
- * @param type - The contract type
27
- * @returns The handler, or undefined if not found
28
- */
29
19
  get(type) {
30
20
  return this.handlers.get(type);
31
21
  }
32
- /**
33
- * Get a handler by type, throwing if not found.
34
- *
35
- * @param type - The contract type
36
- * @returns The handler
37
- * @throws If no handler is registered for this type
38
- */
22
+ /** @throws If no handler is registered for this type */
39
23
  getOrThrow(type) {
40
24
  const handler = this.get(type);
41
25
  if (!handler) {
@@ -43,29 +27,17 @@ var ContractHandlerRegistry = class {
43
27
  }
44
28
  return handler;
45
29
  }
46
- /**
47
- * Check if a handler is registered.
48
- *
49
- * @param type - The contract type
50
- */
51
30
  has(type) {
52
31
  return this.handlers.has(type);
53
32
  }
54
- /**
55
- * Get all registered types.
56
- */
57
33
  getRegisteredTypes() {
58
34
  return Array.from(this.handlers.keys());
59
35
  }
60
- /**
61
- * Unregister a handler (mainly for testing).
62
- */
36
+ /** Unregister a handler (mainly for testing). */
63
37
  unregister(type) {
64
38
  return this.handlers.delete(type);
65
39
  }
66
- /**
67
- * Clear all handlers (mainly for testing).
68
- */
40
+ /** Clear all handlers (mainly for testing). */
69
41
  clear() {
70
42
  this.handlers.clear();
71
43
  }
@@ -311,6 +283,135 @@ function assertVhtlcSpendableNow(contract, context) {
311
283
  `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.`
312
284
  );
313
285
  }
286
+ function selectVhtlcPath(script, contract, context) {
287
+ const role = resolveRole(contract, context);
288
+ const preimage = contract.params?.preimage;
289
+ const refundLocktime = BigInt(contract.params.refundLocktime);
290
+ if (!role) {
291
+ return null;
292
+ }
293
+ if (context.collaborative) {
294
+ if (role === "receiver" && preimage) {
295
+ return {
296
+ leaf: script.claim(),
297
+ extraWitness: [hex.decode(preimage)]
298
+ };
299
+ }
300
+ if (role === "sender" && isCltvSatisfied(context, refundLocktime)) {
301
+ return {
302
+ leaf: script.refundWithoutReceiver()
303
+ };
304
+ }
305
+ return null;
306
+ }
307
+ if (role === "receiver" && preimage) {
308
+ const sequence = Number(contract.params.claimDelay);
309
+ if (!isCsvSpendable(context, sequence)) return null;
310
+ return {
311
+ leaf: script.unilateralClaim(),
312
+ extraWitness: [hex.decode(preimage)],
313
+ sequence
314
+ };
315
+ }
316
+ if (role === "sender") {
317
+ const sequence = Number(contract.params.refundNoReceiverDelay);
318
+ if (!isCsvSpendable(context, sequence)) return null;
319
+ return {
320
+ leaf: script.unilateralRefundWithoutReceiver(),
321
+ sequence
322
+ };
323
+ }
324
+ return null;
325
+ }
326
+ function vhtlcAllSpendingPaths(script, contract, context) {
327
+ const role = resolveRole(contract, context);
328
+ const paths = [];
329
+ if (!role) {
330
+ return paths;
331
+ }
332
+ const preimage = contract.params?.preimage;
333
+ if (context.collaborative) {
334
+ if (role === "receiver" && preimage) {
335
+ paths.push({
336
+ leaf: script.claim(),
337
+ extraWitness: [hex.decode(preimage)]
338
+ });
339
+ }
340
+ if (role === "sender") {
341
+ paths.push({
342
+ leaf: script.refundWithoutReceiver()
343
+ });
344
+ }
345
+ } else {
346
+ if (role === "receiver" && preimage) {
347
+ const sequence = Number(contract.params.claimDelay);
348
+ paths.push({
349
+ leaf: script.unilateralClaim(),
350
+ extraWitness: [hex.decode(preimage)],
351
+ sequence
352
+ });
353
+ }
354
+ if (role === "sender") {
355
+ const sequence = Number(contract.params.refundNoReceiverDelay);
356
+ paths.push({
357
+ leaf: script.unilateralRefundWithoutReceiver(),
358
+ sequence
359
+ });
360
+ }
361
+ }
362
+ return paths;
363
+ }
364
+ function vhtlcSpendablePaths(script, contract, context) {
365
+ const role = resolveRole(contract, context);
366
+ const paths = [];
367
+ if (!role) {
368
+ return paths;
369
+ }
370
+ const preimage = contract.params?.preimage;
371
+ const refundLocktime = BigInt(contract.params.refundLocktime);
372
+ if (context.collaborative) {
373
+ if (role === "receiver" && preimage) {
374
+ paths.push({
375
+ leaf: script.claim(),
376
+ extraWitness: [hex.decode(preimage)]
377
+ });
378
+ }
379
+ if (role === "sender" && isCltvSatisfied(context, refundLocktime)) {
380
+ paths.push({
381
+ leaf: script.refundWithoutReceiver()
382
+ });
383
+ }
384
+ return paths;
385
+ }
386
+ if (role === "receiver" && preimage) {
387
+ const sequence = Number(contract.params.claimDelay);
388
+ if (isCsvSpendable(context, sequence)) {
389
+ paths.push({
390
+ leaf: script.unilateralClaim(),
391
+ extraWitness: [hex.decode(preimage)],
392
+ sequence
393
+ });
394
+ }
395
+ }
396
+ if (role === "sender") {
397
+ const sequence = Number(contract.params.refundNoReceiverDelay);
398
+ if (isCsvSpendable(context, sequence)) {
399
+ paths.push({
400
+ leaf: script.unilateralRefundWithoutReceiver(),
401
+ sequence
402
+ });
403
+ }
404
+ }
405
+ return paths;
406
+ }
407
+ function deriveVhtlcTapscripts(script) {
408
+ const leaf = script.refundWithoutReceiver();
409
+ return {
410
+ forfeitTapLeafScript: leaf,
411
+ intentTapLeafScript: leaf,
412
+ tapTree: script.encode()
413
+ };
414
+ }
314
415
  function isCsvSpendable(context, sequence) {
315
416
  if (sequence === void 0) return true;
316
417
  if (!context.vtxo) return false;
@@ -479,11 +580,7 @@ function defaultCandidatesAt(_index, descriptor, deps) {
479
580
  (opts) => new DefaultVtxo.Script(opts)
480
581
  ).map((c) => ({
481
582
  type: "default",
482
- params: {
483
- pubKey: hex.encode(c.pubKey),
484
- serverPubKey: hex.encode(c.serverPubKey),
485
- csvTimelock: timelockToSequence(c.csvTimelock).toString()
486
- },
583
+ params: DefaultContractHandler.serializeParams(c),
487
584
  script: c.scriptHex,
488
585
  address: c.script.address(deps.network.hrp, c.serverPubKey).encode()
489
586
  }));
@@ -582,12 +679,7 @@ function delegateCandidatesAt(_index, descriptor, deps) {
582
679
  (opts) => new DelegateVtxo.Script({ ...opts, delegatePubKey })
583
680
  ).map((c) => ({
584
681
  type: "delegate",
585
- params: {
586
- pubKey: hex.encode(c.pubKey),
587
- serverPubKey: hex.encode(c.serverPubKey),
588
- delegatePubKey: hex.encode(delegatePubKey),
589
- csvTimelock: timelockToSequence(c.csvTimelock).toString()
590
- },
682
+ params: DelegateContractHandler.serializeParams({ ...c, delegatePubKey }),
591
683
  script: c.scriptHex,
592
684
  address: c.script.address(deps.network.hrp, c.serverPubKey).encode()
593
685
  }));
@@ -603,512 +695,6 @@ function discoverDelegateRange(entries, deps) {
603
695
  (c, index, descriptor) => ({ ...c, ...rotatedReceiveMetadata(index, descriptor) })
604
696
  );
605
697
  }
606
- function equalBytes(a, b) {
607
- if (a.length !== b.length)
608
- return false;
609
- for (let i = 0; i < a.length; i++)
610
- if (a[i] !== b[i])
611
- return false;
612
- return true;
613
- }
614
- function isBytes(a) {
615
- return a instanceof Uint8Array || ArrayBuffer.isView(a) && a.constructor.name === "Uint8Array";
616
- }
617
- var createView = (arr) => new DataView(arr.buffer, arr.byteOffset, arr.byteLength);
618
- function isPlainObject(obj) {
619
- return Object.prototype.toString.call(obj) === "[object Object]";
620
- }
621
- function isNum(num) {
622
- return Number.isSafeInteger(num);
623
- }
624
- var Bitset = {
625
- BITS: 32,
626
- FULL_MASK: -1 >>> 0,
627
- // 1<<32 will overflow
628
- len: (len) => Math.ceil(len / 32),
629
- create: (len) => new Uint32Array(Bitset.len(len)),
630
- clean: (bs) => bs.fill(0),
631
- debug: (bs) => Array.from(bs).map((i) => (i >>> 0).toString(2).padStart(32, "0")),
632
- checkLen: (bs, len) => {
633
- if (Bitset.len(len) === bs.length)
634
- return;
635
- throw new Error(`wrong length=${bs.length}. Expected: ${Bitset.len(len)}`);
636
- },
637
- chunkLen: (bsLen, pos, len) => {
638
- if (pos < 0)
639
- throw new Error(`wrong pos=${pos}`);
640
- if (pos + len > bsLen)
641
- throw new Error(`wrong range=${pos}/${len} of ${bsLen}`);
642
- },
643
- set: (bs, chunk, value, allowRewrite = true) => {
644
- if (!allowRewrite && (bs[chunk] & value) !== 0)
645
- return false;
646
- bs[chunk] |= value;
647
- return true;
648
- },
649
- pos: (pos, i) => ({
650
- chunk: Math.floor((pos + i) / 32),
651
- mask: 1 << 32 - (pos + i) % 32 - 1
652
- }),
653
- indices: (bs, len, invert = false) => {
654
- Bitset.checkLen(bs, len);
655
- const { FULL_MASK, BITS } = Bitset;
656
- const left = BITS - len % BITS;
657
- const lastMask = left ? FULL_MASK >>> left << left : FULL_MASK;
658
- const res = [];
659
- for (let i = 0; i < bs.length; i++) {
660
- let c = bs[i];
661
- if (invert)
662
- c = ~c;
663
- if (i === bs.length - 1)
664
- c &= lastMask;
665
- if (c === 0)
666
- continue;
667
- for (let j = 0; j < BITS; j++) {
668
- const m = 1 << BITS - j - 1;
669
- if (c & m)
670
- res.push(i * BITS + j);
671
- }
672
- }
673
- return res;
674
- },
675
- range: (arr) => {
676
- const res = [];
677
- let cur;
678
- for (const i of arr) {
679
- if (cur === void 0 || i !== cur.pos + cur.length)
680
- res.push(cur = { pos: i, length: 1 });
681
- else
682
- cur.length += 1;
683
- }
684
- return res;
685
- },
686
- rangeDebug: (bs, len, invert = false) => `[${Bitset.range(Bitset.indices(bs, len, invert)).map((i) => `(${i.pos}/${i.length})`).join(", ")}]`,
687
- setRange: (bs, bsLen, pos, len, allowRewrite = true) => {
688
- Bitset.chunkLen(bsLen, pos, len);
689
- const { FULL_MASK, BITS } = Bitset;
690
- const first = pos % BITS ? Math.floor(pos / BITS) : void 0;
691
- const lastPos = pos + len;
692
- const last = lastPos % BITS ? Math.floor(lastPos / BITS) : void 0;
693
- if (first !== void 0 && first === last)
694
- return Bitset.set(bs, first, FULL_MASK >>> BITS - len << BITS - len - pos, allowRewrite);
695
- if (first !== void 0) {
696
- if (!Bitset.set(bs, first, FULL_MASK >>> pos % BITS, allowRewrite))
697
- return false;
698
- }
699
- const start = first !== void 0 ? first + 1 : pos / BITS;
700
- const end = last !== void 0 ? last : lastPos / BITS;
701
- for (let i = start; i < end; i++)
702
- if (!Bitset.set(bs, i, FULL_MASK, allowRewrite))
703
- return false;
704
- if (last !== void 0 && first !== last) {
705
- if (!Bitset.set(bs, last, FULL_MASK << BITS - lastPos % BITS, allowRewrite))
706
- return false;
707
- }
708
- return true;
709
- }
710
- };
711
- var Path = {
712
- /**
713
- * Internal method for handling stack of paths (debug, errors, dynamic fields via path)
714
- * This is looks ugly (callback), but allows us to force stack cleaning by construction (.pop always after function).
715
- * Also, this makes impossible:
716
- * - pushing field when stack is empty
717
- * - pushing field inside of field (real bug)
718
- * NOTE: we don't want to do '.pop' on error!
719
- */
720
- pushObj: (stack, obj, objFn) => {
721
- const last = { obj };
722
- stack.push(last);
723
- objFn((field, fieldFn) => {
724
- last.field = field;
725
- fieldFn();
726
- last.field = void 0;
727
- });
728
- stack.pop();
729
- },
730
- path: (stack) => {
731
- const res = [];
732
- for (const i of stack)
733
- if (i.field !== void 0)
734
- res.push(i.field);
735
- return res.join("/");
736
- },
737
- err: (name, stack, msg) => {
738
- const err = new Error(`${name}(${Path.path(stack)}): ${typeof msg === "string" ? msg : msg.message}`);
739
- if (msg instanceof Error && msg.stack)
740
- err.stack = msg.stack;
741
- return err;
742
- },
743
- resolve: (stack, path) => {
744
- const parts = path.split("/");
745
- const objPath = stack.map((i2) => i2.obj);
746
- let i = 0;
747
- for (; i < parts.length; i++) {
748
- if (parts[i] === "..")
749
- objPath.pop();
750
- else
751
- break;
752
- }
753
- let cur = objPath.pop();
754
- for (; i < parts.length; i++) {
755
- if (!cur || cur[parts[i]] === void 0)
756
- return void 0;
757
- cur = cur[parts[i]];
758
- }
759
- return cur;
760
- }
761
- };
762
- var _Reader = class __Reader {
763
- pos = 0;
764
- data;
765
- opts;
766
- stack;
767
- parent;
768
- parentOffset;
769
- bitBuf = 0;
770
- bitPos = 0;
771
- bs;
772
- // bitset
773
- view;
774
- constructor(data, opts = {}, stack = [], parent = void 0, parentOffset = 0) {
775
- this.data = data;
776
- this.opts = opts;
777
- this.stack = stack;
778
- this.parent = parent;
779
- this.parentOffset = parentOffset;
780
- this.view = createView(data);
781
- }
782
- /** Internal method for pointers. */
783
- _enablePointers() {
784
- if (this.parent)
785
- return this.parent._enablePointers();
786
- if (this.bs)
787
- return;
788
- this.bs = Bitset.create(this.data.length);
789
- Bitset.setRange(this.bs, this.data.length, 0, this.pos, this.opts.allowMultipleReads);
790
- }
791
- markBytesBS(pos, len) {
792
- if (this.parent)
793
- return this.parent.markBytesBS(this.parentOffset + pos, len);
794
- if (!len)
795
- return true;
796
- if (!this.bs)
797
- return true;
798
- return Bitset.setRange(this.bs, this.data.length, pos, len, false);
799
- }
800
- markBytes(len) {
801
- const pos = this.pos;
802
- this.pos += len;
803
- const res = this.markBytesBS(pos, len);
804
- if (!this.opts.allowMultipleReads && !res)
805
- throw this.err(`multiple read pos=${this.pos} len=${len}`);
806
- return res;
807
- }
808
- pushObj(obj, objFn) {
809
- return Path.pushObj(this.stack, obj, objFn);
810
- }
811
- readView(n, fn) {
812
- if (!Number.isFinite(n))
813
- throw this.err(`readView: wrong length=${n}`);
814
- if (this.pos + n > this.data.length)
815
- throw this.err("readView: Unexpected end of buffer");
816
- const res = fn(this.view, this.pos);
817
- this.markBytes(n);
818
- return res;
819
- }
820
- // read bytes by absolute offset
821
- absBytes(n) {
822
- if (n > this.data.length)
823
- throw new Error("Unexpected end of buffer");
824
- return this.data.subarray(n);
825
- }
826
- finish() {
827
- if (this.opts.allowUnreadBytes)
828
- return;
829
- if (this.bitPos) {
830
- throw this.err(`${this.bitPos} bits left after unpack: ${hex.encode(this.data.slice(this.pos))}`);
831
- }
832
- if (this.bs && !this.parent) {
833
- const notRead = Bitset.indices(this.bs, this.data.length, true);
834
- if (notRead.length) {
835
- const formatted = Bitset.range(notRead).map(({ pos, length }) => `(${pos}/${length})[${hex.encode(this.data.subarray(pos, pos + length))}]`).join(", ");
836
- throw this.err(`unread byte ranges: ${formatted} (total=${this.data.length})`);
837
- } else
838
- return;
839
- }
840
- if (!this.isEnd()) {
841
- throw this.err(`${this.leftBytes} bytes ${this.bitPos} bits left after unpack: ${hex.encode(this.data.slice(this.pos))}`);
842
- }
843
- }
844
- // User methods
845
- err(msg) {
846
- return Path.err("Reader", this.stack, msg);
847
- }
848
- offsetReader(n) {
849
- if (n > this.data.length)
850
- throw this.err("offsetReader: Unexpected end of buffer");
851
- return new __Reader(this.absBytes(n), this.opts, this.stack, this, n);
852
- }
853
- bytes(n, peek = false) {
854
- if (this.bitPos)
855
- throw this.err("readBytes: bitPos not empty");
856
- if (!Number.isFinite(n))
857
- throw this.err(`readBytes: wrong length=${n}`);
858
- if (this.pos + n > this.data.length)
859
- throw this.err("readBytes: Unexpected end of buffer");
860
- const slice = this.data.subarray(this.pos, this.pos + n);
861
- if (!peek)
862
- this.markBytes(n);
863
- return slice;
864
- }
865
- byte(peek = false) {
866
- if (this.bitPos)
867
- throw this.err("readByte: bitPos not empty");
868
- if (this.pos + 1 > this.data.length)
869
- throw this.err("readBytes: Unexpected end of buffer");
870
- const data = this.data[this.pos];
871
- if (!peek)
872
- this.markBytes(1);
873
- return data;
874
- }
875
- get leftBytes() {
876
- return this.data.length - this.pos;
877
- }
878
- get totalBytes() {
879
- return this.data.length;
880
- }
881
- isEnd() {
882
- return this.pos >= this.data.length && !this.bitPos;
883
- }
884
- // bits are read in BE mode (left to right): (0b1000_0000).readBits(1) == 1
885
- bits(bits) {
886
- if (bits > 32)
887
- throw this.err("BitReader: cannot read more than 32 bits in single call");
888
- let out = 0;
889
- while (bits) {
890
- if (!this.bitPos) {
891
- this.bitBuf = this.byte();
892
- this.bitPos = 8;
893
- }
894
- const take = Math.min(bits, this.bitPos);
895
- this.bitPos -= take;
896
- out = out << take | this.bitBuf >> this.bitPos & 2 ** take - 1;
897
- this.bitBuf &= 2 ** this.bitPos - 1;
898
- bits -= take;
899
- }
900
- return out >>> 0;
901
- }
902
- find(needle, pos = this.pos) {
903
- if (!isBytes(needle))
904
- throw this.err(`find: needle is not bytes! ${needle}`);
905
- if (this.bitPos)
906
- throw this.err("findByte: bitPos not empty");
907
- if (!needle.length)
908
- throw this.err(`find: needle is empty`);
909
- for (let idx = pos; (idx = this.data.indexOf(needle[0], idx)) !== -1; idx++) {
910
- if (idx === -1)
911
- return;
912
- const leftBytes = this.data.length - idx;
913
- if (leftBytes < needle.length)
914
- return;
915
- if (equalBytes(needle, this.data.subarray(idx, idx + needle.length)))
916
- return idx;
917
- }
918
- return;
919
- }
920
- };
921
- var _Writer = class {
922
- pos = 0;
923
- stack;
924
- // We could have a single buffer here and re-alloc it with
925
- // x1.5-2 size each time it full, but it will be slower:
926
- // basic/encode bench: 395ns -> 560ns
927
- buffers = [];
928
- ptrs = [];
929
- bitBuf = 0;
930
- bitPos = 0;
931
- viewBuf = new Uint8Array(8);
932
- view;
933
- finished = false;
934
- constructor(stack = []) {
935
- this.stack = stack;
936
- this.view = createView(this.viewBuf);
937
- }
938
- pushObj(obj, objFn) {
939
- return Path.pushObj(this.stack, obj, objFn);
940
- }
941
- writeView(len, fn) {
942
- if (this.finished)
943
- throw this.err("buffer: finished");
944
- if (!isNum(len) || len > 8)
945
- throw new Error(`wrong writeView length=${len}`);
946
- fn(this.view);
947
- this.bytes(this.viewBuf.slice(0, len));
948
- this.viewBuf.fill(0);
949
- }
950
- // User methods
951
- err(msg) {
952
- if (this.finished)
953
- throw this.err("buffer: finished");
954
- return Path.err("Reader", this.stack, msg);
955
- }
956
- bytes(b) {
957
- if (this.finished)
958
- throw this.err("buffer: finished");
959
- if (this.bitPos)
960
- throw this.err("writeBytes: ends with non-empty bit buffer");
961
- this.buffers.push(b);
962
- this.pos += b.length;
963
- }
964
- byte(b) {
965
- if (this.finished)
966
- throw this.err("buffer: finished");
967
- if (this.bitPos)
968
- throw this.err("writeByte: ends with non-empty bit buffer");
969
- this.buffers.push(new Uint8Array([b]));
970
- this.pos++;
971
- }
972
- finish(clean = true) {
973
- if (this.finished)
974
- throw this.err("buffer: finished");
975
- if (this.bitPos)
976
- throw this.err("buffer: ends with non-empty bit buffer");
977
- const buffers = this.buffers.concat(this.ptrs.map((i) => i.buffer));
978
- const sum = buffers.map((b) => b.length).reduce((a, b) => a + b, 0);
979
- const buf = new Uint8Array(sum);
980
- for (let i = 0, pad = 0; i < buffers.length; i++) {
981
- const a = buffers[i];
982
- buf.set(a, pad);
983
- pad += a.length;
984
- }
985
- for (let pos = this.pos, i = 0; i < this.ptrs.length; i++) {
986
- const ptr = this.ptrs[i];
987
- buf.set(ptr.ptr.encode(pos), ptr.pos);
988
- pos += ptr.buffer.length;
989
- }
990
- if (clean) {
991
- this.buffers = [];
992
- for (const p of this.ptrs)
993
- p.buffer.fill(0);
994
- this.ptrs = [];
995
- this.finished = true;
996
- this.bitBuf = 0;
997
- }
998
- return buf;
999
- }
1000
- bits(value, bits) {
1001
- if (bits > 32)
1002
- throw this.err("writeBits: cannot write more than 32 bits in single call");
1003
- if (value >= 2 ** bits)
1004
- throw this.err(`writeBits: value (${value}) >= 2**bits (${bits})`);
1005
- while (bits) {
1006
- const take = Math.min(bits, 8 - this.bitPos);
1007
- this.bitBuf = this.bitBuf << take | value >> bits - take;
1008
- this.bitPos += take;
1009
- bits -= take;
1010
- value &= 2 ** bits - 1;
1011
- if (this.bitPos === 8) {
1012
- this.bitPos = 0;
1013
- this.buffers.push(new Uint8Array([this.bitBuf]));
1014
- this.pos++;
1015
- }
1016
- }
1017
- }
1018
- };
1019
- function _wrap(inner) {
1020
- return {
1021
- // NOTE: we cannot export validate here, since it is likely mistake.
1022
- encodeStream: inner.encodeStream,
1023
- decodeStream: inner.decodeStream,
1024
- size: inner.size,
1025
- encode: (value) => {
1026
- const w = new _Writer();
1027
- inner.encodeStream(w, value);
1028
- return w.finish();
1029
- },
1030
- decode: (data, opts = {}) => {
1031
- const r = new _Reader(data, opts);
1032
- const res = inner.decodeStream(r);
1033
- r.finish();
1034
- return res;
1035
- }
1036
- };
1037
- }
1038
- function validate(inner, fn) {
1039
- if (!isCoder(inner))
1040
- throw new Error(`validate: invalid inner value ${inner}`);
1041
- if (typeof fn !== "function")
1042
- throw new Error("validate: fn should be function");
1043
- return _wrap({
1044
- size: inner.size,
1045
- encodeStream: (w, value) => {
1046
- let res;
1047
- try {
1048
- res = fn(value);
1049
- } catch (e) {
1050
- throw w.err(e);
1051
- }
1052
- inner.encodeStream(w, res);
1053
- },
1054
- decodeStream: (r) => {
1055
- const res = inner.decodeStream(r);
1056
- try {
1057
- return fn(res);
1058
- } catch (e) {
1059
- throw r.err(e);
1060
- }
1061
- }
1062
- });
1063
- }
1064
- var wrap = (inner) => {
1065
- const res = _wrap(inner);
1066
- return inner.validate ? validate(res, inner.validate) : res;
1067
- };
1068
- var isBaseCoder = (elm) => isPlainObject(elm) && typeof elm.decode === "function" && typeof elm.encode === "function";
1069
- function isCoder(elm) {
1070
- return isPlainObject(elm) && isBaseCoder(elm) && typeof elm.encodeStream === "function" && typeof elm.decodeStream === "function" && (elm.size === void 0 || isNum(elm.size));
1071
- }
1072
- var view = (len, opts) => wrap({
1073
- size: len,
1074
- encodeStream: (w, value) => w.writeView(len, (view2) => opts.write(view2, value)),
1075
- decodeStream: (r) => r.readView(len, opts.read),
1076
- validate: (value) => {
1077
- if (typeof value !== "number")
1078
- throw new Error(`viewCoder: expected number, got ${typeof value}`);
1079
- if (opts.validate)
1080
- opts.validate(value);
1081
- return value;
1082
- }
1083
- });
1084
- var intView = (len, signed, opts) => {
1085
- const bits = len * 8;
1086
- const maxVal = 2 ** bits;
1087
- const validateUnsigned = (value) => {
1088
- if (!isNum(value))
1089
- throw new Error(`uintView: value is not safe integer: ${value}`);
1090
- if (0 > value || value >= maxVal) {
1091
- throw new Error(`uintView: value out of bounds. Expected 0 <= ${value} < ${maxVal}`);
1092
- }
1093
- };
1094
- return view(len, {
1095
- write: opts.write,
1096
- read: opts.read,
1097
- validate: validateUnsigned
1098
- });
1099
- };
1100
- var U32LE = /* @__PURE__ */ intView(4, false, {
1101
- read: (view2, pos) => view2.getUint32(pos, true),
1102
- write: (view2, value) => view2.setUint32(0, value, true)
1103
- });
1104
- var U16LE = /* @__PURE__ */ intView(2, false, {
1105
- read: (view2, pos) => view2.getUint16(pos, true),
1106
- write: (view2, value) => view2.setUint16(0, value, true)
1107
- });
1108
- var U8 = /* @__PURE__ */ intView(1, false, {
1109
- read: (view2, pos) => view2.getUint8(pos),
1110
- write: (view2, value) => view2.setUint8(0, value)
1111
- });
1112
698
  var ARKADE_OP = {
1113
699
  // Merkle Branch Verification (0xb3 — repurposed NOP4 slot)
1114
700
  MERKLEBRANCHVERIFY: 179,
@@ -1145,7 +731,6 @@ var ARKADE_OP = {
1145
731
  MODEXP: 218,
1146
732
  // VTXO Expiry & Emulator Clock (0xdb-0xdc)
1147
733
  PUSHEXPIRY: 219,
1148
- // compiler artifacts use OP_CHECKTIME.
1149
734
  CHECKTIME: 220,
1150
735
  // 0xdd-0xdf are unassigned (OP_UNKNOWN221-223)
1151
736
  // EC Operations (0xe0-0xe2)
@@ -1275,7 +860,7 @@ var ArkadeOPNames = {};
1275
860
  for (const [k, v] of Object.entries(ARKADE_OPS)) {
1276
861
  if (typeof v === "number") ArkadeOPNames[v] = k;
1277
862
  }
1278
- var ArkadeScript = wrap({
863
+ var ArkadeScript = P.wrap({
1279
864
  encodeStream: (w, value) => {
1280
865
  for (let o of value) {
1281
866
  if (typeof o === "string") {
@@ -1301,10 +886,10 @@ var ArkadeScript = wrap({
1301
886
  w.byte(len);
1302
887
  } else if (len <= 65535) {
1303
888
  w.byte(OP.PUSHDATA2);
1304
- w.bytes(U16LE.encode(len));
889
+ w.bytes(P.U16LE.encode(len));
1305
890
  } else {
1306
891
  w.byte(OP.PUSHDATA4);
1307
- w.bytes(U32LE.encode(len));
892
+ w.bytes(P.U32LE.encode(len));
1308
893
  }
1309
894
  w.bytes(o);
1310
895
  }
@@ -1316,9 +901,9 @@ var ArkadeScript = wrap({
1316
901
  if (OP.OP_0 < cur && cur <= OP.PUSHDATA4) {
1317
902
  let len;
1318
903
  if (cur < OP.PUSHDATA1) len = cur;
1319
- else if (cur === OP.PUSHDATA1) len = U8.decodeStream(r);
1320
- else if (cur === OP.PUSHDATA2) len = U16LE.decodeStream(r);
1321
- else if (cur === OP.PUSHDATA4) len = U32LE.decodeStream(r);
904
+ else if (cur === OP.PUSHDATA1) len = P.U8.decodeStream(r);
905
+ else if (cur === OP.PUSHDATA2) len = P.U16LE.decodeStream(r);
906
+ else if (cur === OP.PUSHDATA4) len = P.U32LE.decodeStream(r);
1322
907
  else throw new Error("Should be not possible");
1323
908
  out.push(r.bytes(len));
1324
909
  } else if (cur === 0) {
@@ -1600,54 +1185,19 @@ var VHTLC;
1600
1185
  /**
1601
1186
  * Return the timelocked non-interactive refund tapleaf and its ArkadeScript.
1602
1187
  *
1603
- * SPENDING THIS LEAF, CONCRETELY — confirmed from this SDK's own
1604
- * emulator client and covenant-spend builder ({@link
1605
- * EmulatorProvider} in `src/providers/emulator.ts`, and {@link
1606
- * ArkadeTransactionBuilder} in `src/arkade/contract.ts`), which is
1607
- * the general machinery every covenant leaf in this file goes
1608
- * through:
1609
- *
1610
- * 1. Build the ark tx spending this leaf (this method's {@link
1611
- * TapLeafScript} as `tapLeafScript`, this script's {@link
1612
- * VtxoScript.encode} as `tapTree`), plus its checkpoint tx.
1613
- * 2. Attach the ArkadeScript this method returns to the spent
1614
- * input via an `EmulatorPacket` (type 1) — `{ vin, script:
1615
- * <this ArkadeScript>, witness: <empty> }`, empty because the
1616
- * script pushes its own input/output index with
1617
- * `PUSHCURRENTINPUTINDEX` rather than reading one from the
1618
- * witness — wrapped in an `Extension` OP_RETURN output on the
1619
- * ark tx.
1620
- * 3. Attach the spent input's OWN creating ark tx as `PrevArkTx` on
1621
- * input 0 — required so the emulator can resolve that input's
1622
- * prevout pkScript/value; it does not otherwise have them.
1623
- * 4. POST the (base64-PSBT) ark tx and checkpoint(s) as `{ arkTx,
1624
- * checkpointTxs }` to the emulator's `POST /v1/tx`.
1188
+ * Spent through the generic covenant path ({@link EmulatorProvider},
1189
+ * {@link ArkadeTransactionBuilder}): build the ark tx on this leaf plus its checkpoint;
1190
+ * attach this ArkadeScript to the input in an `EmulatorPacket` (type 1, empty witness:
1191
+ * the script pushes its own index with `PUSHCURRENTINPUTINDEX`) inside an `Extension`
1192
+ * OP_RETURN; attach the input's creating ark tx as `PrevArkTx` (the emulator can't
1193
+ * otherwise resolve its prevout); POST to the emulator's `/v1/tx`. The emulator co-signs
1194
+ * for `nirCosigner` only if `enforcePayTo(senderPkScript)` holds, and finalizes with arkd.
1195
+ * `server`'s signature is still required at consensus level, outside the covenant.
1625
1196
  *
1626
- * THE COVENANT CHECK the emulator runs against that ArkadeScript —
1627
- * `enforcePayTo(senderPkScript)`, see that function above — is: the
1628
- * output at this SAME input's index is a v1 P2TR output paying
1629
- * `senderPkScript`, with a value at least the input's. Only on that
1630
- * check passing does the emulator sign for `nirCosigner` (the
1631
- * `emulatorPubkey` tweaked by this ArkadeScript's hash); the
1632
- * response carries the fully co-signed `signedArkTx` and
1633
- * `signedCheckpointTxs` — `ArkadeTransactionBuilder.send()`'s own
1634
- * comment on this path: "the emulator executes the arkade script
1635
- * and finalizes with arkd" — no separate call to arkd is made by
1636
- * this SDK for a covenant spend.
1637
- * Neither `server` nor `receiver` play any part in that check: this
1638
- * leaf's tapscript still requires `server`'s own signature
1639
- * alongside `nirCosigner`'s (see the class doc above), but that is
1640
- * consensus-level multisig, not something the covenant enforces.
1641
- *
1642
- * NOT SPENDABLE UNTIL `refundLocktime` MATURES — the tapscript's
1643
- * `OP_CHECKLOCKTIMEVERIFY` is arkd's concern, not the emulator's;
1644
- * the emulator will happily co-sign an immature spend and arkd will
1645
- * then refuse it. A block-height-typed `refundLocktime` (below the
1646
- * standard height/timestamp boundary this SDK names
1647
- * `CLTV_HEIGHT_THRESHOLD`, 500,000,000, in
1648
- * `src/contracts/handlers/helpers.ts`) is refused by arkd for a
1649
- * forfeit-eligible leaf independent of maturity — use a
1650
- * seconds-typed (absolute Unix timestamp) value.
1197
+ * The emulator does not check `refundLocktime`: it co-signs an immature spend and arkd's
1198
+ * CLTV refuses it. arkd also refuses a block-height `refundLocktime`
1199
+ * (< `CLTV_HEIGHT_THRESHOLD`, 500,000,000) on a forfeit-eligible leaf regardless of
1200
+ * maturity: use a seconds-typed (Unix timestamp) value.
1651
1201
  */
1652
1202
  nonInteractiveRefundWithoutReceiver() {
1653
1203
  if (!this.nonInteractiveRefundWithoutReceiverScript || !this.nonInteractiveRefundWithoutReceiverArkadeScript) {
@@ -1790,10 +1340,7 @@ function enforcePayToAsset(destinationPkScript, asset) {
1790
1340
  }
1791
1341
  const inspectionTxid = Uint8Array.from(asset.txid).reverse();
1792
1342
  return ArkadeScript.encode([
1793
- // The output carries at least as much of the asset as the input did.
1794
- // Output index is the input's -- the same index alignment the sat
1795
- // covenant relies on, and the same liveness obligation on whoever
1796
- // assembles the spend.
1343
+ // The output at the input's index carries at least the input's amount of the asset.
1797
1344
  "PUSHCURRENTINPUTINDEX",
1798
1345
  inspectionTxid,
1799
1346
  asset.groupIndex,
@@ -1857,139 +1404,18 @@ var VHTLCContractHandler = {
1857
1404
  };
1858
1405
  },
1859
1406
  /**
1860
- * Select spending path based on context.
1861
- *
1862
- * Role is determined from `context.role` or by matching
1863
- * `context.walletDescriptor`
1864
- * against sender/receiver in contract params.
1407
+ * Select spending path based on context. Role comes from `context.role` or from matching
1408
+ * `context.walletDescriptor` against sender/receiver.
1865
1409
  */
1866
1410
  selectPath(script, contract, context) {
1867
- const role = resolveRole(contract, context);
1868
- const preimage = contract.params?.preimage;
1869
- const refundLocktime = BigInt(contract.params.refundLocktime);
1870
- if (!role) {
1871
- return null;
1872
- }
1873
- if (context.collaborative) {
1874
- if (role === "receiver" && preimage) {
1875
- return {
1876
- leaf: script.claim(),
1877
- extraWitness: [hex.decode(preimage)]
1878
- };
1879
- }
1880
- if (role === "sender" && isCltvSatisfied(context, refundLocktime)) {
1881
- return {
1882
- leaf: script.refundWithoutReceiver()
1883
- };
1884
- }
1885
- return null;
1886
- }
1887
- if (role === "receiver" && preimage) {
1888
- const sequence = Number(contract.params.claimDelay);
1889
- if (!isCsvSpendable(context, sequence)) return null;
1890
- return {
1891
- leaf: script.unilateralClaim(),
1892
- extraWitness: [hex.decode(preimage)],
1893
- sequence
1894
- };
1895
- }
1896
- if (role === "sender") {
1897
- const sequence = Number(contract.params.refundNoReceiverDelay);
1898
- if (!isCsvSpendable(context, sequence)) return null;
1899
- return {
1900
- leaf: script.unilateralRefundWithoutReceiver(),
1901
- sequence
1902
- };
1903
- }
1904
- return null;
1411
+ return selectVhtlcPath(script, contract, context);
1905
1412
  },
1906
- /**
1907
- * Get all possible spending paths (no timelock checks).
1908
- *
1909
- * Role is determined from `context.role` or by matching
1910
- * `context.walletDescriptor`
1911
- * against sender/receiver in contract params.
1912
- */
1413
+ /** All possible spending paths (no timelock checks); role as in {@link selectPath}. */
1913
1414
  getAllSpendingPaths(script, contract, context) {
1914
- const role = resolveRole(contract, context);
1915
- const paths = [];
1916
- if (!role) {
1917
- return paths;
1918
- }
1919
- const preimage = contract.params?.preimage;
1920
- if (context.collaborative) {
1921
- if (role === "receiver" && preimage) {
1922
- paths.push({
1923
- leaf: script.claim(),
1924
- extraWitness: [hex.decode(preimage)]
1925
- });
1926
- }
1927
- if (role === "sender") {
1928
- paths.push({
1929
- leaf: script.refundWithoutReceiver()
1930
- });
1931
- }
1932
- } else {
1933
- if (role === "receiver" && preimage) {
1934
- const sequence = Number(contract.params.claimDelay);
1935
- paths.push({
1936
- leaf: script.unilateralClaim(),
1937
- extraWitness: [hex.decode(preimage)],
1938
- sequence
1939
- });
1940
- }
1941
- if (role === "sender") {
1942
- const sequence = Number(contract.params.refundNoReceiverDelay);
1943
- paths.push({
1944
- leaf: script.unilateralRefundWithoutReceiver(),
1945
- sequence
1946
- });
1947
- }
1948
- }
1949
- return paths;
1415
+ return vhtlcAllSpendingPaths(script, contract, context);
1950
1416
  },
1951
1417
  getSpendablePaths(script, contract, context) {
1952
- const role = resolveRole(contract, context);
1953
- const paths = [];
1954
- if (!role) {
1955
- return paths;
1956
- }
1957
- const preimage = contract.params?.preimage;
1958
- const refundLocktime = BigInt(contract.params.refundLocktime);
1959
- if (context.collaborative) {
1960
- if (role === "receiver" && preimage) {
1961
- paths.push({
1962
- leaf: script.claim(),
1963
- extraWitness: [hex.decode(preimage)]
1964
- });
1965
- }
1966
- if (role === "sender" && isCltvSatisfied(context, refundLocktime)) {
1967
- paths.push({
1968
- leaf: script.refundWithoutReceiver()
1969
- });
1970
- }
1971
- return paths;
1972
- }
1973
- if (role === "receiver" && preimage) {
1974
- const sequence = Number(contract.params.claimDelay);
1975
- if (isCsvSpendable(context, sequence)) {
1976
- paths.push({
1977
- leaf: script.unilateralClaim(),
1978
- extraWitness: [hex.decode(preimage)],
1979
- sequence
1980
- });
1981
- }
1982
- }
1983
- if (role === "sender") {
1984
- const sequence = Number(contract.params.refundNoReceiverDelay);
1985
- if (isCsvSpendable(context, sequence)) {
1986
- paths.push({
1987
- leaf: script.unilateralRefundWithoutReceiver(),
1988
- sequence
1989
- });
1990
- }
1991
- }
1992
- return paths;
1418
+ return vhtlcSpendablePaths(script, contract, context);
1993
1419
  },
1994
1420
  /**
1995
1421
  * Refuse an explicit spend of a lockup whose refund path has not opened.
@@ -2000,70 +1426,38 @@ var VHTLCContractHandler = {
2000
1426
  assertVhtlcSpendableNow(contract, context);
2001
1427
  },
2002
1428
  /**
2003
- * Never. A live VHTLC is escrow: the counterparty's claim leaf is armed,
2004
- * and generic selection — send, settle, renewal, offboard — would move the
2005
- * lockup out from under a swap the counterparty is still entitled to
2006
- * complete, or race a claim already in flight.
1429
+ * Never. A live VHTLC is escrow: generic selection (send, settle, renewal, offboard) would
1430
+ * move the lockup out from under a swap the counterparty may still complete, or race a claim.
2007
1431
  *
2008
- * Two narrow reasons, and neither is "spending this would destroy the
2009
- * VTXO". It would not: the leaf {@link deriveTapscripts} stamps is
2010
- * `refundWithoutReceiver` — `CLTV[sender, server]`, the sender's OWN
2011
- * refund, which the server rejects until `refundLocktime` matures. The
2012
- * reasons are that an escrowed lockup must not count toward the
2013
- * `available` balance bucket, and that generic RENEWAL must not silently
2014
- * execute a refund the caller never asked for — `runPeriodicSettle` selects
2015
- * through `getSpendableVtxos`, which this gate filters, and it runs
2016
- * unprompted whenever a wallet is built without an explicit
2017
- * `settlementConfig`.
1432
+ * The stamped leaf is the sender's own CLTV refund, so this is not about destroying the
1433
+ * VTXO: an escrowed lockup must not count toward the `available` balance, and generic
1434
+ * renewal (`runPeriodicSettle`, unprompted without an explicit `settlementConfig`) must not
1435
+ * silently execute a refund nobody asked for.
2018
1436
  *
2019
- * **Inseparable from {@link deriveTapscripts}.** Deriving the leaf without
2020
- * closing the gate hands generic renewal an escrow it had never been
2021
- * offered before; before that method existed the answer here was
2022
- * unobservable, so the two only ever made sense together.
2023
- *
2024
- * Recovery is filtered on {@link assertSpendableNow}, not on this: the gate
2025
- * is permanent, and applying it there would strand a matured lockup.
1437
+ * **Inseparable from {@link deriveTapscripts}**: deriving the leaf without closing this gate
1438
+ * hands renewal an escrow. Recovery filters on {@link assertSpendableNow} instead; this
1439
+ * permanent gate would strand a matured lockup.
2026
1440
  */
2027
1441
  isGenericallySpendable: () => false,
2028
1442
  /**
2029
1443
  * The annotation leaf stamped onto every VTXO locked to this contract.
2030
1444
  *
2031
- * **Without this the contract is unusable, not merely unoptimized.**
2032
- * `deriveContractTapscripts` falls back to `script.forfeit()` for any
2033
- * handler that does not implement {@link TapscriptDeriving}, and no VHTLC
2034
- * script version has a `forfeit()` — `VtxoScript` does not define one and
2035
- * `VHTLC.BaseScript` does not add one. The fallback therefore threw
2036
- * `legacy.forfeit is not a function`, `annotatableIn` caught it, and
2037
- * `fetchContractVtxosBulk` dropped this contract's VTXOs before they were
2038
- * persisted. The row existed and was watched while its balance stayed
2039
- * permanently invisible, and `getSyncState()` reported `degraded` forever.
2040
- *
2041
- * `refundWithoutReceiver` is the leaf, for both roles the annotation
2042
- * serves, because it is the only collaborative path this wallet can
2043
- * actually satisfy as the sender — the same conclusion `selectPath`
2044
- * reaches, kept in step with it deliberately. The claim leaves belong to
2045
- * the receiver and need a preimage the sender does not have; `refund` and
2046
- * `unilateralRefund` both need the counterparty's signature, which this
2047
- * protocol has no message to ask for. V1 has no covenant leaves at all.
1445
+ * **Required, not an optimization.** Without it `deriveContractTapscripts` falls back to
1446
+ * `script.forfeit()`, which no VHTLC script has, so the contract's VTXOs are dropped before
1447
+ * persisting: balance invisible, `getSyncState()` degraded forever.
2048
1448
  *
2049
- * **It carries a CLTV, and callers must respect it.** A settlement built
2050
- * on this leaf is only valid once `refundLocktime` has matured, so a
2051
- * recovery round that sweeps this VTXO early is rejected by the server.
2052
- * Nothing in this handler can enforce that, because the annotation is
2053
- * derived per contract and knows no clock; `recoverVtxos` filters on
2054
- * {@link assertSpendableNow} for it.
1449
+ * `refundWithoutReceiver` is the only collaborative path this wallet can satisfy as sender
1450
+ * (kept in step with `selectPath`): the claim leaves need the preimage, and `refund` /
1451
+ * `unilateralRefund` need a counterparty signature the protocol can't request. V1 has no
1452
+ * covenant leaves.
2055
1453
  *
2056
- * **Role-blind.** A receiver's row gets the same leaf, which names keys that
2057
- * wallet does not hold, so it is satisfiable only by the wallet holding the
2058
- * lockup's `sender` key and fails at intent registration for anyone else.
1454
+ * **It carries a CLTV**: a settlement on it is rejected until `refundLocktime` matures, and
1455
+ * the per-contract annotation knows no clock, so `recoverVtxos` filters on
1456
+ * {@link assertSpendableNow}. **Role-blind**: a receiver's row gets the same leaf, satisfiable
1457
+ * only by the `sender` key holder; anyone else fails at intent registration.
2059
1458
  */
2060
1459
  deriveTapscripts(script) {
2061
- const leaf = script.refundWithoutReceiver();
2062
- return {
2063
- forfeitTapLeafScript: leaf,
2064
- intentTapLeafScript: leaf,
2065
- tapTree: script.encode()
2066
- };
1460
+ return deriveVhtlcTapscripts(script);
2067
1461
  }
2068
1462
  };
2069
1463
  var parseGroupIndex = (raw) => {
@@ -2102,43 +1496,21 @@ var VHTLCV2ContractHandler = {
2102
1496
  refundNoReceiverDelay: timelockToSequence(
2103
1497
  params.unilateralRefundWithoutReceiverDelay
2104
1498
  ).toString(),
2105
- // The on-disk keys keep their per-leaf names even though the typed
2106
- // param is ONE group: rows written by older SDKs carry exactly
2107
- // these keys, and changing the storage encoding would orphan them.
2108
- // The group maps onto them without loss — one emulator key serves
2109
- // both covenant destinations by construction (see the option's doc
2110
- // on VHTLC.Options), so the two emulator-key columns always hold
2111
- // the same value on a row written from here.
2112
- //
2113
- // Spread rather than written as `undefined`: a `Record<string,
2114
- // string>` round-tripped through a repository must not gain keys
2115
- // whose value is the string "undefined".
1499
+ // Per-leaf on-disk keys predate the grouped param and older rows carry exactly these;
1500
+ // both emulator columns hold the same key. Spread, not `undefined`, so a repository
1501
+ // round-trip never gains "undefined" string values.
2116
1502
  ...covenants && {
2117
1503
  nonInteractiveClaimReceiverPkScript: hex.encode(covenants.receiverPkScript),
2118
1504
  nonInteractiveClaimEmulatorPubkey: hex.encode(covenants.emulatorPubkey),
2119
1505
  nonInteractiveRefundSenderPkScript: hex.encode(covenants.senderPkScript),
2120
1506
  nonInteractiveRefundEmulatorPubkey: hex.encode(covenants.emulatorPubkey),
2121
- // The legacy marker is precisely the ABSENCE of this key: a
2122
- // row carrying it re-derives the full suite (nine leaves), a
2123
- // row lacking it the pre-timelocked-refund eight — the same
2124
- // encoding older SDKs wrote for eight-leaf rows, so those rows
2125
- // read back unchanged. Spread rather than a "false"/"undefined"
2126
- // string: a dropped flag re-derives the EIGHT-leaf script, a
2127
- // different pkScript, and registration dies at
2128
- // `upsertContractRow` with an opaque `Script mismatch`.
1507
+ // The legacy (eight-leaf) marker is this key's ABSENCE, as older SDKs wrote it.
2129
1508
  ...covenants.legacy === void 0 && {
2130
1509
  nonInteractiveRefundWithoutReceiver: "1"
2131
1510
  }
2132
1511
  },
2133
- // The asset must round-trip, and its absence here used to be silent
2134
- // in the worst way. `ContractManager` re-derives a contract from
2135
- // these params; dropping the asset derives the SAT-ONLY script, so
2136
- // registration died at `upsertContractRow` with a `Script mismatch`
2137
- // naming two hex strings and no cause. Same silent-drop class
2138
- // `validateOptions` refuses one layer up.
2139
- //
2140
- // Two keys rather than one blob, mirroring the script's own view of
2141
- // an Asset ID as a (txid, groupIndex) pair.
1512
+ // Must round-trip: dropping the asset re-derives the sat-only script and registration
1513
+ // dies at `upsertContractRow` with an opaque `Script mismatch`.
2142
1514
  ...params.asset && {
2143
1515
  assetTxid: hex.encode(params.asset.txid),
2144
1516
  assetGroupIndex: params.asset.groupIndex.toString()
@@ -2209,8 +1581,6 @@ var VHTLCV2ContractHandler = {
2209
1581
  receiverPkScript: claim.destination,
2210
1582
  senderPkScript: refund.destination,
2211
1583
  emulatorPubkey: claim.emulatorPubkey,
2212
- // Absence of the flag IS the legacy marker — the same
2213
- // encoding older SDKs wrote for eight-leaf rows.
2214
1584
  ...params.nonInteractiveRefundWithoutReceiver !== "1" && {
2215
1585
  legacy: "preTimelockedRefund"
2216
1586
  }
@@ -2219,139 +1589,18 @@ var VHTLCV2ContractHandler = {
2219
1589
  };
2220
1590
  },
2221
1591
  /**
2222
- * Select spending path based on context.
2223
- *
2224
- * Role is determined from `context.role` or by matching
2225
- * `context.walletDescriptor`
2226
- * against sender/receiver in contract params.
1592
+ * Select spending path based on context. Role comes from `context.role` or by matching
1593
+ * `context.walletDescriptor` against sender/receiver.
2227
1594
  */
2228
1595
  selectPath(script, contract, context) {
2229
- const role = resolveRole(contract, context);
2230
- const preimage = contract.params?.preimage;
2231
- const refundLocktime = BigInt(contract.params.refundLocktime);
2232
- if (!role) {
2233
- return null;
2234
- }
2235
- if (context.collaborative) {
2236
- if (role === "receiver" && preimage) {
2237
- return {
2238
- leaf: script.claim(),
2239
- extraWitness: [hex.decode(preimage)]
2240
- };
2241
- }
2242
- if (role === "sender" && isCltvSatisfied(context, refundLocktime)) {
2243
- return {
2244
- leaf: script.refundWithoutReceiver()
2245
- };
2246
- }
2247
- return null;
2248
- }
2249
- if (role === "receiver" && preimage) {
2250
- const sequence = Number(contract.params.claimDelay);
2251
- if (!isCsvSpendable(context, sequence)) return null;
2252
- return {
2253
- leaf: script.unilateralClaim(),
2254
- extraWitness: [hex.decode(preimage)],
2255
- sequence
2256
- };
2257
- }
2258
- if (role === "sender") {
2259
- const sequence = Number(contract.params.refundNoReceiverDelay);
2260
- if (!isCsvSpendable(context, sequence)) return null;
2261
- return {
2262
- leaf: script.unilateralRefundWithoutReceiver(),
2263
- sequence
2264
- };
2265
- }
2266
- return null;
1596
+ return selectVhtlcPath(script, contract, context);
2267
1597
  },
2268
- /**
2269
- * Get all possible spending paths (no timelock checks).
2270
- *
2271
- * Role is determined from `context.role` or by matching
2272
- * `context.walletDescriptor`
2273
- * against sender/receiver in contract params.
2274
- */
1598
+ /** Get all possible spending paths (no timelock checks); role resolved as in `selectPath`. */
2275
1599
  getAllSpendingPaths(script, contract, context) {
2276
- const role = resolveRole(contract, context);
2277
- const paths = [];
2278
- if (!role) {
2279
- return paths;
2280
- }
2281
- const preimage = contract.params?.preimage;
2282
- if (context.collaborative) {
2283
- if (role === "receiver" && preimage) {
2284
- paths.push({
2285
- leaf: script.claim(),
2286
- extraWitness: [hex.decode(preimage)]
2287
- });
2288
- }
2289
- if (role === "sender") {
2290
- paths.push({
2291
- leaf: script.refundWithoutReceiver()
2292
- });
2293
- }
2294
- } else {
2295
- if (role === "receiver" && preimage) {
2296
- const sequence = Number(contract.params.claimDelay);
2297
- paths.push({
2298
- leaf: script.unilateralClaim(),
2299
- extraWitness: [hex.decode(preimage)],
2300
- sequence
2301
- });
2302
- }
2303
- if (role === "sender") {
2304
- const sequence = Number(contract.params.refundNoReceiverDelay);
2305
- paths.push({
2306
- leaf: script.unilateralRefundWithoutReceiver(),
2307
- sequence
2308
- });
2309
- }
2310
- }
2311
- return paths;
1600
+ return vhtlcAllSpendingPaths(script, contract, context);
2312
1601
  },
2313
1602
  getSpendablePaths(script, contract, context) {
2314
- const role = resolveRole(contract, context);
2315
- const paths = [];
2316
- if (!role) {
2317
- return paths;
2318
- }
2319
- const preimage = contract.params?.preimage;
2320
- const refundLocktime = BigInt(contract.params.refundLocktime);
2321
- if (context.collaborative) {
2322
- if (role === "receiver" && preimage) {
2323
- paths.push({
2324
- leaf: script.claim(),
2325
- extraWitness: [hex.decode(preimage)]
2326
- });
2327
- }
2328
- if (role === "sender" && isCltvSatisfied(context, refundLocktime)) {
2329
- paths.push({
2330
- leaf: script.refundWithoutReceiver()
2331
- });
2332
- }
2333
- return paths;
2334
- }
2335
- if (role === "receiver" && preimage) {
2336
- const sequence = Number(contract.params.claimDelay);
2337
- if (isCsvSpendable(context, sequence)) {
2338
- paths.push({
2339
- leaf: script.unilateralClaim(),
2340
- extraWitness: [hex.decode(preimage)],
2341
- sequence
2342
- });
2343
- }
2344
- }
2345
- if (role === "sender") {
2346
- const sequence = Number(contract.params.refundNoReceiverDelay);
2347
- if (isCsvSpendable(context, sequence)) {
2348
- paths.push({
2349
- leaf: script.unilateralRefundWithoutReceiver(),
2350
- sequence
2351
- });
2352
- }
2353
- }
2354
- return paths;
1603
+ return vhtlcSpendablePaths(script, contract, context);
2355
1604
  },
2356
1605
  /**
2357
1606
  * Refuse an explicit spend of a lockup whose refund path has not opened.
@@ -2361,62 +1610,28 @@ var VHTLCV2ContractHandler = {
2361
1610
  assertVhtlcSpendableNow(contract, context);
2362
1611
  },
2363
1612
  /**
2364
- * Never. A live VHTLC is escrow: the counterparty's claim leaf is armed,
2365
- * and generic selection — send, settle, RENEWAL, offboard — would move the
2366
- * lockup out from under a swap the counterparty is still entitled to
2367
- * complete, or race a claim already in flight. The `vhtlc` handler now
2368
- * answers the same, for the same reason.
2369
- *
2370
- * The gate covers `available` and renewal. Recovery is generic and names
2371
- * nothing, so it is not covered here: `recoverVtxos` filters on
2372
- * {@link assertSpendableNow} instead, which drops a lockup only while its
2373
- * refund path is shut rather than forever. The explicit routes out —
2374
- * `settle({ inputs })`, or `@arkade-os/swap`'s `pushRefundWithoutReceiver`
2375
- * over `findLockupVtxos`' outpoints — are unaffected either way.
1613
+ * Never. A live VHTLC is escrow with the counterparty's claim leaf armed; generic selection
1614
+ * (send, settle, renewal, offboard) would move it out from under the swap or race a claim.
1615
+ * Recovery isn't covered by this gate: `recoverVtxos` filters on {@link assertSpendableNow},
1616
+ * dropping a lockup only while its refund path is shut. Explicit routes (`settle({ inputs })`,
1617
+ * `pushRefundWithoutReceiver`) are unaffected.
2376
1618
  */
2377
1619
  isGenericallySpendable: () => false,
2378
1620
  /**
2379
1621
  * The annotation leaf stamped onto every VTXO locked to this contract.
2380
1622
  *
2381
- * **Without this the contract is unusable, not merely unoptimized.**
2382
- * `deriveContractTapscripts` falls back to `script.forfeit()` for any
2383
- * handler that does not implement {@link TapscriptDeriving}, and no VHTLC
2384
- * script version has a `forfeit()` — `VtxoScript` does not define one and
2385
- * `VHTLC.BaseScript` does not add one. The fallback therefore throws
2386
- * `legacy.forfeit is not a function`, `annotatableIn` catches it, and
2387
- * `fetchContractVtxosBulk` drops this contract's VTXOs before they are
2388
- * persisted. The row would exist and be watched while its balance stayed
2389
- * permanently invisible, and `getSyncState()` would report `degraded`
2390
- * forever. (The `vhtlc` handler had the same gap and now closes it the same
2391
- * way; nothing registers V1 rows through `ContractManager` in this repo, so
2392
- * it was never hit here.)
1623
+ * Required, not an optimization: without {@link TapscriptDeriving} the fallback calls
1624
+ * `script.forfeit()`, which no VHTLC version has, so the VTXOs are silently dropped before
1625
+ * persisting and `getSyncState()` stays `degraded`.
2393
1626
  *
2394
- * `refundWithoutReceiver` is the leaf, for both roles the annotation
2395
- * serves, because it is the only collaborative path this wallet can
2396
- * actually satisfy as the sender — the same conclusion `selectPath`
2397
- * reaches, kept in step with it deliberately. The claim leaves belong to
2398
- * the receiver and need a preimage the sender does not have; `refund` and
2399
- * `unilateralRefund` need the counterparty; the covenant leaves are the
2400
- * emulator's.
2401
- *
2402
- * **It carries a CLTV, and callers must respect it.** A settlement built on
2403
- * this leaf is only valid once `refundLocktime` has matured, so a recovery
2404
- * round that sweeps this VTXO early is rejected by the server. Nothing in
2405
- * this handler can enforce it, because the annotation is derived per
2406
- * contract and knows no clock. `recoverVtxos` filters on
2407
- * {@link assertSpendableNow} for it.
2408
- *
2409
- * **Role-blind.** A receiver's row gets the same leaf, which names keys that
2410
- * wallet does not hold, so it is satisfiable only by the wallet holding the
2411
- * lockup's `sender` key and fails at intent registration for anyone else.
1627
+ * `refundWithoutReceiver` is used (in step with `selectPath`) as the only collaborative leaf
1628
+ * the sender can satisfy. It carries a CLTV: settling on it before `refundLocktime` matures is
1629
+ * rejected, and this handler has no clock, so `recoverVtxos` filters on
1630
+ * {@link assertSpendableNow}. Role-blind: a receiver's row gets the same leaf and fails at
1631
+ * intent registration.
2412
1632
  */
2413
1633
  deriveTapscripts(script) {
2414
- const leaf = script.refundWithoutReceiver();
2415
- return {
2416
- forfeitTapLeafScript: leaf,
2417
- intentTapLeafScript: leaf,
2418
- tapTree: script.encode()
2419
- };
1634
+ return deriveVhtlcTapscripts(script);
2420
1635
  }
2421
1636
  };
2422
1637
  var BoardingContractHandler = {
@@ -2442,24 +1657,10 @@ var BoardingContractHandler = {
2442
1657
  /** Preserves today's settle/board behaviour. */
2443
1658
  isGenericallySpendable: () => true,
2444
1659
  /**
2445
- * Probe the on-chain UTXO set for a boarding output at this HD index.
2446
- *
2447
- * Boarding's source of truth is the **current** on-chain coin set
2448
- * (`OnchainProvider.getCoins`), not the Ark indexer: a boarded (spent)
2449
- * boarding output becomes an L2 VTXO at the receive index, so the
2450
- * indexer probe already keeps the gap window open for it; only an
2451
- * *unspent* boarding output needs the on-chain probe (see plan §2).
2452
- *
2453
- * No-ops (returns `[]`) when boarding discovery is not plumbed — i.e.
2454
- * `deps.boardingTimelock` or `deps.onchainNetwork` is absent — so the
2455
- * scanner unit harness (which sets neither) is unaffected.
2456
- *
2457
- * Always emits `type: "boarding"`, even in the degenerate equal-delay case
2458
- * where the boarding script is byte-identical to a `default` script at this
2459
- * index (`boardingExitDelay === unilateralExitDelay`). The collision is no
2460
- * longer pre-judged in discovery; it is tolerated FIRST-WINS at the
2461
- * persistence layer ({@link ContractManager.upsertContract}). `deps.csvTimelocks`
2462
- * is intentionally not read here.
1660
+ * Probe the **current** on-chain coin set for a boarding output at this HD index. Only an
1661
+ * unspent boarding output needs this: a boarded one became an L2 VTXO at the receive index,
1662
+ * which the indexer probe already covers. Always emits `type: "boarding"` (see the handler
1663
+ * doc on collisions); `deps.csvTimelocks` is intentionally not read.
2463
1664
  */
2464
1665
  async discoverAt(index, descriptor, deps) {
2465
1666
  if (!deps.boardingTimelock || !deps.onchainNetwork) return [];
@@ -2473,26 +1674,19 @@ var BoardingContractHandler = {
2473
1674
  const coins = await deps.onchainProvider.getCoins(onchainAddress);
2474
1675
  if (coins.length === 0) return [];
2475
1676
  const scriptHex = hex.encode(script.pkScript);
2476
- const boardingSeq = timelockToSequence(deps.boardingTimelock);
2477
1677
  return [
2478
1678
  {
2479
1679
  type: "boarding",
2480
- params: {
2481
- pubKey: hex.encode(pubKey),
2482
- serverPubKey: hex.encode(deps.serverPubKey),
2483
- csvTimelock: boardingSeq.toString()
2484
- },
1680
+ params: BoardingContractHandler.serializeParams({
1681
+ pubKey,
1682
+ serverPubKey: deps.serverPubKey,
1683
+ csvTimelock: deps.boardingTimelock
1684
+ }),
2485
1685
  script: scriptHex,
2486
- // The persisted row's `address` is the *Ark* address (not the
2487
- // on-chain P2TR), matching the row registered at init so the
2488
- // ContractWatcher keeps monitoring the same L2 script and the
2489
- // VTXO repository bucket lines up (plan §6-I.4). The P2TR is
2490
- // recomputed from params only for the `getCoins` probe.
1686
+ // The Ark address, not the P2TR: must match the row registered at init so the
1687
+ // watcher monitors the same L2 script and the VTXO repository bucket lines up.
2491
1688
  address: script.address(deps.network.hrp, deps.serverPubKey).encode(),
2492
- // Tag rotated rows (index > 0) so boot resolution can find the
2493
- // newest boarding address and descriptor-aware signing can
2494
- // recover the per-index key (plan §6-II/§6-III.3). The index-0
2495
- // baseline stays untagged, matching `default`/`delegate`.
1689
+ // Tag rotated rows (index > 0) for boot resolution and per-index signing.
2496
1690
  ...rotatedReceiveMetadata(index, descriptor)
2497
1691
  }
2498
1692
  ];
@@ -2567,6 +1761,7 @@ function collectParamRefs(program) {
2567
1761
  const collect = (items) => {
2568
1762
  for (const t of items ?? []) {
2569
1763
  if (typeof t === "string" && t.startsWith("$")) refs.add(t.slice(1));
1764
+ else if (isTweakedSigner(t) && t.tweak.startsWith("$")) refs.add(t.tweak.slice(1));
2570
1765
  }
2571
1766
  };
2572
1767
  for (const fn of Object.values(program.functions)) {
@@ -2619,10 +1814,15 @@ function validateProgram(program, args) {
2619
1814
  if (typeof p !== "string") validateParamValue(p, args[p.name]);
2620
1815
  }
2621
1816
  }
1817
+ function isTweakedSigner(ref) {
1818
+ if (typeof ref !== "object" || ref === null || ref instanceof Uint8Array) return false;
1819
+ const { tweak, fn } = ref;
1820
+ return typeof tweak === "string" && typeof fn === "string";
1821
+ }
2622
1822
  function resolveSigner(ref, args) {
2623
1823
  if (ref instanceof Uint8Array) return ref;
2624
- if (!ref.startsWith("$")) {
2625
- throw new Error(`unknown signer reference '${ref}' \u2014 use '$${ref}'`);
1824
+ if (typeof ref !== "string" || !ref.startsWith("$")) {
1825
+ throw new Error(`unknown signer reference '${ref}' \u2014 use a '$param' or key bytes`);
2626
1826
  }
2627
1827
  const v = bindValue(args, ref.slice(1));
2628
1828
  if (!(v instanceof Uint8Array)) {
@@ -2677,9 +1877,21 @@ function compileFunctions(program, args, keys) {
2677
1877
  }
2678
1878
  return defs.map((def, i) => {
2679
1879
  validateTapscript(def.tapscript);
2680
- const pubkeys = def.tapscript.signers.map((s) => resolveSigner(s, args));
1880
+ const pubkeys = def.tapscript.signers.map((signer) => {
1881
+ if (!isTweakedSigner(signer)) return resolveSigner(signer, args);
1882
+ const asm = functions[signer.fn]?.arkadeScript?.asm;
1883
+ if (!asm) {
1884
+ throw new Error(
1885
+ `ArkadeContract: function '${names[i]}' tweaks '${signer.fn}', which has no arkade script`
1886
+ );
1887
+ }
1888
+ return computeArkadeScriptPublicKey(
1889
+ resolveSigner(signer.tweak, args),
1890
+ resolveAsm(asm, args)
1891
+ );
1892
+ });
2681
1893
  const arkadeScript = def.arkadeScript ? resolveAsm(def.arkadeScript.asm, args) : void 0;
2682
- const leafPubkeys = arkadeScript ? [...pubkeys, computeArkadeScriptPublicKey(keys.emulatorKey, arkadeScript)] : pubkeys;
1894
+ const leafPubkeys = arkadeScript && def.tapscript.emulator !== null ? [...pubkeys, computeArkadeScriptPublicKey(keys.emulatorKey, arkadeScript)] : pubkeys;
2683
1895
  const leafScript = encodeTapscriptSegment(def.tapscript, leafPubkeys, args).script;
2684
1896
  return { name: names[i], def, leafScript, arkadeScript, signerKeys: pubkeys };
2685
1897
  });
@@ -2707,17 +1919,31 @@ var ArkadeProgramScript = class extends VtxoScript {
2707
1919
  }
2708
1920
  };
2709
1921
  function parseArtifact(artifact) {
1922
+ if (Array.isArray(artifact.functions)) {
1923
+ throw new Error(
1924
+ "parseArtifact: `functions` is an array, which is the arkadec artifact shape, not an SDK program \u2014 read it with programFromArtifact instead"
1925
+ );
1926
+ }
1927
+ if (typeof artifact.functions !== "object" || artifact.functions === null) {
1928
+ throw new Error("parseArtifact: `functions` must be an object keyed by function name");
1929
+ }
2710
1930
  const hexToken = (t) => typeof t === "string" && t.startsWith("0x") ? hex.decode(t.slice(2)) : t;
1931
+ const signerRef = (s) => {
1932
+ if (s instanceof Uint8Array || typeof s === "string") return hexToken(s);
1933
+ if (isTweakedSigner(s)) return { tweak: s.tweak, fn: s.fn };
1934
+ throw new Error("parseArtifact: a tweaked signer needs string `tweak` and `fn`");
1935
+ };
2711
1936
  const timelockValue = (v) => typeof v === "string" && v.startsWith("$") ? v : BigInt(v);
2712
1937
  const functions = {};
2713
1938
  for (const [name, fn] of Object.entries(artifact.functions)) {
2714
1939
  const tap = fn.tapscript ?? {};
2715
1940
  const tapscript = {
2716
- signers: (tap.signers ?? []).map(hexToken),
1941
+ signers: (tap.signers ?? []).map(signerRef),
2717
1942
  ...tap.asm ? { asm: tap.asm.map(hexToken) } : {},
2718
1943
  ...tap.witness ? { witness: tap.witness.map(hexToken) } : {},
2719
1944
  ...tap.csv ? { csv: { type: tap.csv.type, value: timelockValue(tap.csv.value) } } : {},
2720
- ...tap.cltv !== void 0 ? { cltv: timelockValue(tap.cltv) } : {}
1945
+ ...tap.cltv !== void 0 ? { cltv: timelockValue(tap.cltv) } : {},
1946
+ ..."emulator" in tap ? { emulator: tap.emulator } : {}
2721
1947
  };
2722
1948
  const arkadeScript = fn.arkadeScript ? {
2723
1949
  asm: fn.arkadeScript.asm.map(hexToken),
@@ -2757,7 +1983,8 @@ function stringifyArtifact(program) {
2757
1983
  ...tap.asm ? { asm: tap.asm.map(token) } : {},
2758
1984
  ...tap.witness ? { witness: tap.witness.map(token) } : {},
2759
1985
  ...tap.csv ? { csv: { type: tap.csv.type, value: tap.csv.value.toString() } } : {},
2760
- ...tap.cltv !== void 0 ? { cltv: tap.cltv.toString() } : {}
1986
+ ...tap.cltv !== void 0 ? { cltv: tap.cltv.toString() } : {},
1987
+ ...tap.emulator !== void 0 ? { emulator: tap.emulator } : {}
2761
1988
  },
2762
1989
  ...fn.arkadeScript ? {
2763
1990
  arkadeScript: {
@@ -2970,5 +2197,5 @@ contractHandlers.register(BoardingContractHandler);
2970
2197
  contractHandlers.register(ArkadeContractHandler);
2971
2198
 
2972
2199
  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, toXOnly, validateProgram, witnessRefToBytes };
2973
- //# sourceMappingURL=chunk-OMJRW2D5.js.map
2974
- //# sourceMappingURL=chunk-OMJRW2D5.js.map
2200
+ //# sourceMappingURL=chunk-JOUPIZ32.js.map
2201
+ //# sourceMappingURL=chunk-JOUPIZ32.js.map