@arkade-os/sdk 0.5.0-rc.2 → 0.5.0-rc.21

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 (126) hide show
  1. package/README.md +90 -83
  2. package/dist/adapters/expo.cjs +5 -6
  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 -4
  6. package/dist/{asyncStorageTaskQueue-C5rBYlru.d.ts → asyncStorageTaskQueue-aL7K14e4.d.ts} +2 -2
  7. package/dist/{asyncStorageTaskQueue-BuDvgWh6.d.cts → asyncStorageTaskQueue-x8ok3tJy.d.cts} +2 -2
  8. package/dist/{chunk-ISO4TI5J.js → chunk-4FDNOLBO.js} +566 -459
  9. package/dist/chunk-4FDNOLBO.js.map +1 -0
  10. package/dist/{chunk-JZWLXNHV.cjs → chunk-5XBH5SAL.cjs} +574 -464
  11. package/dist/chunk-5XBH5SAL.cjs.map +1 -0
  12. package/dist/{chunk-3R476TDV.js → chunk-CXHJGED6.js} +293 -1071
  13. package/dist/chunk-CXHJGED6.js.map +1 -0
  14. package/dist/{chunk-ZRNQV6HV.js → chunk-EXFU6MTA.js} +4578 -4396
  15. package/dist/chunk-EXFU6MTA.js.map +1 -0
  16. package/dist/{chunk-43FKNLX4.cjs → chunk-GVF4OCTL.cjs} +357 -1114
  17. package/dist/chunk-GVF4OCTL.cjs.map +1 -0
  18. package/dist/{chunk-HLCGYOIH.cjs → chunk-HIZNPHB2.cjs} +37 -36
  19. package/dist/chunk-HIZNPHB2.cjs.map +1 -0
  20. package/dist/{chunk-4PKLULWT.js → chunk-JZAEFOE4.js} +109 -246
  21. package/dist/chunk-JZAEFOE4.js.map +1 -0
  22. package/dist/{chunk-ZOTXDOS5.js → chunk-LNJPVLIC.js} +30 -29
  23. package/dist/chunk-LNJPVLIC.js.map +1 -0
  24. package/dist/{chunk-NVZJSKZS.cjs → chunk-Q7PC3NHX.cjs} +121 -257
  25. package/dist/chunk-Q7PC3NHX.cjs.map +1 -0
  26. package/dist/{chunk-GMZZQXV5.cjs → chunk-R5OFJ23O.cjs} +4961 -4767
  27. package/dist/chunk-R5OFJ23O.cjs.map +1 -0
  28. package/dist/{chunk-6DNR46C7.js → chunk-S2WWOVFC.js} +7 -5
  29. package/dist/chunk-S2WWOVFC.js.map +1 -0
  30. package/dist/{chunk-D25O4EGY.cjs → chunk-VZMLADXQ.cjs} +12 -10
  31. package/dist/chunk-VZMLADXQ.cjs.map +1 -0
  32. package/dist/contracts/handlers/index.cjs +9 -10
  33. package/dist/contracts/handlers/index.d.cts +5 -5
  34. package/dist/contracts/handlers/index.d.ts +5 -5
  35. package/dist/contracts/handlers/index.js +2 -3
  36. package/dist/{delegate-BAX5QftB.d.ts → delegate-BT5iON1T.d.ts} +1 -1
  37. package/dist/{delegate-1yp1HyP7.d.cts → delegate-u6kXTtWY.d.cts} +1 -1
  38. package/dist/index-BCnhItOQ.d.cts +639 -0
  39. package/dist/{index-DUxufw76.d.cts → index-CK5QbTmZ.d.cts} +1387 -2404
  40. package/dist/{index-DUxufw76.d.ts → index-CK5QbTmZ.d.ts} +1387 -2404
  41. package/dist/index-D1D4cR-G.d.ts +639 -0
  42. package/dist/index.cjs +375 -340
  43. package/dist/index.d.cts +784 -1197
  44. package/dist/index.d.ts +784 -1197
  45. package/dist/index.js +4 -5
  46. package/dist/repositories/realm/index.cjs +201 -106
  47. package/dist/repositories/realm/index.cjs.map +1 -1
  48. package/dist/repositories/realm/index.d.cts +28 -16
  49. package/dist/repositories/realm/index.d.ts +28 -16
  50. package/dist/repositories/realm/index.js +190 -95
  51. package/dist/repositories/realm/index.js.map +1 -1
  52. package/dist/repositories/sqlite/index.cjs +301 -189
  53. package/dist/repositories/sqlite/index.cjs.map +1 -1
  54. package/dist/repositories/sqlite/index.d.cts +32 -21
  55. package/dist/repositories/sqlite/index.d.ts +32 -21
  56. package/dist/repositories/sqlite/index.js +294 -182
  57. package/dist/repositories/sqlite/index.js.map +1 -1
  58. package/dist/{taskRunner-D_iPiZIi.d.ts → taskRunner-BeYR6NpQ.d.ts} +2 -2
  59. package/dist/{taskRunner-Dr3NStc0.d.cts → taskRunner-Csey3EYJ.d.cts} +2 -2
  60. package/dist/wallet/expo/background.cjs +14 -15
  61. package/dist/wallet/expo/background.cjs.map +1 -1
  62. package/dist/wallet/expo/background.d.cts +10 -16
  63. package/dist/wallet/expo/background.d.ts +10 -16
  64. package/dist/wallet/expo/background.js +6 -7
  65. package/dist/wallet/expo/background.js.map +1 -1
  66. package/dist/wallet/expo/index.cjs +16 -23
  67. package/dist/wallet/expo/index.cjs.map +1 -1
  68. package/dist/wallet/expo/index.d.cts +6 -9
  69. package/dist/wallet/expo/index.d.ts +6 -9
  70. package/dist/wallet/expo/index.js +8 -15
  71. package/dist/wallet/expo/index.js.map +1 -1
  72. package/dist/wallet-DG3Qmbmt.d.cts +1848 -0
  73. package/dist/wallet-DzSLMxBZ.d.ts +1848 -0
  74. package/dist/worker/expo/index.cjs +9 -10
  75. package/dist/worker/expo/index.cjs.map +1 -1
  76. package/dist/worker/expo/index.d.cts +6 -6
  77. package/dist/worker/expo/index.d.ts +6 -6
  78. package/dist/worker/expo/index.js +5 -6
  79. package/dist/worker/expo/index.js.map +1 -1
  80. package/package.json +12 -51
  81. package/dist/adapters/asyncStorage.cjs +0 -48
  82. package/dist/adapters/asyncStorage.cjs.map +0 -1
  83. package/dist/adapters/asyncStorage.d.cts +0 -16
  84. package/dist/adapters/asyncStorage.d.ts +0 -16
  85. package/dist/adapters/asyncStorage.js +0 -46
  86. package/dist/adapters/asyncStorage.js.map +0 -1
  87. package/dist/adapters/fileSystem.cjs +0 -116
  88. package/dist/adapters/fileSystem.cjs.map +0 -1
  89. package/dist/adapters/fileSystem.d.cts +0 -17
  90. package/dist/adapters/fileSystem.d.ts +0 -17
  91. package/dist/adapters/fileSystem.js +0 -93
  92. package/dist/adapters/fileSystem.js.map +0 -1
  93. package/dist/adapters/indexedDB.cjs +0 -103
  94. package/dist/adapters/indexedDB.cjs.map +0 -1
  95. package/dist/adapters/indexedDB.d.cts +0 -18
  96. package/dist/adapters/indexedDB.d.ts +0 -18
  97. package/dist/adapters/indexedDB.js +0 -101
  98. package/dist/adapters/indexedDB.js.map +0 -1
  99. package/dist/adapters/localStorage.cjs +0 -50
  100. package/dist/adapters/localStorage.cjs.map +0 -1
  101. package/dist/adapters/localStorage.d.cts +0 -14
  102. package/dist/adapters/localStorage.d.ts +0 -14
  103. package/dist/adapters/localStorage.js +0 -48
  104. package/dist/adapters/localStorage.js.map +0 -1
  105. package/dist/chunk-3R476TDV.js.map +0 -1
  106. package/dist/chunk-43FKNLX4.cjs.map +0 -1
  107. package/dist/chunk-4PKLULWT.js.map +0 -1
  108. package/dist/chunk-5BLDMQED.cjs +0 -18
  109. package/dist/chunk-5BLDMQED.cjs.map +0 -1
  110. package/dist/chunk-6DNR46C7.js.map +0 -1
  111. package/dist/chunk-D25O4EGY.cjs.map +0 -1
  112. package/dist/chunk-GMZZQXV5.cjs.map +0 -1
  113. package/dist/chunk-HLCGYOIH.cjs.map +0 -1
  114. package/dist/chunk-ISO4TI5J.js.map +0 -1
  115. package/dist/chunk-JZWLXNHV.cjs.map +0 -1
  116. package/dist/chunk-NSBPE2FW.js +0 -15
  117. package/dist/chunk-NSBPE2FW.js.map +0 -1
  118. package/dist/chunk-NVZJSKZS.cjs.map +0 -1
  119. package/dist/chunk-ZOTXDOS5.js.map +0 -1
  120. package/dist/chunk-ZRNQV6HV.js.map +0 -1
  121. package/dist/hdWalletCapable-CmEU0NP1.d.ts +0 -2232
  122. package/dist/hdWalletCapable-eJgaT-ec.d.cts +0 -2232
  123. package/dist/index-Bem5vKzX.d.ts +0 -850
  124. package/dist/index-C0IanN1m.d.cts +0 -11
  125. package/dist/index-C0IanN1m.d.ts +0 -11
  126. package/dist/index-W_oMcOmG.d.cts +0 -850
