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