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