@@ -1,42 +1,25 @@
1
- import { VtxoScript, MultisigTapscript, CSVMultisigTapscript, timelockToSequence, sequenceToTimelock, ConditionMultisigTapscript, CLTVMultisigTapscript, ConditionCSVMultisigTapscript, SCRIPT_QUERY_CHUNK_SIZE, getNormalizedVtxos, DEFAULT_PAGE_SIZE } from './chunk-ISO4TI5J.js';
2
- import { __export } from './chunk-NSBPE2FW.js';
1
+ import { __export, VtxoScript, MultisigTapscript, CSVMultisigTapscript, timelockToSequence, sequenceToTimelock, ConditionMultisigTapscript, CLTVMultisigTapscript, ConditionCSVMultisigTapscript, SCRIPT_QUERY_CHUNK_SIZE, getNormalizedVtxos, DEFAULT_PAGE_SIZE, sequenceToCanonicalTimelock } from './chunk-4FDNOLBO.js';
3
2
  import { hex } from '@scure/base';
4
3
  import { networks, expand } from '@bitcoinerlab/descriptors-scure';
5
4
  import { ScriptNum, OP, Script } from '@scure/btc-signer';
5
+ import * as P from 'micro-packed';
6
6
  import { schnorr, secp256k1 } from '@noble/curves/secp256k1.js';
7
7
  import { bytesToNumberBE } from '@noble/curves/utils.js';
8
8
 
9
9
  // src/contracts/handlers/registry.ts
10
10
  var ContractHandlerRegistry = class {
11
11
  handlers = /* @__PURE__ */ new Map();
12
- /**
13
- * Register a contract handler.
14
- *
15
- * @param handler - The handler to register
16
- * @throws If a handler for this type is already registered
17
- */
12
+ /** @throws If a handler for this type is already registered */
18
13
  register(handler) {
19
14
  if (this.handlers.has(handler.type)) {
20
15
  throw new Error(`Contract handler for type '${handler.type}' is already registered`);
21
16
  }
22
17
  this.handlers.set(handler.type, handler);
23
18
  }
24
- /**
25
- * Get a handler by type.
26
- *
27
- * @param type - The contract type
28
- * @returns The handler, or undefined if not found
29
- */
30
19
  get(type) {
31
20
  return this.handlers.get(type);
32
21
  }
33
- /**
34
- * Get a handler by type, throwing if not found.
35
- *
36
- * @param type - The contract type
37
- * @returns The handler
38
- * @throws If no handler is registered for this type
39
- */
22
+ /** @throws If no handler is registered for this type */
40
23
  getOrThrow(type) {
41
24
  const handler = this.get(type);
42
25
  if (!handler) {
@@ -44,29 +27,17 @@ var ContractHandlerRegistry = class {
44
27
  }
45
28
  return handler;
46
29
  }
47
- /**
48
- * Check if a handler is registered.
49
- *
50
- * @param type - The contract type
51
- */
52
30
  has(type) {
53
31
  return this.handlers.has(type);
54
32
  }
55
- /**
56
- * Get all registered types.
57
- */
58
33
  getRegisteredTypes() {
59
34
  return Array.from(this.handlers.keys());
60
35
  }
61
- /**
62
- * Unregister a handler (mainly for testing).
63
- */
36
+ /** Unregister a handler (mainly for testing). */
64
37
  unregister(type) {
65
38
  return this.handlers.delete(type);
66
39
  }
67
- /**
68
- * Clear all handlers (mainly for testing).
69
- */
40
+ /** Clear all handlers (mainly for testing). */
70
41
  clear() {
71
42
  this.handlers.clear();
72
43
  }
@@ -266,21 +237,8 @@ function resolveRole(contract, context) {
266
237
  }
267
238
  return void 0;
268
239
  };
269
- if (context.walletDescriptor) {
270
- const walletDescriptorKey = extractRawPubKey(context.walletDescriptor);
271
- const matchedRole = matchRole(walletDescriptorKey);
272
- if (matchedRole) {
273
- return matchedRole;
274
- }
275
- if (!walletDescriptorKey && context.walletPubKey) {
276
- return matchRole(extractRawPubKey(context.walletPubKey));
277
- }
278
- return void 0;
279
- }
280
- if (context.walletPubKey) {
281
- return matchRole(extractRawPubKey(context.walletPubKey));
282
- }
283
- return void 0;
240
+ if (!context.walletDescriptor) return void 0;
241
+ return matchRole(extractRawPubKey(context.walletDescriptor));
284
242
  }
