@arkade-os/swap 0.0.10 → 0.0.11
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 +17 -0
- package/dist/{chunk-5NPYNQ5V.js → chunk-RIC5ZTZK.js} +68 -28
- package/dist/index.cjs +196 -43
- package/dist/index.d.cts +61 -15
- package/dist/index.d.ts +61 -15
- package/dist/index.js +129 -16
- package/dist/nostr.d.cts +1 -1
- package/dist/nostr.d.ts +1 -1
- package/dist/nostr.js +1 -1
- package/dist/repositories/realm/index.d.cts +2 -2
- package/dist/repositories/realm/index.d.ts +2 -2
- package/dist/repositories/sqlite/index.d.cts +2 -2
- package/dist/repositories/sqlite/index.d.ts +2 -2
- package/dist/{repository-C8FXlmHY.d.cts → repository-B02vsgcV.d.cts} +42 -5
- package/dist/{repository-Cw2EvvZG.d.ts → repository-CfE18Fif.d.ts} +42 -5
- package/dist/{rfq-DglvHMNC.d.ts → rfq-DkckzRKK.d.cts} +44 -16
- package/dist/{rfq-DglvHMNC.d.cts → rfq-DkckzRKK.d.ts} +44 -16
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -759,6 +759,23 @@ through their stored `preimageHex` or their HD descriptor, and `DB_VERSION` is u
|
|
|
759
759
|
|
|
760
760
|
Notes from before 0.0.1, kept for consumers who tracked the branch.
|
|
761
761
|
|
|
762
|
+
- **`refundIfUnresolved` reports an exited lockup, and its input gained `paymentHash`.**
|
|
763
|
+
`RefundOutcome` has a new `{ outcome: "exited"; outpoints; status }` variant: a lockup whose
|
|
764
|
+
outputs were unilaterally exited lives onchain under the VHTLC script, where no offchain refund
|
|
765
|
+
reaches it. It used to come back as `nothing_to_refund`, which reads as "already resolved" over
|
|
766
|
+
money still sitting at the script — the swept case gets `needs_recovery` for the same reason, and
|
|
767
|
+
this is deliberately **not** that variant: recovery into a fresh batch is a spend no batch can
|
|
768
|
+
make for an onchain output. Complete the unroll and spend the outputs onchain instead.
|
|
769
|
+
|
|
770
|
+
Two required changes for direct callers. The input gains **`paymentHash`** (`sha256(P)` hex — the
|
|
771
|
+
quote's `payment_hash`, which callers already hold): the exit is read through `readLockupFate`,
|
|
772
|
+
and the VHTLC script cannot supply it, since its `preimageHash` is a `hash160` of the same secret.
|
|
773
|
+
And the `indexer` parameter widened from `RefundIndexer` to **`LockupSpendIndexer`**, so it must
|
|
774
|
+
now carry `getVirtualTxs` as well as `getVtxos` — a real `RestIndexerProvider` already does.
|
|
775
|
+
|
|
776
|
+
A lockup funded in two sends of which only one exited reports `exited` for the whole thing and
|
|
777
|
+
leaves the live half unrefunded. That matches `RfqSwapManager`, which reports the same lockup
|
|
778
|
+
`exited` on the same any-output rule; the two must not disagree.
|
|
762
779
|
- **`RfqSwapManager` can own its own persistence.** New optional
|
|
763
780
|
`RfqSwapManagerDeps.repository`, new `restoreFromRepository()` and `pruneRetiredSwaps()`, and
|
|
764
781
|
`addSwap(swap, origin?)` gains an optional second argument. Nothing narrows and nothing is
|
|
@@ -422,8 +422,22 @@ var assertPairLength = (pair) => {
|
|
|
422
422
|
};
|
|
423
423
|
var verifyLockupAddress = (quote, derivedAddress) => {
|
|
424
424
|
const quoted = quote.profile?.lockup_address;
|
|
425
|
-
|
|
426
|
-
|
|
425
|
+
const candidates = Array.isArray(derivedAddress) ? derivedAddress : [derivedAddress];
|
|
426
|
+
const matched = candidates.find((address) => address === quoted);
|
|
427
|
+
if (matched === void 0) throw new AddressMismatch(candidates, quoted);
|
|
428
|
+
return matched;
|
|
429
|
+
};
|
|
430
|
+
var LOCKUP_SHAPE_VARIANTS = [void 0, "preTimelockedRefund"];
|
|
431
|
+
var matchQuotedLockup = (quote, hrp, serverPubkey, build) => {
|
|
432
|
+
const candidates = LOCKUP_SHAPE_VARIANTS.map((legacy) => {
|
|
433
|
+
const script = build(legacy);
|
|
434
|
+
return { script, address: script.address(hrp, serverPubkey).encode(), legacy };
|
|
435
|
+
});
|
|
436
|
+
const matchedAddress = verifyLockupAddress(
|
|
437
|
+
quote,
|
|
438
|
+
candidates.map((candidate) => candidate.address)
|
|
439
|
+
);
|
|
440
|
+
return candidates.find((candidate) => candidate.address === matchedAddress);
|
|
427
441
|
};
|
|
428
442
|
var assertFundable = (input) => {
|
|
429
443
|
const fail = (reason, message) => {
|
|
@@ -625,13 +639,11 @@ function lightningSendVtxoScript(params) {
|
|
|
625
639
|
unilateralRefundWithoutReceiverDelay: seconds(
|
|
626
640
|
unilateralRefundWithoutReceiverDelay(params.claimDelay)
|
|
627
641
|
),
|
|
628
|
-
|
|
642
|
+
nonInteractiveParameters: {
|
|
629
643
|
receiverPkScript: params.receiverPkScript,
|
|
630
|
-
emulatorPubkey: params.emulatorPubkey
|
|
631
|
-
},
|
|
632
|
-
nonInteractiveRefund: {
|
|
633
644
|
senderPkScript: params.refundPkScript,
|
|
634
|
-
emulatorPubkey: params.emulatorPubkey
|
|
645
|
+
emulatorPubkey: params.emulatorPubkey,
|
|
646
|
+
...params.legacy !== void 0 && { legacy: params.legacy }
|
|
635
647
|
}
|
|
636
648
|
});
|
|
637
649
|
}
|
|
@@ -677,9 +689,18 @@ async function requestLightningSend(wallet, arkServerUrl, transport, params) {
|
|
|
677
689
|
receiverPkScript: solverHex(receiverPkScriptHex, "profile.receiver_pk_script"),
|
|
678
690
|
refundPkScript: secrets.pkScript
|
|
679
691
|
};
|
|
680
|
-
const
|
|
681
|
-
|
|
682
|
-
|
|
692
|
+
const matched = matchQuotedLockup(
|
|
693
|
+
quote,
|
|
694
|
+
network.hrp,
|
|
695
|
+
serverPubkey,
|
|
696
|
+
(legacy) => lightningSendVtxoScript({ ...treeParams, ...legacy !== void 0 && { legacy } })
|
|
697
|
+
);
|
|
698
|
+
const script = matched.script;
|
|
699
|
+
const address = matched.address;
|
|
700
|
+
const matchedTreeParams = {
|
|
701
|
+
...treeParams,
|
|
702
|
+
...matched.legacy !== void 0 && { legacy: matched.legacy }
|
|
703
|
+
};
|
|
683
704
|
assertFundable({
|
|
684
705
|
quote,
|
|
685
706
|
invoiceExpiresAt: params.invoice.expiresAt,
|
|
@@ -698,7 +719,7 @@ async function requestLightningSend(wallet, arkServerUrl, transport, params) {
|
|
|
698
719
|
refundAddress,
|
|
699
720
|
senderPubkey,
|
|
700
721
|
secrets,
|
|
701
|
-
treeParams
|
|
722
|
+
treeParams: matchedTreeParams
|
|
702
723
|
};
|
|
703
724
|
}
|
|
704
725
|
var offerTermsFromQuote = (quote, assets) => {
|
|
@@ -763,7 +784,7 @@ function deriveOnchainSend(input) {
|
|
|
763
784
|
if (refundLocktime === void 0 || htlcPubkey === void 0 || htlcLocktime === void 0 || minConfirmations === void 0 || receiverPkScriptHex === void 0) {
|
|
764
785
|
throw new Error("onchain-send quote is missing a binding field");
|
|
765
786
|
}
|
|
766
|
-
const
|
|
787
|
+
const treeParams = {
|
|
767
788
|
solverPubkey: toXOnly(hex3.decode(quote.solver_pubkey), "solver key"),
|
|
768
789
|
refundLocktime,
|
|
769
790
|
serverPubkey: input.serverPubkey,
|
|
@@ -773,9 +794,13 @@ function deriveOnchainSend(input) {
|
|
|
773
794
|
senderPubkey: input.senderPubkey,
|
|
774
795
|
receiverPkScript: solverHex(receiverPkScriptHex, "profile.receiver_pk_script"),
|
|
775
796
|
refundPkScript: ArkAddress2.decode(input.refundAddress).pkScript
|
|
776
|
-
}
|
|
777
|
-
const address =
|
|
778
|
-
|
|
797
|
+
};
|
|
798
|
+
const { script, address } = matchQuotedLockup(
|
|
799
|
+
quote,
|
|
800
|
+
input.hrp,
|
|
801
|
+
input.serverPubkey,
|
|
802
|
+
(legacy) => lightningSendVtxoScript({ ...treeParams, ...legacy !== void 0 && { legacy } })
|
|
803
|
+
);
|
|
779
804
|
const htlcParams = {
|
|
780
805
|
paymentHash: input.paymentHash,
|
|
781
806
|
claimKey: input.payoutPubkey,
|
|
@@ -950,13 +975,11 @@ function receiveVtxoScript(params) {
|
|
|
950
975
|
unilateralRefundWithoutReceiverDelay: seconds(
|
|
951
976
|
unilateralRefundWithoutReceiverDelay(params.claimDelay)
|
|
952
977
|
),
|
|
953
|
-
|
|
978
|
+
nonInteractiveParameters: {
|
|
954
979
|
receiverPkScript: params.payoutPkScript,
|
|
955
|
-
emulatorPubkey: params.emulatorPubkey
|
|
956
|
-
},
|
|
957
|
-
nonInteractiveRefund: {
|
|
958
980
|
senderPkScript: params.solverRefundPkScript,
|
|
959
|
-
emulatorPubkey: params.emulatorPubkey
|
|
981
|
+
emulatorPubkey: params.emulatorPubkey,
|
|
982
|
+
...params.legacy !== void 0 && { legacy: params.legacy }
|
|
960
983
|
}
|
|
961
984
|
});
|
|
962
985
|
}
|
|
@@ -980,10 +1003,23 @@ function deriveLightningReceive(input) {
|
|
|
980
1003
|
payoutPubkey: input.payoutPubkey,
|
|
981
1004
|
payoutPkScript: ArkAddress2.decode(input.payoutAddress).pkScript
|
|
982
1005
|
};
|
|
983
|
-
const
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
1006
|
+
const matched = matchQuotedLockup(
|
|
1007
|
+
quote,
|
|
1008
|
+
input.hrp,
|
|
1009
|
+
input.serverPubkey,
|
|
1010
|
+
(legacy) => receiveVtxoScript({ ...treeParams, ...legacy !== void 0 && { legacy } })
|
|
1011
|
+
);
|
|
1012
|
+
return {
|
|
1013
|
+
address: matched.address,
|
|
1014
|
+
swapPkScript: matched.script.pkScript,
|
|
1015
|
+
script: matched.script,
|
|
1016
|
+
invoice,
|
|
1017
|
+
refundLocktime,
|
|
1018
|
+
treeParams: {
|
|
1019
|
+
...treeParams,
|
|
1020
|
+
...matched.legacy !== void 0 && { legacy: matched.legacy }
|
|
1021
|
+
}
|
|
1022
|
+
};
|
|
987
1023
|
}
|
|
988
1024
|
async function requestLightningReceive(wallet, arkServerUrl, transport, params) {
|
|
989
1025
|
const rfqId = params.rfqId ?? newRfqId();
|
|
@@ -1071,7 +1107,7 @@ function deriveOnchainReceive(input) {
|
|
|
1071
1107
|
if (refundLocktime === void 0 || claimPubkey === void 0 || htlcLocktime === void 0 || minConfirmations === void 0 || solverRefundPkScriptHex === void 0) {
|
|
1072
1108
|
throw new Error("onchain-receive quote is missing a binding field");
|
|
1073
1109
|
}
|
|
1074
|
-
const
|
|
1110
|
+
const treeParams = {
|
|
1075
1111
|
solverPubkey: toXOnly(hex3.decode(quote.solver_pubkey), "solver key"),
|
|
1076
1112
|
refundLocktime,
|
|
1077
1113
|
serverPubkey: input.serverPubkey,
|
|
@@ -1081,9 +1117,13 @@ function deriveOnchainReceive(input) {
|
|
|
1081
1117
|
solverRefundPkScript: solverHex(solverRefundPkScriptHex, "profile.solver_refund_pk_script"),
|
|
1082
1118
|
payoutPubkey: input.payoutPubkey,
|
|
1083
1119
|
payoutPkScript: ArkAddress2.decode(input.payoutAddress).pkScript
|
|
1084
|
-
}
|
|
1085
|
-
const address =
|
|
1086
|
-
|
|
1120
|
+
};
|
|
1121
|
+
const { script, address } = matchQuotedLockup(
|
|
1122
|
+
quote,
|
|
1123
|
+
input.hrp,
|
|
1124
|
+
input.serverPubkey,
|
|
1125
|
+
(legacy) => receiveVtxoScript({ ...treeParams, ...legacy !== void 0 && { legacy } })
|
|
1126
|
+
);
|
|
1087
1127
|
const htlc = onchainHtlcScript(
|
|
1088
1128
|
{
|
|
1089
1129
|
paymentHash: input.paymentHash,
|
package/dist/index.cjs
CHANGED
|
@@ -411,12 +411,30 @@ var swapPrograms = {
|
|
|
411
411
|
wantAsset: import_sdk2.arkade.parseArtifact(swap_want_asset_program_default),
|
|
412
412
|
wantBtc: import_sdk2.arkade.parseArtifact(swap_want_btc_program_default)
|
|
413
413
|
};
|
|
414
|
+
function withExitClosure(program, exit) {
|
|
415
|
+
if (!exit) return program;
|
|
416
|
+
return {
|
|
417
|
+
...program,
|
|
418
|
+
// typed params are authoritative: an undeclared `$exitDelay` fails
|
|
419
|
+
// validateProgram instead of compiling against an unbound value
|
|
420
|
+
params: [...program.params ?? [], { name: "exitDelay", type: "int" }],
|
|
421
|
+
functions: {
|
|
422
|
+
...program.functions,
|
|
423
|
+
exit: {
|
|
424
|
+
tapscript: { signers: ["$user"], csv: { type: exit.type, value: "$exitDelay" } }
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
};
|
|
428
|
+
}
|
|
414
429
|
function swapProgramBinding(offer, serverPubkey) {
|
|
415
430
|
if (offer.makerPkScript.length !== FIELDS.makerPkScript.width) {
|
|
416
431
|
throw new Error("makerPkScript is not a 34-byte taproot scriptPubKey");
|
|
417
432
|
}
|
|
418
433
|
return {
|
|
419
|
-
program:
|
|
434
|
+
program: withExitClosure(
|
|
435
|
+
offer.wantAsset ? swapPrograms.wantAsset : swapPrograms.wantBtc,
|
|
436
|
+
offer.exitDelay
|
|
437
|
+
),
|
|
420
438
|
args: {
|
|
421
439
|
makerWP: offer.makerPkScript.subarray(2),
|
|
422
440
|
wantAmount: offer.wantAmount,
|
|
@@ -426,7 +444,8 @@ function swapProgramBinding(offer, serverPubkey) {
|
|
|
426
444
|
...offer.wantAsset && {
|
|
427
445
|
wantAssetTxid: offer.wantAsset.txid.slice().reverse(),
|
|
428
446
|
wantAssetGroupIndex: offer.wantAsset.groupIndex
|
|
429
|
-
}
|
|
447
|
+
},
|
|
448
|
+
...offer.exitDelay && { exitDelay: offer.exitDelay.value }
|
|
430
449
|
},
|
|
431
450
|
keys: {
|
|
432
451
|
serverKey: serverPubkey,
|
|
@@ -447,9 +466,27 @@ var FIELDS = {
|
|
|
447
466
|
makerPkScript: { tag: 5, width: 34 },
|
|
448
467
|
makerPublicKey: { tag: 7, width: 32 },
|
|
449
468
|
emulatorPubkey: { tag: 8, width: 32 },
|
|
450
|
-
|
|
469
|
+
ratioNum: { tag: 9, width: 8 },
|
|
470
|
+
ratioDen: { tag: 10, width: 8 },
|
|
471
|
+
offerAsset: { tag: 11, width: void 0 },
|
|
472
|
+
exitTimelock: { tag: 12, width: 9 }
|
|
451
473
|
};
|
|
474
|
+
var EXIT_TYPES = ["blocks", "seconds"];
|
|
452
475
|
var NAMES = Object.fromEntries(Object.entries(FIELDS).map(([k, f]) => [f.tag, k]));
|
|
476
|
+
function u64(name, value) {
|
|
477
|
+
if (value < BigInt(0) || value >> BigInt(64) > BigInt(0)) {
|
|
478
|
+
throw new Error(`${name} does not fit the offer wire format (u64)`);
|
|
479
|
+
}
|
|
480
|
+
const out = new Uint8Array(FIELDS.wantAmount.width);
|
|
481
|
+
new DataView(out.buffer).setBigUint64(0, value, false);
|
|
482
|
+
return out;
|
|
483
|
+
}
|
|
484
|
+
var readU64 = (value) => new DataView(value.buffer, value.byteOffset).getBigUint64(0, false);
|
|
485
|
+
var setRatio = (name, value) => {
|
|
486
|
+
if (value === void 0 || value === BigInt(0)) return void 0;
|
|
487
|
+
if (value < BigInt(0)) throw new Error(`${name} does not fit the offer wire format (u64)`);
|
|
488
|
+
return value;
|
|
489
|
+
};
|
|
453
490
|
function tlv(type, value) {
|
|
454
491
|
if (value.length > 65535) throw new Error("TLV value exceeds the u16 length field");
|
|
455
492
|
return (0, import_utils.concatBytes)(Uint8Array.of(type, value.length >> 8 & 255, value.length & 255), value);
|
|
@@ -468,24 +505,45 @@ function encodeOffer(offer) {
|
|
|
468
505
|
throw new Error(`${name} must be ${FIELDS[name].width} bytes`);
|
|
469
506
|
}
|
|
470
507
|
}
|
|
471
|
-
|
|
472
|
-
|
|
508
|
+
const ratioNum = setRatio("ratioNum", offer.ratioNum);
|
|
509
|
+
const ratioDen = setRatio("ratioDen", offer.ratioDen);
|
|
510
|
+
if (ratioNum === void 0 !== (ratioDen === void 0)) {
|
|
511
|
+
throw new Error("offer must carry both ratioNum and ratioDen, or neither");
|
|
473
512
|
}
|
|
474
|
-
const amount = new Uint8Array(FIELDS.wantAmount.width);
|
|
475
|
-
new DataView(amount.buffer).setBigUint64(0, offer.wantAmount, false);
|
|
476
513
|
const recs = [
|
|
477
514
|
tlv(FIELDS.swapPkScript.tag, offer.swapPkScript),
|
|
478
|
-
tlv(FIELDS.wantAmount.tag,
|
|
515
|
+
tlv(FIELDS.wantAmount.tag, u64("wantAmount", offer.wantAmount))
|
|
479
516
|
];
|
|
480
517
|
if (offer.wantAsset) recs.push(tlv(FIELDS.wantAsset.tag, offer.wantAsset.serialize()));
|
|
518
|
+
if (ratioNum !== void 0) recs.push(tlv(FIELDS.ratioNum.tag, u64("ratioNum", ratioNum)));
|
|
519
|
+
if (ratioDen !== void 0) recs.push(tlv(FIELDS.ratioDen.tag, u64("ratioDen", ratioDen)));
|
|
481
520
|
if (offer.offerAsset) recs.push(tlv(FIELDS.offerAsset.tag, offer.offerAsset.serialize()));
|
|
482
521
|
recs.push(
|
|
483
522
|
tlv(FIELDS.makerPkScript.tag, offer.makerPkScript),
|
|
484
523
|
tlv(FIELDS.makerPublicKey.tag, offer.makerPublicKey),
|
|
485
524
|
tlv(FIELDS.emulatorPubkey.tag, offer.emulatorPubkey)
|
|
486
525
|
);
|
|
526
|
+
if (offer.exitDelay) recs.push(tlv(FIELDS.exitTimelock.tag, encodeExitDelay(offer.exitDelay)));
|
|
487
527
|
return (0, import_utils.concatBytes)(...recs);
|
|
488
528
|
}
|
|
529
|
+
function encodeExitDelay(exit) {
|
|
530
|
+
return (0, import_utils.concatBytes)(
|
|
531
|
+
Uint8Array.of(EXIT_TYPES.indexOf(assertExitDelay(exit).type)),
|
|
532
|
+
u64("exitDelay", exit.value)
|
|
533
|
+
);
|
|
534
|
+
}
|
|
535
|
+
function assertExitDelay(exit) {
|
|
536
|
+
if (EXIT_TYPES.indexOf(exit.type) < 0) {
|
|
537
|
+
throw new Error(`unknown exitDelay locktime type: ${exit.type}`);
|
|
538
|
+
}
|
|
539
|
+
if (exit.value <= BigInt(0)) {
|
|
540
|
+
throw new Error("exitDelay must be a positive relative locktime");
|
|
541
|
+
}
|
|
542
|
+
if (exit.value >> BigInt(32) > BigInt(0)) {
|
|
543
|
+
throw new Error("exitDelay does not fit the locktime field (u32)");
|
|
544
|
+
}
|
|
545
|
+
return exit;
|
|
546
|
+
}
|
|
489
547
|
function decodeOffer(data) {
|
|
490
548
|
const fields = {};
|
|
491
549
|
let off = 0;
|
|
@@ -505,6 +563,12 @@ function decodeOffer(data) {
|
|
|
505
563
|
for (const name of ["wantAsset", "offerAsset"]) {
|
|
506
564
|
if (fields[name]?.length === 0) throw new Error(`missing/invalid ${name}`);
|
|
507
565
|
}
|
|
566
|
+
for (const [name, value] of Object.entries(fields)) {
|
|
567
|
+
const width = FIELDS[name].width;
|
|
568
|
+
if (width !== void 0 && value.length !== width) {
|
|
569
|
+
throw new Error(`missing/invalid ${name}`);
|
|
570
|
+
}
|
|
571
|
+
}
|
|
508
572
|
const need = (name) => {
|
|
509
573
|
const v = fields[name];
|
|
510
574
|
const len = FIELDS[name].width;
|
|
@@ -516,16 +580,36 @@ function decodeOffer(data) {
|
|
|
516
580
|
if (Boolean(fields.wantAsset) === Boolean(fields.offerAsset)) {
|
|
517
581
|
throw new Error("offer must carry exactly one of wantAsset or offerAsset");
|
|
518
582
|
}
|
|
583
|
+
const readRatio = (name) => {
|
|
584
|
+
const raw = fields[name];
|
|
585
|
+
if (!raw) return void 0;
|
|
586
|
+
const value = readU64(raw);
|
|
587
|
+
if (value === BigInt(0)) throw new Error(`missing/invalid ${name}`);
|
|
588
|
+
return value;
|
|
589
|
+
};
|
|
590
|
+
const ratioNum = readRatio("ratioNum");
|
|
591
|
+
const ratioDen = readRatio("ratioDen");
|
|
592
|
+
if (ratioNum === void 0 !== (ratioDen === void 0)) {
|
|
593
|
+
throw new Error("offer must carry both ratioNum and ratioDen, or neither");
|
|
594
|
+
}
|
|
519
595
|
return {
|
|
520
596
|
swapPkScript: need("swapPkScript"),
|
|
521
|
-
wantAmount:
|
|
597
|
+
wantAmount: readU64(amount),
|
|
522
598
|
...fields.wantAsset && { wantAsset: import_sdk2.asset.AssetId.fromBytes(fields.wantAsset) },
|
|
523
599
|
...fields.offerAsset && { offerAsset: import_sdk2.asset.AssetId.fromBytes(fields.offerAsset) },
|
|
524
600
|
makerPkScript: need("makerPkScript"),
|
|
525
601
|
makerPublicKey: need("makerPublicKey"),
|
|
526
|
-
emulatorPubkey: need("emulatorPubkey")
|
|
602
|
+
emulatorPubkey: need("emulatorPubkey"),
|
|
603
|
+
...ratioNum !== void 0 && { ratioNum },
|
|
604
|
+
...ratioDen !== void 0 && { ratioDen },
|
|
605
|
+
...fields.exitTimelock && { exitDelay: decodeExitDelay(fields.exitTimelock) }
|
|
527
606
|
};
|
|
528
607
|
}
|
|
608
|
+
function decodeExitDelay(value) {
|
|
609
|
+
const type = EXIT_TYPES[value[0]];
|
|
610
|
+
if (!type) throw new Error(`unknown exitDelay locktime type: 0x${value[0].toString(16)}`);
|
|
611
|
+
return { type, value: readU64(value.subarray(1)) };
|
|
612
|
+
}
|
|
529
613
|
var OFFER_CONTRACT_LABEL = "Arkade swap offer";
|
|
530
614
|
var OFFER_CONTRACT_KIND = "asset-swap-offer";
|
|
531
615
|
async function registerOfferContract(wallet, arkServerUrl, network, binding, serverPubkey, expectedPkScript) {
|
|
@@ -551,6 +635,14 @@ async function registerOfferContract(wallet, arkServerUrl, network, binding, ser
|
|
|
551
635
|
});
|
|
552
636
|
await promoteOfferContract(contractManager, import_base2.hex.encode(expectedPkScript));
|
|
553
637
|
}
|
|
638
|
+
function serverExitDelay(delay) {
|
|
639
|
+
if (typeof delay !== "bigint" || delay <= BigInt(0)) {
|
|
640
|
+
throw new Error(
|
|
641
|
+
"the server reports no usable unilateralExitDelay; pass `exitDelay` to set the offer's exit closure explicitly, or `noExit: true` to publish without one"
|
|
642
|
+
);
|
|
643
|
+
}
|
|
644
|
+
return assertExitDelay({ value: delay, type: delay < BigInt(512) ? "blocks" : "seconds" });
|
|
645
|
+
}
|
|
554
646
|
async function createOffer(wallet, arkServerUrl, params) {
|
|
555
647
|
if (Boolean(params.wantAsset) === Boolean(params.offerAsset)) {
|
|
556
648
|
throw new Error("set exactly one of wantAsset (BTC->asset) or offerAsset (asset->BTC)");
|
|
@@ -571,7 +663,11 @@ async function createOffer(wallet, arkServerUrl, params) {
|
|
|
571
663
|
offerAsset: params.offerAsset,
|
|
572
664
|
makerPkScript: import_sdk2.ArkAddress.decode(makerAddress).pkScript,
|
|
573
665
|
makerPublicKey,
|
|
574
|
-
emulatorPubkey: emuKey
|
|
666
|
+
emulatorPubkey: emuKey,
|
|
667
|
+
// checked HERE, before the covenant is derived and registered below:
|
|
668
|
+
// deferring it to `encodeOffer` leaves a registered contract behind for
|
|
669
|
+
// an offer that then fails to encode. @see assertExitDelay
|
|
670
|
+
exitDelay: params.noExit ? void 0 : params.exitDelay ? assertExitDelay(params.exitDelay) : serverExitDelay(info.unilateralExitDelay)
|
|
575
671
|
};
|
|
576
672
|
const script = offerVtxoScript(binding, serverPubKey);
|
|
577
673
|
const offer = { ...binding, swapPkScript: script.pkScript };
|
|
@@ -1512,7 +1608,9 @@ function classifySpend(offer, serverPubkey, spendTx, deposit) {
|
|
|
1512
1608
|
const script = offerVtxoScript(offer, serverPubkey);
|
|
1513
1609
|
if (import_base6.hex.encode(script.pkScript) !== import_base6.hex.encode(offer.swapPkScript)) return "indeterminate";
|
|
1514
1610
|
leaves = {
|
|
1515
|
-
|
|
1611
|
+
// both routes that hand the deposit back; `exit` is absent on an
|
|
1612
|
+
// offer that carries no exit closure, and drops out here
|
|
1613
|
+
returned: ["cancel", "exit"].map((name) => script.functionByName(name)?.leafScript).filter((leaf) => leaf !== void 0),
|
|
1516
1614
|
fulfill: script.functionByName("fulfill")?.leafScript
|
|
1517
1615
|
};
|
|
1518
1616
|
} catch {
|
|
@@ -1524,7 +1622,7 @@ function classifySpend(offer, serverPubkey, spendTx, deposit) {
|
|
|
1524
1622
|
if (import_base6.hex.encode(input.txid) !== deposit.txid) continue;
|
|
1525
1623
|
for (const leaf of input.tapLeafScript ?? []) {
|
|
1526
1624
|
const spent = import_base6.hex.encode((0, import_sdk6.scriptFromTapLeafScript)(leaf));
|
|
1527
|
-
if (leaves.
|
|
1625
|
+
if (leaves.returned.some((back) => spent === import_base6.hex.encode(back))) return "cancelled";
|
|
1528
1626
|
if (leaves.fulfill && spent === import_base6.hex.encode(leaves.fulfill)) return "fulfilled";
|
|
1529
1627
|
}
|
|
1530
1628
|
}
|
|
@@ -1918,8 +2016,22 @@ var assertPairLength = (pair) => {
|
|
|
1918
2016
|
};
|
|
1919
2017
|
var verifyLockupAddress = (quote, derivedAddress) => {
|
|
1920
2018
|
const quoted = quote.profile?.lockup_address;
|
|
1921
|
-
|
|
1922
|
-
|
|
2019
|
+
const candidates = Array.isArray(derivedAddress) ? derivedAddress : [derivedAddress];
|
|
2020
|
+
const matched = candidates.find((address) => address === quoted);
|
|
2021
|
+
if (matched === void 0) throw new AddressMismatch(candidates, quoted);
|
|
2022
|
+
return matched;
|
|
2023
|
+
};
|
|
2024
|
+
var LOCKUP_SHAPE_VARIANTS = [void 0, "preTimelockedRefund"];
|
|
2025
|
+
var matchQuotedLockup = (quote, hrp, serverPubkey, build) => {
|
|
2026
|
+
const candidates = LOCKUP_SHAPE_VARIANTS.map((legacy) => {
|
|
2027
|
+
const script = build(legacy);
|
|
2028
|
+
return { script, address: script.address(hrp, serverPubkey).encode(), legacy };
|
|
2029
|
+
});
|
|
2030
|
+
const matchedAddress = verifyLockupAddress(
|
|
2031
|
+
quote,
|
|
2032
|
+
candidates.map((candidate) => candidate.address)
|
|
2033
|
+
);
|
|
2034
|
+
return candidates.find((candidate) => candidate.address === matchedAddress);
|
|
1923
2035
|
};
|
|
1924
2036
|
var assertFundable = (input) => {
|
|
1925
2037
|
const fail = (reason, message) => {
|
|
@@ -2121,13 +2233,11 @@ function lightningSendVtxoScript(params) {
|
|
|
2121
2233
|
unilateralRefundWithoutReceiverDelay: seconds(
|
|
2122
2234
|
unilateralRefundWithoutReceiverDelay(params.claimDelay)
|
|
2123
2235
|
),
|
|
2124
|
-
|
|
2236
|
+
nonInteractiveParameters: {
|
|
2125
2237
|
receiverPkScript: params.receiverPkScript,
|
|
2126
|
-
emulatorPubkey: params.emulatorPubkey
|
|
2127
|
-
},
|
|
2128
|
-
nonInteractiveRefund: {
|
|
2129
2238
|
senderPkScript: params.refundPkScript,
|
|
2130
|
-
emulatorPubkey: params.emulatorPubkey
|
|
2239
|
+
emulatorPubkey: params.emulatorPubkey,
|
|
2240
|
+
...params.legacy !== void 0 && { legacy: params.legacy }
|
|
2131
2241
|
}
|
|
2132
2242
|
});
|
|
2133
2243
|
}
|
|
@@ -2173,9 +2283,18 @@ async function requestLightningSend(wallet, arkServerUrl, transport, params) {
|
|
|
2173
2283
|
receiverPkScript: solverHex(receiverPkScriptHex, "profile.receiver_pk_script"),
|
|
2174
2284
|
refundPkScript: secrets.pkScript
|
|
2175
2285
|
};
|
|
2176
|
-
const
|
|
2177
|
-
|
|
2178
|
-
|
|
2286
|
+
const matched = matchQuotedLockup(
|
|
2287
|
+
quote,
|
|
2288
|
+
network.hrp,
|
|
2289
|
+
serverPubkey,
|
|
2290
|
+
(legacy) => lightningSendVtxoScript({ ...treeParams, ...legacy !== void 0 && { legacy } })
|
|
2291
|
+
);
|
|
2292
|
+
const script = matched.script;
|
|
2293
|
+
const address = matched.address;
|
|
2294
|
+
const matchedTreeParams = {
|
|
2295
|
+
...treeParams,
|
|
2296
|
+
...matched.legacy !== void 0 && { legacy: matched.legacy }
|
|
2297
|
+
};
|
|
2179
2298
|
assertFundable({
|
|
2180
2299
|
quote,
|
|
2181
2300
|
invoiceExpiresAt: params.invoice.expiresAt,
|
|
@@ -2194,7 +2313,7 @@ async function requestLightningSend(wallet, arkServerUrl, transport, params) {
|
|
|
2194
2313
|
refundAddress,
|
|
2195
2314
|
senderPubkey,
|
|
2196
2315
|
secrets,
|
|
2197
|
-
treeParams
|
|
2316
|
+
treeParams: matchedTreeParams
|
|
2198
2317
|
};
|
|
2199
2318
|
}
|
|
2200
2319
|
var offerTermsFromQuote = (quote, assets) => {
|
|
@@ -2259,7 +2378,7 @@ function deriveOnchainSend(input) {
|
|
|
2259
2378
|
if (refundLocktime === void 0 || htlcPubkey === void 0 || htlcLocktime === void 0 || minConfirmations === void 0 || receiverPkScriptHex === void 0) {
|
|
2260
2379
|
throw new Error("onchain-send quote is missing a binding field");
|
|
2261
2380
|
}
|
|
2262
|
-
const
|
|
2381
|
+
const treeParams = {
|
|
2263
2382
|
solverPubkey: (0, import_sdk9.toXOnly)(import_base10.hex.decode(quote.solver_pubkey), "solver key"),
|
|
2264
2383
|
refundLocktime,
|
|
2265
2384
|
serverPubkey: input.serverPubkey,
|
|
@@ -2269,9 +2388,13 @@ function deriveOnchainSend(input) {
|
|
|
2269
2388
|
senderPubkey: input.senderPubkey,
|
|
2270
2389
|
receiverPkScript: solverHex(receiverPkScriptHex, "profile.receiver_pk_script"),
|
|
2271
2390
|
refundPkScript: import_sdk9.ArkAddress.decode(input.refundAddress).pkScript
|
|
2272
|
-
}
|
|
2273
|
-
const address =
|
|
2274
|
-
|
|
2391
|
+
};
|
|
2392
|
+
const { script, address } = matchQuotedLockup(
|
|
2393
|
+
quote,
|
|
2394
|
+
input.hrp,
|
|
2395
|
+
input.serverPubkey,
|
|
2396
|
+
(legacy) => lightningSendVtxoScript({ ...treeParams, ...legacy !== void 0 && { legacy } })
|
|
2397
|
+
);
|
|
2275
2398
|
const htlcParams = {
|
|
2276
2399
|
paymentHash: input.paymentHash,
|
|
2277
2400
|
claimKey: input.payoutPubkey,
|
|
@@ -2446,13 +2569,11 @@ function receiveVtxoScript(params) {
|
|
|
2446
2569
|
unilateralRefundWithoutReceiverDelay: seconds(
|
|
2447
2570
|
unilateralRefundWithoutReceiverDelay(params.claimDelay)
|
|
2448
2571
|
),
|
|
2449
|
-
|
|
2572
|
+
nonInteractiveParameters: {
|
|
2450
2573
|
receiverPkScript: params.payoutPkScript,
|
|
2451
|
-
emulatorPubkey: params.emulatorPubkey
|
|
2452
|
-
},
|
|
2453
|
-
nonInteractiveRefund: {
|
|
2454
2574
|
senderPkScript: params.solverRefundPkScript,
|
|
2455
|
-
emulatorPubkey: params.emulatorPubkey
|
|
2575
|
+
emulatorPubkey: params.emulatorPubkey,
|
|
2576
|
+
...params.legacy !== void 0 && { legacy: params.legacy }
|
|
2456
2577
|
}
|
|
2457
2578
|
});
|
|
2458
2579
|
}
|
|
@@ -2476,10 +2597,23 @@ function deriveLightningReceive(input) {
|
|
|
2476
2597
|
payoutPubkey: input.payoutPubkey,
|
|
2477
2598
|
payoutPkScript: import_sdk9.ArkAddress.decode(input.payoutAddress).pkScript
|
|
2478
2599
|
};
|
|
2479
|
-
const
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2600
|
+
const matched = matchQuotedLockup(
|
|
2601
|
+
quote,
|
|
2602
|
+
input.hrp,
|
|
2603
|
+
input.serverPubkey,
|
|
2604
|
+
(legacy) => receiveVtxoScript({ ...treeParams, ...legacy !== void 0 && { legacy } })
|
|
2605
|
+
);
|
|
2606
|
+
return {
|
|
2607
|
+
address: matched.address,
|
|
2608
|
+
swapPkScript: matched.script.pkScript,
|
|
2609
|
+
script: matched.script,
|
|
2610
|
+
invoice,
|
|
2611
|
+
refundLocktime,
|
|
2612
|
+
treeParams: {
|
|
2613
|
+
...treeParams,
|
|
2614
|
+
...matched.legacy !== void 0 && { legacy: matched.legacy }
|
|
2615
|
+
}
|
|
2616
|
+
};
|
|
2483
2617
|
}
|
|
2484
2618
|
async function requestLightningReceive(wallet, arkServerUrl, transport, params) {
|
|
2485
2619
|
const rfqId = params.rfqId ?? newRfqId();
|
|
@@ -2567,7 +2701,7 @@ function deriveOnchainReceive(input) {
|
|
|
2567
2701
|
if (refundLocktime === void 0 || claimPubkey === void 0 || htlcLocktime === void 0 || minConfirmations === void 0 || solverRefundPkScriptHex === void 0) {
|
|
2568
2702
|
throw new Error("onchain-receive quote is missing a binding field");
|
|
2569
2703
|
}
|
|
2570
|
-
const
|
|
2704
|
+
const treeParams = {
|
|
2571
2705
|
solverPubkey: (0, import_sdk9.toXOnly)(import_base10.hex.decode(quote.solver_pubkey), "solver key"),
|
|
2572
2706
|
refundLocktime,
|
|
2573
2707
|
serverPubkey: input.serverPubkey,
|
|
@@ -2577,9 +2711,13 @@ function deriveOnchainReceive(input) {
|
|
|
2577
2711
|
solverRefundPkScript: solverHex(solverRefundPkScriptHex, "profile.solver_refund_pk_script"),
|
|
2578
2712
|
payoutPubkey: input.payoutPubkey,
|
|
2579
2713
|
payoutPkScript: import_sdk9.ArkAddress.decode(input.payoutAddress).pkScript
|
|
2580
|
-
}
|
|
2581
|
-
const address =
|
|
2582
|
-
|
|
2714
|
+
};
|
|
2715
|
+
const { script, address } = matchQuotedLockup(
|
|
2716
|
+
quote,
|
|
2717
|
+
input.hrp,
|
|
2718
|
+
input.serverPubkey,
|
|
2719
|
+
(legacy) => receiveVtxoScript({ ...treeParams, ...legacy !== void 0 && { legacy } })
|
|
2720
|
+
);
|
|
2583
2721
|
const htlc = onchainHtlcScript(
|
|
2584
2722
|
{
|
|
2585
2723
|
paymentHash: input.paymentHash,
|
|
@@ -2820,10 +2958,10 @@ async function pushRefundWithoutReceiver(ark, input) {
|
|
|
2820
2958
|
input.script.options.refundLocktime
|
|
2821
2959
|
);
|
|
2822
2960
|
}
|
|
2823
|
-
const refundPkScript = input.refundPkScript ?? input.script.options.
|
|
2961
|
+
const refundPkScript = input.refundPkScript ?? input.script.options.nonInteractiveParameters?.senderPkScript;
|
|
2824
2962
|
if (!refundPkScript) {
|
|
2825
2963
|
throw new Error(
|
|
2826
|
-
"no refund destination: the contract carries no
|
|
2964
|
+
"no refund destination: the contract carries no emulator covenant suite, so pass refundPkScript explicitly"
|
|
2827
2965
|
);
|
|
2828
2966
|
}
|
|
2829
2967
|
const info = await ark.getInfo();
|
|
@@ -2875,6 +3013,21 @@ async function refundIfUnresolved(transport, ark, indexer, input) {
|
|
|
2875
3013
|
const status = await transport.status(input.rfqId);
|
|
2876
3014
|
if (status && isResolved(status.state)) return { outcome: "resolved", status };
|
|
2877
3015
|
if (now() >= input.refundLocktime) {
|
|
3016
|
+
let fate = { fate: "unknown" };
|
|
3017
|
+
try {
|
|
3018
|
+
fate = await readLockupFate(indexer, {
|
|
3019
|
+
swapPkScript: input.script.pkScript,
|
|
3020
|
+
paymentHash: input.paymentHash
|
|
3021
|
+
});
|
|
3022
|
+
} catch {
|
|
3023
|
+
}
|
|
3024
|
+
if (fate.fate === "exited") {
|
|
3025
|
+
return {
|
|
3026
|
+
outcome: "exited",
|
|
3027
|
+
outpoints: fate.outpoints.map((o) => `${o.txid}:${o.vout}`),
|
|
3028
|
+
status
|
|
3029
|
+
};
|
|
3030
|
+
}
|
|
2878
3031
|
const vtxos = await findLockupVtxos(indexer, input.script.pkScript);
|
|
2879
3032
|
if (vtxos.length === 0) return { outcome: "nothing_to_refund", status };
|
|
2880
3033
|
try {
|