@arkade-os/swap 0.0.11 → 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/dist/index.cjs CHANGED
@@ -66,6 +66,8 @@ __export(index_exports, {
66
66
  RfqSwapManager: () => RfqSwapManager,
67
67
  RfqSwapOriginRequired: () => RfqSwapOriginRequired,
68
68
  SOLO_REFUND_HEADROOM_SECONDS: () => SOLO_REFUND_HEADROOM_SECONDS,
69
+ SOLVER_LIGHTNING_RAIL: () => SOLVER_LIGHTNING_RAIL,
70
+ SOLVER_ONCHAIN_RAIL: () => SOLVER_ONCHAIN_RAIL,
69
71
  SWAP_LOCKUP_CONTRACT_KIND: () => SWAP_LOCKUP_CONTRACT_KIND,
70
72
  SWAP_LOCKUP_CONTRACT_LABEL: () => SWAP_LOCKUP_CONTRACT_LABEL,
71
73
  SWAP_LOCKUP_CONTRACT_TYPE: () => SWAP_LOCKUP_CONTRACT_TYPE,
@@ -82,6 +84,7 @@ __export(index_exports, {
82
84
  buildHtlcClaim: () => buildHtlcClaim,
83
85
  buildHtlcRefund: () => buildHtlcRefund,
84
86
  cancelOffer: () => cancelOffer,
87
+ chainSourceFrom: () => chainSourceFrom,
85
88
  claimOnchainFill: () => claimOnchainFill,
86
89
  claimReceiveLockup: () => claimReceiveLockup,
87
90
  classifyDepositSpend: () => classifyDepositSpend,
@@ -103,6 +106,7 @@ __export(index_exports, {
103
106
  httpTransport: () => httpTransport,
104
107
  isRfqSwapTerminal: () => isRfqSwapTerminal,
105
108
  isRfqTerminal: () => isRfqTerminal,
109
+ l1ScriptForAddress: () => l1ScriptForAddress,
106
110
  lightningReceiveRequest: () => lightningReceiveRequest,
107
111
  lightningSendRequest: () => lightningSendRequest,
108
112
  lightningSendVtxoScript: () => lightningSendVtxoScript,
@@ -142,6 +146,11 @@ __export(index_exports, {
142
146
  sealClaimPacket: () => sealClaimPacket,
143
147
  senderIdentityForSwapRecord: () => senderIdentityForSwapRecord,
144
148
  shouldRetainRfqSwap: () => shouldRetainRfqSwap,
149
+ solverLightningRail: () => solverLightningRail,
150
+ solverLightningRendezvous: () => solverLightningRendezvous,
151
+ solverOnchainRail: () => solverOnchainRail,
152
+ solverOnchainRendezvous: () => solverOnchainRendezvous,
153
+ solverRendezvous: () => solverRendezvous,
145
154
  spendTxidsOf: () => spendTxidsOf,
146
155
  spendUpdate: () => spendUpdate,
147
156
  swapActivityResolver: () => swapActivityResolver,
@@ -1158,6 +1167,7 @@ var L1_NETWORKS = {
1158
1167
  testnet: btc.TEST_NETWORK,
1159
1168
  regtest: { ...btc.TEST_NETWORK, bech32: "bcrt" }
1160
1169
  };
1170
+ var l1ScriptForAddress = (address, network) => btc.OutScript.encode(btc.Address(L1_NETWORKS[network]).decode(address));
1161
1171
  function onchainHtlcScript(params, network) {
1162
1172
  if (params.claimKey.length !== 32 || params.refundKey.length !== 32) {
1163
1173
  throw new Error("claimKey and refundKey must be 32-byte x-only keys");
@@ -1340,7 +1350,11 @@ async function classifyOnchainHtlc(chain, input) {
1340
1350
  const best = utxos.sort((a, b) => b.amount > a.amount ? 1 : -1)[0];
1341
1351
  if (!best) {
1342
1352
  if (!input.funding) return { phase: "unfunded" };
1343
- const spend = await chain.getSpendingTx(input.funding.txid, input.funding.vout);
1353
+ const spend = await chain.getSpendingTx(
1354
+ input.funding.txid,
1355
+ input.funding.vout,
1356
+ input.htlc.pkScript
1357
+ );
1344
1358
  if (!spend) return { phase: "unfunded" };
1345
1359
  const preimage = extractPreimage(spend.txHex, input.htlc.paymentHash);
1346
1360
  const txid = btc.Transaction.fromRaw(import_base3.hex.decode(spend.txHex), {
@@ -1399,7 +1413,8 @@ function onchainSendProfile(result) {
1399
1413
  htlcLocktime: result.htlcParams.refundLocktime,
1400
1414
  network: result.l1Network,
1401
1415
  htlcAddress: result.htlc.address,
1402
- minConfirmations: result.minConfirmations
1416
+ minConfirmations: result.minConfirmations,
1417
+ payoutPkScript: import_base4.hex.encode(result.payoutPkScript)
1403
1418
  };
1404
1419
  }
1405
1420
  var OnchainSendCorridor = {
@@ -1448,6 +1463,9 @@ var OnchainSendCorridor = {
1448
1463
  paymentHash,
1449
1464
  htlc,
1450
1465
  minConfirmations: profile.minConfirmations,
1466
+ // Optional here, required at the write: throwing on an older
1467
+ // record would strand the refund it is still owed.
1468
+ ...profile.payoutPkScript ? { payoutPkScript: import_base4.hex.decode(profile.payoutPkScript) } : {},
1451
1469
  ...profile.funding ? { funding: profile.funding } : {},
1452
1470
  ...profile.claimTxid ? { claimTxid: profile.claimTxid } : {}
1453
1471
  };
@@ -2045,6 +2063,43 @@ var assertFundable = (input) => {
2045
2063
  if (input.quote.refund_locktime !== void 0 && input.quote.refund_locktime - input.now < MIN_HEADROOM_SECONDS) {
2046
2064
  fail("insufficient_headroom", "refund deadline headroom below 90 minutes");
2047
2065
  }
2066
+ if (input.maxFee) {
2067
+ const { bps, sats, referenceRate } = input.maxFee;
2068
+ if (bps === void 0 && sats === void 0) {
2069
+ fail("max_fee_unbounded", "maxFee names neither bps nor sats");
2070
+ }
2071
+ if (bps !== void 0 && (!Number.isInteger(bps) || bps < 0 || bps > 1e4)) {
2072
+ fail("max_fee_out_of_range", `maxFee.bps must be an integer in 0..10000, got ${bps}`);
2073
+ }
2074
+ if (sats !== void 0 && (!Number.isInteger(sats) || sats < 0)) {
2075
+ fail("max_fee_out_of_range", `maxFee.sats must be a non-negative integer, got ${sats}`);
2076
+ }
2077
+ const legs = input.quote.pair.split("->");
2078
+ const assetOf = (leg) => leg.slice(leg.indexOf(":") + 1);
2079
+ const sameAsset = legs.length === 2 && assetOf(legs[0]) === assetOf(legs[1]);
2080
+ if (!sameAsset && referenceRate === void 0) {
2081
+ fail(
2082
+ "fee_gate_unavailable",
2083
+ `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`
2084
+ );
2085
+ }
2086
+ if (!sameAsset && (!Number.isFinite(referenceRate) || referenceRate <= 0)) {
2087
+ fail(
2088
+ "max_fee_out_of_range",
2089
+ `maxFee.referenceRate must be a positive finite number, got ${referenceRate}`
2090
+ );
2091
+ }
2092
+ const fee = sameAsset ? input.quote.from_amount - input.quote.to_amount : Math.ceil(
2093
+ (input.quote.from_amount * referenceRate - input.quote.to_amount) / referenceRate
2094
+ );
2095
+ const allowed = Math.max(
2096
+ sats ?? 0,
2097
+ Math.floor(input.quote.from_amount * (bps ?? 0) / 1e4)
2098
+ );
2099
+ if (fee > allowed) {
2100
+ fail("fee_too_high", `fee ${fee} exceeds the ${allowed} this client allows`);
2101
+ }
2102
+ }
2048
2103
  if (input.onchain) {
2049
2104
  const { htlcLocktime, minConfirmations, direction } = input.onchain;
2050
2105
  if (!Number.isInteger(minConfirmations) || minConfirmations < 1 || minConfirmations > MAX_MIN_CONFIRMATIONS) {
@@ -2440,6 +2495,7 @@ async function requestOnchainSend(wallet, arkServerUrl, transport, params) {
2440
2495
  amountSide: params.amountSide
2441
2496
  })
2442
2497
  );
2498
+ assertQuotedAmount(quote, params.amountSide, params.amount);
2443
2499
  const network = (0, import_sdk9.getNetwork)(info.network);
2444
2500
  const derived = deriveOnchainSend({
2445
2501
  quote,
@@ -2815,16 +2871,302 @@ async function requestOnchainReceive(wallet, arkServerUrl, transport, params) {
2815
2871
  };
2816
2872
  }
2817
2873
 
2818
- // src/claim.ts
2874
+ // src/chainSource.ts
2875
+ var btc2 = __toESM(require("@scure/btc-signer"), 1);
2876
+ var import_base11 = require("@scure/base");
2877
+ var addressOf = (pkScript, network) => btc2.Address(L1_NETWORKS[network]).encode(btc2.OutScript.decode(pkScript));
2878
+ var chainSourceFrom = (provider, network) => {
2879
+ const spenderFromHistory = async (txid, vout, pkScript) => {
2880
+ const txs = await provider.getTransactions(addressOf(pkScript, network));
2881
+ return txs.find((tx) => tx.vin?.some((i) => i.txid === txid && i.vout === vout))?.txid;
2882
+ };
2883
+ return {
2884
+ async getScriptUtxos(pkScript) {
2885
+ const address = addressOf(pkScript, network);
2886
+ const [coins, tip] = await Promise.all([
2887
+ provider.getCoins(address),
2888
+ provider.getChainTip()
2889
+ ]);
2890
+ return coins.map((coin) => ({
2891
+ txid: coin.txid,
2892
+ vout: coin.vout,
2893
+ amount: BigInt(coin.value),
2894
+ // Zero, not one: calling a mempool output "1 deep" would let a
2895
+ // 1-confirmation policy claim against a replaceable transaction.
2896
+ confirmations: coin.status.confirmed && typeof coin.status.block_height === "number" ? Math.max(0, tip.height - coin.status.block_height + 1) : 0
2897
+ }));
2898
+ },
2899
+ async getSpendingTx(txid, vout, pkScript) {
2900
+ const outspends = await provider.getTxOutspends(txid);
2901
+ const outspend = outspends[vout];
2902
+ if (!outspend?.spent) return null;
2903
+ const spender = outspend.txid || await spenderFromHistory(txid, vout, pkScript);
2904
+ if (!spender) return null;
2905
+ const raw = await provider.getRawTransaction(spender);
2906
+ return { txHex: import_base11.hex.encode(raw) };
2907
+ },
2908
+ broadcast(txHex) {
2909
+ return provider.broadcastTransaction(txHex);
2910
+ },
2911
+ async getMtp() {
2912
+ return (await provider.getChainTip()).time;
2913
+ }
2914
+ };
2915
+ };
2916
+
2917
+ // src/payment/rendezvous.ts
2918
+ var import_solver_discovery2 = require("@arkade-os/solver-discovery");
2819
2919
  var import_base12 = require("@scure/base");
2920
+ var XONLY_HEX = /^[0-9a-f]{64}$/;
2921
+ var rendezvousOf = (market, pinned) => {
2922
+ const transports = { nostr: { relays: market.transports?.nostr?.relays ?? [] } };
2923
+ if (!market.discovery_pubkey || !transports.nostr.relays.length) return void 0;
2924
+ const advertised = market.emulator_pubkey;
2925
+ const emulatorPubkey = advertised === void 0 || advertised === null || advertised === "" ? pinned : typeof advertised === "string" && XONLY_HEX.test(advertised) ? advertised : void 0;
2926
+ if (!emulatorPubkey) return void 0;
2927
+ if (pinned && emulatorPubkey !== pinned) return void 0;
2928
+ const bounds = (0, import_solver_discovery2.sideLimits)(market, "quote");
2929
+ if (!bounds) return void 0;
2930
+ return {
2931
+ solverPubkey: market.discovery_pubkey,
2932
+ transports,
2933
+ minSats: Number(bounds.min),
2934
+ maxSats: Number(bounds.max)
2935
+ };
2936
+ };
2937
+ var solverRendezvous = (markets, payoutCorridor, amountSats, fallbackEmulatorPubkey) => {
2938
+ const encoded = fallbackEmulatorPubkey ? import_base12.hex.encode(fallbackEmulatorPubkey) : void 0;
2939
+ if (encoded !== void 0 && !XONLY_HEX.test(encoded)) return void 0;
2940
+ const pinned = encoded;
2941
+ const candidates = (0, import_solver_discovery2.selectMarkets)(markets, {
2942
+ baseId: BTC_ASSET_ID,
2943
+ quoteId: BTC_ASSET_ID,
2944
+ baseCorridor: "arkade",
2945
+ quoteCorridor: payoutCorridor
2946
+ });
2947
+ for (const market of candidates) {
2948
+ const rendezvous = rendezvousOf(market, pinned);
2949
+ if (!rendezvous) continue;
2950
+ if (amountSats >= rendezvous.minSats && amountSats <= rendezvous.maxSats) {
2951
+ return rendezvous;
2952
+ }
2953
+ }
2954
+ return void 0;
2955
+ };
2956
+
2957
+ // src/payment/solverOnchain.ts
2958
+ var import_sdk11 = require("@arkade-os/sdk");
2959
+ var SOLVER_ONCHAIN_RAIL = "solver-onchain";
2960
+ var solverOnchainRendezvous = (markets, amountSats, fallbackEmulatorPubkey) => solverRendezvous(markets, "onchain", amountSats, fallbackEmulatorPubkey);
2961
+ function solverOnchainRail(deps) {
2962
+ const rendezvousFor = async (amount) => {
2963
+ if (amount === void 0) return void 0;
2964
+ const markets = await deps.discover();
2965
+ return solverOnchainRendezvous(markets, amount, deps.fallbackEmulatorPubkey);
2966
+ };
2967
+ return {
2968
+ id: SOLVER_ONCHAIN_RAIL,
2969
+ match: (req) => (0, import_sdk11.btcTarget)(req.raw) !== void 0,
2970
+ available: async (req) => {
2971
+ const address = (0, import_sdk11.btcTarget)(req.raw);
2972
+ if (!address) return false;
2973
+ try {
2974
+ l1ScriptForAddress(address, deps.l1Network);
2975
+ } catch {
2976
+ return false;
2977
+ }
2978
+ const amount = (0, import_sdk11.tryResolveSendAmount)(req.raw, req.amount);
2979
+ if (amount === void 0) return false;
2980
+ return await rendezvousFor(amount) !== void 0;
2981
+ },
2982
+ quote: async (req, ctx) => {
2983
+ const address = (0, import_sdk11.btcTarget)(req.raw);
2984
+ const amount = (0, import_sdk11.resolveSendAmount)(SOLVER_ONCHAIN_RAIL, req.raw, req.amount);
2985
+ const payoutPkScript = l1ScriptForAddress(address, deps.l1Network);
2986
+ const rendezvous = await rendezvousFor(amount);
2987
+ if (!rendezvous) {
2988
+ throw new Error(
2989
+ `${SOLVER_ONCHAIN_RAIL}: no solver serves arkade:BTC -> onchain:BTC at ${amount} sats`
2990
+ );
2991
+ }
2992
+ const negotiated = await deps.connect(
2993
+ rendezvous,
2994
+ (transport) => requestOnchainSend(ctx.wallet, deps.arkServerUrl, transport, {
2995
+ amount,
2996
+ amountSide: "to",
2997
+ payoutPubkey: deps.payoutPubkey,
2998
+ ...deps.emulatorPubkey ? { emulatorPubkey: deps.emulatorPubkey } : {}
2999
+ })
3000
+ );
3001
+ if (negotiated.l1Network !== deps.l1Network) {
3002
+ throw new Error(
3003
+ `${SOLVER_ONCHAIN_RAIL}: rail built for ${deps.l1Network} but the swap was negotiated on ${negotiated.l1Network}`
3004
+ );
3005
+ }
3006
+ const swap = { ...negotiated, rendezvous, payoutPkScript };
3007
+ return {
3008
+ railId: SOLVER_ONCHAIN_RAIL,
3009
+ amount,
3010
+ fee: swap.fundAmount - amount,
3011
+ total: swap.fundAmount,
3012
+ meta: {
3013
+ rfqId: swap.rfqId,
3014
+ validUntil: swap.quote.valid_until,
3015
+ htlcAddress: swap.htlc.address,
3016
+ minConfirmations: swap.minConfirmations,
3017
+ solverPubkey: rendezvous.solverPubkey,
3018
+ // The claim tx's fee comes out of the HTLC output at a rate
3019
+ // not knowable now, so `amount` is the payout, not the net.
3020
+ claimFeeDeductedFromPayout: true
3021
+ },
3022
+ send: async () => (0, import_sdk11.makeHandle)(SOLVER_ONCHAIN_RAIL, async (emit) => {
3023
+ assertFundable({
3024
+ quote: swap.quote,
3025
+ now: Math.floor(Date.now() / 1e3),
3026
+ onchain: {
3027
+ htlcLocktime: swap.htlcParams.refundLocktime,
3028
+ minConfirmations: swap.minConfirmations,
3029
+ direction: "send"
3030
+ }
3031
+ });
3032
+ await deps.persist(swap);
3033
+ await ctx.wallet.send({
3034
+ address: swap.address,
3035
+ amount: swap.fundAmount
3036
+ });
3037
+ emit({ status: "sent" });
3038
+ const result = { railId: SOLVER_ONCHAIN_RAIL, swapId: swap.rfqId };
3039
+ if (!deps.awaitSettlement) return result;
3040
+ let txid;
3041
+ try {
3042
+ ({ txid } = await deps.awaitSettlement(swap));
3043
+ } catch (e) {
3044
+ console.warn(
3045
+ `${SOLVER_ONCHAIN_RAIL}: settlement watch failed; the payment is sent`,
3046
+ e
3047
+ );
3048
+ return result;
3049
+ }
3050
+ const settled = { ...result, txid };
3051
+ emit({ status: "settled", result: settled });
3052
+ return settled;
3053
+ })
3054
+ };
3055
+ }
3056
+ };
3057
+ }
3058
+
3059
+ // src/payment/solverLightning.ts
3060
+ var import_sdk12 = require("@arkade-os/sdk");
3061
+ var SOLVER_LIGHTNING_RAIL = "solver-lightning";
3062
+ var factsOf = (raw, decode, now) => {
3063
+ const invoice = (0, import_sdk12.invoiceTarget)(raw);
3064
+ if (!invoice) return void 0;
3065
+ let facts;
3066
+ try {
3067
+ facts = decode(invoice);
3068
+ } catch {
3069
+ return void 0;
3070
+ }
3071
+ if (!Number.isInteger(facts.amountSats) || facts.amountSats <= 0) return void 0;
3072
+ if (facts.expiresAt <= now) return void 0;
3073
+ return facts;
3074
+ };
3075
+ var solverLightningRendezvous = (markets, amountSats, fallbackEmulatorPubkey) => solverRendezvous(markets, "lightning", amountSats, fallbackEmulatorPubkey);
3076
+ function solverLightningRail(deps) {
3077
+ const rendezvousFor = async (amountSats) => solverLightningRendezvous(await deps.discover(), amountSats, deps.fallbackEmulatorPubkey);
3078
+ return {
3079
+ id: SOLVER_LIGHTNING_RAIL,
3080
+ match: (req) => (0, import_sdk12.invoiceTarget)(req.raw) !== void 0,
3081
+ available: async (req) => {
3082
+ const facts = factsOf(req.raw, deps.decodeInvoice, Math.floor(Date.now() / 1e3));
3083
+ if (!facts) return false;
3084
+ if (req.amount !== void 0 && req.amount !== facts.amountSats) return false;
3085
+ return await rendezvousFor(facts.amountSats) !== void 0;
3086
+ },
3087
+ quote: async (req, ctx) => {
3088
+ const facts = factsOf(req.raw, deps.decodeInvoice, Math.floor(Date.now() / 1e3));
3089
+ if (!facts) {
3090
+ throw new Error(
3091
+ `${SOLVER_LIGHTNING_RAIL}: the request carries no payable BOLT11 invoice (amountless, expired, or undecodable)`
3092
+ );
3093
+ }
3094
+ if (req.amount !== void 0 && req.amount !== facts.amountSats) {
3095
+ throw new Error(
3096
+ `${SOLVER_LIGHTNING_RAIL}: the request names ${req.amount} sats but the invoice is for ${facts.amountSats} \u2014 the payee is paid the invoice`
3097
+ );
3098
+ }
3099
+ const rendezvous = await rendezvousFor(facts.amountSats);
3100
+ if (!rendezvous) {
3101
+ throw new Error(
3102
+ `${SOLVER_LIGHTNING_RAIL}: no solver serves arkade:BTC -> lightning:BTC at ${facts.amountSats} sats`
3103
+ );
3104
+ }
3105
+ const negotiated = await deps.connect(
3106
+ rendezvous,
3107
+ (transport) => requestLightningSend(ctx.wallet, deps.arkServerUrl, transport, {
3108
+ invoice: facts,
3109
+ ...deps.emulatorPubkey ? { emulatorPubkey: deps.emulatorPubkey } : {}
3110
+ })
3111
+ );
3112
+ const swap = { ...negotiated, invoice: facts, rendezvous };
3113
+ return {
3114
+ railId: SOLVER_LIGHTNING_RAIL,
3115
+ // `requestLightningSend` refuses a quote that reprices the
3116
+ // invoice, so the spread is a fee on top.
3117
+ amount: facts.amountSats,
3118
+ fee: swap.fundAmount - facts.amountSats,
3119
+ total: swap.fundAmount,
3120
+ meta: {
3121
+ rfqId: swap.rfqId,
3122
+ validUntil: swap.quote.valid_until,
3123
+ paymentHash: facts.paymentHash,
3124
+ invoiceExpiresAt: facts.expiresAt,
3125
+ solverPubkey: rendezvous.solverPubkey
3126
+ },
3127
+ send: async () => (0, import_sdk12.makeHandle)(SOLVER_LIGHTNING_RAIL, async (emit) => {
3128
+ assertFundable({
3129
+ quote: swap.quote,
3130
+ invoiceExpiresAt: facts.expiresAt,
3131
+ now: Math.floor(Date.now() / 1e3)
3132
+ });
3133
+ await deps.persist(swap);
3134
+ await ctx.wallet.send({
3135
+ address: swap.address,
3136
+ amount: swap.fundAmount
3137
+ });
3138
+ emit({ status: "sent" });
3139
+ const result = { railId: SOLVER_LIGHTNING_RAIL, swapId: swap.rfqId };
3140
+ if (!deps.awaitSettlement) return result;
3141
+ let preimage;
3142
+ try {
3143
+ ({ preimage } = await deps.awaitSettlement(swap));
3144
+ } catch (e) {
3145
+ console.warn(
3146
+ `${SOLVER_LIGHTNING_RAIL}: settlement watch failed; the payment is sent`,
3147
+ e
3148
+ );
3149
+ return result;
3150
+ }
3151
+ const settled = { ...result, ...preimage !== void 0 && { preimage } };
3152
+ emit({ status: "settled", result: settled });
3153
+ return settled;
3154
+ })
3155
+ };
3156
+ }
3157
+ };
3158
+ }
3159
+
3160
+ // src/claim.ts
3161
+ var import_base14 = require("@scure/base");
2820
3162
  var import_legacy3 = require("@noble/hashes/legacy.js");
2821
3163
  var import_sha25 = require("@noble/hashes/sha2.js");
2822
- var import_sdk12 = require("@arkade-os/sdk");
3164
+ var import_sdk14 = require("@arkade-os/sdk");
2823
3165
 
2824
3166
  // src/refund.ts
2825
- var import_base11 = require("@scure/base");
3167
+ var import_base13 = require("@scure/base");
2826
3168
  var import_sha24 = require("@noble/hashes/sha2.js");
2827
- var import_sdk11 = require("@arkade-os/sdk");
3169
+ var import_sdk13 = require("@arkade-os/sdk");
2828
3170
  var sleep2 = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
2829
3171
  var isRfqTerminal = (state) => RFQ_TERMINAL_STATES.includes(state);
2830
3172
  var RFQ_RESOLVED_STATES = ["settled", "refunded"];
@@ -2871,7 +3213,7 @@ var LockupNeedsRecoveryError = class extends Error {
2871
3213
  }
2872
3214
  };
2873
3215
  async function findLockupVtxos(indexer, swapPkScript) {
2874
- const scripts = [import_base11.hex.encode(swapPkScript)];
3216
+ const scripts = [import_base13.hex.encode(swapPkScript)];
2875
3217
  const [spendable, recoverable] = await Promise.all([
2876
3218
  indexer.getVtxos({ scripts, spendableOnly: true }),
2877
3219
  indexer.getVtxos({ scripts, recoverableOnly: true })
@@ -2897,16 +3239,16 @@ async function findLockupVtxos(indexer, swapPkScript) {
2897
3239
  }
2898
3240
  return out;
2899
3241
  }
2900
- var hashesTo = (candidate, paymentHash) => import_base11.hex.encode((0, import_sha24.sha256)(candidate)) === paymentHash;
3242
+ var hashesTo = (candidate, paymentHash) => import_base13.hex.encode((0, import_sha24.sha256)(candidate)) === paymentHash;
2901
3243
  var candidateWitnessItems = (tx, inputIndex) => [
2902
- ...(0, import_sdk11.getArkPsbtFields)(tx, inputIndex, import_sdk11.ConditionWitness).flat(),
3244
+ ...(0, import_sdk13.getArkPsbtFields)(tx, inputIndex, import_sdk13.ConditionWitness).flat(),
2903
3245
  ...tx.getInput(inputIndex).finalScriptWitness ?? []
2904
3246
  ];
2905
3247
  async function readLockupFate(indexer, input) {
2906
- const { vtxos } = await indexer.getVtxos({ scripts: [import_base11.hex.encode(input.swapPkScript)] });
3248
+ const { vtxos } = await indexer.getVtxos({ scripts: [import_base13.hex.encode(input.swapPkScript)] });
2907
3249
  const all = vtxos ?? [];
2908
3250
  if (all.length === 0) return { fate: "unknown" };
2909
- const exited = all.filter((vtxo) => vtxo.isUnrolled && !(0, import_sdk11.hasTerminalSpend)(vtxo));
3251
+ const exited = all.filter((vtxo) => vtxo.isUnrolled && !(0, import_sdk13.hasTerminalSpend)(vtxo));
2910
3252
  if (exited.length > 0) {
2911
3253
  return {
2912
3254
  fate: "exited",
@@ -2916,7 +3258,7 @@ async function readLockupFate(indexer, input) {
2916
3258
  const spentBy = /* @__PURE__ */ new Map();
2917
3259
  let everySpendNamed = true;
2918
3260
  for (const vtxo of all) {
2919
- if (!(0, import_sdk11.hasTerminalSpend)(vtxo)) return { fate: "open" };
3261
+ if (!(0, import_sdk13.hasTerminalSpend)(vtxo)) return { fate: "open" };
2920
3262
  if (vtxo.spentBy)
2921
3263
  spentBy.set(vtxo.spentBy, {
2922
3264
  checkpointTxid: vtxo.spentBy,
@@ -2930,7 +3272,7 @@ async function readLockupFate(indexer, input) {
2930
3272
  for (const raw of txs) {
2931
3273
  let tx;
2932
3274
  try {
2933
- tx = import_sdk11.Transaction.fromPSBT(import_base11.base64.decode(raw));
3275
+ tx = import_sdk13.Transaction.fromPSBT(import_base13.base64.decode(raw));
2934
3276
  } catch {
2935
3277
  continue;
2936
3278
  }
@@ -2938,7 +3280,7 @@ async function readLockupFate(indexer, input) {
2938
3280
  for (let i = 0; i < tx.inputsLength; i++) {
2939
3281
  const spent = tx.getInput(i);
2940
3282
  if (!spent.txid) continue;
2941
- const txid = import_base11.hex.encode(spent.txid);
3283
+ const txid = import_base13.hex.encode(spent.txid);
2942
3284
  if (!all.some((vtxo) => vtxo.txid === txid && vtxo.vout === spent.index)) continue;
2943
3285
  for (const candidate of candidateWitnessItems(tx, i)) {
2944
3286
  if (hashesTo(candidate, input.paymentHash)) {
@@ -2967,14 +3309,14 @@ async function pushRefundWithoutReceiver(ark, input) {
2967
3309
  const info = await ark.getInfo();
2968
3310
  let serverUnrollScript;
2969
3311
  try {
2970
- serverUnrollScript = import_sdk11.CSVMultisigTapscript.decode(import_base11.hex.decode(info.checkpointTapscript));
3312
+ serverUnrollScript = import_sdk13.CSVMultisigTapscript.decode(import_base13.hex.decode(info.checkpointTapscript));
2971
3313
  } catch {
2972
3314
  throw new Error("invalid checkpointTapscript from the Arkade server");
2973
3315
  }
2974
3316
  const leaf = input.script.refundWithoutReceiver();
2975
3317
  const tapTree = input.script.encode();
2976
3318
  const amount = input.vtxos.reduce((sum, vtxo) => sum + vtxo.value, 0);
2977
- const { arkTx, checkpoints } = (0, import_sdk11.buildOffchainTx)(
3319
+ const { arkTx, checkpoints } = (0, import_sdk13.buildOffchainTx)(
2978
3320
  input.vtxos.map((vtxo) => ({
2979
3321
  txid: vtxo.txid,
2980
3322
  vout: vtxo.vout,
@@ -2987,18 +3329,18 @@ async function pushRefundWithoutReceiver(ark, input) {
2987
3329
  );
2988
3330
  const signedArkTx = await input.sender.sign(arkTx);
2989
3331
  const submitted = await ark.submitTx(
2990
- import_base11.base64.encode(signedArkTx.toPSBT()),
2991
- checkpoints.map((c) => import_base11.base64.encode(c.toPSBT()))
3332
+ import_base13.base64.encode(signedArkTx.toPSBT()),
3333
+ checkpoints.map((c) => import_base13.base64.encode(c.toPSBT()))
2992
3334
  );
2993
- (0, import_sdk11.assertSubmittedArkTxid)(submitted, signedArkTx, "refundWithoutReceiver");
2994
- const matched = (0, import_sdk11.matchServerCheckpoints)(
3335
+ (0, import_sdk13.assertSubmittedArkTxid)(submitted, signedArkTx, "refundWithoutReceiver");
3336
+ const matched = (0, import_sdk13.matchServerCheckpoints)(
2995
3337
  submitted.signedCheckpointTxs,
2996
3338
  checkpoints,
2997
3339
  "refundWithoutReceiver"
2998
3340
  );
2999
3341
  const finalCheckpoints = await Promise.all(
3000
3342
  matched.map(
3001
- async ({ server }) => import_base11.base64.encode((await input.sender.sign(server, [0])).toPSBT())
3343
+ async ({ server }) => import_base13.base64.encode((await input.sender.sign(server, [0])).toPSBT())
3002
3344
  )
3003
3345
  );
3004
3346
  await ark.finalizeTx(submitted.arkTxid, finalCheckpoints);
@@ -3093,20 +3435,20 @@ async function pushClaim(ark, input) {
3093
3435
  }
3094
3436
  }
3095
3437
  const committed = input.script.options.preimageHash;
3096
- if (import_base12.hex.encode((0, import_legacy3.ripemd160)((0, import_sha25.sha256)(input.preimage))) !== import_base12.hex.encode(committed)) {
3438
+ if (import_base14.hex.encode((0, import_legacy3.ripemd160)((0, import_sha25.sha256)(input.preimage))) !== import_base14.hex.encode(committed)) {
3097
3439
  throw new Error("preimage does not match the covenant's payment hash");
3098
3440
  }
3099
3441
  const info = await ark.getInfo();
3100
3442
  let serverUnrollScript;
3101
3443
  try {
3102
- serverUnrollScript = import_sdk12.CSVMultisigTapscript.decode(import_base12.hex.decode(info.checkpointTapscript));
3444
+ serverUnrollScript = import_sdk14.CSVMultisigTapscript.decode(import_base14.hex.decode(info.checkpointTapscript));
3103
3445
  } catch {
3104
3446
  throw new Error("invalid checkpointTapscript from the Arkade server");
3105
3447
  }
3106
3448
  const leaf = input.script.claim();
3107
3449
  const tapTree = input.script.encode();
3108
- const arkTxid = await (0, import_sdk12.signAndSubmitOffchainTx)({
3109
- identity: (0, import_sdk12.claimWithPreimageIdentity)(input.receiver, input.preimage),
3450
+ const arkTxid = await (0, import_sdk14.signAndSubmitOffchainTx)({
3451
+ identity: (0, import_sdk14.claimWithPreimageIdentity)(input.receiver, input.preimage),
3110
3452
  provider: ark,
3111
3453
  inputs: input.vtxos.map((vtxo) => ({
3112
3454
  txid: vtxo.txid,
@@ -3153,7 +3495,7 @@ async function claimReceiveLockup(indexer, ark, input) {
3153
3495
  }
3154
3496
 
3155
3497
  // src/refundBlocked.ts
3156
- var import_sdk13 = require("@arkade-os/sdk");
3498
+ var import_sdk15 = require("@arkade-os/sdk");
3157
3499
  var RefundNotLocallyPossibleError = class extends Error {
3158
3500
  constructor(reason, message, options) {
3159
3501
  super(message, options);
@@ -3170,16 +3512,16 @@ async function senderIdentityForSwapRecord(wallet, record) {
3170
3512
  );
3171
3513
  }
3172
3514
  try {
3173
- return await (0, import_sdk13.contractSigner)(wallet, record.signingDescriptor);
3515
+ return await (0, import_sdk15.contractSigner)(wallet, record.signingDescriptor);
3174
3516
  } catch (cause) {
3175
- if (cause instanceof import_sdk13.WalletCannotSignError) {
3517
+ if (cause instanceof import_sdk15.WalletCannotSignError) {
3176
3518
  throw new RefundNotLocallyPossibleError(
3177
3519
  "unsignable-wallet",
3178
3520
  `this wallet holds ${record.signingDescriptor} but cannot sign with it; attach its signer`,
3179
3521
  { cause }
3180
3522
  );
3181
3523
  }
3182
- if (cause instanceof import_sdk13.ForeignDescriptorError) {
3524
+ if (cause instanceof import_sdk15.ForeignDescriptorError) {
3183
3525
  throw new RefundNotLocallyPossibleError(
3184
3526
  "foreign-descriptor",
3185
3527
  `this wallet cannot derive ${record.signingDescriptor}; the swap was created on another wallet`,
@@ -3214,7 +3556,7 @@ function arkadeRefunder(deps) {
3214
3556
  }
3215
3557
 
3216
3558
  // src/swapManager.ts
3217
- var import_base13 = require("@scure/base");
3559
+ var import_base15 = require("@scure/base");
3218
3560
  function nextOnchainAction(input) {
3219
3561
  switch (input.phase.phase) {
3220
3562
  case "unfunded":
@@ -3662,7 +4004,7 @@ var RfqSwapManager = class {
3662
4004
  // ── internals ────────────────────────────────────────────────────────────
3663
4005
  track(swap) {
3664
4006
  this.monitored.set(swap.rfqId, swap);
3665
- this.byLockupScript.set(import_base13.hex.encode(swap.lockupPkScript), swap);
4007
+ this.byLockupScript.set(import_base15.hex.encode(swap.lockupPkScript), swap);
3666
4008
  }
3667
4009
  /** Drops the swap from BOTH indexes. The event index is the one that stops
3668
4010
  * a late event finding a swap that is gone; `pollSwap`'s own
@@ -3671,7 +4013,7 @@ var RfqSwapManager = class {
3671
4013
  * them from silently re-driving a cancelled swap. */
3672
4014
  untrack(rfqId) {
3673
4015
  const swap = this.monitored.get(rfqId);
3674
- if (swap) this.byLockupScript.delete(import_base13.hex.encode(swap.lockupPkScript));
4016
+ if (swap) this.byLockupScript.delete(import_base15.hex.encode(swap.lockupPkScript));
3675
4017
  this.monitored.delete(rfqId);
3676
4018
  this.refundRefused.delete(rfqId);
3677
4019
  this.lastClaimError.delete(rfqId);
@@ -3733,7 +4075,7 @@ var RfqSwapManager = class {
3733
4075
  if (!lockup) {
3734
4076
  try {
3735
4077
  const [existing] = await contracts.getContracts({
3736
- script: import_base13.hex.encode(swap.lockupPkScript)
4078
+ script: import_base15.hex.encode(swap.lockupPkScript)
3737
4079
  });
3738
4080
  if (existing) {
3739
4081
  this.registered.set(swap.rfqId, true);
@@ -3752,13 +4094,13 @@ var RfqSwapManager = class {
3752
4094
  );
3753
4095
  return;
3754
4096
  }
3755
- const script = import_base13.hex.encode(lockup.script.pkScript);
3756
- if (script !== import_base13.hex.encode(swap.lockupPkScript)) {
4097
+ const script = import_base15.hex.encode(lockup.script.pkScript);
4098
+ if (script !== import_base15.hex.encode(swap.lockupPkScript)) {
3757
4099
  this.registered.set(swap.rfqId, false);
3758
4100
  this.emitFailed(
3759
4101
  swap,
3760
4102
  new Error(
3761
- `swap ${swap.rfqId} lockup script ${script} does not match its lockupPkScript ${import_base13.hex.encode(swap.lockupPkScript)}`
4103
+ `swap ${swap.rfqId} lockup script ${script} does not match its lockupPkScript ${import_base15.hex.encode(swap.lockupPkScript)}`
3762
4104
  )
3763
4105
  );
3764
4106
  return;
@@ -3777,7 +4119,7 @@ var RfqSwapManager = class {
3777
4119
  * script for its whole life. Best-effort — the swap is over either way. */
3778
4120
  retireContract(swap) {
3779
4121
  if (!this.deps.contracts || !this.registered.get(swap.rfqId)) return;
3780
- void this.deps.contracts.setContractWatchState(import_base13.hex.encode(swap.lockupPkScript), "retained").catch((error) => this.emitFailed(swap, error));
4122
+ void this.deps.contracts.setContractWatchState(import_base15.hex.encode(swap.lockupPkScript), "retained").catch((error) => this.emitFailed(swap, error));
3781
4123
  }
3782
4124
  arm() {
3783
4125
  if (!this.running) return;
@@ -4250,8 +4592,8 @@ var errorMessage = (error) => error instanceof Error ? error.message : String(er
4250
4592
  var outpointKey = (vtxo) => `${vtxo.txid}:${vtxo.vout}`;
4251
4593
 
4252
4594
  // src/activity.ts
4253
- var import_sdk14 = require("@arkade-os/sdk");
4254
- var import_base14 = require("@scure/base");
4595
+ var import_sdk16 = require("@arkade-os/sdk");
4596
+ var import_base16 = require("@scure/base");
4255
4597
  var LABELS = {
4256
4598
  lightning_send: "Lightning send",
4257
4599
  lightning_receive: "Lightning receive",
@@ -4326,7 +4668,7 @@ async function activityInputOf(record, indexer) {
4326
4668
  async function lockupTxids(indexer, record, wantFunding) {
4327
4669
  let script;
4328
4670
  try {
4329
- script = import_base14.hex.encode(import_sdk14.ArkAddress.decode(record.lockupAddress).pkScript);
4671
+ script = import_base16.hex.encode(import_sdk16.ArkAddress.decode(record.lockupAddress).pkScript);
4330
4672
  } catch {
4331
4673
  return [];
4332
4674
  }
@@ -4380,6 +4722,8 @@ async function lockupTxids(indexer, record, wantFunding) {
4380
4722
  RfqSwapManager,
4381
4723
  RfqSwapOriginRequired,
4382
4724
  SOLO_REFUND_HEADROOM_SECONDS,
4725
+ SOLVER_LIGHTNING_RAIL,
4726
+ SOLVER_ONCHAIN_RAIL,
4383
4727
  SWAP_LOCKUP_CONTRACT_KIND,
4384
4728
  SWAP_LOCKUP_CONTRACT_LABEL,
4385
4729
  SWAP_LOCKUP_CONTRACT_TYPE,
@@ -4396,6 +4740,7 @@ async function lockupTxids(indexer, record, wantFunding) {
4396
4740
  buildHtlcClaim,
4397
4741
  buildHtlcRefund,
4398
4742
  cancelOffer,
4743
+ chainSourceFrom,
4399
4744
  claimOnchainFill,
4400
4745
  claimReceiveLockup,
4401
4746
  classifyDepositSpend,
@@ -4417,6 +4762,7 @@ async function lockupTxids(indexer, record, wantFunding) {
4417
4762
  httpTransport,
4418
4763
  isRfqSwapTerminal,
4419
4764
  isRfqTerminal,
4765
+ l1ScriptForAddress,
4420
4766
  lightningReceiveRequest,
4421
4767
  lightningSendRequest,
4422
4768
  lightningSendVtxoScript,
@@ -4456,6 +4802,11 @@ async function lockupTxids(indexer, record, wantFunding) {
4456
4802
  sealClaimPacket,
4457
4803
  senderIdentityForSwapRecord,
4458
4804
  shouldRetainRfqSwap,
4805
+ solverLightningRail,
4806
+ solverLightningRendezvous,
4807
+ solverOnchainRail,
4808
+ solverOnchainRendezvous,
4809
+ solverRendezvous,
4459
4810
  spendTxidsOf,
4460
4811
  spendUpdate,
4461
4812
  swapActivityResolver,