285
243
  function extractPubKeyBytes(value) {
286
244
  return hex.decode(extractPubKey(normalizeToDescriptor(value)));
@@ -325,6 +283,135 @@ function assertVhtlcSpendableNow(contract, context) {
325
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.`
326
284
  );
327
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
+ }
328
415
  function isCsvSpendable(context, sequence) {
329
416
  if (sequence === void 0) return true;
330
417
  if (!context.vtxo) return false;
@@ -493,11 +580,7 @@ function defaultCandidatesAt(_index, descriptor, deps) {
493
580
  (opts) => new DefaultVtxo.Script(opts)
494
581
  ).map((c) => ({
495
582
  type: "default",
496
- params: {
497
- pubKey: hex.encode(c.pubKey),
498
- serverPubKey: hex.encode(c.serverPubKey),
499
- csvTimelock: timelockToSequence(c.csvTimelock).toString()
500
- },
583
+ params: DefaultContractHandler.serializeParams(c),
501
584
  script: c.scriptHex,
502
585
  address: c.script.address(deps.network.hrp, c.serverPubKey).encode()
503
586
  }));
@@ -596,12 +679,7 @@ function delegateCandidatesAt(_index, descriptor, deps) {
596
679
  (opts) => new DelegateVtxo.Script({ ...opts, delegatePubKey })
597
680
  ).map((c) => ({
598
681
  type: "delegate",
599
- params: {
600
- pubKey: hex.encode(c.pubKey),
601
- serverPubKey: hex.encode(c.serverPubKey),
602
- delegatePubKey: hex.encode(delegatePubKey),
603
- csvTimelock: timelockToSequence(c.csvTimelock).toString()
604
- },
682
+ params: DelegateContractHandler.serializeParams({ ...c, delegatePubKey }),
605
683
  script: c.scriptHex,
606
684
  address: c.script.address(deps.network.hrp, c.serverPubKey).encode()
607
685
  }));
@@ -617,515 +695,10 @@ function discoverDelegateRange(entries, deps) {
617
695
  (c, index, descriptor) => ({ ...c, ...rotatedReceiveMetadata(index, descriptor) })
618
696
  );
619
697
  }
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
- }
686
- }
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;
697
- }
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;
712
- }
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;
721
- }
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;
766
- }
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]];
772
- }
773
- return cur;
774
- }
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))}`);
845
- }
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;
853
- }
854
- if (!this.isEnd()) {
855
- throw this.err(`${this.leftBytes} bytes ${this.bitPos} bits left after unpack: ${hex.encode(this.data.slice(this.pos))}`);
856
- }
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;
913
- }
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;
931
- }
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;
998
- }
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;
1003
- }
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;
1011
- }
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
- }
1030
- }
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;
1049
- }
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
- }
1075
- }
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}`);
1106
- }
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
698
  var ARKADE_OP = {
1127
699
  // Merkle Branch Verification (0xb3 — repurposed NOP4 slot)
1128
700
  MERKLEBRANCHVERIFY: 179,
701
+ PUT: 187,
1129
702
  // Digest (0xc3)
1130
703
  DIGEST: 195,
1131
704
  // SHA256 Streaming (0xc4-0xc6)
@@ -1157,7 +730,10 @@ var ARKADE_OP = {
1157
730
  BIN2NUM: 216,
1158
731
  REVERSEBYTES: 217,
1159
732
  MODEXP: 218,
1160
- // 0xdb-0xdf are unassigned (OP_UNKNOWN219-223)
733
+ // VTXO Expiry & Emulator Clock (0xdb-0xdc)
734
+ PUSHEXPIRY: 219,
735
+ CHECKTIME: 220,
736
+ // 0xdd-0xdf are unassigned (OP_UNKNOWN221-223)
1161
737
  // EC Operations (0xe0-0xe2)
1162
738
  ECADD: 224,
1163
739
  ECMUL: 225,
@@ -1186,7 +762,10 @@ var ARKADE_OP = {
1186
762
  INSPECTPACKET: 244,
1187
763
  INSPECTINPUTPACKET: 245,
1188
764
  // Signature Hash (0xf6)
1189
- SIGHASH: 246
765
+ SIGHASH: 246,
766
+ // Continuation & Intent Introspection (0xf7-0xf8)
767
+ TUNNEL: 247,
768
+ INSPECTINTENTMESSAGE: 248
1190
769
  };
1191
770
  var ARKADE_OPCODES = Object.values(ARKADE_OP);
1192
771
  var ARKADE_OPCODE_NAMES = Object.fromEntries(
@@ -1282,7 +861,7 @@ var ArkadeOPNames = {};
1282
861
  for (const [k, v] of Object.entries(ARKADE_OPS)) {
1283
862
  if (typeof v === "number") ArkadeOPNames[v] = k;
1284
863
  }
1285
- var ArkadeScript = wrap({
864
+ var ArkadeScript = P.wrap({
1286
865
  encodeStream: (w, value) => {
1287
866
  for (let o of value) {
1288
867
  if (typeof o === "string") {
@@ -1308,10 +887,10 @@ var ArkadeScript = wrap({
1308
887
  w.byte(len);
1309
888
  } else if (len <= 65535) {
1310
889
  w.byte(OP.PUSHDATA2);
1311
- w.bytes(U16LE.encode(len));
890
+ w.bytes(P.U16LE.encode(len));
1312
891
  } else {
1313
892
  w.byte(OP.PUSHDATA4);
1314
- w.bytes(U32LE.encode(len));
893
+ w.bytes(P.U32LE.encode(len));
1315
894
  }
1316
895
  w.bytes(o);
1317
896
  }
@@ -1323,9 +902,9 @@ var ArkadeScript = wrap({
1323
902
  if (OP.OP_0 < cur && cur <= OP.PUSHDATA4) {
1324
903
  let len;
1325
904
  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);
905
+ else if (cur === OP.PUSHDATA1) len = P.U8.decodeStream(r);
906
+ else if (cur === OP.PUSHDATA2) len = P.U16LE.decodeStream(r);
907
+ else if (cur === OP.PUSHDATA4) len = P.U32LE.decodeStream(r);
1329
908
  else throw new Error("Should be not possible");
1330
909
  out.push(r.bytes(len));
1331
910
  } else if (cur === 0) {
@@ -1607,54 +1186,19 @@ var VHTLC;
1607
1186
  /**
1608
1187
  * Return the timelocked non-interactive refund tapleaf and its ArkadeScript.
1609
1188
  *
1610
- * SPENDING THIS LEAF, CONCRETELY — confirmed from this SDK's own
1611
- * emulator client and covenant-spend builder ({@link
1612
- * EmulatorProvider} in `src/providers/emulator.ts`, and {@link
1613
- * ArkadeTransactionBuilder} in `src/arkade/contract.ts`), which is
1614
- * the general machinery every covenant leaf in this file goes
1615
- * through:
1616
- *
1617
- * 1. Build the ark tx spending this leaf (this method's {@link
1618
- * TapLeafScript} as `tapLeafScript`, this script's {@link
1619
- * VtxoScript.encode} as `tapTree`), plus its checkpoint tx.
1620
- * 2. Attach the ArkadeScript this method returns to the spent
1621
- * input via an `EmulatorPacket` (type 1) — `{ vin, script:
1622
- * <this ArkadeScript>, witness: <empty> }`, empty because the
1623
- * script pushes its own input/output index with
1624
- * `PUSHCURRENTINPUTINDEX` rather than reading one from the
1625
- * witness — wrapped in an `Extension` OP_RETURN output on the
1626
- * ark tx.
1627
- * 3. Attach the spent input's OWN creating ark tx as `PrevArkTx` on
1628
- * input 0 — required so the emulator can resolve that input's
1629
- * prevout pkScript/value; it does not otherwise have them.
1630
- * 4. POST the (base64-PSBT) ark tx and checkpoint(s) as `{ arkTx,
1631
- * checkpointTxs }` to the emulator's `POST /v1/tx`.
1189
+ * Spent through the generic covenant path ({@link EmulatorProvider},
1190
+ * {@link ArkadeTransactionBuilder}): build the ark tx on this leaf plus its checkpoint;
1191
+ * attach this ArkadeScript to the input in an `EmulatorPacket` (type 1, empty witness:
1192
+ * the script pushes its own index with `PUSHCURRENTINPUTINDEX`) inside an `Extension`
1193
+ * OP_RETURN; attach the input's creating ark tx as `PrevArkTx` (the emulator can't
1194
+ * otherwise resolve its prevout); POST to the emulator's `/v1/tx`. The emulator co-signs
1195
+ * for `nirCosigner` only if `enforcePayTo(senderPkScript)` holds, and finalizes with arkd.
1196
+ * `server`'s signature is still required at consensus level, outside the covenant.
1632
1197
  *
1633
- * THE COVENANT CHECK the emulator runs against that ArkadeScript —
1634
- * `enforcePayTo(senderPkScript)`, see that function above — is: the
1635
- * output at this SAME input's index is a v1 P2TR output paying
1636
- * `senderPkScript`, with a value at least the input's. Only on that
1637
- * check passing does the emulator sign for `nirCosigner` (the
1638
- * `emulatorPubkey` tweaked by this ArkadeScript's hash); the
1639
- * response carries the fully co-signed `signedArkTx` and
1640
- * `signedCheckpointTxs` — `ArkadeTransactionBuilder.send()`'s own
1641
- * comment on this path: "the emulator executes the arkade script
1642
- * and finalizes with arkd" — no separate call to arkd is made by
1643
- * this SDK for a covenant spend.
1644
- * Neither `server` nor `receiver` play any part in that check: this
1645
- * leaf's tapscript still requires `server`'s own signature
1646
- * alongside `nirCosigner`'s (see the class doc above), but that is
1647
- * consensus-level multisig, not something the covenant enforces.
1648
- *
1649
- * NOT SPENDABLE UNTIL `refundLocktime` MATURES — the tapscript's
1650
- * `OP_CHECKLOCKTIMEVERIFY` is arkd's concern, not the emulator's;
1651
- * the emulator will happily co-sign an immature spend and arkd will
1652
- * then refuse it. A block-height-typed `refundLocktime` (below the
1653
- * standard height/timestamp boundary this SDK names
1654
- * `CLTV_HEIGHT_THRESHOLD`, 500,000,000, in
1655
- * `src/contracts/handlers/helpers.ts`) is refused by arkd for a
1656
- * forfeit-eligible leaf independent of maturity — use a
1657
- * seconds-typed (absolute Unix timestamp) value.
1198
+ * The emulator does not check `refundLocktime`: it co-signs an immature spend and arkd's
1199
+ * CLTV refuses it. arkd also refuses a block-height `refundLocktime`
1200
+ * (< `CLTV_HEIGHT_THRESHOLD`, 500,000,000) on a forfeit-eligible leaf regardless of
1201
+ * maturity: use a seconds-typed (Unix timestamp) value.
1658
1202
  */
1659
1203
  nonInteractiveRefundWithoutReceiver() {
1660
1204
  if (!this.nonInteractiveRefundWithoutReceiverScript || !this.nonInteractiveRefundWithoutReceiverArkadeScript) {
@@ -1797,10 +1341,7 @@ function enforcePayToAsset(destinationPkScript, asset) {
1797
1341
  }
1798
1342
  const inspectionTxid = Uint8Array.from(asset.txid).reverse();
1799
1343
  return ArkadeScript.encode([
1800
- // The output carries at least as much of the asset as the input did.
1801
- // Output index is the input's -- the same index alignment the sat
1802
- // covenant relies on, and the same liveness obligation on whoever
1803
- // assembles the spend.
1344
+ // The output at the input's index carries at least the input's amount of the asset.
1804
1345
  "PUSHCURRENTINPUTINDEX",
1805
1346
  inspectionTxid,
1806
1347
  asset.groupIndex,
@@ -1864,139 +1405,18 @@ var VHTLCContractHandler = {
1864
1405
  };
1865
1406
  },
1866
1407
  /**
1867
- * Select spending path based on context.
1868
- *
1869
- * Role is determined from `context.role` or by matching
1870
- * `context.walletDescriptor` (preferred) / `context.walletPubKey`
1871
- * against sender/receiver in contract params.
1408
+ * Select spending path based on context. Role comes from `context.role` or from matching
1409
+ * `context.walletDescriptor` against sender/receiver.
1872
1410
  */
1873
1411
  selectPath(script, contract, context) {
1874
- const role = resolveRole(contract, context);
1875
- const preimage = contract.params?.preimage;
1876
- const refundLocktime = BigInt(contract.params.refundLocktime);
1877
- if (!role) {
1878
- return null;
1879
- }
1880
- if (context.collaborative) {
1881
- if (role === "receiver" && preimage) {
1882
- return {
1883
- leaf: script.claim(),
1884
- extraWitness: [hex.decode(preimage)]
1885
- };
1886
- }
1887
- if (role === "sender" && isCltvSatisfied(context, refundLocktime)) {
1888
- return {
1889
- leaf: script.refundWithoutReceiver()
1890
- };
1891
- }
1892
- return null;
1893
- }
1894
- if (role === "receiver" && preimage) {
1895
- const sequence = Number(contract.params.claimDelay);
1896
- if (!isCsvSpendable(context, sequence)) return null;
1897
- return {
1898
- leaf: script.unilateralClaim(),
1899
- extraWitness: [hex.decode(preimage)],
1900
- sequence
1901
- };
1902
- }
1903
- if (role === "sender") {
1904
- const sequence = Number(contract.params.refundNoReceiverDelay);
1905
- if (!isCsvSpendable(context, sequence)) return null;
1906
- return {
1907
- leaf: script.unilateralRefundWithoutReceiver(),
1908
- sequence
1909
- };
1910
- }
1911
- return null;
1412
+ return selectVhtlcPath(script, contract, context);
1912
1413
  },
1913
- /**
1914
- * Get all possible spending paths (no timelock checks).
1915
- *
1916
- * Role is determined from `context.role` or by matching
1917
- * `context.walletDescriptor` (preferred) / `context.walletPubKey`
1918
- * against sender/receiver in contract params.
1919
- */
1414
+ /** All possible spending paths (no timelock checks); role as in {@link selectPath}. */
1920
1415
  getAllSpendingPaths(script, contract, context) {
1921
- const role = resolveRole(contract, context);
1922
- const paths = [];
1923
- if (!role) {
1924
- return paths;
1925
- }
1926
- const preimage = contract.params?.preimage;
1927
- if (context.collaborative) {
1928
- if (role === "receiver" && preimage) {
1929
- paths.push({
1930
- leaf: script.claim(),
1931
- extraWitness: [hex.decode(preimage)]
1932
- });
1933
- }
1934
- if (role === "sender") {
1935
- paths.push({
1936
- leaf: script.refundWithoutReceiver()
1937
- });
1938
- }
1939
- } else {
1940
- if (role === "receiver" && preimage) {
1941
- const sequence = Number(contract.params.claimDelay);
1942
- paths.push({
1943
- leaf: script.unilateralClaim(),
1944
- extraWitness: [hex.decode(preimage)],
1945
- sequence
1946
- });
1947
- }
1948
- if (role === "sender") {
1949
- const sequence = Number(contract.params.refundNoReceiverDelay);
1950
- paths.push({
1951
- leaf: script.unilateralRefundWithoutReceiver(),
1952
- sequence
1953
- });
1954
- }
1955
- }
1956
- return paths;
1416
+ return vhtlcAllSpendingPaths(script, contract, context);
1957
1417
  },
1958
1418
  getSpendablePaths(script, contract, context) {
1959
- const role = resolveRole(contract, context);
1960
- const paths = [];
1961
- if (!role) {
1962
- return paths;
1963
- }
1964
- const preimage = contract.params?.preimage;
1965
- const refundLocktime = BigInt(contract.params.refundLocktime);
1966
- if (context.collaborative) {
1967
- if (role === "receiver" && preimage) {
1968
- paths.push({
1969
- leaf: script.claim(),
1970
- extraWitness: [hex.decode(preimage)]
1971
- });
1972
- }
1973
- if (role === "sender" && isCltvSatisfied(context, refundLocktime)) {
1974
- paths.push({
1975
- leaf: script.refundWithoutReceiver()
1976
- });
1977
- }
1978
- return paths;
1979
- }
1980
- if (role === "receiver" && preimage) {
1981
- const sequence = Number(contract.params.claimDelay);
1982
- if (isCsvSpendable(context, sequence)) {
1983
- paths.push({
1984
- leaf: script.unilateralClaim(),
1985
- extraWitness: [hex.decode(preimage)],
1986
- sequence
1987
- });
1988
- }
1989
- }
1990
- if (role === "sender") {
1991
- const sequence = Number(contract.params.refundNoReceiverDelay);
1992
- if (isCsvSpendable(context, sequence)) {
1993
- paths.push({
1994
- leaf: script.unilateralRefundWithoutReceiver(),
1995
- sequence
1996
- });
1997
- }
1998
- }
1999
- return paths;
1419
+ return vhtlcSpendablePaths(script, contract, context);
2000
1420
  },
2001
1421
  /**
2002
1422
  * Refuse an explicit spend of a lockup whose refund path has not opened.
@@ -2007,70 +1427,38 @@ var VHTLCContractHandler = {
2007
1427
  assertVhtlcSpendableNow(contract, context);
2008
1428
  },
2009
1429
  /**
2010
- * Never. A live VHTLC is escrow: the counterparty's claim leaf is armed,
2011
- * and generic selection — send, settle, renewal, offboard — would move the
2012
- * lockup out from under a swap the counterparty is still entitled to
2013
- * complete, or race a claim already in flight.
2014
- *
2015
- * Two narrow reasons, and neither is "spending this would destroy the
2016
- * VTXO". It would not: the leaf {@link deriveTapscripts} stamps is
2017
- * `refundWithoutReceiver` — `CLTV[sender, server]`, the sender's OWN
2018
- * refund, which the server rejects until `refundLocktime` matures. The
2019
- * reasons are that an escrowed lockup must not count toward the
2020
- * `available` balance bucket, and that generic RENEWAL must not silently
2021
- * execute a refund the caller never asked for — `runPeriodicSettle` selects
2022
- * through `getSpendableVtxos`, which this gate filters, and it runs
2023
- * unprompted whenever a wallet is built without an explicit
2024
- * `settlementConfig`.
1430
+ * Never. A live VHTLC is escrow: generic selection (send, settle, renewal, offboard) would
1431
+ * move the lockup out from under a swap the counterparty may still complete, or race a claim.
2025
1432
  *
2026
- * **Inseparable from {@link deriveTapscripts}.** Deriving the leaf without
2027
- * closing the gate hands generic renewal an escrow it had never been
2028
- * offered before; before that method existed the answer here was
2029
- * unobservable, so the two only ever made sense together.
1433
+ * The stamped leaf is the sender's own CLTV refund, so this is not about destroying the
1434
+ * VTXO: an escrowed lockup must not count toward the `available` balance, and generic
1435
+ * renewal (`runPeriodicSettle`, unprompted without an explicit `settlementConfig`) must not
1436
+ * silently execute a refund nobody asked for.
2030
1437
  *
2031
- * Recovery is filtered on {@link assertSpendableNow}, not on this: the gate
2032
- * is permanent, and applying it there would strand a matured lockup.
1438
+ * **Inseparable from {@link deriveTapscripts}**: deriving the leaf without closing this gate
1439
+ * hands renewal an escrow. Recovery filters on {@link assertSpendableNow} instead; this
1440
+ * permanent gate would strand a matured lockup.
2033
1441
  */
2034
1442
  isGenericallySpendable: () => false,
2035
1443
  /**
2036
1444
  * The annotation leaf stamped onto every VTXO locked to this contract.
2037
1445
  *
2038
- * **Without this the contract is unusable, not merely unoptimized.**
2039
- * `deriveContractTapscripts` falls back to `script.forfeit()` for any
2040
- * handler that does not implement {@link TapscriptDeriving}, and no VHTLC
2041
- * script version has a `forfeit()` — `VtxoScript` does not define one and
2042
- * `VHTLC.BaseScript` does not add one. The fallback therefore threw
2043
- * `legacy.forfeit is not a function`, `annotatableIn` caught it, and
2044
- * `fetchContractVtxosBulk` dropped this contract's VTXOs before they were
2045
- * persisted. The row existed and was watched while its balance stayed
2046
- * permanently invisible, and `getSyncState()` reported `degraded` forever.
1446
+ * **Required, not an optimization.** Without it `deriveContractTapscripts` falls back to
1447
+ * `script.forfeit()`, which no VHTLC script has, so the contract's VTXOs are dropped before
1448
+ * persisting: balance invisible, `getSyncState()` degraded forever.
2047
1449
  *
2048
- * `refundWithoutReceiver` is the leaf, for both roles the annotation
2049
- * serves, because it is the only collaborative path this wallet can
2050
- * actually satisfy as the sender — the same conclusion `selectPath`
2051
- * reaches, kept in step with it deliberately. The claim leaves belong to
2052
- * the receiver and need a preimage the sender does not have; `refund` and
2053
- * `unilateralRefund` both need the counterparty's signature, which this
2054
- * protocol has no message to ask for. V1 has no covenant leaves at all.
1450
+ * `refundWithoutReceiver` is the only collaborative path this wallet can satisfy as sender
1451
+ * (kept in step with `selectPath`): the claim leaves need the preimage, and `refund` /
1452
+ * `unilateralRefund` need a counterparty signature the protocol can't request. V1 has no
1453
+ * covenant leaves.
2055
1454
  *
2056
- * **It carries a CLTV, and callers must respect it.** A settlement built
2057
- * on this leaf is only valid once `refundLocktime` has matured, so a
2058
- * recovery round that sweeps this VTXO early is rejected by the server.
2059
- * Nothing in this handler can enforce that, because the annotation is
2060
- * derived per contract and knows no clock; `recoverVtxos` filters on
2061
- * {@link assertSpendableNow} for it.
2062
- *
2063
- * **Role-blind.** A receiver's row gets the same leaf, which names keys that
2064
- * wallet does not hold, so it is satisfiable only by the wallet holding the
2065
- * lockup's `sender` key and fails at intent registration for anyone else.
1455
+ * **It carries a CLTV**: a settlement on it is rejected until `refundLocktime` matures, and
1456
+ * the per-contract annotation knows no clock, so `recoverVtxos` filters on
1457
+ * {@link assertSpendableNow}. **Role-blind**: a receiver's row gets the same leaf, satisfiable
1458
+ * only by the `sender` key holder; anyone else fails at intent registration.
2066
1459
  */
2067
1460
  deriveTapscripts(script) {
2068
- const leaf = script.refundWithoutReceiver();
2069
- return {
2070
- forfeitTapLeafScript: leaf,
2071
- intentTapLeafScript: leaf,
2072
- tapTree: script.encode()
2073
- };
1461
+ return deriveVhtlcTapscripts(script);
2074
1462
  }
2075
1463
  };
2076
1464
  var parseGroupIndex = (raw) => {
@@ -2109,43 +1497,21 @@ var VHTLCV2ContractHandler = {
2109
1497
  refundNoReceiverDelay: timelockToSequence(
2110
1498
  params.unilateralRefundWithoutReceiverDelay
2111
1499
  ).toString(),
2112
- // The on-disk keys keep their per-leaf names even though the typed
2113
- // param is ONE group: rows written by older SDKs carry exactly
2114
- // these keys, and changing the storage encoding would orphan them.
2115
- // The group maps onto them without loss — one emulator key serves
2116
- // both covenant destinations by construction (see the option's doc
2117
- // on VHTLC.Options), so the two emulator-key columns always hold
2118
- // the same value on a row written from here.
2119
- //
2120
- // Spread rather than written as `undefined`: a `Record<string,
2121
- // string>` round-tripped through a repository must not gain keys
2122
- // whose value is the string "undefined".
1500
+ // Per-leaf on-disk keys predate the grouped param and older rows carry exactly these;
1501
+ // both emulator columns hold the same key. Spread, not `undefined`, so a repository
1502
+ // round-trip never gains "undefined" string values.
2123
1503
  ...covenants && {
2124
1504
  nonInteractiveClaimReceiverPkScript: hex.encode(covenants.receiverPkScript),
2125
1505
  nonInteractiveClaimEmulatorPubkey: hex.encode(covenants.emulatorPubkey),
2126
1506
  nonInteractiveRefundSenderPkScript: hex.encode(covenants.senderPkScript),
2127
1507
  nonInteractiveRefundEmulatorPubkey: hex.encode(covenants.emulatorPubkey),
2128
- // The legacy marker is precisely the ABSENCE of this key: a
2129
- // row carrying it re-derives the full suite (nine leaves), a
2130
- // row lacking it the pre-timelocked-refund eight — the same
2131
- // encoding older SDKs wrote for eight-leaf rows, so those rows
2132
- // read back unchanged. Spread rather than a "false"/"undefined"
2133
- // string: a dropped flag re-derives the EIGHT-leaf script, a
2134
- // different pkScript, and registration dies at
2135
- // `upsertContractRow` with an opaque `Script mismatch`.
1508
+ // The legacy (eight-leaf) marker is this key's ABSENCE, as older SDKs wrote it.
2136
1509
  ...covenants.legacy === void 0 && {
2137
1510
  nonInteractiveRefundWithoutReceiver: "1"
2138
1511
  }
2139
1512
  },
