@arkade-os/swap 0.0.14 → 0.0.15

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
@@ -32,6 +32,7 @@ var index_exports = {};
32
32
  __export(index_exports, {
33
33
  ARKADE_ASSET: () => ARKADE_ASSET,
34
34
  ARKADE_BTC: () => ARKADE_BTC,
35
+ ASSET_CARRIER_SATS: () => ASSET_CARRIER_SATS,
35
36
  AddressMismatch: () => AddressMismatch,
36
37
  BTC_ASSET_ID: () => BTC_ASSET_ID,
37
38
  CROSS_ASSET_RAIL: () => CROSS_ASSET_RAIL,
@@ -46,6 +47,7 @@ __export(index_exports, {
46
47
  LockupContractMissing: () => LockupContractMissing,
47
48
  LockupNeedsRecoveryError: () => LockupNeedsRecoveryError,
48
49
  LockupRegistrationFailed: () => LockupRegistrationFailed,
50
+ MARKET_CORRIDORS: () => MARKET_CORRIDORS,
49
51
  MAX_MIN_CONFIRMATIONS: () => MAX_MIN_CONFIRMATIONS,
50
52
  MIN_CLAIM_WINDOW_SECONDS: () => MIN_CLAIM_WINDOW_SECONDS,
51
53
  MIN_HEADROOM_SECONDS: () => MIN_HEADROOM_SECONDS,
@@ -60,6 +62,7 @@ __export(index_exports, {
60
62
  PreimageNotRecoverableError: () => PreimageNotRecoverableError,
61
63
  QUOTE_OPTIONS: () => QUOTE_OPTIONS,
62
64
  REFUND_MTP_LAG_SECONDS: () => REFUND_MTP_LAG_SECONDS,
65
+ RFQ_REFUSAL_ERROR_CODES: () => RFQ_REFUSAL_ERROR_CODES,
63
66
  RFQ_RESOLVED_STATES: () => RFQ_RESOLVED_STATES,
64
67
  RFQ_SWAP_RETENTION_SECONDS: () => RFQ_SWAP_RETENTION_SECONDS,
65
68
  RFQ_SWAP_TERMINAL_STATES: () => RFQ_SWAP_TERMINAL_STATES,
@@ -103,11 +106,14 @@ __export(index_exports, {
103
106
  discoverMarkets: () => discoverMarkets,
104
107
  encodeOffer: () => encodeOffer,
105
108
  extractPreimage: () => extractPreimage,
109
+ fillOffer: () => fillOffer,
106
110
  findLockupVtxos: () => findLockupVtxos,
107
111
  findMarket: () => findMarket,
108
112
  getAssetSwaps: () => getAssetSwaps,
109
113
  getAssetSwapsOrThrow: () => getAssetSwapsOrThrow,
110
114
  httpTransport: () => httpTransport,
115
+ isRfqMarket: () => isRfqMarket,
116
+ isRfqRefusalErrorCode: () => isRfqRefusalErrorCode,
111
117
  isRfqSwapTerminal: () => isRfqSwapTerminal,
112
118
  isRfqTerminal: () => isRfqTerminal,
113
119
  l1ScriptForAddress: () => l1ScriptForAddress,
@@ -116,6 +122,9 @@ __export(index_exports, {
116
122
  lightningSendVtxoScript: () => lightningSendVtxoScript,
117
123
  lockupContractParams: () => lockupContractParams,
118
124
  makeCachedFeedFetch: () => makeCachedFeedFetch,
125
+ marketAssetId: () => marketAssetId,
126
+ marketCorridor: () => marketCorridor,
127
+ marketPairLabel: () => marketPairLabel,
119
128
  newPreimage: () => newPreimage,
120
129
  newRfqId: () => newRfqId,
121
130
  nextOnchainAction: () => nextOnchainAction,
@@ -133,13 +142,16 @@ __export(index_exports, {
133
142
  rebuildRfqSwap: () => rebuildRfqSwap,
134
143
  receiveVtxoScript: () => receiveVtxoScript,
135
144
  refundIfUnresolved: () => refundIfUnresolved,
145
+ registerAssetSwapRestore: () => registerAssetSwapRestore,
136
146
  registerLockupContract: () => registerLockupContract,
137
147
  relayTransport: () => relayTransport,
138
148
  requestLightningReceive: () => requestLightningReceive,
139
149
  requestLightningSend: () => requestLightningSend,
140
150
  requestOnchainReceive: () => requestOnchainReceive,
141
151
  requestOnchainSend: () => requestOnchainSend,
152
+ restoreAssetSwapRepository: () => restoreAssetSwapRepository,
142
153
  restoreAssetSwaps: () => restoreAssetSwaps,
154
+ restoreOfferCoverage: () => restoreOfferCoverage,
143
155
  retireSettledOfferContracts: () => retireSettledOfferContracts,
144
156
  revealClaimPacket: () => revealClaimPacket,
145
157
  revealFieldsFromScript: () => revealFieldsFromScript,
@@ -283,10 +295,10 @@ function addressOutstanding(swaps, script) {
283
295
  }
284
296
  return true;
285
297
  }
286
- async function promoteOfferContract(manager, script) {
298
+ async function promoteOfferContract(manager, script, issued = Date.now()) {
287
299
  await serialize(script, async () => {
288
300
  await manager.setContractWatchState(script, "watched");
289
- issuedAt.set(script, Date.now());
301
+ issuedAt.set(script, issued);
290
302
  });
291
303
  }
292
304
  async function retireOfferContract(manager, swaps, script) {
@@ -492,7 +504,7 @@ function u64(name, value) {
492
504
  if (value < BigInt(0) || value >> BigInt(64) > BigInt(0)) {
493
505
  throw new Error(`${name} does not fit the offer wire format (u64)`);
494
506
  }
495
- const out = new Uint8Array(FIELDS.wantAmount.width);
507
+ const out = new Uint8Array(8);
496
508
  new DataView(out.buffer).setBigUint64(0, value, false);
497
509
  return out;
498
510
  }
@@ -627,10 +639,9 @@ function decodeExitDelay(value) {
627
639
  }
628
640
  var OFFER_CONTRACT_LABEL = "Arkade swap offer";
629
641
  var OFFER_CONTRACT_KIND = "asset-swap-offer";
630
- async function registerOfferContract(wallet, arkServerUrl, network, binding, serverPubkey, expectedPkScript) {
631
- const { program, args, keys } = swapProgramBinding(binding, serverPubkey);
632
- const contractManager = await wallet.getContractManager();
633
- const client = await import_sdk2.arkade.Arkade.connect({
642
+ async function registerOfferContract(wallet, arkServerUrl, network, binding, serverPubkey, expectedPkScript, opts = {}) {
643
+ const contractManager = opts.contractManager ?? await wallet.getContractManager();
644
+ const client = opts.client ?? await import_sdk2.arkade.Arkade.connect({
634
645
  arkade: new import_sdk2.RestArkProvider(arkServerUrl),
635
646
  indexer: new import_sdk2.RestIndexerProvider(arkServerUrl),
636
647
  identity: wallet.identity,
@@ -640,15 +651,66 @@ async function registerOfferContract(wallet, arkServerUrl, network, binding, ser
640
651
  network: (0, import_sdk2.getNetwork)(network),
641
652
  contractManager
642
653
  });
654
+ await contractManager.createContract(
655
+ offerContractParams(client, binding, serverPubkey, expectedPkScript)
656
+ );
657
+ await promoteOfferContract(contractManager, import_base2.hex.encode(expectedPkScript), opts.issued);
658
+ }
659
+ function offerContractParams(client, binding, serverPubkey, expectedPkScript) {
660
+ const { program, args, keys } = swapProgramBinding(binding, serverPubkey);
643
661
  const contract = new import_sdk2.arkade.ArkadeContract(client, program, args, keys);
644
662
  if (import_base2.hex.encode(contract.pkScript) !== import_base2.hex.encode(expectedPkScript)) {
645
663
  throw new Error("derived covenant does not match the offer's swapPkScript");
646
664
  }
647
- await contract.register({
665
+ return {
666
+ ...contract.toContractParams(),
648
667
  label: OFFER_CONTRACT_LABEL,
649
668
  metadata: { genericallySpendable: false, kind: OFFER_CONTRACT_KIND }
669
+ };
670
+ }
671
+ async function restoreOfferCoverage(wallet, arkServerUrl, swaps) {
672
+ const live = swaps.filter((s) => !RETIRABLE.includes(s.status));
673
+ if (live.length === 0) return;
674
+ const arkProvider = new import_sdk2.RestArkProvider(arkServerUrl);
675
+ const info = await arkProvider.getInfo();
676
+ const serverPubKey = import_base2.hex.decode((0, import_sdk2.toXOnlySignerHex)(info.signerPubkey));
677
+ const contractManager = await wallet.getContractManager();
678
+ const client = await import_sdk2.arkade.Arkade.connect({
679
+ arkade: arkProvider,
680
+ indexer: new import_sdk2.RestIndexerProvider(arkServerUrl),
681
+ identity: wallet.identity,
682
+ network: (0, import_sdk2.getNetwork)(info.network),
683
+ contractManager
650
684
  });
651
- await promoteOfferContract(contractManager, import_base2.hex.encode(expectedPkScript));
685
+ const done = /* @__PURE__ */ new Set();
686
+ const covenants = [];
687
+ for (const swap of live) {
688
+ if (done.has(swap.swapPkScript)) continue;
689
+ done.add(swap.swapPkScript);
690
+ try {
691
+ const { swapPkScript: _, ...binding } = decodeOffer(import_base2.hex.decode(swap.offerHex));
692
+ covenants.push({
693
+ script: swap.swapPkScript,
694
+ issued: swap.createdAt,
695
+ params: offerContractParams(
696
+ client,
697
+ binding,
698
+ serverPubKey,
699
+ import_base2.hex.decode(swap.swapPkScript)
700
+ )
701
+ });
702
+ } catch (err) {
703
+ console.warn(`[swap] could not restore coverage for ${swap.swapPkScript}`, err);
704
+ }
705
+ }
706
+ if (covenants.length === 0) return;
707
+ const paramsList = covenants.map((c) => c.params);
708
+ if (contractManager.createContracts) {
709
+ await contractManager.createContracts(paramsList);
710
+ } else {
711
+ for (const params of paramsList) await contractManager.createContract(params);
712
+ }
713
+ for (const c of covenants) await promoteOfferContract(contractManager, c.script, c.issued);
652
714
  }
653
715
  function serverExitDelay(delay) {
654
716
  if (typeof delay !== "bigint" || delay <= BigInt(0)) {
@@ -761,10 +823,170 @@ async function cancelOffer(wallet, arkServerUrl, offerHex, opts) {
761
823
  }
762
824
  return txid;
763
825
  }
826
+ var ASSET_CARRIER_SATS = BigInt(import_sdk2.ASSET_CARRIER_SATS);
827
+ async function fillOffer(wallet, arkServerUrl, offerHex, opts) {
828
+ const {
829
+ fund,
830
+ payoutScript,
831
+ fundingTxid,
832
+ swapAddress,
833
+ assetCarrierSats = ASSET_CARRIER_SATS,
834
+ emulator,
835
+ emulatorPubkey
836
+ } = opts;
837
+ const offer = decodeOffer(import_base2.hex.decode(offerHex));
838
+ const wantedAssetId = offer.wantAsset?.toString();
839
+ if (fund.length === 0) {
840
+ throw new Error("fillOffer needs coins to pay wantAmount with \u2014 `fund` is empty");
841
+ }
842
+ if (wantedAssetId !== void 0) {
843
+ const supplied = fund.reduce(
844
+ (sum, coin) => sum + amountOfAsset(coin.assets, wantedAssetId),
845
+ BigInt(0)
846
+ );
847
+ if (supplied < offer.wantAmount) {
848
+ throw new Error(
849
+ `fillOffer needs ${offer.wantAmount} of ${wantedAssetId} to pay the maker, but \`fund\` declares ${supplied} \u2014 pass coins carrying it, and declare their assets`
850
+ );
851
+ }
852
+ }
853
+ const contractManager = await wallet.getContractManager();
854
+ const client = await import_sdk2.arkade.Arkade.connect({
855
+ arkade: new import_sdk2.RestArkProvider(arkServerUrl),
856
+ indexer: new import_sdk2.RestIndexerProvider(arkServerUrl),
857
+ identity: wallet.identity,
858
+ contractManager,
859
+ // `fulfill` is a covenant path, so the emulator executes the arkade
860
+ // script and finalizes with arkd. Without it the spend is built and then
861
+ // refused at submission — there is no default to fall back on.
862
+ emulator: typeof emulator === "string" ? new import_sdk2.RestEmulatorProvider(emulator) : emulator,
863
+ // Only reached for the client's own emulatorKey, which this fill never
864
+ // derives against — the offer's own key is bound below. It still has to
865
+ // resolve, and on a network with no pinned key it throws without this.
866
+ ...emulatorPubkey ? { emulatorPubkey } : {}
867
+ });
868
+ const serverKey = swapAddress ? import_sdk2.ArkAddress.decode(swapAddress).serverPubKey : client.serverKey;
869
+ const { program, args, keys } = swapProgramBinding(offer, serverKey);
870
+ const rebuilt = new import_sdk2.arkade.ArkadeProgramScript(program, args, keys);
871
+ if (import_base2.hex.encode(rebuilt.pkScript) !== import_base2.hex.encode(offer.swapPkScript)) {
872
+ throw new Error(
873
+ "rebuilt covenant does not match the offer's swapPkScript \u2014 the server signing key has likely rotated since funding; pass swapAddress (the funded address) to pin the original key"
874
+ );
875
+ }
876
+ const contract = new import_sdk2.arkade.ArkadeContract(client, program, args, keys);
877
+ const [vtxos, takerAddress] = await Promise.all([contract.getUtxos(), wallet.getAddress()]);
878
+ if (!fundingTxid && vtxos.length > 1) {
879
+ throw new Error(
880
+ "multiple spendable deposits at the swap address \u2014 pass fundingTxid to select one"
881
+ );
882
+ }
883
+ const vtxo = fundingTxid ? vtxos.find((v) => v.txid === fundingTxid) : vtxos[0];
884
+ if (!vtxo) throw new Error("no spendable VTXO at the swap address");
885
+ if (offer.offerAsset) {
886
+ const offered = offer.offerAsset.toString();
887
+ const deposited = amountOfAsset(vtxo.assets, offered);
888
+ if (deposited <= BigInt(0)) {
889
+ throw new Error(
890
+ `the deposit at the swap address carries no ${offered}, which this offer sells \u2014 filling it would pay wantAmount for nothing. The indexer may be behind, or the offer may not be backed by what it advertises`
891
+ );
892
+ }
893
+ }
894
+ const payout = payoutScript ?? import_sdk2.ArkAddress.decode(takerAddress).pkScript;
895
+ const makerSats = wantedAssetId === void 0 ? offer.wantAmount : assetCarrierSats;
896
+ const fill = contract.functions.fulfill().from({ txid: vtxo.txid, vout: vtxo.vout, value: vtxo.value }).fund(fund).to(offer.makerPkScript, makerSats).change(payout);
897
+ const held = /* @__PURE__ */ new Map();
898
+ const hold = (vin, assets) => {
899
+ for (const a of assets ?? []) {
900
+ const amount = BigInt(a.amount);
901
+ if (amount <= BigInt(0)) continue;
902
+ held.set(a.assetId, [...held.get(a.assetId) ?? [], { vin, amount }]);
903
+ }
904
+ };
905
+ hold(0, vtxo.assets);
906
+ fund.forEach((coin, i) => hold(i + 1, coin.assets));
907
+ const outputsSum = makerSats;
908
+ const inputsSum = fund.reduce((s, c) => s + BigInt(c.value), BigInt(vtxo.value));
909
+ const hasPayoutOutput = inputsSum > outputsSum;
910
+ const emitWanted = () => {
911
+ if (wantedAssetId === void 0) return;
912
+ const supplying = held.get(wantedAssetId) ?? [];
913
+ const supplied = supplying.reduce((s, i) => s + i.amount, BigInt(0));
914
+ const outputs = [{ vout: 0, amount: offer.wantAmount }];
915
+ const surplus = supplied - offer.wantAmount;
916
+ if (surplus > BigInt(0)) {
917
+ if (!hasPayoutOutput) {
918
+ throw new Error(
919
+ `fillOffer has ${surplus} of ${wantedAssetId} to return but no payout output \u2014 fund with more sats than the maker's output takes`
920
+ );
921
+ }
922
+ outputs.push({ vout: 1, amount: surplus });
923
+ }
924
+ fill.withAsset({ assetId: wantedAssetId, inputs: supplying, outputs });
925
+ held.delete(wantedAssetId);
926
+ };
927
+ emitWanted();
928
+ for (const [assetId, inputs] of held) {
929
+ const amount = inputs.reduce((s, i) => s + i.amount, BigInt(0));
930
+ if (!hasPayoutOutput) {
931
+ throw new Error(
932
+ `fillOffer has ${amount} of ${assetId} to return but no payout output \u2014 fund with more sats than the maker's output takes`
933
+ );
934
+ }
935
+ fill.withAsset({ assetId, inputs, outputs: [{ vout: 1, amount }] });
936
+ }
937
+ const { txid } = await fill.send();
938
+ return txid;
939
+ }
940
+ var amountOfAsset = (assets, assetId) => {
941
+ let total = BigInt(0);
942
+ for (const a of assets ?? []) {
943
+ if (a.assetId === assetId) total += BigInt(a.amount);
944
+ }
945
+ return total;
946
+ };
764
947
 
765
948
  // src/markets.ts
766
949
  var import_solver_discovery = require("@arkade-os/solver-discovery");
767
950
  var import_sdk3 = require("@arkade-os/sdk");
951
+
952
+ // src/marketShape.ts
953
+ var MARKET_CORRIDORS = ["arkade", "bolt11", "bitcoin", "eip155"];
954
+ var LEGACY_CORRIDORS = {
955
+ arkade: "arkade",
956
+ lightning: "bolt11",
957
+ onchain: "bitcoin",
958
+ eip155: "eip155"
959
+ };
960
+ var assetOf = (market, side) => side === "base" ? market.base_asset : market.quote_asset;
961
+ var marketAssetId = (market, side) => {
962
+ const asset3 = assetOf(market, side);
963
+ if (typeof asset3?.caip19_id === "string") return asset3.caip19_id;
964
+ return typeof asset3?.id === "string" ? asset3.id : void 0;
965
+ };
966
+ var chainNamespaceOf = (id) => {
967
+ if (id === void 0) return void 0;
968
+ const slash = id.indexOf("/");
969
+ const colon = id.indexOf(":");
970
+ return slash === -1 || colon === -1 || colon > slash ? void 0 : id.slice(0, colon);
971
+ };
972
+ var marketCorridor = (market, side) => {
973
+ const namespace = chainNamespaceOf(marketAssetId(market, side));
974
+ if (namespace !== void 0) {
975
+ return MARKET_CORRIDORS.includes(namespace) ? namespace : "arkade";
976
+ }
977
+ const legacy = side === "base" ? market.base_corridor : market.quote_corridor;
978
+ return typeof legacy === "string" ? LEGACY_CORRIDORS[legacy] ?? "arkade" : "arkade";
979
+ };
980
+ var isRfqMarket = (market) => marketCorridor(market, "base") !== "arkade" || marketCorridor(market, "quote") !== "arkade";
981
+ var sideLabel = (market, side) => {
982
+ const ticker = assetOf(market, side)?.ticker;
983
+ const label = typeof ticker === "string" && ticker.length > 0 ? ticker : "?";
984
+ const corridor = marketCorridor(market, side);
985
+ return corridor === "arkade" ? label : `${corridor}:${label}`;
986
+ };
987
+ var marketPairLabel = (market) => `${sideLabel(market, "base")}/${sideLabel(market, "quote")}`;
988
+
989
+ // src/markets.ts
768
990
  var QUOTE_OPTIONS = { safetyBps: 0 };
769
991
  var makeCachedFeedFetch = (ttlMs = 3e4, fetchImpl = fetch) => {
770
992
  const cache = /* @__PURE__ */ new Map();
@@ -813,14 +1035,17 @@ var makeCachedFeedFetch = (ttlMs = 3e4, fetchImpl = fetch) => {
813
1035
  var MARKETS_CACHE_TTL_MS = 60 * 60 * 1e3;
814
1036
  var isMarketShaped = (m) => {
815
1037
  const market = m;
816
- return typeof market?.pair === "string" && typeof market.base_asset?.id === "string" && typeof market.quote_asset?.id === "string" && typeof market.quote_asset.decimals === "number";
1038
+ if (!market) return false;
1039
+ return typeof market.base_asset?.id === "string" && typeof market.quote_asset?.id === "string" && typeof market.quote_asset.decimals === "number";
817
1040
  };
818
1041
  var readMarketsCache = async (repository, network, registry) => {
819
1042
  try {
820
1043
  const entry = await repository.getCachedMarkets(network, registry);
821
1044
  if (!Array.isArray(entry?.markets) || typeof entry?.fetchedAt !== "number")
822
1045
  return void 0;
823
- return entry.markets.every(isMarketShaped) ? entry : void 0;
1046
+ const markets = entry.markets.filter(isMarketShaped);
1047
+ if (entry.markets.length > 0 && markets.length === 0) return void 0;
1048
+ return { ...entry, markets };
824
1049
  } catch {
825
1050
  return void 0;
826
1051
  }
@@ -861,10 +1086,33 @@ var discoverMarkets = async (options) => {
861
1086
  };
862
1087
  var findMarket = (markets, fromId, toId) => {
863
1088
  if (fromId === toId) return void 0;
864
- const givingBase = (0, import_solver_discovery.bestMarket)(markets, { baseId: fromId, quoteId: toId, wantSide: "quote" });
1089
+ const resolveMarketId = (id) => {
1090
+ for (const market of markets) {
1091
+ for (const side of ["base", "quote"]) {
1092
+ const asset3 = side === "base" ? market.base_asset : market.quote_asset;
1093
+ if (asset3.id === id) return id;
1094
+ const canonical = marketAssetId(market, side) ?? "";
1095
+ const matchesBtc = id === BTC_ASSET_ID && /^arkade:[^/]+\/slip44:(?:0|1)$/.test(canonical);
1096
+ const matchesAsset = /^[0-9a-f]{68}$/.test(id) && canonical.endsWith(`/asset:${id}`);
1097
+ if ((matchesBtc || matchesAsset) && typeof asset3.id === "string") return asset3.id;
1098
+ }
1099
+ }
1100
+ return id;
1101
+ };
1102
+ const resolvedFrom = resolveMarketId(fromId);
1103
+ const resolvedTo = resolveMarketId(toId);
1104
+ const givingBase = (0, import_solver_discovery.bestMarket)(markets, {
1105
+ baseId: resolvedFrom,
1106
+ quoteId: resolvedTo,
1107
+ wantSide: "quote"
1108
+ });
865
1109
  if (givingBase) return { market: givingBase, give: "base" };
866
1110
  return {
867
- market: (0, import_solver_discovery.bestMarket)(markets, { baseId: toId, quoteId: fromId, wantSide: "base" }),
1111
+ market: (0, import_solver_discovery.bestMarket)(markets, {
1112
+ baseId: resolvedTo,
1113
+ quoteId: resolvedFrom,
1114
+ wantSide: "base"
1115
+ }),
868
1116
  give: "quote"
869
1117
  };
870
1118
  };
@@ -1035,8 +1283,316 @@ var IndexedDbAssetSwapRepository = class {
1035
1283
  }
1036
1284
  };
1037
1285
 
1286
+ // src/restore.ts
1287
+ var import_base3 = require("@scure/base");
1288
+ var import_sdk5 = require("@arkade-os/sdk");
1289
+ var TXS_PER_REQUEST = 50;
1290
+ async function fetchParsedTxs(indexer, txids) {
1291
+ const parsedByTxid = /* @__PURE__ */ new Map();
1292
+ if (txids.length === 0) return parsedByTxid;
1293
+ const chunks = [];
1294
+ for (let i = 0; i < txids.length; i += TXS_PER_REQUEST) {
1295
+ chunks.push(txids.slice(i, i + TXS_PER_REQUEST));
1296
+ }
1297
+ const chunkResults = await Promise.allSettled(
1298
+ chunks.map(async (ids) => (await indexer.getVirtualTxs(ids)).txs)
1299
+ );
1300
+ for (const result of chunkResults) {
1301
+ if (result.status !== "fulfilled") continue;
1302
+ for (const psbt of result.value) {
1303
+ try {
1304
+ const parsed = import_sdk5.Transaction.fromPSBT(import_base3.base64.decode(psbt));
1305
+ parsedByTxid.set(parsed.id, parsed);
1306
+ } catch {
1307
+ }
1308
+ }
1309
+ }
1310
+ return parsedByTxid;
1311
+ }
1312
+ var unscannedSwapCandidates = (txs, existingIds, scanned) => txs.filter(
1313
+ (tx) => tx.type === "sent" && tx.redeemTxid && !existingIds.has(tx.redeemTxid) && !scanned.has(tx.redeemTxid)
1314
+ );
1315
+ function classifySpend(offer, serverPubkey, spendTx, deposit) {
1316
+ let leaves;
1317
+ try {
1318
+ const script = offerVtxoScript(offer, serverPubkey);
1319
+ if (import_base3.hex.encode(script.pkScript) !== import_base3.hex.encode(offer.swapPkScript)) return "indeterminate";
1320
+ leaves = {
1321
+ // both routes that hand the deposit back; `exit` is absent on an
1322
+ // offer that carries no exit closure, and drops out here
1323
+ returned: ["cancel", "exit"].map((name) => script.functionByName(name)?.leafScript).filter((leaf) => leaf !== void 0),
1324
+ fulfill: script.functionByName("fulfill")?.leafScript
1325
+ };
1326
+ } catch {
1327
+ return "indeterminate";
1328
+ }
1329
+ for (let i = 0; i < spendTx.inputsLength; i++) {
1330
+ const input = spendTx.getInput(i);
1331
+ if (!input.txid || input.index !== deposit.vout) continue;
1332
+ if (import_base3.hex.encode(input.txid) !== deposit.txid) continue;
1333
+ for (const leaf of input.tapLeafScript ?? []) {
1334
+ const spent = import_base3.hex.encode((0, import_sdk5.scriptFromTapLeafScript)(leaf));
1335
+ if (leaves.returned.some((back) => spent === import_base3.hex.encode(back))) return "cancelled";
1336
+ if (leaves.fulfill && spent === import_base3.hex.encode(leaves.fulfill)) return "fulfilled";
1337
+ }
1338
+ }
1339
+ return "indeterminate";
1340
+ }
1341
+ var spendTxidsOf = (vtxo) => [vtxo.spentBy, vtxo.arkTxId].filter((id) => Boolean(id));
1342
+ function classifyDepositSpend(offer, serverPubkey, spendTxs, deposit) {
1343
+ for (const tx of spendTxs) {
1344
+ const kind = classifySpend(offer, serverPubkey, tx, deposit);
1345
+ if (kind !== "indeterminate") return kind;
1346
+ }
1347
+ return "indeterminate";
1348
+ }
1349
+ async function restoreAssetSwaps(indexer, txs, existingIds, opts) {
1350
+ const { serverPubkey, scanned = /* @__PURE__ */ new Set(), reopen = [] } = opts;
1351
+ const reopened = [];
1352
+ for (const swap of reopen) {
1353
+ try {
1354
+ reopened.push({
1355
+ fundingTx: { type: "sent", redeemTxid: swap.fundingTxid },
1356
+ offer: decodeOffer(import_base3.hex.decode(swap.offerHex)),
1357
+ offerHex: swap.offerHex,
1358
+ existing: swap
1359
+ });
1360
+ } catch {
1361
+ }
1362
+ }
1363
+ const reopenedTxids = new Set(reopened.map((f) => f.fundingTx.redeemTxid));
1364
+ const candidates = unscannedSwapCandidates(txs, existingIds, scanned).filter(
1365
+ (tx) => !reopenedTxids.has(tx.redeemTxid)
1366
+ );
1367
+ if (candidates.length === 0 && reopened.length === 0) {
1368
+ return { restored: [], scannedTxids: [] };
1369
+ }
1370
+ const byTxid = new Map(candidates.map((tx) => [tx.redeemTxid, tx]));
1371
+ const parsedByTxid = await fetchParsedTxs(
1372
+ indexer,
1373
+ candidates.map((tx) => tx.redeemTxid)
1374
+ );
1375
+ const fetchedTxids = [];
1376
+ const found = [...reopened];
1377
+ for (const [txid, parsed] of parsedByTxid) {
1378
+ const fundingTx = byTxid.get(txid);
1379
+ if (!fundingTx) continue;
1380
+ fetchedTxids.push(txid);
1381
+ try {
1382
+ const packet = import_sdk5.Extension.fromTx(parsed).getPacketByType(OFFER_PACKET_TYPE);
1383
+ if (!packet) continue;
1384
+ const payload = packet.serialize();
1385
+ found.push({
1386
+ fundingTx,
1387
+ offer: decodeOffer(payload),
1388
+ offerHex: import_base3.hex.encode(payload)
1389
+ });
1390
+ } catch {
1391
+ }
1392
+ }
1393
+ if (found.length === 0) return { restored: [], scannedTxids: fetchedTxids };
1394
+ const scripts = [...new Set(found.map((f) => import_base3.hex.encode(f.offer.swapPkScript)))];
1395
+ const { vtxos } = await indexer.getVtxos({ scripts });
1396
+ const vtxoByScriptAndTxid = new Map(vtxos.map((v) => [`${v.script}:${v.txid}`, v]));
1397
+ const txByAnyId = /* @__PURE__ */ new Map();
1398
+ for (const tx of txs) {
1399
+ for (const id of [tx.boardingTxid, tx.redeemTxid, tx.roundTxid]) {
1400
+ if (id) txByAnyId.set(id, tx);
1401
+ }
1402
+ }
1403
+ const spendTxids = /* @__PURE__ */ new Set();
1404
+ for (const { fundingTx, offer } of found) {
1405
+ const vtxo = vtxoByScriptAndTxid.get(
1406
+ `${import_base3.hex.encode(offer.swapPkScript)}:${fundingTx.redeemTxid}`
1407
+ );
1408
+ if (vtxo?.virtualStatus.state !== "spent") continue;
1409
+ for (const txid of spendTxidsOf(vtxo)) spendTxids.add(txid);
1410
+ }
1411
+ const spendTxByTxid = await fetchParsedTxs(indexer, [...spendTxids]);
1412
+ const restored = [];
1413
+ const unresolved = /* @__PURE__ */ new Set();
1414
+ for (const { fundingTx, offer, offerHex, existing } of found) {
1415
+ const swapPkScript = import_base3.hex.encode(offer.swapPkScript);
1416
+ const vtxo = vtxoByScriptAndTxid.get(`${swapPkScript}:${fundingTx.redeemTxid}`);
1417
+ if (!vtxo) {
1418
+ unresolved.add(fundingTx.redeemTxid);
1419
+ continue;
1420
+ }
1421
+ const depositRider = vtxo.assets?.length === 1 && vtxo.assets[0].amount > BigInt(0) ? vtxo.assets[0] : void 0;
1422
+ const fromAsset = offer.offerAsset?.toString() ?? depositRider?.assetId ?? BTC_ASSET_ID;
1423
+ const toAsset = offer.wantAsset?.toString() ?? BTC_ASSET_ID;
1424
+ const depositAmount = fromAsset === BTC_ASSET_ID ? BigInt(vtxo.value) : vtxo.assets?.find((a) => a.assetId === fromAsset)?.amount;
1425
+ if (depositAmount === void 0) {
1426
+ unresolved.add(fundingTx.redeemTxid);
1427
+ continue;
1428
+ }
1429
+ const fromAmount = depositAmount.toString();
1430
+ const state = vtxo.virtualStatus.state;
1431
+ const spentTxid = state === "spent" ? vtxo.arkTxId || vtxo.spentBy : void 0;
1432
+ let status = "pending";
1433
+ if (state === "swept") status = "recoverable";
1434
+ else if (state === "spent") {
1435
+ const spendTxs = spendTxidsOf(vtxo).map((id) => spendTxByTxid.get(id)).filter((tx) => tx !== void 0);
1436
+ const kind = classifyDepositSpend(offer, serverPubkey, spendTxs, {
1437
+ txid: vtxo.txid,
1438
+ vout: vtxo.vout
1439
+ });
1440
+ if (kind === "indeterminate") {
1441
+ unresolved.add(fundingTx.redeemTxid);
1442
+ continue;
1443
+ }
1444
+ status = kind;
1445
+ }
1446
+ const completion = status === "fulfilled" && spentTxid && txByAnyId.get(spentTxid)?.createdAt ? { completedAt: txByAnyId.get(spentTxid).createdAt * 1e3 } : {};
1447
+ if (existing) {
1448
+ if (status === "pending") continue;
1449
+ restored.push({ ...existing, status, spentTxid, ...completion });
1450
+ continue;
1451
+ }
1452
+ restored.push({
1453
+ id: fundingTx.redeemTxid,
1454
+ fromAsset,
1455
+ toAsset,
1456
+ fromAmount,
1457
+ toAmount: offer.wantAmount.toString(),
1458
+ // ponytail(arkade-os/ts-sdk#680): empty address makes cancel fall back
1459
+ // to the current server key; store the funded address if server-key
1460
+ // rotations become real (cancelOffer now at least diagnoses the
1461
+ // mismatch instead of reporting a missing VTXO)
1462
+ swapAddress: "",
1463
+ swapPkScript,
1464
+ offerHex,
1465
+ fundingTxid: fundingTx.redeemTxid,
1466
+ spentTxid,
1467
+ status,
1468
+ createdAt: fundingTx.createdAt ? fundingTx.createdAt * 1e3 : vtxo.createdAt.getTime(),
1469
+ ...completion
1470
+ });
1471
+ }
1472
+ return { restored, scannedTxids: fetchedTxids.filter((id) => !unresolved.has(id)) };
1473
+ }
1474
+
1475
+ // src/restoreRepository.ts
1476
+ var isOpen = (swap) => swap.status === "pending" || swap.status === "cancelling";
1477
+ var isOfferSwap = (swap) => typeof swap.offerHex === "string";
1478
+ var restoreCoverage = async (wallet, arkServerUrl, swaps) => {
1479
+ const offers = swaps.filter(isOfferSwap);
1480
+ if (offers.length > 0) await restoreOfferCoverage(wallet, arkServerUrl, offers);
1481
+ };
1482
+ var aborted = (swaps) => ({
1483
+ swaps,
1484
+ changes: [],
1485
+ scannedTxids: [],
1486
+ aborted: true
1487
+ });
1488
+ async function restoreAssetSwapRepository(opts) {
1489
+ const { wallet, arkServerUrl, indexer, repository, txs, serverPubkey, prepareNew, signal } = opts;
1490
+ const [existing, scanned] = await Promise.all([
1491
+ getAssetSwapsOrThrow(repository),
1492
+ repository.getScannedTxids()
1493
+ ]);
1494
+ if (signal?.aborted) return aborted(existing);
1495
+ let scan;
1496
+ try {
1497
+ scan = await restoreAssetSwaps(indexer, txs, new Set(existing.map((swap) => swap.id)), {
1498
+ serverPubkey,
1499
+ scanned,
1500
+ reopen: existing.filter((swap) => isOpen(swap) && isOfferSwap(swap))
1501
+ });
1502
+ } catch (scanError) {
1503
+ if (!signal?.aborted) {
1504
+ try {
1505
+ await restoreCoverage(wallet, arkServerUrl, existing);
1506
+ } catch (coverageError2) {
1507
+ throw new AggregateError(
1508
+ [scanError, coverageError2],
1509
+ "asset-swap scan and covenant coverage restore both failed"
1510
+ );
1511
+ }
1512
+ }
1513
+ throw scanError;
1514
+ }
1515
+ if (signal?.aborted) return aborted(existing);
1516
+ const before = new Map(existing.map((swap) => [swap.id, swap]));
1517
+ const changes = [];
1518
+ const cancelledAfterCommit = async (scannedTxids = []) => ({
1519
+ swaps: await getAssetSwapsOrThrow(repository),
1520
+ changes,
1521
+ scannedTxids,
1522
+ aborted: true
1523
+ });
1524
+ for (const restored of scan.restored) {
1525
+ if (signal?.aborted) return cancelledAfterCommit();
1526
+ const previous = before.get(restored.id);
1527
+ const current = previous !== void 0 || !prepareNew ? restored : await prepareNew(restored);
1528
+ if (current.id !== restored.id) {
1529
+ throw new Error("prepareNew must not change an asset swap id");
1530
+ }
1531
+ if (signal?.aborted) return cancelledAfterCommit();
1532
+ await repository.saveSwap(current);
1533
+ changes.push(previous ? { previous, current } : { current });
1534
+ if (signal?.aborted) return cancelledAfterCommit();
1535
+ }
1536
+ if (signal?.aborted) return cancelledAfterCommit();
1537
+ if (scan.scannedTxids.length > 0) {
1538
+ await repository.markTxidsScanned(scan.scannedTxids);
1539
+ }
1540
+ const swaps = await getAssetSwapsOrThrow(repository);
1541
+ if (signal?.aborted) return cancelledAfterCommit(scan.scannedTxids);
1542
+ let coverageError;
1543
+ try {
1544
+ await restoreCoverage(wallet, arkServerUrl, swaps);
1545
+ } catch (error) {
1546
+ coverageError = error;
1547
+ }
1548
+ return {
1549
+ swaps,
1550
+ changes,
1551
+ scannedTxids: scan.scannedTxids,
1552
+ aborted: false,
1553
+ ...coverageError === void 0 ? {} : { coverageError }
1554
+ };
1555
+ }
1556
+
1557
+ // src/registerRestore.ts
1558
+ var import_sdk6 = require("@arkade-os/sdk");
1559
+ var toRestoreTx = (tx) => ({
1560
+ type: tx.type.toLowerCase(),
1561
+ redeemTxid: tx.key.arkTxid,
1562
+ boardingTxid: tx.key.boardingTxid,
1563
+ roundTxid: tx.key.commitmentTxid,
1564
+ createdAt: Math.floor(tx.createdAt / 1e3)
1565
+ });
1566
+ function registerAssetSwapRestore(wallet, options) {
1567
+ return (0, import_sdk6.registerWalletRestoreHook)(wallet, {
1568
+ id: "arkade-os:asset-swap",
1569
+ restore: async (restoredWallet) => {
1570
+ const dependencies = restoredWallet;
1571
+ const indexer = options.indexer ?? dependencies.indexerProvider;
1572
+ const serverPubkey = options.serverPubkey ?? dependencies.arkServerPublicKey;
1573
+ if (!indexer) {
1574
+ throw new Error("asset-swap restore requires an indexer");
1575
+ }
1576
+ if (!serverPubkey) {
1577
+ throw new Error("asset-swap restore requires an Ark server public key");
1578
+ }
1579
+ const history = await restoredWallet.getTransactionHistory();
1580
+ const result = await restoreAssetSwapRepository({
1581
+ wallet: restoredWallet,
1582
+ arkServerUrl: options.arkServerUrl,
1583
+ indexer,
1584
+ repository: options.repository,
1585
+ txs: history.map(toRestoreTx),
1586
+ serverPubkey,
1587
+ prepareNew: options.prepareNew
1588
+ });
1589
+ await options.onResult?.(result);
1590
+ }
1591
+ });
1592
+ }
1593
+
1038
1594
  // src/rfqCorridors.ts
1039
- var import_base4 = require("@scure/base");
1595
+ var import_base5 = require("@scure/base");
1040
1596
 
1041
1597
  // src/rfqCorridor.ts
1042
1598
  var RfqCorridorRegistry = class {
@@ -1155,7 +1711,7 @@ var rfqClaimSecretOf = (record) => {
1155
1711
  };
1156
1712
 
1157
1713
  // src/onchainHtlc.ts
1158
- var import_base3 = require("@scure/base");
1714
+ var import_base4 = require("@scure/base");
1159
1715
  var import_sha22 = require("@noble/hashes/sha2.js");
1160
1716
  var import_legacy = require("@noble/hashes/legacy.js");
1161
1717
  var btc = __toESM(require("@scure/btc-signer"), 1);
@@ -1166,8 +1722,8 @@ var LOCKTIME_THRESHOLD = 5e8;
1166
1722
  var ONCHAIN_SECONDS_PER_BLOCK = 600;
1167
1723
  var ONCHAIN_DUST_SATS = BigInt(330);
1168
1724
  var newPreimage = () => crypto.getRandomValues(new Uint8Array(32));
1169
- var paymentHashOf = (preimage) => import_base3.hex.encode((0, import_sha22.sha256)(preimage));
1170
- var h160FromPaymentHash = (paymentHash) => (0, import_legacy.ripemd160)(import_base3.hex.decode(paymentHash));
1725
+ var paymentHashOf = (preimage) => import_base4.hex.encode((0, import_sha22.sha256)(preimage));
1726
+ var h160FromPaymentHash = (paymentHash) => (0, import_legacy.ripemd160)(import_base4.hex.decode(paymentHash));
1171
1727
  var L1_NETWORKS = {
1172
1728
  bitcoin: btc.NETWORK,
1173
1729
  testnet: btc.TEST_NETWORK,
@@ -1219,7 +1775,7 @@ function onchainHtlcScript(params, network) {
1219
1775
  );
1220
1776
  const controlBlockFor = (leaf) => {
1221
1777
  for (const [block, script] of payment.tapLeafScript ?? []) {
1222
- if (script.length - 1 === leaf.length && import_base3.hex.encode(script.subarray(0, leaf.length)) === import_base3.hex.encode(leaf)) {
1778
+ if (script.length - 1 === leaf.length && import_base4.hex.encode(script.subarray(0, leaf.length)) === import_base4.hex.encode(leaf)) {
1223
1779
  return btc.TaprootControlBlock.encode(block);
1224
1780
  }
1225
1781
  }
@@ -1312,13 +1868,13 @@ var buildHtlcRefund = (input) => buildLeafSpend({
1312
1868
  function extractPreimage(txHex, paymentHash) {
1313
1869
  let raw;
1314
1870
  try {
1315
- raw = btc.RawTx.decode(import_base3.hex.decode(txHex));
1871
+ raw = btc.RawTx.decode(import_base4.hex.decode(txHex));
1316
1872
  } catch {
1317
1873
  return null;
1318
1874
  }
1319
1875
  for (const stack of raw.witnesses ?? []) {
1320
1876
  for (const item of stack) {
1321
- if (item.length === 32 && import_base3.hex.encode((0, import_sha22.sha256)(item)) === paymentHash) return item;
1877
+ if (item.length === 32 && import_base4.hex.encode((0, import_sha22.sha256)(item)) === paymentHash) return item;
1322
1878
  }
1323
1879
  }
1324
1880
  return null;
@@ -1363,7 +1919,7 @@ async function classifyOnchainHtlc(chain, input) {
1363
1919
  );
1364
1920
  if (!spend) return { phase: "unfunded" };
1365
1921
  const preimage = extractPreimage(spend.txHex, input.htlc.paymentHash);
1366
- const txid = btc.Transaction.fromRaw(import_base3.hex.decode(spend.txHex), {
1922
+ const txid = btc.Transaction.fromRaw(import_base4.hex.decode(spend.txHex), {
1367
1923
  allowUnknownInputs: true,
1368
1924
  allowUnknownOutputs: true
1369
1925
  }).id;
@@ -1414,13 +1970,13 @@ var LightningReceiveCorridor = {
1414
1970
  };
1415
1971
  function onchainSendProfile(result) {
1416
1972
  return {
1417
- claimKey: import_base4.hex.encode(result.htlcParams.claimKey),
1418
- refundKey: import_base4.hex.encode(result.htlcParams.refundKey),
1973
+ claimKey: import_base5.hex.encode(result.htlcParams.claimKey),
1974
+ refundKey: import_base5.hex.encode(result.htlcParams.refundKey),
1419
1975
  htlcLocktime: result.htlcParams.refundLocktime,
1420
1976
  network: result.l1Network,
1421
1977
  htlcAddress: result.htlc.address,
1422
1978
  minConfirmations: result.minConfirmations,
1423
- payoutPkScript: import_base4.hex.encode(result.payoutPkScript)
1979
+ payoutPkScript: import_base5.hex.encode(result.payoutPkScript)
1424
1980
  };
1425
1981
  }
1426
1982
  var OnchainSendCorridor = {
@@ -1454,8 +2010,8 @@ var OnchainSendCorridor = {
1454
2010
  // unlocks both legs, but only one of the two hashes of it
1455
2011
  // is recoverable here.
1456
2012
  paymentHash,
1457
- claimKey: import_base4.hex.decode(profile.claimKey),
1458
- refundKey: import_base4.hex.decode(profile.refundKey),
2013
+ claimKey: import_base5.hex.decode(profile.claimKey),
2014
+ refundKey: import_base5.hex.decode(profile.refundKey),
1459
2015
  refundLocktime: profile.htlcLocktime
1460
2016
  },
1461
2017
  profile.network
@@ -1471,7 +2027,7 @@ var OnchainSendCorridor = {
1471
2027
  minConfirmations: profile.minConfirmations,
1472
2028
  // Optional here, required at the write: throwing on an older
1473
2029
  // record would strand the refund it is still owed.
1474
- ...profile.payoutPkScript ? { payoutPkScript: import_base4.hex.decode(profile.payoutPkScript) } : {},
2030
+ ...profile.payoutPkScript ? { payoutPkScript: import_base5.hex.decode(profile.payoutPkScript) } : {},
1475
2031
  ...profile.funding ? { funding: profile.funding } : {},
1476
2032
  ...profile.claimTxid ? { claimTxid: profile.claimTxid } : {}
1477
2033
  };
@@ -1489,8 +2045,8 @@ rfqCorridorHandlers.register(LightningReceiveCorridor);
1489
2045
  rfqCorridorHandlers.register(OnchainSendCorridor);
1490
2046
 
1491
2047
  // src/rfqRecord.ts
1492
- var import_sdk5 = require("@arkade-os/sdk");
1493
- var import_base5 = require("@scure/base");
2048
+ var import_sdk7 = require("@arkade-os/sdk");
2049
+ var import_base6 = require("@scure/base");
1494
2050
 
1495
2051
  // src/rfqSwapState.ts
1496
2052
  var RFQ_SWAP_TERMINAL_STATES = ["settled", "refunded", "failed"];
@@ -1514,8 +2070,8 @@ function assertSameSwap(origin, swap) {
1514
2070
  `rfq swap record is a ${origin.kind} origin paired with a ${swap.kind} swap`
1515
2071
  );
1516
2072
  }
1517
- const funded = import_base5.hex.encode(import_sdk5.ArkAddress.decode(origin.lockupAddress).pkScript);
1518
- const watched = import_base5.hex.encode(swap.lockupPkScript);
2073
+ const funded = import_base6.hex.encode(import_sdk7.ArkAddress.decode(origin.lockupAddress).pkScript);
2074
+ const watched = import_base6.hex.encode(swap.lockupPkScript);
1519
2075
  if (funded !== watched) {
1520
2076
  throw new Error(
1521
2077
  `rfq swap record's lockup address holds ${funded}, but the swap watches ${watched} \u2014 these are not the same swap`
@@ -1559,11 +2115,11 @@ function rfqSwapOriginOf(record) {
1559
2115
  };
1560
2116
  }
1561
2117
  function lockupScript(params, lockupAddress) {
1562
- const script = import_sdk5.VHTLCV2ContractHandler.createScript(params);
1563
- const funded = import_sdk5.ArkAddress.decode(lockupAddress).pkScript;
1564
- if (import_base5.hex.encode(funded) !== import_base5.hex.encode(script.pkScript)) {
2118
+ const script = import_sdk7.VHTLCV2ContractHandler.createScript(params);
2119
+ const funded = import_sdk7.ArkAddress.decode(lockupAddress).pkScript;
2120
+ if (import_base6.hex.encode(funded) !== import_base6.hex.encode(script.pkScript)) {
1565
2121
  throw new Error(
1566
- `rfq swap covenant params derive ${import_base5.hex.encode(script.pkScript)}, but the record's lockup address holds ${import_base5.hex.encode(funded)} \u2014 these params are not this swap's`
2122
+ `rfq swap covenant params derive ${import_base6.hex.encode(script.pkScript)}, but the record's lockup address holds ${import_base6.hex.encode(funded)} \u2014 these params are not this swap's`
1567
2123
  );
1568
2124
  }
1569
2125
  return script;
@@ -1597,177 +2153,9 @@ function shouldRetainRfqSwap(record, now) {
1597
2153
  return now - record.updatedAt < RFQ_SWAP_RETENTION_SECONDS;
1598
2154
  }
1599
2155
 
1600
- // src/restore.ts
1601
- var import_base6 = require("@scure/base");
1602
- var import_sdk6 = require("@arkade-os/sdk");
1603
- var TXS_PER_REQUEST = 50;
1604
- async function fetchParsedTxs(indexer, txids) {
1605
- const parsedByTxid = /* @__PURE__ */ new Map();
1606
- if (txids.length === 0) return parsedByTxid;
1607
- const chunks = [];
1608
- for (let i = 0; i < txids.length; i += TXS_PER_REQUEST) {
1609
- chunks.push(txids.slice(i, i + TXS_PER_REQUEST));
1610
- }
1611
- const chunkResults = await Promise.allSettled(
1612
- chunks.map(async (ids) => (await indexer.getVirtualTxs(ids)).txs)
1613
- );
1614
- for (const result of chunkResults) {
1615
- if (result.status !== "fulfilled") continue;
1616
- for (const psbt of result.value) {
1617
- try {
1618
- const parsed = import_sdk6.Transaction.fromPSBT(import_base6.base64.decode(psbt));
1619
- parsedByTxid.set(parsed.id, parsed);
1620
- } catch {
1621
- }
1622
- }
1623
- }
1624
- return parsedByTxid;
1625
- }
1626
- var unscannedSwapCandidates = (txs, existingIds, scanned) => txs.filter(
1627
- (tx) => tx.type === "sent" && tx.redeemTxid && !existingIds.has(tx.redeemTxid) && !scanned.has(tx.redeemTxid)
1628
- );
1629
- function classifySpend(offer, serverPubkey, spendTx, deposit) {
1630
- let leaves;
1631
- try {
1632
- const script = offerVtxoScript(offer, serverPubkey);
1633
- if (import_base6.hex.encode(script.pkScript) !== import_base6.hex.encode(offer.swapPkScript)) return "indeterminate";
1634
- leaves = {
1635
- // both routes that hand the deposit back; `exit` is absent on an
1636
- // offer that carries no exit closure, and drops out here
1637
- returned: ["cancel", "exit"].map((name) => script.functionByName(name)?.leafScript).filter((leaf) => leaf !== void 0),
1638
- fulfill: script.functionByName("fulfill")?.leafScript
1639
- };
1640
- } catch {
1641
- return "indeterminate";
1642
- }
1643
- for (let i = 0; i < spendTx.inputsLength; i++) {
1644
- const input = spendTx.getInput(i);
1645
- if (!input.txid || input.index !== deposit.vout) continue;
1646
- if (import_base6.hex.encode(input.txid) !== deposit.txid) continue;
1647
- for (const leaf of input.tapLeafScript ?? []) {
1648
- const spent = import_base6.hex.encode((0, import_sdk6.scriptFromTapLeafScript)(leaf));
1649
- if (leaves.returned.some((back) => spent === import_base6.hex.encode(back))) return "cancelled";
1650
- if (leaves.fulfill && spent === import_base6.hex.encode(leaves.fulfill)) return "fulfilled";
1651
- }
1652
- }
1653
- return "indeterminate";
1654
- }
1655
- var spendTxidsOf = (vtxo) => [vtxo.spentBy, vtxo.arkTxId].filter((id) => Boolean(id));
1656
- function classifyDepositSpend(offer, serverPubkey, spendTxs, deposit) {
1657
- for (const tx of spendTxs) {
1658
- const kind = classifySpend(offer, serverPubkey, tx, deposit);
1659
- if (kind !== "indeterminate") return kind;
1660
- }
1661
- return "indeterminate";
1662
- }
1663
- async function restoreAssetSwaps(indexer, txs, existingIds, opts) {
1664
- const { serverPubkey, scanned = /* @__PURE__ */ new Set() } = opts;
1665
- const candidates = unscannedSwapCandidates(txs, existingIds, scanned);
1666
- if (candidates.length === 0) return { restored: [], scannedTxids: [] };
1667
- const byTxid = new Map(candidates.map((tx) => [tx.redeemTxid, tx]));
1668
- const parsedByTxid = await fetchParsedTxs(
1669
- indexer,
1670
- candidates.map((tx) => tx.redeemTxid)
1671
- );
1672
- const fetchedTxids = [];
1673
- const found = [];
1674
- for (const [txid, parsed] of parsedByTxid) {
1675
- const fundingTx = byTxid.get(txid);
1676
- if (!fundingTx) continue;
1677
- fetchedTxids.push(txid);
1678
- try {
1679
- const packet = import_sdk6.Extension.fromTx(parsed).getPacketByType(OFFER_PACKET_TYPE);
1680
- if (!packet) continue;
1681
- const payload = packet.serialize();
1682
- found.push({
1683
- fundingTx,
1684
- offer: decodeOffer(payload),
1685
- offerHex: import_base6.hex.encode(payload)
1686
- });
1687
- } catch {
1688
- }
1689
- }
1690
- if (found.length === 0) return { restored: [], scannedTxids: fetchedTxids };
1691
- const scripts = [...new Set(found.map((f) => import_base6.hex.encode(f.offer.swapPkScript)))];
1692
- const { vtxos } = await indexer.getVtxos({ scripts });
1693
- const vtxoByScriptAndTxid = new Map(vtxos.map((v) => [`${v.script}:${v.txid}`, v]));
1694
- const txByAnyId = /* @__PURE__ */ new Map();
1695
- for (const tx of txs) {
1696
- for (const id of [tx.boardingTxid, tx.redeemTxid, tx.roundTxid]) {
1697
- if (id) txByAnyId.set(id, tx);
1698
- }
1699
- }
1700
- const spendTxids = /* @__PURE__ */ new Set();
1701
- for (const { fundingTx, offer } of found) {
1702
- const vtxo = vtxoByScriptAndTxid.get(
1703
- `${import_base6.hex.encode(offer.swapPkScript)}:${fundingTx.redeemTxid}`
1704
- );
1705
- if (vtxo?.virtualStatus.state !== "spent") continue;
1706
- for (const txid of spendTxidsOf(vtxo)) spendTxids.add(txid);
1707
- }
1708
- const spendTxByTxid = await fetchParsedTxs(indexer, [...spendTxids]);
1709
- const restored = [];
1710
- const unresolved = /* @__PURE__ */ new Set();
1711
- for (const { fundingTx, offer, offerHex } of found) {
1712
- const swapPkScript = import_base6.hex.encode(offer.swapPkScript);
1713
- const vtxo = vtxoByScriptAndTxid.get(`${swapPkScript}:${fundingTx.redeemTxid}`);
1714
- if (!vtxo) {
1715
- unresolved.add(fundingTx.redeemTxid);
1716
- continue;
1717
- }
1718
- const depositRider = vtxo.assets?.length === 1 && vtxo.assets[0].amount > BigInt(0) ? vtxo.assets[0] : void 0;
1719
- const fromAsset = offer.offerAsset?.toString() ?? depositRider?.assetId ?? BTC_ASSET_ID;
1720
- const toAsset = offer.wantAsset?.toString() ?? BTC_ASSET_ID;
1721
- const depositAmount = fromAsset === BTC_ASSET_ID ? BigInt(vtxo.value) : vtxo.assets?.find((a) => a.assetId === fromAsset)?.amount;
1722
- if (depositAmount === void 0) {
1723
- unresolved.add(fundingTx.redeemTxid);
1724
- continue;
1725
- }
1726
- const fromAmount = depositAmount.toString();
1727
- const state = vtxo.virtualStatus.state;
1728
- const spentTxid = state === "spent" ? vtxo.arkTxId || vtxo.spentBy : void 0;
1729
- let status = "pending";
1730
- if (state === "swept") status = "recoverable";
1731
- else if (state === "spent") {
1732
- const spendTxs = spendTxidsOf(vtxo).map((id) => spendTxByTxid.get(id)).filter((tx) => tx !== void 0);
1733
- const kind = classifyDepositSpend(offer, serverPubkey, spendTxs, {
1734
- txid: vtxo.txid,
1735
- vout: vtxo.vout
1736
- });
1737
- if (kind === "indeterminate") {
1738
- unresolved.add(fundingTx.redeemTxid);
1739
- continue;
1740
- }
1741
- status = kind;
1742
- }
1743
- restored.push({
1744
- id: fundingTx.redeemTxid,
1745
- fromAsset,
1746
- toAsset,
1747
- fromAmount,
1748
- toAmount: offer.wantAmount.toString(),
1749
- // ponytail(arkade-os/ts-sdk#680): empty address makes cancel fall back
1750
- // to the current server key; store the funded address if server-key
1751
- // rotations become real (cancelOffer now at least diagnoses the
1752
- // mismatch instead of reporting a missing VTXO)
1753
- swapAddress: "",
1754
- swapPkScript,
1755
- offerHex,
1756
- fundingTxid: fundingTx.redeemTxid,
1757
- spentTxid,
1758
- status,
1759
- createdAt: fundingTx.createdAt ? fundingTx.createdAt * 1e3 : vtxo.createdAt.getTime(),
1760
- // the completion time is the caller's record of the spend, if it
1761
- // has one — the psbt that classified it carries no timestamp
1762
- ...status === "fulfilled" && spentTxid && txByAnyId.get(spentTxid)?.createdAt ? { completedAt: txByAnyId.get(spentTxid).createdAt * 1e3 } : {}
1763
- });
1764
- }
1765
- return { restored, scannedTxids: fetchedTxids.filter((id) => !unresolved.has(id)) };
1766
- }
1767
-
1768
2156
  // src/watch.ts
1769
2157
  var import_base7 = require("@scure/base");
1770
- var import_sdk7 = require("@arkade-os/sdk");
2158
+ var import_sdk8 = require("@arkade-os/sdk");
1771
2159
  var TERMINAL = ["fulfilled", "cancelled", "recoverable"];
1772
2160
  function spendUpdate(swap, spend) {
1773
2161
  if (TERMINAL.includes(swap.status)) return void 0;
@@ -1787,8 +2175,8 @@ async function watchOfferSwaps({
1787
2175
  onUpdate
1788
2176
  }) {
1789
2177
  const manager = await wallet.getContractManager();
1790
- const serverPubkey = import_sdk7.ArkAddress.decode(await wallet.getAddress()).serverPubKey;
1791
- const indexer = new import_sdk7.RestIndexerProvider(arkServerUrl);
2178
+ const serverPubkey = import_sdk8.ArkAddress.decode(await wallet.getAddress()).serverPubKey;
2179
+ const indexer = new import_sdk8.RestIndexerProvider(arkServerUrl);
1792
2180
  let queue = Promise.resolve();
1793
2181
  const enqueue = (task) => {
1794
2182
  queue = queue.then(task).catch(() => {
@@ -1803,41 +2191,61 @@ async function watchOfferSwaps({
1803
2191
  return classifyDepositSpend(
1804
2192
  decodeOffer(import_base7.hex.decode(swap.offerHex)),
1805
2193
  serverPubkey,
1806
- txs.map((psbt) => import_sdk7.Transaction.fromPSBT(import_base7.base64.decode(psbt))),
2194
+ txs.map((psbt) => import_sdk8.Transaction.fromPSBT(import_base7.base64.decode(psbt))),
1807
2195
  { txid: vtxo.txid, vout: vtxo.vout }
1808
2196
  );
1809
2197
  } catch {
1810
2198
  return "indeterminate";
1811
2199
  }
1812
2200
  };
1813
- const handleSpend = async (event) => {
1814
- if (event.contract.metadata?.kind !== OFFER_CONTRACT_KIND) return;
1815
- for (const vtxo of event.vtxos) {
2201
+ const resolveSpends = async (contractScript, vtxos, { at, known } = {}) => {
2202
+ let swaps = known ?? await getAssetSwaps(repository);
2203
+ for (const vtxo of vtxos) {
1816
2204
  const spentTxid = vtxo.arkTxId || vtxo.spentBy;
1817
2205
  if (!spentTxid) continue;
1818
- const swap = (await getAssetSwaps(repository)).find(
1819
- (s) => s.fundingTxid === vtxo.txid && s.swapPkScript === event.contractScript
2206
+ const swap = swaps.find(
2207
+ (s) => s.fundingTxid === vtxo.txid && s.swapPkScript === contractScript
1820
2208
  );
1821
2209
  if (!swap) continue;
1822
2210
  const kind = await classify(swap, vtxo, spentTxid);
1823
- const changes = spendUpdate(swap, { txid: spentTxid, kind, at: event.timestamp });
2211
+ const changes = spendUpdate(swap, { txid: spentTxid, kind, at });
1824
2212
  if (!changes) continue;
1825
- const { persisted, swaps } = await updateAssetSwapBestEffort(
2213
+ const { persisted, swaps: written } = await updateAssetSwapBestEffort(
1826
2214
  repository,
1827
2215
  swap.id,
1828
2216
  changes
1829
2217
  );
1830
2218
  if (!persisted) continue;
2219
+ swaps = written;
1831
2220
  onUpdate?.({ ...swap, ...changes });
1832
2221
  if (changes.status && RETIRABLE.includes(changes.status)) {
1833
- await retireOfferContract(manager, swaps, event.contractScript);
2222
+ await retireOfferContract(manager, swaps, contractScript);
1834
2223
  }
1835
2224
  }
1836
2225
  };
2226
+ const handleSpend = async (event) => {
2227
+ if (event.contract.metadata?.kind !== OFFER_CONTRACT_KIND) return;
2228
+ await resolveSpends(event.contractScript, event.vtxos, { at: event.timestamp });
2229
+ };
2230
+ const resolveOpenSwaps = async () => {
2231
+ const swaps = await getAssetSwaps(repository);
2232
+ const open = swaps.filter((s) => !TERMINAL.includes(s.status));
2233
+ if (open.length === 0) return;
2234
+ const script = [...new Set(open.map((s) => s.swapPkScript))];
2235
+ for (const { contract, vtxos } of await manager.getContractsWithVtxos({ script })) {
2236
+ if (contract.metadata?.kind !== OFFER_CONTRACT_KIND) continue;
2237
+ await resolveSpends(
2238
+ contract.script,
2239
+ vtxos.filter((v) => v.isSpent),
2240
+ { known: swaps }
2241
+ );
2242
+ }
2243
+ };
1837
2244
  const unsubscribe = manager.onContractEvent((event) => {
1838
- if (!(0, import_sdk7.isContractVtxoEvent)(event) || event.type !== "vtxo_spent") return;
2245
+ if (!(0, import_sdk8.isContractVtxoEvent)(event) || event.type !== "vtxo_spent") return;
1839
2246
  enqueue(() => handleSpend(event));
1840
2247
  });
2248
+ enqueue(resolveOpenSwaps);
1841
2249
  return {
1842
2250
  stop: unsubscribe,
1843
2251
  idle: () => queue
@@ -1847,8 +2255,8 @@ async function watchOfferSwaps({
1847
2255
  // src/rfq.ts
1848
2256
  var import_base10 = require("@scure/base");
1849
2257
  var import_legacy2 = require("@noble/hashes/legacy.js");
1850
- var import_sdk9 = require("@arkade-os/sdk");
1851
2258
  var import_sdk10 = require("@arkade-os/sdk");
2259
+ var import_sdk11 = require("@arkade-os/sdk");
1852
2260
 
1853
2261
  // src/claimPacket.ts
1854
2262
  var import_base8 = require("@scure/base");
@@ -1883,7 +2291,7 @@ async function sealWithEntropy(input, ephemeralKey, nonce) {
1883
2291
 
1884
2292
  // src/lockupContract.ts
1885
2293
  var import_base9 = require("@scure/base");
1886
- var import_sdk8 = require("@arkade-os/sdk");
2294
+ var import_sdk9 = require("@arkade-os/sdk");
1887
2295
  var SWAP_LOCKUP_CONTRACT_TYPE = "vhtlc-v2";
1888
2296
  var SWAP_LOCKUP_CONTRACT_LABEL = "Arkade RFQ swap lockup";
1889
2297
  var SWAP_LOCKUP_CONTRACT_KIND = "rfq-swap-lockup";
@@ -1919,7 +2327,7 @@ async function registerLockupContract(contracts, script, address) {
1919
2327
  try {
1920
2328
  await contracts.createContract({
1921
2329
  type: SWAP_LOCKUP_CONTRACT_TYPE,
1922
- params: import_sdk8.VHTLCV2ContractHandler.serializeParams(script.options),
2330
+ params: import_sdk9.VHTLCV2ContractHandler.serializeParams(script.options),
1923
2331
  script: import_base9.hex.encode(script.pkScript),
1924
2332
  address,
1925
2333
  label: SWAP_LOCKUP_CONTRACT_LABEL,
@@ -1930,7 +2338,7 @@ async function registerLockupContract(contracts, script, address) {
1930
2338
  }
1931
2339
  }
1932
2340
  async function lockupContractParams(contracts, lockupAddress) {
1933
- const script = import_base9.hex.encode(import_sdk8.ArkAddress.decode(lockupAddress).pkScript);
2341
+ const script = import_base9.hex.encode(import_sdk9.ArkAddress.decode(lockupAddress).pkScript);
1934
2342
  const [row] = await contracts.getContracts({ script });
1935
2343
  if (!row) throw new LockupContractMissing(lockupAddress, script);
1936
2344
  return row.params;
@@ -1955,14 +2363,78 @@ var LIGHTNING_RECEIVE_PAIR = rfqPair(LIGHTNING_BTC, ARKADE_BTC);
1955
2363
  var ONCHAIN_SEND_PAIR = rfqPair(ARKADE_BTC, ONCHAIN_BTC);
1956
2364
  var ONCHAIN_RECEIVE_PAIR = rfqPair(ONCHAIN_BTC, ARKADE_BTC);
1957
2365
  var RFQ_TERMINAL_STATES = ["settled", "refused", "expired", "refunded", "stuck"];
2366
+ var RFQ_REFUSAL_ERROR_CODES = [
2367
+ "amount_side_unsupported",
2368
+ "exact_out_unsupported",
2369
+ "invalid_amount",
2370
+ "invalid_payout_address",
2371
+ "invalid_refund_address",
2372
+ "invoice_amount_mismatch",
2373
+ "invoice_cltv_too_large",
2374
+ "invoice_malformed",
2375
+ "invoice_missing_amount",
2376
+ "invoice_missing_network",
2377
+ "invoice_missing_payment_hash",
2378
+ "invoice_missing_timestamp",
2379
+ "invoice_mixed_case",
2380
+ "invoice_sub_satoshi_amount",
2381
+ "invoice_too_long",
2382
+ "invoice_wrong_network"
2383
+ ];
2384
+ var isRfqRefusalErrorCode = (value) => typeof value === "string" && RFQ_REFUSAL_ERROR_CODES.includes(value);
2385
+ var REFUSAL_FIELDS = /* @__PURE__ */ new Set([
2386
+ "amount",
2387
+ "amount_side",
2388
+ "profile.invoice",
2389
+ "profile.payout_address",
2390
+ "profile.refund_address"
2391
+ ]);
2392
+ var REFUSAL_UNITS = /* @__PURE__ */ new Set(["blocks", "characters", "sats"]);
2393
+ var safeInteger = (value) => typeof value === "number" && Number.isSafeInteger(value) && value >= 0 ? value : void 0;
2394
+ var safeRefusalDetail = (detail) => {
2395
+ if (!isRfqRefusalErrorCode(detail.errorCode)) return {};
2396
+ return {
2397
+ errorCode: detail.errorCode,
2398
+ field: typeof detail.field === "string" && REFUSAL_FIELDS.has(detail.field) ? detail.field : void 0,
2399
+ actual: safeInteger(detail.actual),
2400
+ expected: safeInteger(detail.expected),
2401
+ limit: safeInteger(detail.limit),
2402
+ unit: typeof detail.unit === "string" && REFUSAL_UNITS.has(detail.unit) ? detail.unit : void 0
2403
+ };
2404
+ };
2405
+ var refusalMessage = (reason, detail) => {
2406
+ if (!detail.errorCode) return `solver refused: ${reason}`;
2407
+ const where = detail.field ? ` at ${detail.field}` : "";
2408
+ const unit = detail.unit ? ` ${detail.unit}` : "";
2409
+ if (detail.actual !== void 0 && detail.limit !== void 0) {
2410
+ return `solver refused: ${reason} (${detail.errorCode}${where}: ${detail.actual}${unit}, limit ${detail.limit})`;
2411
+ }
2412
+ if (detail.actual !== void 0 && detail.expected !== void 0) {
2413
+ return `solver refused: ${reason} (${detail.errorCode}${where}: ${detail.actual}${unit}, expected ${detail.expected})`;
2414
+ }
2415
+ return `solver refused: ${reason} (${detail.errorCode}${where})`;
2416
+ };
1958
2417
  var SwapRefusal = class extends Error {
1959
2418
  reason;
1960
2419
  rfqId;
1961
- constructor(reason, rfqId) {
1962
- super(`solver refused: ${reason}`);
2420
+ errorCode;
2421
+ field;
2422
+ actual;
2423
+ expected;
2424
+ limit;
2425
+ unit;
2426
+ constructor(reason, rfqId, detail = {}) {
2427
+ const safeDetail = safeRefusalDetail(detail);
2428
+ super(refusalMessage(reason, safeDetail));
1963
2429
  this.name = "SwapRefusal";
1964
2430
  this.reason = reason;
1965
2431
  this.rfqId = rfqId;
2432
+ this.errorCode = safeDetail.errorCode;
2433
+ this.field = safeDetail.field;
2434
+ this.actual = safeDetail.actual;
2435
+ this.expected = safeDetail.expected;
2436
+ this.limit = safeDetail.limit;
2437
+ this.unit = safeDetail.unit;
1966
2438
  }
1967
2439
  };
1968
2440
  var AddressMismatch = class extends Error {
@@ -2081,8 +2553,8 @@ var assertFundable = (input) => {
2081
2553
  fail("max_fee_out_of_range", `maxFee.sats must be a non-negative integer, got ${sats}`);
2082
2554
  }
2083
2555
  const legs = input.quote.pair.split("->");
2084
- const assetOf = (leg) => leg.slice(leg.indexOf(":") + 1);
2085
- const sameAsset = legs.length === 2 && assetOf(legs[0]) === assetOf(legs[1]);
2556
+ const assetOf2 = (leg) => leg.slice(leg.indexOf(":") + 1);
2557
+ const sameAsset = legs.length === 2 && assetOf2(legs[0]) === assetOf2(legs[1]);
2086
2558
  if (!sameAsset && referenceRate === void 0) {
2087
2559
  fail(
2088
2560
  "fee_gate_unavailable",
@@ -2130,7 +2602,20 @@ var assertFundable = (input) => {
2130
2602
  };
2131
2603
  var expectQuote = (payload, rfqId, requestedPair) => {
2132
2604
  const p = payload;
2133
- if (p?.type === "rfq_refusal") throw new SwapRefusal(p.reason ?? "unknown", p.rfq_id ?? rfqId);
2605
+ if (p?.type === "rfq_refusal") {
2606
+ throw new SwapRefusal(
2607
+ p.reason ?? "unknown",
2608
+ p.rfq_id ?? rfqId,
2609
+ safeRefusalDetail({
2610
+ errorCode: p.error_code,
2611
+ field: p.field,
2612
+ actual: p.actual,
2613
+ expected: p.expected,
2614
+ limit: p.limit,
2615
+ unit: p.unit
2616
+ })
2617
+ );
2618
+ }
2134
2619
  if (p?.type !== "rfq_quote" || p.rfq_id !== rfqId) {
2135
2620
  throw new Error(`unexpected reply: ${p?.type ?? "no payload"}`);
2136
2621
  }
@@ -2283,7 +2768,7 @@ function lightningSendVtxoScript(params) {
2283
2768
  type: "seconds",
2284
2769
  value: BigInt(value)
2285
2770
  });
2286
- return new import_sdk9.VHTLC.ScriptV2({
2771
+ return new import_sdk10.VHTLC.ScriptV2({
2287
2772
  sender: params.senderPubkey,
2288
2773
  receiver: params.solverPubkey,
2289
2774
  server: params.serverPubkey,
@@ -2304,10 +2789,10 @@ function lightningSendVtxoScript(params) {
2304
2789
  }
2305
2790
  async function requestLightningSend(wallet, arkServerUrl, transport, params) {
2306
2791
  const rfqId = params.rfqId ?? newRfqId();
2307
- const secrets = await (0, import_sdk10.provisionRefundKey)(wallet);
2792
+ const secrets = await (0, import_sdk11.provisionRefundKey)(wallet);
2308
2793
  const senderPubkey = secrets.pubkey;
2309
2794
  const refundAddress = secrets.address;
2310
- const info = await new import_sdk9.RestArkProvider(arkServerUrl).getInfo();
2795
+ const info = await new import_sdk10.RestArkProvider(arkServerUrl).getInfo();
2311
2796
  const quote = await transport.requestQuote(
2312
2797
  lightningSendRequest({ rfqId, invoice: params.invoice.raw, refundAddress, senderPubkey })
2313
2798
  );
@@ -2328,16 +2813,16 @@ async function requestLightningSend(wallet, arkServerUrl, transport, params) {
2328
2813
  `quote from_amount ${quote.from_amount} is below the invoice amount \u2014 a negative spread is not a quote`
2329
2814
  );
2330
2815
  }
2331
- const serverPubkey = (0, import_sdk9.toXOnly)(import_base10.hex.decode(info.signerPubkey), "ark signer key");
2332
- const network = (0, import_sdk9.getNetwork)(info.network);
2816
+ const serverPubkey = (0, import_sdk10.toXOnly)(import_base10.hex.decode(info.signerPubkey), "ark signer key");
2817
+ const network = (0, import_sdk10.getNetwork)(info.network);
2333
2818
  const treeParams = {
2334
- solverPubkey: (0, import_sdk9.toXOnly)(import_base10.hex.decode(quote.solver_pubkey), "solver key"),
2819
+ solverPubkey: (0, import_sdk10.toXOnly)(import_base10.hex.decode(quote.solver_pubkey), "solver key"),
2335
2820
  refundLocktime: quote.refund_locktime,
2336
2821
  serverPubkey,
2337
2822
  paymentHash: params.invoice.paymentHash,
2338
2823
  claimDelay: unilateralClaimDelay(Number(info.unilateralExitDelay)),
2339
- emulatorPubkey: (0, import_sdk9.toXOnly)(
2340
- import_base10.hex.decode((0, import_sdk9.resolveEmulatorPubkey)(network, params.emulatorPubkey)),
2824
+ emulatorPubkey: (0, import_sdk10.toXOnly)(
2825
+ import_base10.hex.decode((0, import_sdk10.resolveEmulatorPubkey)(network, params.emulatorPubkey)),
2341
2826
  "emulator signer key"
2342
2827
  ),
2343
2828
  senderPubkey,
@@ -2440,7 +2925,7 @@ function deriveOnchainSend(input) {
2440
2925
  throw new Error("onchain-send quote is missing a binding field");
2441
2926
  }
2442
2927
  const treeParams = {
2443
- solverPubkey: (0, import_sdk9.toXOnly)(import_base10.hex.decode(quote.solver_pubkey), "solver key"),
2928
+ solverPubkey: (0, import_sdk10.toXOnly)(import_base10.hex.decode(quote.solver_pubkey), "solver key"),
2444
2929
  refundLocktime,
2445
2930
  serverPubkey: input.serverPubkey,
2446
2931
  paymentHash: input.paymentHash,
@@ -2448,7 +2933,7 @@ function deriveOnchainSend(input) {
2448
2933
  emulatorPubkey: input.emulatorPubkey,
2449
2934
  senderPubkey: input.senderPubkey,
2450
2935
  receiverPkScript: solverHex(receiverPkScriptHex, "profile.receiver_pk_script"),
2451
- refundPkScript: import_sdk9.ArkAddress.decode(input.refundAddress).pkScript
2936
+ refundPkScript: import_sdk10.ArkAddress.decode(input.refundAddress).pkScript
2452
2937
  };
2453
2938
  const { script, address } = matchQuotedLockup(
2454
2939
  quote,
@@ -2459,7 +2944,7 @@ function deriveOnchainSend(input) {
2459
2944
  const htlcParams = {
2460
2945
  paymentHash: input.paymentHash,
2461
2946
  claimKey: input.payoutPubkey,
2462
- refundKey: (0, import_sdk9.toXOnly)(import_base10.hex.decode(htlcPubkey), "solver L1 htlc key"),
2947
+ refundKey: (0, import_sdk10.toXOnly)(import_base10.hex.decode(htlcPubkey), "solver L1 htlc key"),
2463
2948
  refundLocktime: htlcLocktime
2464
2949
  };
2465
2950
  const htlc = onchainHtlcScript(htlcParams, input.l1Network);
@@ -2478,7 +2963,7 @@ function deriveOnchainSend(input) {
2478
2963
  }
2479
2964
  async function requestOnchainSend(wallet, arkServerUrl, transport, params) {
2480
2965
  const rfqId = params.rfqId ?? newRfqId();
2481
- const secrets = await (0, import_sdk10.provisionClaimSecret)(wallet, { preimage: params.preimage });
2966
+ const secrets = await (0, import_sdk11.provisionClaimSecret)(wallet, { preimage: params.preimage });
2482
2967
  if (secrets.mustPersistPreimage) {
2483
2968
  console.warn(
2484
2969
  "[swap] this swap's preimage cannot be re-derived from the seed and MUST be persisted with the record before funding"
@@ -2487,7 +2972,7 @@ async function requestOnchainSend(wallet, arkServerUrl, transport, params) {
2487
2972
  const paymentHash = import_base10.hex.encode(secrets.paymentHash);
2488
2973
  const senderPubkey = secrets.pubkey;
2489
2974
  const [info, refundAddress] = await Promise.all([
2490
- new import_sdk9.RestArkProvider(arkServerUrl).getInfo(),
2975
+ new import_sdk10.RestArkProvider(arkServerUrl).getInfo(),
2491
2976
  wallet.getAddress()
2492
2977
  ]);
2493
2978
  const quote = await transport.requestQuote(
@@ -2502,14 +2987,14 @@ async function requestOnchainSend(wallet, arkServerUrl, transport, params) {
2502
2987
  })
2503
2988
  );
2504
2989
  assertQuotedAmount(quote, params.amountSide, params.amount);
2505
- const network = (0, import_sdk9.getNetwork)(info.network);
2990
+ const network = (0, import_sdk10.getNetwork)(info.network);
2506
2991
  const derived = deriveOnchainSend({
2507
2992
  quote,
2508
2993
  paymentHash,
2509
2994
  payoutPubkey: params.payoutPubkey,
2510
- serverPubkey: (0, import_sdk9.toXOnly)(import_base10.hex.decode(info.signerPubkey), "ark signer key"),
2511
- emulatorPubkey: (0, import_sdk9.toXOnly)(
2512
- import_base10.hex.decode((0, import_sdk9.resolveEmulatorPubkey)(network, params.emulatorPubkey)),
2995
+ serverPubkey: (0, import_sdk10.toXOnly)(import_base10.hex.decode(info.signerPubkey), "ark signer key"),
2996
+ emulatorPubkey: (0, import_sdk10.toXOnly)(
2997
+ import_base10.hex.decode((0, import_sdk10.resolveEmulatorPubkey)(network, params.emulatorPubkey)),
2513
2998
  "emulator signer key"
2514
2999
  ),
2515
3000
  claimDelay: unilateralClaimDelay(Number(info.unilateralExitDelay)),
@@ -2620,7 +3105,7 @@ function receiveVtxoScript(params) {
2620
3105
  type: "seconds",
2621
3106
  value: BigInt(value)
2622
3107
  });
2623
- return new import_sdk9.VHTLC.ScriptV2({
3108
+ return new import_sdk10.VHTLC.ScriptV2({
2624
3109
  sender: params.solverPubkey,
2625
3110
  receiver: params.payoutPubkey,
2626
3111
  server: params.serverPubkey,
@@ -2649,7 +3134,7 @@ function deriveLightningReceive(input) {
2649
3134
  throw new Error("lightning-receive quote is missing a binding field");
2650
3135
  }
2651
3136
  const treeParams = {
2652
- solverPubkey: (0, import_sdk9.toXOnly)(import_base10.hex.decode(quote.solver_pubkey), "solver key"),
3137
+ solverPubkey: (0, import_sdk10.toXOnly)(import_base10.hex.decode(quote.solver_pubkey), "solver key"),
2653
3138
  refundLocktime,
2654
3139
  serverPubkey: input.serverPubkey,
2655
3140
  paymentHash: input.paymentHash,
@@ -2657,7 +3142,7 @@ function deriveLightningReceive(input) {
2657
3142
  emulatorPubkey: input.emulatorPubkey,
2658
3143
  solverRefundPkScript: solverHex(solverRefundPkScriptHex, "profile.solver_refund_pk_script"),
2659
3144
  payoutPubkey: input.payoutPubkey,
2660
- payoutPkScript: import_sdk9.ArkAddress.decode(input.payoutAddress).pkScript
3145
+ payoutPkScript: import_sdk10.ArkAddress.decode(input.payoutAddress).pkScript
2661
3146
  };
2662
3147
  const matched = matchQuotedLockup(
2663
3148
  quote,
@@ -2679,7 +3164,7 @@ function deriveLightningReceive(input) {
2679
3164
  }
2680
3165
  async function requestLightningReceive(wallet, arkServerUrl, transport, params) {
2681
3166
  const rfqId = params.rfqId ?? newRfqId();
2682
- const secrets = await (0, import_sdk10.provisionClaimSecret)(wallet);
3167
+ const secrets = await (0, import_sdk11.provisionClaimSecret)(wallet);
2683
3168
  if (secrets.mustPersistPreimage) {
2684
3169
  console.warn(
2685
3170
  "[swap] this swap's preimage cannot be re-derived from the seed and MUST be persisted with the record before paying"
@@ -2689,7 +3174,7 @@ async function requestLightningReceive(wallet, arkServerUrl, transport, params)
2689
3174
  const paymentHash = import_base10.hex.encode(secrets.paymentHash);
2690
3175
  const payoutPubkey = secrets.pubkey;
2691
3176
  const [info, payoutAddress] = await Promise.all([
2692
- new import_sdk9.RestArkProvider(arkServerUrl).getInfo(),
3177
+ new import_sdk10.RestArkProvider(arkServerUrl).getInfo(),
2693
3178
  wallet.getAddress()
2694
3179
  ]);
2695
3180
  const claimPacket = params.covclaimdPubkey ? await sealClaimPacket({
@@ -2708,15 +3193,15 @@ async function requestLightningReceive(wallet, arkServerUrl, transport, params)
2708
3193
  })
2709
3194
  );
2710
3195
  assertQuotedAmount(quote, params.amountSide, params.amount);
2711
- const network = (0, import_sdk9.getNetwork)(info.network);
3196
+ const network = (0, import_sdk10.getNetwork)(info.network);
2712
3197
  const derived = deriveLightningReceive({
2713
3198
  quote,
2714
3199
  paymentHash,
2715
3200
  payoutPubkey,
2716
3201
  payoutAddress,
2717
- serverPubkey: (0, import_sdk9.toXOnly)(import_base10.hex.decode(info.signerPubkey), "ark signer key"),
2718
- emulatorPubkey: (0, import_sdk9.toXOnly)(
2719
- import_base10.hex.decode((0, import_sdk9.resolveEmulatorPubkey)(network, params.emulatorPubkey)),
3202
+ serverPubkey: (0, import_sdk10.toXOnly)(import_base10.hex.decode(info.signerPubkey), "ark signer key"),
3203
+ emulatorPubkey: (0, import_sdk10.toXOnly)(
3204
+ import_base10.hex.decode((0, import_sdk10.resolveEmulatorPubkey)(network, params.emulatorPubkey)),
2720
3205
  "emulator signer key"
2721
3206
  ),
2722
3207
  claimDelay: unilateralClaimDelay(Number(info.unilateralExitDelay)),
@@ -2764,7 +3249,7 @@ function deriveOnchainReceive(input) {
2764
3249
  throw new Error("onchain-receive quote is missing a binding field");
2765
3250
  }
2766
3251
  const treeParams = {
2767
- solverPubkey: (0, import_sdk9.toXOnly)(import_base10.hex.decode(quote.solver_pubkey), "solver key"),
3252
+ solverPubkey: (0, import_sdk10.toXOnly)(import_base10.hex.decode(quote.solver_pubkey), "solver key"),
2768
3253
  refundLocktime,
2769
3254
  serverPubkey: input.serverPubkey,
2770
3255
  paymentHash: input.paymentHash,
@@ -2772,7 +3257,7 @@ function deriveOnchainReceive(input) {
2772
3257
  emulatorPubkey: input.emulatorPubkey,
2773
3258
  solverRefundPkScript: solverHex(solverRefundPkScriptHex, "profile.solver_refund_pk_script"),
2774
3259
  payoutPubkey: input.payoutPubkey,
2775
- payoutPkScript: import_sdk9.ArkAddress.decode(input.payoutAddress).pkScript
3260
+ payoutPkScript: import_sdk10.ArkAddress.decode(input.payoutAddress).pkScript
2776
3261
  };
2777
3262
  const { script, address } = matchQuotedLockup(
2778
3263
  quote,
@@ -2783,7 +3268,7 @@ function deriveOnchainReceive(input) {
2783
3268
  const htlc = onchainHtlcScript(
2784
3269
  {
2785
3270
  paymentHash: input.paymentHash,
2786
- claimKey: (0, import_sdk9.toXOnly)(import_base10.hex.decode(claimPubkey), "solver L1 claim key"),
3271
+ claimKey: (0, import_sdk10.toXOnly)(import_base10.hex.decode(claimPubkey), "solver L1 claim key"),
2787
3272
  refundKey: input.refundPubkey,
2788
3273
  refundLocktime: htlcLocktime
2789
3274
  },
@@ -2802,7 +3287,7 @@ function deriveOnchainReceive(input) {
2802
3287
  }
2803
3288
  async function requestOnchainReceive(wallet, arkServerUrl, transport, params) {
2804
3289
  const rfqId = params.rfqId ?? newRfqId();
2805
- const secrets = await (0, import_sdk10.provisionClaimSecret)(wallet);
3290
+ const secrets = await (0, import_sdk11.provisionClaimSecret)(wallet);
2806
3291
  if (secrets.mustPersistPreimage) {
2807
3292
  console.warn(
2808
3293
  "[swap] this swap's preimage cannot be re-derived from the seed and MUST be persisted with the record before funding"
@@ -2812,7 +3297,7 @@ async function requestOnchainReceive(wallet, arkServerUrl, transport, params) {
2812
3297
  const paymentHash = import_base10.hex.encode(secrets.paymentHash);
2813
3298
  const payoutPubkey = secrets.pubkey;
2814
3299
  const [info, payoutAddress] = await Promise.all([
2815
- new import_sdk9.RestArkProvider(arkServerUrl).getInfo(),
3300
+ new import_sdk10.RestArkProvider(arkServerUrl).getInfo(),
2816
3301
  wallet.getAddress()
2817
3302
  ]);
2818
3303
  const claimPacket = params.covclaimdPubkey ? await sealClaimPacket({
@@ -2832,16 +3317,16 @@ async function requestOnchainReceive(wallet, arkServerUrl, transport, params) {
2832
3317
  })
2833
3318
  );
2834
3319
  assertQuotedAmount(quote, params.amountSide, params.amount);
2835
- const network = (0, import_sdk9.getNetwork)(info.network);
3320
+ const network = (0, import_sdk10.getNetwork)(info.network);
2836
3321
  const derived = deriveOnchainReceive({
2837
3322
  quote,
2838
3323
  paymentHash,
2839
3324
  payoutPubkey,
2840
3325
  payoutAddress,
2841
3326
  refundPubkey: params.refundPubkey,
2842
- serverPubkey: (0, import_sdk9.toXOnly)(import_base10.hex.decode(info.signerPubkey), "ark signer key"),
2843
- emulatorPubkey: (0, import_sdk9.toXOnly)(
2844
- import_base10.hex.decode((0, import_sdk9.resolveEmulatorPubkey)(network, params.emulatorPubkey)),
3327
+ serverPubkey: (0, import_sdk10.toXOnly)(import_base10.hex.decode(info.signerPubkey), "ark signer key"),
3328
+ emulatorPubkey: (0, import_sdk10.toXOnly)(
3329
+ import_base10.hex.decode((0, import_sdk10.resolveEmulatorPubkey)(network, params.emulatorPubkey)),
2845
3330
  "emulator signer key"
2846
3331
  ),
2847
3332
  claimDelay: unilateralClaimDelay(Number(info.unilateralExitDelay)),
@@ -2961,7 +3446,7 @@ var solverRendezvous = (markets, payoutCorridor, amountSats, fallbackEmulatorPub
2961
3446
  };
2962
3447
 
2963
3448
  // src/payment/solverOnchain.ts
2964
- var import_sdk11 = require("@arkade-os/sdk");
3449
+ var import_sdk12 = require("@arkade-os/sdk");
2965
3450
  var SOLVER_ONCHAIN_RAIL = "solver-onchain";
2966
3451
  var solverOnchainRendezvous = (markets, amountSats, fallbackEmulatorPubkey) => solverRendezvous(markets, "onchain", amountSats, fallbackEmulatorPubkey);
2967
3452
  function solverOnchainRail(deps) {
@@ -2972,24 +3457,24 @@ function solverOnchainRail(deps) {
2972
3457
  };
2973
3458
  return {
2974
3459
  id: SOLVER_ONCHAIN_RAIL,
2975
- match: (req) => (0, import_sdk11.btcTarget)(req.raw) !== void 0,
3460
+ match: (req) => (0, import_sdk12.btcTarget)(req.raw) !== void 0,
2976
3461
  available: async (req) => {
2977
- if ((0, import_sdk11.assetsOf)(req).length > 0) return false;
2978
- const address = (0, import_sdk11.btcTarget)(req.raw);
3462
+ if ((0, import_sdk12.assetsOf)(req).length > 0) return false;
3463
+ const address = (0, import_sdk12.btcTarget)(req.raw);
2979
3464
  if (!address) return false;
2980
3465
  try {
2981
3466
  l1ScriptForAddress(address, deps.l1Network);
2982
3467
  } catch {
2983
3468
  return false;
2984
3469
  }
2985
- const amount = (0, import_sdk11.tryResolveSendAmount)(req.raw, req.amount);
3470
+ const amount = (0, import_sdk12.tryResolveSendAmount)(req.raw, req.amount);
2986
3471
  if (amount === void 0) return false;
2987
3472
  return await rendezvousFor(amount) !== void 0;
2988
3473
  },
2989
3474
  quote: async (req, ctx) => {
2990
- const address = (0, import_sdk11.btcTarget)(req.raw);
2991
- (0, import_sdk11.assertNoAssets)(SOLVER_ONCHAIN_RAIL, req);
2992
- const amount = (0, import_sdk11.resolveSendAmount)(SOLVER_ONCHAIN_RAIL, req.raw, req.amount);
3475
+ const address = (0, import_sdk12.btcTarget)(req.raw);
3476
+ (0, import_sdk12.assertNoAssets)(SOLVER_ONCHAIN_RAIL, req);
3477
+ const amount = (0, import_sdk12.resolveSendAmount)(SOLVER_ONCHAIN_RAIL, req.raw, req.amount);
2993
3478
  const payoutPkScript = l1ScriptForAddress(address, deps.l1Network);
2994
3479
  const rendezvous = await rendezvousFor(amount);
2995
3480
  if (!rendezvous) {
@@ -3017,6 +3502,7 @@ function solverOnchainRail(deps) {
3017
3502
  amount,
3018
3503
  fee: swap.fundAmount - amount,
3019
3504
  total: swap.fundAmount,
3505
+ validUntil: swap.quote.valid_until,
3020
3506
  meta: {
3021
3507
  rfqId: swap.rfqId,
3022
3508
  validUntil: swap.quote.valid_until,
@@ -3027,7 +3513,7 @@ function solverOnchainRail(deps) {
3027
3513
  // not knowable now, so `amount` is the payout, not the net.
3028
3514
  claimFeeDeductedFromPayout: true
3029
3515
  },
3030
- send: async () => (0, import_sdk11.makeHandle)(SOLVER_ONCHAIN_RAIL, async (emit) => {
3516
+ send: async () => (0, import_sdk12.makeHandle)(SOLVER_ONCHAIN_RAIL, async (emit) => {
3031
3517
  assertFundable({
3032
3518
  quote: swap.quote,
3033
3519
  now: Math.floor(Date.now() / 1e3),
@@ -3065,10 +3551,10 @@ function solverOnchainRail(deps) {
3065
3551
  }
3066
3552
 
3067
3553
  // src/payment/solverLightning.ts
3068
- var import_sdk12 = require("@arkade-os/sdk");
3554
+ var import_sdk13 = require("@arkade-os/sdk");
3069
3555
  var SOLVER_LIGHTNING_RAIL = "solver-lightning";
3070
3556
  var factsOf = (raw, decode, now) => {
3071
- const invoice = (0, import_sdk12.invoiceTarget)(raw);
3557
+ const invoice = (0, import_sdk13.invoiceTarget)(raw);
3072
3558
  if (!invoice) return void 0;
3073
3559
  let facts;
3074
3560
  try {
@@ -3085,16 +3571,16 @@ function solverLightningRail(deps) {
3085
3571
  const rendezvousFor = async (amountSats) => solverLightningRendezvous(await deps.discover(), amountSats, deps.fallbackEmulatorPubkey);
3086
3572
  return {
3087
3573
  id: SOLVER_LIGHTNING_RAIL,
3088
- match: (req) => (0, import_sdk12.invoiceTarget)(req.raw) !== void 0,
3574
+ match: (req) => (0, import_sdk13.invoiceTarget)(req.raw) !== void 0,
3089
3575
  available: async (req) => {
3090
- if ((0, import_sdk12.assetsOf)(req).length > 0) return false;
3576
+ if ((0, import_sdk13.assetsOf)(req).length > 0) return false;
3091
3577
  const facts = factsOf(req.raw, deps.decodeInvoice, Math.floor(Date.now() / 1e3));
3092
3578
  if (!facts) return false;
3093
3579
  if (req.amount !== void 0 && req.amount !== facts.amountSats) return false;
3094
3580
  return await rendezvousFor(facts.amountSats) !== void 0;
3095
3581
  },
3096
3582
  quote: async (req, ctx) => {
3097
- (0, import_sdk12.assertNoAssets)(SOLVER_LIGHTNING_RAIL, req);
3583
+ (0, import_sdk13.assertNoAssets)(SOLVER_LIGHTNING_RAIL, req);
3098
3584
  const facts = factsOf(req.raw, deps.decodeInvoice, Math.floor(Date.now() / 1e3));
3099
3585
  if (!facts) {
3100
3586
  throw new Error(
@@ -3127,6 +3613,9 @@ function solverLightningRail(deps) {
3127
3613
  amount: facts.amountSats,
3128
3614
  fee: swap.fundAmount - facts.amountSats,
3129
3615
  total: swap.fundAmount,
3616
+ // The invoice can lapse before the quote does; the earlier bound
3617
+ // is the one a holder must observe.
3618
+ validUntil: Math.min(swap.quote.valid_until, facts.expiresAt),
3130
3619
  meta: {
3131
3620
  rfqId: swap.rfqId,
3132
3621
  validUntil: swap.quote.valid_until,
@@ -3134,7 +3623,7 @@ function solverLightningRail(deps) {
3134
3623
  invoiceExpiresAt: facts.expiresAt,
3135
3624
  solverPubkey: rendezvous.solverPubkey
3136
3625
  },
3137
- send: async () => (0, import_sdk12.makeHandle)(SOLVER_LIGHTNING_RAIL, async (emit) => {
3626
+ send: async () => (0, import_sdk13.makeHandle)(SOLVER_LIGHTNING_RAIL, async (emit) => {
3138
3627
  assertFundable({
3139
3628
  quote: swap.quote,
3140
3629
  invoiceExpiresAt: facts.expiresAt,
@@ -3168,16 +3657,17 @@ function solverLightningRail(deps) {
3168
3657
  }
3169
3658
 
3170
3659
  // src/payment/crossAsset.ts
3171
- var import_sdk13 = require("@arkade-os/sdk");
3660
+ var import_sdk14 = require("@arkade-os/sdk");
3172
3661
  var CROSS_ASSET_RAIL = "cross-asset";
3173
3662
  var DEFAULT_CARRIER_SATS = 330;
3174
3663
  var parseAssetId = (assetId) => {
3175
3664
  try {
3176
- return import_sdk13.asset.AssetId.fromString(assetId);
3665
+ return import_sdk14.asset.AssetId.fromString(assetId);
3177
3666
  } catch {
3178
3667
  return void 0;
3179
3668
  }
3180
3669
  };
3670
+ var marketLabel = (market) => `${market.base_asset.ticker || market.base_asset.id}/${market.quote_asset.ticker || market.quote_asset.id}`;
3181
3671
  function crossAssetRail(deps) {
3182
3672
  const planFor = async (asset3) => {
3183
3673
  const markets = await deps.discover();
@@ -3190,9 +3680,9 @@ function crossAssetRail(deps) {
3190
3680
  };
3191
3681
  return {
3192
3682
  id: CROSS_ASSET_RAIL,
3193
- match: (req) => (0, import_sdk13.arkTarget)(req.raw) !== void 0,
3683
+ match: (req) => (0, import_sdk14.arkTarget)(req.raw) !== void 0,
3194
3684
  available: async (req) => {
3195
- const assets = (0, import_sdk13.assetsOf)(req);
3685
+ const assets = (0, import_sdk14.assetsOf)(req);
3196
3686
  if (assets.length !== 1) return false;
3197
3687
  const [asset3] = assets;
3198
3688
  if (asset3.assetId === BTC_ASSET_ID) return false;
@@ -3202,8 +3692,8 @@ function crossAssetRail(deps) {
3202
3692
  return planned !== void 0 && planned.error === void 0;
3203
3693
  },
3204
3694
  quote: async (req, ctx) => {
3205
- const payTo = (0, import_sdk13.arkTarget)(req.raw);
3206
- const asset3 = (0, import_sdk13.resolveAssetAmount)(CROSS_ASSET_RAIL, req);
3695
+ const payTo = (0, import_sdk14.arkTarget)(req.raw);
3696
+ const asset3 = (0, import_sdk14.resolveAssetAmount)(CROSS_ASSET_RAIL, req);
3207
3697
  const wantAsset = parseAssetId(asset3.assetId);
3208
3698
  if (!wantAsset) {
3209
3699
  throw new Error(
@@ -3240,11 +3730,11 @@ function crossAssetRail(deps) {
3240
3730
  spent: { assetId: BTC_ASSET_ID, amount: plan.deposit.atomic }
3241
3731
  },
3242
3732
  meta: {
3243
- market: plan.market.pair,
3733
+ market: marketLabel(plan.market),
3244
3734
  priceDisplay: plan.priceDisplay,
3245
3735
  give: plan.give
3246
3736
  },
3247
- send: async () => (0, import_sdk13.makeHandle)(CROSS_ASSET_RAIL, async (emit) => {
3737
+ send: async () => (0, import_sdk14.makeHandle)(CROSS_ASSET_RAIL, async (emit) => {
3248
3738
  const offer = await createOffer(ctx.wallet, deps.arkServerUrl, {
3249
3739
  wantAmount: asset3.amount,
3250
3740
  wantAsset,
@@ -3293,7 +3783,7 @@ function crossAssetRail(deps) {
3293
3783
  var import_base13 = require("@scure/base");
3294
3784
  var import_legacy3 = require("@noble/hashes/legacy.js");
3295
3785
  var import_sha24 = require("@noble/hashes/sha2.js");
3296
- var import_sdk14 = require("@arkade-os/sdk");
3786
+ var import_sdk15 = require("@arkade-os/sdk");
3297
3787
  var CovclaimdRevealError = class extends Error {
3298
3788
  name = "CovclaimdRevealError";
3299
3789
  status;
@@ -3397,7 +3887,7 @@ function revealFieldsFromScript(script) {
3397
3887
  }
3398
3888
  async function revealClaimPacket(client, input) {
3399
3889
  const fields = revealFieldsFromScript(input.script);
3400
- if (import_base13.hex.encode(import_sdk14.ArkAddress.decode(input.address).pkScript) !== import_base13.hex.encode(input.script.pkScript)) {
3890
+ if (import_base13.hex.encode(import_sdk15.ArkAddress.decode(input.address).pkScript) !== import_base13.hex.encode(input.script.pkScript)) {
3401
3891
  throw new Error("address does not belong to this covenant");
3402
3892
  }
3403
3893
  if (import_base13.hex.encode((0, import_legacy3.ripemd160)((0, import_sha24.sha256)(input.preimage))) !== import_base13.hex.encode(input.script.options.preimageHash)) {
@@ -3415,12 +3905,12 @@ async function revealClaimPacket(client, input) {
3415
3905
  var import_base15 = require("@scure/base");
3416
3906
  var import_legacy4 = require("@noble/hashes/legacy.js");
3417
3907
  var import_sha26 = require("@noble/hashes/sha2.js");
3418
- var import_sdk16 = require("@arkade-os/sdk");
3908
+ var import_sdk17 = require("@arkade-os/sdk");
3419
3909
 
3420
3910
  // src/refund.ts
3421
3911
  var import_base14 = require("@scure/base");
3422
3912
  var import_sha25 = require("@noble/hashes/sha2.js");
3423
- var import_sdk15 = require("@arkade-os/sdk");
3913
+ var import_sdk16 = require("@arkade-os/sdk");
3424
3914
  var sleep2 = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
3425
3915
  var isRfqTerminal = (state) => RFQ_TERMINAL_STATES.includes(state);
3426
3916
  var RFQ_RESOLVED_STATES = ["settled", "refunded"];
@@ -3495,14 +3985,14 @@ async function findLockupVtxos(indexer, swapPkScript) {
3495
3985
  }
3496
3986
  var hashesTo = (candidate, paymentHash) => import_base14.hex.encode((0, import_sha25.sha256)(candidate)) === paymentHash;
3497
3987
  var candidateWitnessItems = (tx, inputIndex) => [
3498
- ...(0, import_sdk15.getArkPsbtFields)(tx, inputIndex, import_sdk15.ConditionWitness).flat(),
3988
+ ...(0, import_sdk16.getArkPsbtFields)(tx, inputIndex, import_sdk16.ConditionWitness).flat(),
3499
3989
  ...tx.getInput(inputIndex).finalScriptWitness ?? []
3500
3990
  ];
3501
3991
  async function readLockupFate(indexer, input) {
3502
3992
  const { vtxos } = await indexer.getVtxos({ scripts: [import_base14.hex.encode(input.swapPkScript)] });
3503
3993
  const all = vtxos ?? [];
3504
3994
  if (all.length === 0) return { fate: "unknown" };
3505
- const exited = all.filter((vtxo) => vtxo.isUnrolled && !(0, import_sdk15.hasTerminalSpend)(vtxo));
3995
+ const exited = all.filter((vtxo) => vtxo.isUnrolled && !(0, import_sdk16.hasTerminalSpend)(vtxo));
3506
3996
  if (exited.length > 0) {
3507
3997
  return {
3508
3998
  fate: "exited",
@@ -3512,7 +4002,7 @@ async function readLockupFate(indexer, input) {
3512
4002
  const spentBy = /* @__PURE__ */ new Map();
3513
4003
  let everySpendNamed = true;
3514
4004
  for (const vtxo of all) {
3515
- if (!(0, import_sdk15.hasTerminalSpend)(vtxo)) return { fate: "open" };
4005
+ if (!(0, import_sdk16.hasTerminalSpend)(vtxo)) return { fate: "open" };
3516
4006
  if (vtxo.spentBy)
3517
4007
  spentBy.set(vtxo.spentBy, {
3518
4008
  checkpointTxid: vtxo.spentBy,
@@ -3526,7 +4016,7 @@ async function readLockupFate(indexer, input) {
3526
4016
  for (const raw of txs) {
3527
4017
  let tx;
3528
4018
  try {
3529
- tx = import_sdk15.Transaction.fromPSBT(import_base14.base64.decode(raw));
4019
+ tx = import_sdk16.Transaction.fromPSBT(import_base14.base64.decode(raw));
3530
4020
  } catch {
3531
4021
  continue;
3532
4022
  }
@@ -3563,14 +4053,14 @@ async function pushRefundWithoutReceiver(ark, input) {
3563
4053
  const info = await ark.getInfo();
3564
4054
  let serverUnrollScript;
3565
4055
  try {
3566
- serverUnrollScript = import_sdk15.CSVMultisigTapscript.decode(import_base14.hex.decode(info.checkpointTapscript));
4056
+ serverUnrollScript = import_sdk16.CSVMultisigTapscript.decode(import_base14.hex.decode(info.checkpointTapscript));
3567
4057
  } catch {
3568
4058
  throw new Error("invalid checkpointTapscript from the Arkade server");
3569
4059
  }
3570
4060
  const leaf = input.script.refundWithoutReceiver();
3571
4061
  const tapTree = input.script.encode();
3572
4062
  const amount = input.vtxos.reduce((sum, vtxo) => sum + vtxo.value, 0);
3573
- const { arkTx, checkpoints } = (0, import_sdk15.buildOffchainTx)(
4063
+ const { arkTx, checkpoints } = (0, import_sdk16.buildOffchainTx)(
3574
4064
  input.vtxos.map((vtxo) => ({
3575
4065
  txid: vtxo.txid,
3576
4066
  vout: vtxo.vout,
@@ -3586,8 +4076,8 @@ async function pushRefundWithoutReceiver(ark, input) {
3586
4076
  import_base14.base64.encode(signedArkTx.toPSBT()),
3587
4077
  checkpoints.map((c) => import_base14.base64.encode(c.toPSBT()))
3588
4078
  );
3589
- (0, import_sdk15.assertSubmittedArkTxid)(submitted, signedArkTx, "refundWithoutReceiver");
3590
- const matched = (0, import_sdk15.matchServerCheckpoints)(
4079
+ (0, import_sdk16.assertSubmittedArkTxid)(submitted, signedArkTx, "refundWithoutReceiver");
4080
+ const matched = (0, import_sdk16.matchServerCheckpoints)(
3591
4081
  submitted.signedCheckpointTxs,
3592
4082
  checkpoints,
3593
4083
  "refundWithoutReceiver"
@@ -3695,14 +4185,14 @@ async function pushClaim(ark, input) {
3695
4185
  const info = await ark.getInfo();
3696
4186
  let serverUnrollScript;
3697
4187
  try {
3698
- serverUnrollScript = import_sdk16.CSVMultisigTapscript.decode(import_base15.hex.decode(info.checkpointTapscript));
4188
+ serverUnrollScript = import_sdk17.CSVMultisigTapscript.decode(import_base15.hex.decode(info.checkpointTapscript));
3699
4189
  } catch {
3700
4190
  throw new Error("invalid checkpointTapscript from the Arkade server");
3701
4191
  }
3702
4192
  const leaf = input.script.claim();
3703
4193
  const tapTree = input.script.encode();
3704
- const arkTxid = await (0, import_sdk16.signAndSubmitOffchainTx)({
3705
- identity: (0, import_sdk16.claimWithPreimageIdentity)(input.receiver, input.preimage),
4194
+ const arkTxid = await (0, import_sdk17.signAndSubmitOffchainTx)({
4195
+ identity: (0, import_sdk17.claimWithPreimageIdentity)(input.receiver, input.preimage),
3706
4196
  provider: ark,
3707
4197
  inputs: input.vtxos.map((vtxo) => ({
3708
4198
  txid: vtxo.txid,
@@ -3749,7 +4239,7 @@ async function claimReceiveLockup(indexer, ark, input) {
3749
4239
  }
3750
4240
 
3751
4241
  // src/refundBlocked.ts
3752
- var import_sdk17 = require("@arkade-os/sdk");
4242
+ var import_sdk18 = require("@arkade-os/sdk");
3753
4243
  var RefundNotLocallyPossibleError = class extends Error {
3754
4244
  constructor(reason, message, options) {
3755
4245
  super(message, options);
@@ -3766,16 +4256,16 @@ async function senderIdentityForSwapRecord(wallet, record) {
3766
4256
  );
3767
4257
  }
3768
4258
  try {
3769
- return await (0, import_sdk17.contractSigner)(wallet, record.signingDescriptor);
4259
+ return await (0, import_sdk18.contractSigner)(wallet, record.signingDescriptor);
3770
4260
  } catch (cause) {
3771
- if (cause instanceof import_sdk17.WalletCannotSignError) {
4261
+ if (cause instanceof import_sdk18.WalletCannotSignError) {
3772
4262
  throw new RefundNotLocallyPossibleError(
3773
4263
  "unsignable-wallet",
3774
4264
  `this wallet holds ${record.signingDescriptor} but cannot sign with it; attach its signer`,
3775
4265
  { cause }
3776
4266
  );
3777
4267
  }
3778
- if (cause instanceof import_sdk17.ForeignDescriptorError) {
4268
+ if (cause instanceof import_sdk18.ForeignDescriptorError) {
3779
4269
  throw new RefundNotLocallyPossibleError(
3780
4270
  "foreign-descriptor",
3781
4271
  `this wallet cannot derive ${record.signingDescriptor}; the swap was created on another wallet`,
@@ -3811,7 +4301,7 @@ function arkadeRefunder(deps) {
3811
4301
 
3812
4302
  // src/swapManager.ts
3813
4303
  var import_base16 = require("@scure/base");
3814
- var import_sdk18 = require("@arkade-os/sdk");
4304
+ var import_sdk19 = require("@arkade-os/sdk");
3815
4305
  function nextOnchainAction(input) {
3816
4306
  switch (input.phase.phase) {
3817
4307
  case "unfunded":
@@ -4301,7 +4791,7 @@ var RfqSwapManager = class {
4301
4791
  });
4302
4792
  return;
4303
4793
  }
4304
- if (!(0, import_sdk18.isContractVtxoEvent)(event)) return;
4794
+ if (!(0, import_sdk19.isContractVtxoEvent)(event)) return;
4305
4795
  const swap = this.byLockupScript.get(event.contractScript);
4306
4796
  if (!swap) return;
4307
4797
  void this.pollSwap(swap).catch(() => {
@@ -4848,7 +5338,7 @@ var errorMessage = (error) => error instanceof Error ? error.message : String(er
4848
5338
  var outpointKey = (vtxo) => `${vtxo.txid}:${vtxo.vout}`;
4849
5339
 
4850
5340
  // src/activity.ts
4851
- var import_sdk19 = require("@arkade-os/sdk");
5341
+ var import_sdk20 = require("@arkade-os/sdk");
4852
5342
  var import_base17 = require("@scure/base");
4853
5343
  var LABELS = {
4854
5344
  lightning_send: "Lightning send",
@@ -4924,7 +5414,7 @@ async function activityInputOf(record, indexer) {
4924
5414
  async function lockupTxids(indexer, record, wantFunding) {
4925
5415
  let script;
4926
5416
  try {
4927
- script = import_base17.hex.encode(import_sdk19.ArkAddress.decode(record.lockupAddress).pkScript);
5417
+ script = import_base17.hex.encode(import_sdk20.ArkAddress.decode(record.lockupAddress).pkScript);
4928
5418
  } catch {
4929
5419
  return [];
4930
5420
  }
@@ -4944,6 +5434,7 @@ async function lockupTxids(indexer, record, wantFunding) {
4944
5434
  0 && (module.exports = {
4945
5435
  ARKADE_ASSET,
4946
5436
  ARKADE_BTC,
5437
+ ASSET_CARRIER_SATS,
4947
5438
  AddressMismatch,
4948
5439
  BTC_ASSET_ID,
4949
5440
  CROSS_ASSET_RAIL,
@@ -4958,6 +5449,7 @@ async function lockupTxids(indexer, record, wantFunding) {
4958
5449
  LockupContractMissing,
4959
5450
  LockupNeedsRecoveryError,
4960
5451
  LockupRegistrationFailed,
5452
+ MARKET_CORRIDORS,
4961
5453
  MAX_MIN_CONFIRMATIONS,
4962
5454
  MIN_CLAIM_WINDOW_SECONDS,
4963
5455
  MIN_HEADROOM_SECONDS,
@@ -4972,6 +5464,7 @@ async function lockupTxids(indexer, record, wantFunding) {
4972
5464
  PreimageNotRecoverableError,
4973
5465
  QUOTE_OPTIONS,
4974
5466
  REFUND_MTP_LAG_SECONDS,
5467
+ RFQ_REFUSAL_ERROR_CODES,
4975
5468
  RFQ_RESOLVED_STATES,
4976
5469
  RFQ_SWAP_RETENTION_SECONDS,
4977
5470
  RFQ_SWAP_TERMINAL_STATES,
@@ -5015,11 +5508,14 @@ async function lockupTxids(indexer, record, wantFunding) {
5015
5508
  discoverMarkets,
5016
5509
  encodeOffer,
5017
5510
  extractPreimage,
5511
+ fillOffer,
5018
5512
  findLockupVtxos,
5019
5513
  findMarket,
5020
5514
  getAssetSwaps,
5021
5515
  getAssetSwapsOrThrow,
5022
5516
  httpTransport,
5517
+ isRfqMarket,
5518
+ isRfqRefusalErrorCode,
5023
5519
  isRfqSwapTerminal,
5024
5520
  isRfqTerminal,
5025
5521
  l1ScriptForAddress,
@@ -5028,6 +5524,9 @@ async function lockupTxids(indexer, record, wantFunding) {
5028
5524
  lightningSendVtxoScript,
5029
5525
  lockupContractParams,
5030
5526
  makeCachedFeedFetch,
5527
+ marketAssetId,
5528
+ marketCorridor,
5529
+ marketPairLabel,
5031
5530
  newPreimage,
5032
5531
  newRfqId,
5033
5532
  nextOnchainAction,
@@ -5045,13 +5544,16 @@ async function lockupTxids(indexer, record, wantFunding) {
5045
5544
  rebuildRfqSwap,
5046
5545
  receiveVtxoScript,
5047
5546
  refundIfUnresolved,
5547
+ registerAssetSwapRestore,
5048
5548
  registerLockupContract,
5049
5549
  relayTransport,
5050
5550
  requestLightningReceive,
5051
5551
  requestLightningSend,
5052
5552
  requestOnchainReceive,
5053
5553
  requestOnchainSend,
5554
+ restoreAssetSwapRepository,
5054
5555
  restoreAssetSwaps,
5556
+ restoreOfferCoverage,
5055
5557
  retireSettledOfferContracts,
5056
5558
  revealClaimPacket,
5057
5559
  revealFieldsFromScript,