@arkade-os/swap 0.0.10 → 0.0.12
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-XXWOODUE.js} +114 -29
- package/dist/index.cjs +587 -83
- package/dist/index.d.cts +171 -16
- package/dist/index.d.ts +171 -16
- package/dist/index.js +450 -36
- 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-DaK1RzRq.d.cts} +45 -5
- package/dist/{repository-Cw2EvvZG.d.ts → repository-DlLvj_y6.d.ts} +45 -5
- package/dist/{rfq-DglvHMNC.d.ts → rfq-DzsmhXX3.d.cts} +75 -20
- package/dist/{rfq-DglvHMNC.d.cts → rfq-DzsmhXX3.d.ts} +75 -20
- 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
|
|
@@ -17,6 +17,7 @@ var L1_NETWORKS = {
|
|
|
17
17
|
testnet: btc.TEST_NETWORK,
|
|
18
18
|
regtest: { ...btc.TEST_NETWORK, bech32: "bcrt" }
|
|
19
19
|
};
|
|
20
|
+
var l1ScriptForAddress = (address, network) => btc.OutScript.encode(btc.Address(L1_NETWORKS[network]).decode(address));
|
|
20
21
|
function onchainHtlcScript(params, network) {
|
|
21
22
|
if (params.claimKey.length !== 32 || params.refundKey.length !== 32) {
|
|
22
23
|
throw new Error("claimKey and refundKey must be 32-byte x-only keys");
|
|
@@ -199,7 +200,11 @@ async function classifyOnchainHtlc(chain, input) {
|
|
|
199
200
|
const best = utxos.sort((a, b) => b.amount > a.amount ? 1 : -1)[0];
|
|
200
201
|
if (!best) {
|
|
201
202
|
if (!input.funding) return { phase: "unfunded" };
|
|
202
|
-
const spend = await chain.getSpendingTx(
|
|
203
|
+
const spend = await chain.getSpendingTx(
|
|
204
|
+
input.funding.txid,
|
|
205
|
+
input.funding.vout,
|
|
206
|
+
input.htlc.pkScript
|
|
207
|
+
);
|
|
203
208
|
if (!spend) return { phase: "unfunded" };
|
|
204
209
|
const preimage = extractPreimage(spend.txHex, input.htlc.paymentHash);
|
|
205
210
|
const txid = btc.Transaction.fromRaw(hex.decode(spend.txHex), {
|
|
@@ -422,8 +427,22 @@ var assertPairLength = (pair) => {
|
|
|
422
427
|
};
|
|
423
428
|
var verifyLockupAddress = (quote, derivedAddress) => {
|
|
424
429
|
const quoted = quote.profile?.lockup_address;
|
|
425
|
-
|
|
426
|
-
|
|
430
|
+
const candidates = Array.isArray(derivedAddress) ? derivedAddress : [derivedAddress];
|
|
431
|
+
const matched = candidates.find((address) => address === quoted);
|
|
432
|
+
if (matched === void 0) throw new AddressMismatch(candidates, quoted);
|
|
433
|
+
return matched;
|
|
434
|
+
};
|
|
435
|
+
var LOCKUP_SHAPE_VARIANTS = [void 0, "preTimelockedRefund"];
|
|
436
|
+
var matchQuotedLockup = (quote, hrp, serverPubkey, build) => {
|
|
437
|
+
const candidates = LOCKUP_SHAPE_VARIANTS.map((legacy) => {
|
|
438
|
+
const script = build(legacy);
|
|
439
|
+
return { script, address: script.address(hrp, serverPubkey).encode(), legacy };
|
|
440
|
+
});
|
|
441
|
+
const matchedAddress = verifyLockupAddress(
|
|
442
|
+
quote,
|
|
443
|
+
candidates.map((candidate) => candidate.address)
|
|
444
|
+
);
|
|
445
|
+
return candidates.find((candidate) => candidate.address === matchedAddress);
|
|
427
446
|
};
|
|
428
447
|
var assertFundable = (input) => {
|
|
429
448
|
const fail = (reason, message) => {
|
|
@@ -437,6 +456,43 @@ var assertFundable = (input) => {
|
|
|
437
456
|
if (input.quote.refund_locktime !== void 0 && input.quote.refund_locktime - input.now < MIN_HEADROOM_SECONDS) {
|
|
438
457
|
fail("insufficient_headroom", "refund deadline headroom below 90 minutes");
|
|
439
458
|
}
|
|
459
|
+
if (input.maxFee) {
|
|
460
|
+
const { bps, sats, referenceRate } = input.maxFee;
|
|
461
|
+
if (bps === void 0 && sats === void 0) {
|
|
462
|
+
fail("max_fee_unbounded", "maxFee names neither bps nor sats");
|
|
463
|
+
}
|
|
464
|
+
if (bps !== void 0 && (!Number.isInteger(bps) || bps < 0 || bps > 1e4)) {
|
|
465
|
+
fail("max_fee_out_of_range", `maxFee.bps must be an integer in 0..10000, got ${bps}`);
|
|
466
|
+
}
|
|
467
|
+
if (sats !== void 0 && (!Number.isInteger(sats) || sats < 0)) {
|
|
468
|
+
fail("max_fee_out_of_range", `maxFee.sats must be a non-negative integer, got ${sats}`);
|
|
469
|
+
}
|
|
470
|
+
const legs = input.quote.pair.split("->");
|
|
471
|
+
const assetOf = (leg) => leg.slice(leg.indexOf(":") + 1);
|
|
472
|
+
const sameAsset = legs.length === 2 && assetOf(legs[0]) === assetOf(legs[1]);
|
|
473
|
+
if (!sameAsset && referenceRate === void 0) {
|
|
474
|
+
fail(
|
|
475
|
+
"fee_gate_unavailable",
|
|
476
|
+
`maxFee cannot gate ${input.quote.pair}: its legs name different assets, so from_amount - to_amount is not a fee. Supply maxFee.referenceRate (to-units per from-unit) from a source of your OWN \u2014 reading it off the solver's published feed would check the solver against its own number`
|
|
477
|
+
);
|
|
478
|
+
}
|
|
479
|
+
if (!sameAsset && (!Number.isFinite(referenceRate) || referenceRate <= 0)) {
|
|
480
|
+
fail(
|
|
481
|
+
"max_fee_out_of_range",
|
|
482
|
+
`maxFee.referenceRate must be a positive finite number, got ${referenceRate}`
|
|
483
|
+
);
|
|
484
|
+
}
|
|
485
|
+
const fee = sameAsset ? input.quote.from_amount - input.quote.to_amount : Math.ceil(
|
|
486
|
+
(input.quote.from_amount * referenceRate - input.quote.to_amount) / referenceRate
|
|
487
|
+
);
|
|
488
|
+
const allowed = Math.max(
|
|
489
|
+
sats ?? 0,
|
|
490
|
+
Math.floor(input.quote.from_amount * (bps ?? 0) / 1e4)
|
|
491
|
+
);
|
|
492
|
+
if (fee > allowed) {
|
|
493
|
+
fail("fee_too_high", `fee ${fee} exceeds the ${allowed} this client allows`);
|
|
494
|
+
}
|
|
495
|
+
}
|
|
440
496
|
if (input.onchain) {
|
|
441
497
|
const { htlcLocktime, minConfirmations, direction } = input.onchain;
|
|
442
498
|
if (!Number.isInteger(minConfirmations) || minConfirmations < 1 || minConfirmations > MAX_MIN_CONFIRMATIONS) {
|
|
@@ -625,13 +681,11 @@ function lightningSendVtxoScript(params) {
|
|
|
625
681
|
unilateralRefundWithoutReceiverDelay: seconds(
|
|
626
682
|
unilateralRefundWithoutReceiverDelay(params.claimDelay)
|
|
627
683
|
),
|
|
628
|
-
|
|
684
|
+
nonInteractiveParameters: {
|
|
629
685
|
receiverPkScript: params.receiverPkScript,
|
|
630
|
-
emulatorPubkey: params.emulatorPubkey
|
|
631
|
-
},
|
|
632
|
-
nonInteractiveRefund: {
|
|
633
686
|
senderPkScript: params.refundPkScript,
|
|
634
|
-
emulatorPubkey: params.emulatorPubkey
|
|
687
|
+
emulatorPubkey: params.emulatorPubkey,
|
|
688
|
+
...params.legacy !== void 0 && { legacy: params.legacy }
|
|
635
689
|
}
|
|
636
690
|
});
|
|
637
691
|
}
|
|
@@ -677,9 +731,18 @@ async function requestLightningSend(wallet, arkServerUrl, transport, params) {
|
|
|
677
731
|
receiverPkScript: solverHex(receiverPkScriptHex, "profile.receiver_pk_script"),
|
|
678
732
|
refundPkScript: secrets.pkScript
|
|
679
733
|
};
|
|
680
|
-
const
|
|
681
|
-
|
|
682
|
-
|
|
734
|
+
const matched = matchQuotedLockup(
|
|
735
|
+
quote,
|
|
736
|
+
network.hrp,
|
|
737
|
+
serverPubkey,
|
|
738
|
+
(legacy) => lightningSendVtxoScript({ ...treeParams, ...legacy !== void 0 && { legacy } })
|
|
739
|
+
);
|
|
740
|
+
const script = matched.script;
|
|
741
|
+
const address = matched.address;
|
|
742
|
+
const matchedTreeParams = {
|
|
743
|
+
...treeParams,
|
|
744
|
+
...matched.legacy !== void 0 && { legacy: matched.legacy }
|
|
745
|
+
};
|
|
683
746
|
assertFundable({
|
|
684
747
|
quote,
|
|
685
748
|
invoiceExpiresAt: params.invoice.expiresAt,
|
|
@@ -698,7 +761,7 @@ async function requestLightningSend(wallet, arkServerUrl, transport, params) {
|
|
|
698
761
|
refundAddress,
|
|
699
762
|
senderPubkey,
|
|
700
763
|
secrets,
|
|
701
|
-
treeParams
|
|
764
|
+
treeParams: matchedTreeParams
|
|
702
765
|
};
|
|
703
766
|
}
|
|
704
767
|
var offerTermsFromQuote = (quote, assets) => {
|
|
@@ -763,7 +826,7 @@ function deriveOnchainSend(input) {
|
|
|
763
826
|
if (refundLocktime === void 0 || htlcPubkey === void 0 || htlcLocktime === void 0 || minConfirmations === void 0 || receiverPkScriptHex === void 0) {
|
|
764
827
|
throw new Error("onchain-send quote is missing a binding field");
|
|
765
828
|
}
|
|
766
|
-
const
|
|
829
|
+
const treeParams = {
|
|
767
830
|
solverPubkey: toXOnly(hex3.decode(quote.solver_pubkey), "solver key"),
|
|
768
831
|
refundLocktime,
|
|
769
832
|
serverPubkey: input.serverPubkey,
|
|
@@ -773,9 +836,13 @@ function deriveOnchainSend(input) {
|
|
|
773
836
|
senderPubkey: input.senderPubkey,
|
|
774
837
|
receiverPkScript: solverHex(receiverPkScriptHex, "profile.receiver_pk_script"),
|
|
775
838
|
refundPkScript: ArkAddress2.decode(input.refundAddress).pkScript
|
|
776
|
-
}
|
|
777
|
-
const address =
|
|
778
|
-
|
|
839
|
+
};
|
|
840
|
+
const { script, address } = matchQuotedLockup(
|
|
841
|
+
quote,
|
|
842
|
+
input.hrp,
|
|
843
|
+
input.serverPubkey,
|
|
844
|
+
(legacy) => lightningSendVtxoScript({ ...treeParams, ...legacy !== void 0 && { legacy } })
|
|
845
|
+
);
|
|
779
846
|
const htlcParams = {
|
|
780
847
|
paymentHash: input.paymentHash,
|
|
781
848
|
claimKey: input.payoutPubkey,
|
|
@@ -821,6 +888,7 @@ async function requestOnchainSend(wallet, arkServerUrl, transport, params) {
|
|
|
821
888
|
amountSide: params.amountSide
|
|
822
889
|
})
|
|
823
890
|
);
|
|
891
|
+
assertQuotedAmount(quote, params.amountSide, params.amount);
|
|
824
892
|
const network = getNetwork(info.network);
|
|
825
893
|
const derived = deriveOnchainSend({
|
|
826
894
|
quote,
|
|
@@ -950,13 +1018,11 @@ function receiveVtxoScript(params) {
|
|
|
950
1018
|
unilateralRefundWithoutReceiverDelay: seconds(
|
|
951
1019
|
unilateralRefundWithoutReceiverDelay(params.claimDelay)
|
|
952
1020
|
),
|
|
953
|
-
|
|
1021
|
+
nonInteractiveParameters: {
|
|
954
1022
|
receiverPkScript: params.payoutPkScript,
|
|
955
|
-
emulatorPubkey: params.emulatorPubkey
|
|
956
|
-
},
|
|
957
|
-
nonInteractiveRefund: {
|
|
958
1023
|
senderPkScript: params.solverRefundPkScript,
|
|
959
|
-
emulatorPubkey: params.emulatorPubkey
|
|
1024
|
+
emulatorPubkey: params.emulatorPubkey,
|
|
1025
|
+
...params.legacy !== void 0 && { legacy: params.legacy }
|
|
960
1026
|
}
|
|
961
1027
|
});
|
|
962
1028
|
}
|
|
@@ -980,10 +1046,23 @@ function deriveLightningReceive(input) {
|
|
|
980
1046
|
payoutPubkey: input.payoutPubkey,
|
|
981
1047
|
payoutPkScript: ArkAddress2.decode(input.payoutAddress).pkScript
|
|
982
1048
|
};
|
|
983
|
-
const
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
1049
|
+
const matched = matchQuotedLockup(
|
|
1050
|
+
quote,
|
|
1051
|
+
input.hrp,
|
|
1052
|
+
input.serverPubkey,
|
|
1053
|
+
(legacy) => receiveVtxoScript({ ...treeParams, ...legacy !== void 0 && { legacy } })
|
|
1054
|
+
);
|
|
1055
|
+
return {
|
|
1056
|
+
address: matched.address,
|
|
1057
|
+
swapPkScript: matched.script.pkScript,
|
|
1058
|
+
script: matched.script,
|
|
1059
|
+
invoice,
|
|
1060
|
+
refundLocktime,
|
|
1061
|
+
treeParams: {
|
|
1062
|
+
...treeParams,
|
|
1063
|
+
...matched.legacy !== void 0 && { legacy: matched.legacy }
|
|
1064
|
+
}
|
|
1065
|
+
};
|
|
987
1066
|
}
|
|
988
1067
|
async function requestLightningReceive(wallet, arkServerUrl, transport, params) {
|
|
989
1068
|
const rfqId = params.rfqId ?? newRfqId();
|
|
@@ -1071,7 +1150,7 @@ function deriveOnchainReceive(input) {
|
|
|
1071
1150
|
if (refundLocktime === void 0 || claimPubkey === void 0 || htlcLocktime === void 0 || minConfirmations === void 0 || solverRefundPkScriptHex === void 0) {
|
|
1072
1151
|
throw new Error("onchain-receive quote is missing a binding field");
|
|
1073
1152
|
}
|
|
1074
|
-
const
|
|
1153
|
+
const treeParams = {
|
|
1075
1154
|
solverPubkey: toXOnly(hex3.decode(quote.solver_pubkey), "solver key"),
|
|
1076
1155
|
refundLocktime,
|
|
1077
1156
|
serverPubkey: input.serverPubkey,
|
|
@@ -1081,9 +1160,13 @@ function deriveOnchainReceive(input) {
|
|
|
1081
1160
|
solverRefundPkScript: solverHex(solverRefundPkScriptHex, "profile.solver_refund_pk_script"),
|
|
1082
1161
|
payoutPubkey: input.payoutPubkey,
|
|
1083
1162
|
payoutPkScript: ArkAddress2.decode(input.payoutAddress).pkScript
|
|
1084
|
-
}
|
|
1085
|
-
const address =
|
|
1086
|
-
|
|
1163
|
+
};
|
|
1164
|
+
const { script, address } = matchQuotedLockup(
|
|
1165
|
+
quote,
|
|
1166
|
+
input.hrp,
|
|
1167
|
+
input.serverPubkey,
|
|
1168
|
+
(legacy) => receiveVtxoScript({ ...treeParams, ...legacy !== void 0 && { legacy } })
|
|
1169
|
+
);
|
|
1087
1170
|
const htlc = onchainHtlcScript(
|
|
1088
1171
|
{
|
|
1089
1172
|
paymentHash: input.paymentHash,
|
|
@@ -1190,6 +1273,8 @@ export {
|
|
|
1190
1273
|
ONCHAIN_DUST_SATS,
|
|
1191
1274
|
newPreimage,
|
|
1192
1275
|
paymentHashOf,
|
|
1276
|
+
L1_NETWORKS,
|
|
1277
|
+
l1ScriptForAddress,
|
|
1193
1278
|
onchainHtlcScript,
|
|
1194
1279
|
buildHtlcClaim,
|
|
1195
1280
|
buildHtlcRefund,
|