2140
- // The asset must round-trip, and its absence here used to be silent
2141
- // in the worst way. `ContractManager` re-derives a contract from
2142
- // these params; dropping the asset derives the SAT-ONLY script, so
2143
- // registration died at `upsertContractRow` with a `Script mismatch`
2144
- // naming two hex strings and no cause. Same silent-drop class
2145
- // `validateOptions` refuses one layer up.
2146
- //
2147
- // Two keys rather than one blob, mirroring the script's own view of
2148
- // an Asset ID as a (txid, groupIndex) pair.
1513
+ // Must round-trip: dropping the asset re-derives the sat-only script and registration
1514
+ // dies at `upsertContractRow` with an opaque `Script mismatch`.
2149
1515
  ...params.asset && {
2150
1516
  assetTxid: hex.encode(params.asset.txid),
2151
1517
  assetGroupIndex: params.asset.groupIndex.toString()
@@ -2216,8 +1582,6 @@ var VHTLCV2ContractHandler = {
2216
1582
  receiverPkScript: claim.destination,
2217
1583
  senderPkScript: refund.destination,
2218
1584
  emulatorPubkey: claim.emulatorPubkey,
2219
- // Absence of the flag IS the legacy marker — the same
2220
- // encoding older SDKs wrote for eight-leaf rows.
2221
1585
  ...params.nonInteractiveRefundWithoutReceiver !== "1" && {
2222
1586
  legacy: "preTimelockedRefund"
2223
1587
  }
@@ -2226,139 +1590,18 @@ var VHTLCV2ContractHandler = {
2226
1590
  };
2227
1591
  },
2228
1592
  /**
2229
- * Select spending path based on context.
2230
- *
2231
- * Role is determined from `context.role` or by matching
2232
- * `context.walletDescriptor` (preferred) / `context.walletPubKey`
2233
- * against sender/receiver in contract params.
1593
+ * Select spending path based on context. Role comes from `context.role` or by matching
1594
+ * `context.walletDescriptor` against sender/receiver.
2234
1595
  */
2235
1596
  selectPath(script, contract, context) {
2236
- const role = resolveRole(contract, context);
2237
- const preimage = contract.params?.preimage;
2238
- const refundLocktime = BigInt(contract.params.refundLocktime);
2239
- if (!role) {
2240
- return null;
2241
- }
2242
- if (context.collaborative) {
2243
- if (role === "receiver" && preimage) {
2244
- return {
2245
- leaf: script.claim(),
2246
- extraWitness: [hex.decode(preimage)]
2247
- };
2248
- }
2249
- if (role === "sender" && isCltvSatisfied(context, refundLocktime)) {
2250
- return {
2251
- leaf: script.refundWithoutReceiver()
2252
- };
2253
- }
2254
- return null;
2255
- }
2256
- if (role === "receiver" && preimage) {
2257
- const sequence = Number(contract.params.claimDelay);
2258
- if (!isCsvSpendable(context, sequence)) return null;
2259
- return {
2260
- leaf: script.unilateralClaim(),
2261
- extraWitness: [hex.decode(preimage)],
2262
- sequence
2263
- };
2264
- }
2265
- if (role === "sender") {
2266
- const sequence = Number(contract.params.refundNoReceiverDelay);
2267
- if (!isCsvSpendable(context, sequence)) return null;
2268
- return {
2269
- leaf: script.unilateralRefundWithoutReceiver(),
2270
- sequence
2271
- };
2272
- }
2273
- return null;
1597
+ return selectVhtlcPath(script, contract, context);
2274
1598
  },
2275
- /**
2276
- * Get all possible spending paths (no timelock checks).
2277
- *
2278
- * Role is determined from `context.role` or by matching
2279
- * `context.walletDescriptor` (preferred) / `context.walletPubKey`
2280
- * against sender/receiver in contract params.
2281
- */
1599
+ /** Get all possible spending paths (no timelock checks); role resolved as in `selectPath`. */
2282
1600
  getAllSpendingPaths(script, contract, context) {
2283
- const role = resolveRole(contract, context);
2284
- const paths = [];
2285
- if (!role) {
2286
- return paths;
2287
- }
2288
- const preimage = contract.params?.preimage;
2289
- if (context.collaborative) {
2290
- if (role === "receiver" && preimage) {
2291
- paths.push({
2292
- leaf: script.claim(),
2293
- extraWitness: [hex.decode(preimage)]
2294
- });
2295
- }
2296
- if (role === "sender") {
2297
- paths.push({
2298
- leaf: script.refundWithoutReceiver()
2299
- });
2300
- }
2301
- } else {
2302
- if (role === "receiver" && preimage) {
2303
- const sequence = Number(contract.params.claimDelay);
2304
- paths.push({
2305
- leaf: script.unilateralClaim(),
2306
- extraWitness: [hex.decode(preimage)],
2307
- sequence
2308
- });
2309
- }
2310
- if (role === "sender") {
2311
- const sequence = Number(contract.params.refundNoReceiverDelay);
2312
- paths.push({
2313
- leaf: script.unilateralRefundWithoutReceiver(),
2314
- sequence
2315
- });
2316
- }
2317
- }
2318
- return paths;
1601
+ return vhtlcAllSpendingPaths(script, contract, context);
2319
1602
  },
2320
1603
  getSpendablePaths(script, contract, context) {
2321
- const role = resolveRole(contract, context);
2322
- const paths = [];
2323
- if (!role) {
2324
- return paths;
2325
- }
2326
- const preimage = contract.params?.preimage;
2327
- const refundLocktime = BigInt(contract.params.refundLocktime);
2328
- if (context.collaborative) {
2329
- if (role === "receiver" && preimage) {
2330
- paths.push({
2331
- leaf: script.claim(),
2332
- extraWitness: [hex.decode(preimage)]
2333
- });
2334
- }
2335
- if (role === "sender" && isCltvSatisfied(context, refundLocktime)) {
2336
- paths.push({
2337
- leaf: script.refundWithoutReceiver()
2338
- });
2339
- }
2340
- return paths;
2341
- }
2342
- if (role === "receiver" && preimage) {
2343
- const sequence = Number(contract.params.claimDelay);
2344
- if (isCsvSpendable(context, sequence)) {
2345
- paths.push({
2346
- leaf: script.unilateralClaim(),
2347
- extraWitness: [hex.decode(preimage)],
2348
- sequence
2349
- });
2350
- }
2351
- }
2352
- if (role === "sender") {
2353
- const sequence = Number(contract.params.refundNoReceiverDelay);
2354
- if (isCsvSpendable(context, sequence)) {
2355
- paths.push({
2356
- leaf: script.unilateralRefundWithoutReceiver(),
2357
- sequence
2358
- });
2359
- }
2360
- }
2361
- return paths;
1604
+ return vhtlcSpendablePaths(script, contract, context);
2362
1605
  },
2363
1606
  /**
2364
1607
  * Refuse an explicit spend of a lockup whose refund path has not opened.
@@ -2368,62 +1611,28 @@ var VHTLCV2ContractHandler = {
2368
1611
  assertVhtlcSpendableNow(contract, context);
2369
1612
  },
2370
1613
  /**
2371
- * Never. A live VHTLC is escrow: the counterparty's claim leaf is armed,
2372
- * and generic selection — send, settle, RENEWAL, offboard — would move the
2373
- * lockup out from under a swap the counterparty is still entitled to
2374
- * complete, or race a claim already in flight. The `vhtlc` handler now
2375
- * answers the same, for the same reason.
2376
- *
2377
- * The gate covers `available` and renewal. Recovery is generic and names
2378
- * nothing, so it is not covered here: `recoverVtxos` filters on
2379
- * {@link assertSpendableNow} instead, which drops a lockup only while its
2380
- * refund path is shut rather than forever. The explicit routes out —
2381
- * `settle({ inputs })`, or `@arkade-os/swap`'s `pushRefundWithoutReceiver`
2382
- * over `findLockupVtxos`' outpoints — are unaffected either way.
1614
+ * Never. A live VHTLC is escrow with the counterparty's claim leaf armed; generic selection
1615
+ * (send, settle, renewal, offboard) would move it out from under the swap or race a claim.
1616
+ * Recovery isn't covered by this gate: `recoverVtxos` filters on {@link assertSpendableNow},
1617
+ * dropping a lockup only while its refund path is shut. Explicit routes (`settle({ inputs })`,
1618
+ * `pushRefundWithoutReceiver`) are unaffected.
2383
1619
  */
2384
1620
  isGenericallySpendable: () => false,
2385
1621
  /**
2386
1622
  * The annotation leaf stamped onto every VTXO locked to this contract.
2387
1623
  *
2388
- * **Without this the contract is unusable, not merely unoptimized.**
2389
- * `deriveContractTapscripts` falls back to `script.forfeit()` for any
2390
- * handler that does not implement {@link TapscriptDeriving}, and no VHTLC
2391
- * script version has a `forfeit()` — `VtxoScript` does not define one and
2392
- * `VHTLC.BaseScript` does not add one. The fallback therefore throws
2393
- * `legacy.forfeit is not a function`, `annotatableIn` catches it, and
2394
- * `fetchContractVtxosBulk` drops this contract's VTXOs before they are
2395
- * persisted. The row would exist and be watched while its balance stayed
2396
- * permanently invisible, and `getSyncState()` would report `degraded`
2397
- * forever. (The `vhtlc` handler had the same gap and now closes it the same
2398
- * way; nothing registers V1 rows through `ContractManager` in this repo, so
2399
- * it was never hit here.)
1624
+ * Required, not an optimization: without {@link TapscriptDeriving} the fallback calls
1625
+ * `script.forfeit()`, which no VHTLC version has, so the VTXOs are silently dropped before
1626
+ * persisting and `getSyncState()` stays `degraded`.
2400
1627
  *
2401
- * `refundWithoutReceiver` is the leaf, for both roles the annotation
2402
- * serves, because it is the only collaborative path this wallet can
2403
- * actually satisfy as the sender — the same conclusion `selectPath`
2404
- * reaches, kept in step with it deliberately. The claim leaves belong to
2405
- * the receiver and need a preimage the sender does not have; `refund` and
2406
- * `unilateralRefund` need the counterparty; the covenant leaves are the
2407
- * emulator's.
2408
- *
2409
- * **It carries a CLTV, and callers must respect it.** A settlement built on
2410
- * this leaf is only valid once `refundLocktime` has matured, so a recovery
2411
- * round that sweeps this VTXO early is rejected by the server. Nothing in
2412
- * this handler can enforce it, because the annotation is derived per
2413
- * contract and knows no clock. `recoverVtxos` filters on
2414
- * {@link assertSpendableNow} for it.
2415
- *
2416
- * **Role-blind.** A receiver's row gets the same leaf, which names keys that
2417
- * wallet does not hold, so it is satisfiable only by the wallet holding the
2418
- * lockup's `sender` key and fails at intent registration for anyone else.
1628
+ * `refundWithoutReceiver` is used (in step with `selectPath`) as the only collaborative leaf
1629
+ * the sender can satisfy. It carries a CLTV: settling on it before `refundLocktime` matures is
1630
+ * rejected, and this handler has no clock, so `recoverVtxos` filters on
1631
+ * {@link assertSpendableNow}. Role-blind: a receiver's row gets the same leaf and fails at
1632
+ * intent registration.
2419
1633
  */
2420
1634
  deriveTapscripts(script) {
2421
- const leaf = script.refundWithoutReceiver();
2422
- return {
2423
- forfeitTapLeafScript: leaf,
2424
- intentTapLeafScript: leaf,
2425
- tapTree: script.encode()
2426
- };
1635
+ return deriveVhtlcTapscripts(script);
2427
1636
  }
2428
1637
  };
2429
1638
  var BoardingContractHandler = {
@@ -2449,24 +1658,10 @@ var BoardingContractHandler = {
2449
1658
  /** Preserves today's settle/board behaviour. */
2450
1659
  isGenericallySpendable: () => true,
2451
1660
  /**
2452
- * Probe the on-chain UTXO set for a boarding output at this HD index.
2453
- *
2454
- * Boarding's source of truth is the **current** on-chain coin set
2455
- * (`OnchainProvider.getCoins`), not the Ark indexer: a boarded (spent)
2456
- * boarding output becomes an L2 VTXO at the receive index, so the
2457
- * indexer probe already keeps the gap window open for it; only an
2458
- * *unspent* boarding output needs the on-chain probe (see plan §2).
2459
- *
2460
- * No-ops (returns `[]`) when boarding discovery is not plumbed — i.e.
2461
- * `deps.boardingTimelock` or `deps.onchainNetwork` is absent — so the
2462
- * scanner unit harness (which sets neither) is unaffected.
2463
- *
2464
- * Always emits `type: "boarding"`, even in the degenerate equal-delay case
2465
- * where the boarding script is byte-identical to a `default` script at this
2466
- * index (`boardingExitDelay === unilateralExitDelay`). The collision is no
2467
- * longer pre-judged in discovery; it is tolerated FIRST-WINS at the
2468
- * persistence layer ({@link ContractManager.upsertContract}). `deps.csvTimelocks`
2469
- * is intentionally not read here.
1661
+ * Probe the **current** on-chain coin set for a boarding output at this HD index. Only an
1662
+ * unspent boarding output needs this: a boarded one became an L2 VTXO at the receive index,
1663
+ * which the indexer probe already covers. Always emits `type: "boarding"` (see the handler
1664
+ * doc on collisions); `deps.csvTimelocks` is intentionally not read.
2470
1665
  */
2471
1666
  async discoverAt(index, descriptor, deps) {
2472
1667
  if (!deps.boardingTimelock || !deps.onchainNetwork) return [];
@@ -2480,26 +1675,19 @@ var BoardingContractHandler = {
2480
1675
  const coins = await deps.onchainProvider.getCoins(onchainAddress);
2481
1676
  if (coins.length === 0) return [];
2482
1677
  const scriptHex = hex.encode(script.pkScript);
2483
- const boardingSeq = timelockToSequence(deps.boardingTimelock);
2484
1678
  return [
2485
1679
  {
2486
1680
  type: "boarding",
2487
- params: {
2488
- pubKey: hex.encode(pubKey),
2489
- serverPubKey: hex.encode(deps.serverPubKey),
2490
- csvTimelock: boardingSeq.toString()
2491
- },
1681
+ params: BoardingContractHandler.serializeParams({
1682
+ pubKey,
1683
+ serverPubKey: deps.serverPubKey,
1684
+ csvTimelock: deps.boardingTimelock
1685
+ }),
2492
1686
  script: scriptHex,
2493
- // The persisted row's `address` is the *Ark* address (not the
2494
- // on-chain P2TR), matching the row registered at init so the
2495
- // ContractWatcher keeps monitoring the same L2 script and the
2496
- // VTXO repository bucket lines up (plan §6-I.4). The P2TR is
2497
- // recomputed from params only for the `getCoins` probe.
1687
+ // The Ark address, not the P2TR: must match the row registered at init so the
1688
+ // watcher monitors the same L2 script and the VTXO repository bucket lines up.
2498
1689
  address: script.address(deps.network.hrp, deps.serverPubKey).encode(),
2499
- // Tag rotated rows (index > 0) so boot resolution can find the
2500
- // newest boarding address and descriptor-aware signing can
2501
- // recover the per-index key (plan §6-II/§6-III.3). The index-0
2502
- // baseline stays untagged, matching `default`/`delegate`.
1690
+ // Tag rotated rows (index > 0) for boot resolution and per-index signing.
2503
1691
  ...rotatedReceiveMetadata(index, descriptor)
2504
1692
  }
2505
1693
  ];
@@ -2526,6 +1714,17 @@ function resolveTimelockValue(value, args) {
2526
1714
  `invalid timelock value '${value}' \u2014 expected a bigint or a '$param' reference`
2527
1715
  );
2528
1716
  }
1717
+ function resolveCsvTimelock(csv, args) {
1718
+ const value = resolveTimelockValue(csv.value, args);
1719
+ if (csv.type !== "sequence") return { type: csv.type, value };
1720
+ const timelock = sequenceToCanonicalTimelock(value);
1721
+ if (!timelock) {
1722
+ throw new Error(
1723
+ `csv '${csv.value}' resolved to ${value}, which is not a canonical BIP68 sequence`
1724
+ );
1725
+ }
1726
+ return timelock;
1727
+ }
2529
1728
  function resolveAsm(asm, bind) {
2530
1729
  const tokens = asm.map((t) => {
2531
1730
  if (typeof t === "string" && t.startsWith("$")) {
@@ -2574,6 +1773,7 @@ function collectParamRefs(program) {
2574
1773
  const collect = (items) => {
2575
1774
  for (const t of items ?? []) {
2576
1775
  if (typeof t === "string" && t.startsWith("$")) refs.add(t.slice(1));
1776
+ else if (isTweakedSigner(t) && t.tweak.startsWith("$")) refs.add(t.tweak.slice(1));
2577
1777
  }
2578
1778
  };
2579
1779
  for (const fn of Object.values(program.functions)) {
@@ -2626,10 +1826,15 @@ function validateProgram(program, args) {
2626
1826
  if (typeof p !== "string") validateParamValue(p, args[p.name]);
2627
1827
  }
2628
1828
  }
1829
+ function isTweakedSigner(ref) {
1830
+ if (typeof ref !== "object" || ref === null || ref instanceof Uint8Array) return false;
1831
+ const { tweak, fn } = ref;
1832
+ return typeof tweak === "string" && typeof fn === "string";
1833
+ }
2629
1834
  function resolveSigner(ref, args) {
2630
1835
  if (ref instanceof Uint8Array) return ref;
2631
- if (!ref.startsWith("$")) {
2632
- throw new Error(`unknown signer reference '${ref}' \u2014 use '$${ref}'`);
1836
+ if (typeof ref !== "string" || !ref.startsWith("$")) {
1837
+ throw new Error(`unknown signer reference '${ref}' \u2014 use a '$param' or key bytes`);
2633
1838
  }
2634
1839
  const v = bindValue(args, ref.slice(1));
2635
1840
  if (!(v instanceof Uint8Array)) {
@@ -2639,7 +1844,7 @@ function resolveSigner(ref, args) {
2639
1844
  }
2640
1845
  function encodeTapscriptSegment(seg, pubkeys, args) {
2641
1846
  if (seg.csv) {
2642
- const timelock = { type: seg.csv.type, value: resolveTimelockValue(seg.csv.value, args) };
1847
+ const timelock = resolveCsvTimelock(seg.csv, args);
2643
1848
  if (seg.asm) {
2644
1849
  return ConditionCSVMultisigTapscript.encode({
2645
1850
  conditionScript: resolveAsm(seg.asm, args),
@@ -2684,9 +1889,21 @@ function compileFunctions(program, args, keys) {
2684
1889
  }
2685
1890
  return defs.map((def, i) => {
2686
1891
  validateTapscript(def.tapscript);
2687
- const pubkeys = def.tapscript.signers.map((s) => resolveSigner(s, args));
1892
+ const pubkeys = def.tapscript.signers.map((signer) => {
1893
+ if (!isTweakedSigner(signer)) return resolveSigner(signer, args);
1894
+ const asm = functions[signer.fn]?.arkadeScript?.asm;
1895
+ if (!asm) {
1896
+ throw new Error(
1897
+ `ArkadeContract: function '${names[i]}' tweaks '${signer.fn}', which has no arkade script`
1898
+ );
1899
+ }
1900
+ return computeArkadeScriptPublicKey(
1901
+ resolveSigner(signer.tweak, args),
1902
+ resolveAsm(asm, args)
1903
+ );
1904
+ });
2688
1905
  const arkadeScript = def.arkadeScript ? resolveAsm(def.arkadeScript.asm, args) : void 0;
2689
- const leafPubkeys = arkadeScript ? [...pubkeys, computeArkadeScriptPublicKey(keys.emulatorKey, arkadeScript)] : pubkeys;
1906
+ const leafPubkeys = arkadeScript && def.tapscript.emulator !== null ? [...pubkeys, computeArkadeScriptPublicKey(keys.emulatorKey, arkadeScript)] : pubkeys;
2690
1907
  const leafScript = encodeTapscriptSegment(def.tapscript, leafPubkeys, args).script;
2691
1908
  return { name: names[i], def, leafScript, arkadeScript, signerKeys: pubkeys };
2692
1909
  });
@@ -2714,17 +1931,31 @@ var ArkadeProgramScript = class extends VtxoScript {
2714
1931
  }
2715
1932
  };
2716
1933
  function parseArtifact(artifact) {
1934
+ if (Array.isArray(artifact.functions)) {
1935
+ throw new Error(
1936
+ "parseArtifact: `functions` is an array, which is the arkadec artifact shape, not an SDK program \u2014 read it with programFromArtifact instead"
1937
+ );
1938
+ }
1939
+ if (typeof artifact.functions !== "object" || artifact.functions === null) {
1940
+ throw new Error("parseArtifact: `functions` must be an object keyed by function name");
1941
+ }
2717
1942
  const hexToken = (t) => typeof t === "string" && t.startsWith("0x") ? hex.decode(t.slice(2)) : t;
1943
+ const signerRef = (s) => {
1944
+ if (s instanceof Uint8Array || typeof s === "string") return hexToken(s);
1945
+ if (isTweakedSigner(s)) return { tweak: s.tweak, fn: s.fn };
1946
+ throw new Error("parseArtifact: a tweaked signer needs string `tweak` and `fn`");
1947
+ };
2718
1948
  const timelockValue = (v) => typeof v === "string" && v.startsWith("$") ? v : BigInt(v);
2719
1949
  const functions = {};
2720
1950
  for (const [name, fn] of Object.entries(artifact.functions)) {
2721
1951
  const tap = fn.tapscript ?? {};
2722
1952
  const tapscript = {
2723
- signers: (tap.signers ?? []).map(hexToken),
1953
+ signers: (tap.signers ?? []).map(signerRef),
2724
1954
  ...tap.asm ? { asm: tap.asm.map(hexToken) } : {},
2725
1955
  ...tap.witness ? { witness: tap.witness.map(hexToken) } : {},
2726
1956
  ...tap.csv ? { csv: { type: tap.csv.type, value: timelockValue(tap.csv.value) } } : {},
2727
- ...tap.cltv !== void 0 ? { cltv: timelockValue(tap.cltv) } : {}
1957
+ ...tap.cltv !== void 0 ? { cltv: timelockValue(tap.cltv) } : {},
1958
+ ..."emulator" in tap ? { emulator: tap.emulator } : {}
2728
1959
  };
2729
1960
  const arkadeScript = fn.arkadeScript ? {
2730
1961
  asm: fn.arkadeScript.asm.map(hexToken),
@@ -2764,7 +1995,8 @@ function stringifyArtifact(program) {
2764
1995
  ...tap.asm ? { asm: tap.asm.map(token) } : {},
2765
1996
  ...tap.witness ? { witness: tap.witness.map(token) } : {},
2766
1997
  ...tap.csv ? { csv: { type: tap.csv.type, value: tap.csv.value.toString() } } : {},
2767
- ...tap.cltv !== void 0 ? { cltv: tap.cltv.toString() } : {}
1998
+ ...tap.cltv !== void 0 ? { cltv: tap.cltv.toString() } : {},
1999
+ ...tap.emulator !== void 0 ? { emulator: tap.emulator } : {}
2768
2000
  },
2769
2001
  ...fn.arkadeScript ? {
2770
2002
  arkadeScript: {
@@ -2850,22 +2082,12 @@ function deserializeArkadeContractParams(params) {
2850
2082
 
2851
2083
  // src/contracts/handlers/arkade.ts
2852
2084
  function walletKeyFrom(context) {
2853
- const extract = (value) => {
2854
- if (!isDescriptor(value)) return value.toLowerCase();
2855
- try {
2856
- return extractPubKey(value).toLowerCase();
2857
- } catch {
2858
- return void 0;
2859
- }
2860
- };
2861
- if (context.walletDescriptor) {
2862
- const key = extract(context.walletDescriptor);
2863
- if (key) return key;
2864
- }
2865
- if (context.walletPubKey) {
2866
- return extract(context.walletPubKey);
2085
+ if (!context.walletDescriptor) return void 0;
2086
+ try {
2087
+ return extractPubKey(context.walletDescriptor).toLowerCase();
2088
+ } catch {
2089
+ return void 0;
2867
2090
  }
2868
- return void 0;
2869
2091
  }
2870
2092
  function staticWitness(fn, script) {
2871
2093
  const refs = fn.def.tapscript.witness ?? [];
@@ -2890,7 +2112,7 @@ function pathsFor(script, context, opts) {
2890
2112
  continue;
2891
2113
  }
2892
2114
  const csvDef = fn.def.tapscript.csv;
2893
- const csv = csvDef ? { type: csvDef.type, value: resolveTimelockValue(csvDef.value, script.args) } : void 0;
2115
+ const csv = csvDef ? resolveCsvTimelock(csvDef, script.args) : void 0;
2894
2116
  const cltv = fn.def.tapscript.cltv !== void 0 ? resolveTimelockValue(fn.def.tapscript.cltv, script.args) : void 0;
2895
2117
  if (context.collaborative) {
2896
2118
  if (csv) continue;
@@ -2986,6 +2208,6 @@ contractHandlers.register(VHTLCV2ContractHandler);
2986
2208
  contractHandlers.register(BoardingContractHandler);
2987
2209
  contractHandlers.register(ArkadeContractHandler);
2988
2210
 
2989
- 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 };
2990
- //# sourceMappingURL=chunk-3R476TDV.js.map
2991
- //# sourceMappingURL=chunk-3R476TDV.js.map
2211
+ 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, extractPubKey, fromASM, getOpcodeName, getOpcodeValue, identityDescriptor, inputName, isCltvSatisfied, isCsvSpendable, isMainnetDescriptor, normalizeToDescriptor, parseArtifact, parseHDDescriptor, resolveAsm, serializeArkadeContractParams, speculativeReceiveMetadata, stringifyArtifact, toASM, toXOnly, validateProgram, witnessRefToBytes };
2212
+ //# sourceMappingURL=chunk-CXHJGED6.js.map
2213
+ //# sourceMappingURL=chunk-CXHJGED6.js.map