@arkade-os/boltz-swap 0.3.32 → 0.3.34

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.
Files changed (31) hide show
  1. package/README.md +22 -26
  2. package/dist/{arkade-swaps-CS8FZSVL.d.cts → arkade-swaps-BXAD1s8j.d.ts} +69 -8
  3. package/dist/{arkade-swaps-WiKCanCL.d.ts → arkade-swaps-CfMets16.d.cts} +69 -8
  4. package/dist/{chunk-NHBWNN6H.js → chunk-5K2FS2FE.js} +8 -27
  5. package/dist/chunk-SJQJQO7P.js +25 -0
  6. package/dist/{chunk-B3Q4TFWT.js → chunk-TDBUZE4N.js} +817 -866
  7. package/dist/expo/background.cjs +840 -892
  8. package/dist/expo/background.d.cts +3 -3
  9. package/dist/expo/background.d.ts +3 -3
  10. package/dist/expo/background.js +9 -20
  11. package/dist/expo/index.cjs +840 -870
  12. package/dist/expo/index.d.cts +8 -6
  13. package/dist/expo/index.d.ts +8 -6
  14. package/dist/expo/index.js +17 -20
  15. package/dist/index.cjs +1034 -1022
  16. package/dist/index.d.cts +95 -11
  17. package/dist/index.d.ts +95 -11
  18. package/dist/index.js +164 -119
  19. package/dist/repositories/realm/index.cjs +10 -22
  20. package/dist/repositories/realm/index.d.cts +7 -5
  21. package/dist/repositories/realm/index.d.ts +7 -5
  22. package/dist/repositories/realm/index.js +8 -22
  23. package/dist/repositories/sqlite/index.cjs +12 -23
  24. package/dist/repositories/sqlite/index.d.cts +1 -1
  25. package/dist/repositories/sqlite/index.d.ts +1 -1
  26. package/dist/repositories/sqlite/index.js +10 -23
  27. package/dist/{swapsPollProcessor-BF3uTFae.d.cts → swapsPollProcessor-BpAqG0V6.d.cts} +3 -3
  28. package/dist/{swapsPollProcessor-wYOMzldd.d.ts → swapsPollProcessor-DFVOAy_-.d.ts} +3 -3
  29. package/dist/{types-BBI7-KJ0.d.cts → types--axEWA8c.d.cts} +42 -4
  30. package/dist/{types-BBI7-KJ0.d.ts → types--axEWA8c.d.ts} +42 -4
  31. package/package.json +10 -25
@@ -1,3 +1,9 @@
1
+ import {
2
+ applyCreatedAtOrder,
3
+ applySwapsFilter,
4
+ hasImpossibleSwapsFilter
5
+ } from "./chunk-SJQJQO7P.js";
6
+
1
7
  // src/errors.ts
2
8
  var SwapError = class extends Error {
3
9
  /** Whether the swap can still be claimed (default: false). */
@@ -7,7 +13,10 @@ var SwapError = class extends Error {
7
13
  /** The pending swap associated with this error, if available. */
8
14
  pendingSwap;
9
15
  constructor(options = {}) {
10
- super(options.message ?? "Error during swap.");
16
+ super(
17
+ options.message ?? "Error during swap.",
18
+ options.cause !== void 0 ? { cause: options.cause } : void 0
19
+ );
11
20
  this.name = "SwapError";
12
21
  this.isClaimable = options.isClaimable ?? false;
13
22
  this.isRefundable = options.isRefundable ?? false;
@@ -99,6 +108,100 @@ var TransactionRefundedError = class extends SwapError {
99
108
  this.name = "TransactionRefundedError";
100
109
  }
101
110
  };
111
+ var QuoteRejectedError = class _QuoteRejectedError extends SwapError {
112
+ reason;
113
+ quotedAmount;
114
+ floor;
115
+ constructor(options) {
116
+ super({
117
+ message: options.message ?? _QuoteRejectedError.defaultMessage(options),
118
+ ...options
119
+ });
120
+ this.name = "QuoteRejectedError";
121
+ this.reason = options.reason;
122
+ this.quotedAmount = "quotedAmount" in options ? options.quotedAmount : void 0;
123
+ this.floor = "floor" in options ? options.floor : void 0;
124
+ }
125
+ static defaultMessage(options) {
126
+ switch (options.reason) {
127
+ case "below_floor":
128
+ return `Boltz quote ${options.quotedAmount} is below acceptable floor ${options.floor}`;
129
+ case "non_positive":
130
+ return `Boltz quote ${options.quotedAmount} is not positive`;
131
+ case "non_safe_integer":
132
+ return `Boltz quote ${options.quotedAmount} is not a safe positive satoshi integer`;
133
+ case "no_baseline":
134
+ return "Cannot accept quote: no minAcceptableAmount and no stored pending swap";
135
+ }
136
+ }
137
+ /**
138
+ * Serialize into a plain `Error` whose `.message` carries the full
139
+ * rejection payload as JSON behind a marker prefix. Structured clone
140
+ * (used by `postMessage` between page and service worker) preserves
141
+ * `Error.message` reliably but strips custom `.name` and own properties,
142
+ * so we move the typed data into the message field for transport.
143
+ */
144
+ toTransportError() {
145
+ return new Error(
146
+ QUOTE_REJECTION_TRANSPORT_PREFIX + JSON.stringify({
147
+ reason: this.reason,
148
+ message: this.message,
149
+ quotedAmount: this.quotedAmount,
150
+ floor: this.floor
151
+ })
152
+ );
153
+ }
154
+ /**
155
+ * Inverse of `toTransportError`. Returns a real `QuoteRejectedError` if
156
+ * `error` carries the transport prefix, else `null`.
157
+ */
158
+ static fromTransportError(error) {
159
+ if (!(error instanceof Error) || !error.message.startsWith(QUOTE_REJECTION_TRANSPORT_PREFIX)) {
160
+ return null;
161
+ }
162
+ const payload = error.message.slice(QUOTE_REJECTION_TRANSPORT_PREFIX.length);
163
+ let data;
164
+ try {
165
+ data = JSON.parse(payload);
166
+ } catch {
167
+ return null;
168
+ }
169
+ if (typeof data.reason !== "string" || !QUOTE_REJECTION_REASONS.has(data.reason)) {
170
+ return null;
171
+ }
172
+ const message = typeof data.message === "string" ? data.message : void 0;
173
+ const reason = data.reason;
174
+ const quotedAmount = typeof data.quotedAmount === "number" ? data.quotedAmount : null;
175
+ const floor = typeof data.floor === "number" ? data.floor : null;
176
+ switch (reason) {
177
+ case "below_floor":
178
+ if (quotedAmount === null || floor === null) return null;
179
+ return new _QuoteRejectedError({
180
+ reason,
181
+ quotedAmount,
182
+ floor,
183
+ message
184
+ });
185
+ case "non_positive":
186
+ case "non_safe_integer":
187
+ if (quotedAmount === null) return null;
188
+ return new _QuoteRejectedError({
189
+ reason,
190
+ quotedAmount,
191
+ message
192
+ });
193
+ case "no_baseline":
194
+ return new _QuoteRejectedError({ reason, message });
195
+ }
196
+ }
197
+ };
198
+ var QUOTE_REJECTION_TRANSPORT_PREFIX = "QUOTE_REJECTED::";
199
+ var QUOTE_REJECTION_REASONS = /* @__PURE__ */ new Set([
200
+ "below_floor",
201
+ "non_positive",
202
+ "non_safe_integer",
203
+ "no_baseline"
204
+ ]);
102
205
  var BoltzRefundError = class extends Error {
103
206
  constructor(message, cause) {
104
207
  super(message);
@@ -111,18 +214,10 @@ var BoltzRefundError = class extends Error {
111
214
  import { Transaction } from "@arkade-os/sdk";
112
215
  import { base64 } from "@scure/base";
113
216
  var isSubmarineFailedStatus = (status) => {
114
- return [
115
- "invoice.failedToPay",
116
- "transaction.lockupFailed",
117
- "swap.expired"
118
- ].includes(status);
217
+ return ["invoice.failedToPay", "transaction.lockupFailed", "swap.expired"].includes(status);
119
218
  };
120
219
  var isSubmarineFinalStatus = (status) => {
121
- return [
122
- "invoice.failedToPay",
123
- "transaction.claimed",
124
- "swap.expired"
125
- ].includes(status);
220
+ return ["invoice.failedToPay", "transaction.claimed", "swap.expired"].includes(status);
126
221
  };
127
222
  var isSubmarinePendingStatus = (status) => {
128
223
  return [
@@ -136,11 +231,7 @@ var isSubmarinePendingStatus = (status) => {
136
231
  ].includes(status);
137
232
  };
138
233
  var isSubmarineRefundableStatus = (status) => {
139
- return [
140
- "invoice.failedToPay",
141
- "transaction.lockupFailed",
142
- "swap.expired"
143
- ].includes(status);
234
+ return ["invoice.failedToPay", "transaction.lockupFailed", "swap.expired"].includes(status);
144
235
  };
145
236
  var isSubmarineSuccessStatus = (status) => {
146
237
  return status === "transaction.claimed";
@@ -164,11 +255,7 @@ var isReverseFinalStatus = (status) => {
164
255
  ].includes(status);
165
256
  };
166
257
  var isReversePendingStatus = (status) => {
167
- return [
168
- "swap.created",
169
- "transaction.mempool",
170
- "transaction.confirmed"
171
- ].includes(status);
258
+ return ["swap.created", "transaction.mempool", "transaction.confirmed"].includes(status);
172
259
  };
173
260
  var isReverseClaimableStatus = (status) => {
174
261
  return ["transaction.mempool", "transaction.confirmed"].includes(status);
@@ -180,10 +267,7 @@ var isChainFailedStatus = (status) => {
180
267
  return ["transaction.failed", "swap.expired"].includes(status);
181
268
  };
182
269
  var isChainClaimableStatus = (status) => {
183
- return [
184
- "transaction.server.mempool",
185
- "transaction.server.confirmed"
186
- ].includes(status);
270
+ return ["transaction.server.mempool", "transaction.server.confirmed"].includes(status);
187
271
  };
188
272
  var isChainFinalStatus = (status) => {
189
273
  return [
@@ -322,8 +406,7 @@ var BASE_URLS = {
322
406
  var isSwapNotFoundBody = (error) => {
323
407
  const needle = "could not find swap";
324
408
  const fromJson = error.errorData?.error;
325
- if (typeof fromJson === "string" && fromJson.toLowerCase().includes(needle))
326
- return true;
409
+ if (typeof fromJson === "string" && fromJson.toLowerCase().includes(needle)) return true;
327
410
  return error.message.toLowerCase().includes(needle);
328
411
  };
329
412
  var BoltzSwapProvider = class {
@@ -337,10 +420,7 @@ var BoltzSwapProvider = class {
337
420
  this.network = config.network;
338
421
  this.referralId = config.referralId ?? "arkade-ts-sdk";
339
422
  const apiUrl = config.apiUrl || BASE_URLS[config.network];
340
- if (!apiUrl)
341
- throw new Error(
342
- `API URL is required for network: ${config.network}`
343
- );
423
+ if (!apiUrl) throw new Error(`API URL is required for network: ${config.network}`);
344
424
  this.apiUrl = apiUrl;
345
425
  this.wsUrl = this.apiUrl.replace(/^http(s)?:\/\//, "ws$1://").replace("9069", "9004") + "/v2/ws";
346
426
  }
@@ -359,10 +439,7 @@ var BoltzSwapProvider = class {
359
439
  /** Returns current Lightning swap fees (submarine + reverse) from Boltz. */
360
440
  async getFees() {
361
441
  const [submarine, reverse] = await Promise.all([
362
- this.request(
363
- "/v2/swap/submarine",
364
- "GET"
365
- ),
442
+ this.request("/v2/swap/submarine", "GET"),
366
443
  this.request("/v2/swap/reverse", "GET")
367
444
  ]);
368
445
  if (!isGetSubmarinePairsResponse(submarine))
@@ -382,10 +459,7 @@ var BoltzSwapProvider = class {
382
459
  }
383
460
  /** Returns current Lightning swap min/max limits from Boltz. */
384
461
  async getLimits() {
385
- const response = await this.request(
386
- "/v2/swap/submarine",
387
- "GET"
388
- );
462
+ const response = await this.request("/v2/swap/submarine", "GET");
389
463
  if (!isGetSubmarinePairsResponse(response))
390
464
  throw new SchemaError({ message: "error fetching limits" });
391
465
  return {
@@ -395,10 +469,7 @@ var BoltzSwapProvider = class {
395
469
  }
396
470
  /** Returns the current BTC chain tip height from Boltz. */
397
471
  async getChainHeight() {
398
- const response = await this.request(
399
- "/v2/chain/heights",
400
- "GET"
401
- );
472
+ const response = await this.request("/v2/chain/heights", "GET");
402
473
  if (typeof response?.BTC !== "number")
403
474
  throw new SchemaError({
404
475
  message: "error fetching chain heights"
@@ -428,10 +499,7 @@ var BoltzSwapProvider = class {
428
499
  async getSwapStatus(id) {
429
500
  let response;
430
501
  try {
431
- response = await this.request(
432
- `/v2/swap/${id}`,
433
- "GET"
434
- );
502
+ response = await this.request(`/v2/swap/${id}`, "GET");
435
503
  } catch (error) {
436
504
  if (error instanceof NetworkError && error.statusCode === 404 && isSwapNotFoundBody(error)) {
437
505
  throw new SwapNotFoundError(id, error.errorData);
@@ -527,12 +595,10 @@ var BoltzSwapProvider = class {
527
595
  throw new SwapError({ message: "Invalid 'to' chain" });
528
596
  if (["BTC", "ARK"].indexOf(from) === -1)
529
597
  throw new SwapError({ message: "Invalid 'from' chain" });
530
- if (to === from)
531
- throw new SwapError({ message: "Invalid swap direction" });
598
+ if (to === from) throw new SwapError({ message: "Invalid swap direction" });
532
599
  if (!preimageHash || preimageHash.length != 64)
533
600
  throw new SwapError({ message: "Invalid preimageHash" });
534
- if (feeSatsPerByte <= 0)
535
- throw new SwapError({ message: "Invalid feeSatsPerByte" });
601
+ if (feeSatsPerByte <= 0) throw new SwapError({ message: "Invalid feeSatsPerByte" });
536
602
  if (serverLockAmount !== void 0 && userLockAmount !== void 0 || serverLockAmount === void 0 && userLockAmount === void 0)
537
603
  throw new SwapError({
538
604
  message: "Either serverLockAmount or userLockAmount must be provided"
@@ -587,12 +653,8 @@ var BoltzSwapProvider = class {
587
653
  message: "Error refunding submarine swap"
588
654
  });
589
655
  return {
590
- transaction: Transaction.fromPSBT(
591
- base64.decode(response.transaction)
592
- ),
593
- checkpoint: Transaction.fromPSBT(
594
- base64.decode(response.checkpoint)
595
- )
656
+ transaction: Transaction.fromPSBT(base64.decode(response.transaction)),
657
+ checkpoint: Transaction.fromPSBT(base64.decode(response.checkpoint))
596
658
  };
597
659
  }
598
660
  /** Requests Boltz co-signature for a chain swap refund. Returns signed transaction + checkpoint. */
@@ -611,53 +673,53 @@ var BoltzSwapProvider = class {
611
673
  message: "Error refunding chain swap"
612
674
  });
613
675
  return {
614
- transaction: Transaction.fromPSBT(
615
- base64.decode(response.transaction)
616
- ),
617
- checkpoint: Transaction.fromPSBT(
618
- base64.decode(response.checkpoint)
619
- )
676
+ transaction: Transaction.fromPSBT(base64.decode(response.transaction)),
677
+ checkpoint: Transaction.fromPSBT(base64.decode(response.checkpoint))
620
678
  };
621
679
  }
622
- /** Monitors swap status updates via WebSocket. Calls update callback on each status change. Resolves when terminal. */
680
+ /**
681
+ * Monitors swap status updates and forwards them to the update callback.
682
+ * Prefers a WebSocket subscription; on connection error or premature close
683
+ * it falls back to REST polling so callers don't fail when the WS endpoint
684
+ * is flaky (observed in CI). Resolves when the swap reaches a terminal
685
+ * status.
686
+ */
623
687
  async monitorSwap(swapId, update) {
624
688
  return new Promise((resolve, reject) => {
625
- const webSocket = new globalThis.WebSocket(this.wsUrl);
626
- const connectionTimeout = setTimeout(() => {
627
- webSocket.close();
628
- reject(new NetworkError("WebSocket connection timeout"));
629
- }, 3e4);
630
- webSocket.onerror = (error) => {
631
- clearTimeout(connectionTimeout);
632
- reject(
633
- new NetworkError(
634
- `WebSocket error: ${error.message}`
635
- )
636
- );
637
- };
638
- webSocket.onopen = () => {
639
- clearTimeout(connectionTimeout);
640
- webSocket.send(
641
- JSON.stringify({
642
- op: "subscribe",
643
- channel: "swap.update",
644
- args: [swapId]
645
- })
646
- );
689
+ let settled = false;
690
+ let lastStatus = null;
691
+ let pollTimer = null;
692
+ let webSocket = null;
693
+ let connectionTimeout = null;
694
+ const cleanup = () => {
695
+ if (connectionTimeout) {
696
+ clearTimeout(connectionTimeout);
697
+ connectionTimeout = null;
698
+ }
699
+ if (pollTimer) {
700
+ clearInterval(pollTimer);
701
+ pollTimer = null;
702
+ }
703
+ if (webSocket) {
704
+ try {
705
+ webSocket.close();
706
+ } catch {
707
+ }
708
+ webSocket = null;
709
+ }
647
710
  };
648
- webSocket.onclose = () => {
649
- clearTimeout(connectionTimeout);
650
- resolve();
711
+ const finish = (err) => {
712
+ if (settled) return;
713
+ settled = true;
714
+ cleanup();
715
+ if (err) reject(err);
716
+ else resolve();
651
717
  };
652
- webSocket.onmessage = async (rawMsg) => {
653
- const msg = JSON.parse(rawMsg.data);
654
- if (msg.event !== "update" || msg.args[0].id !== swapId) return;
655
- if (msg.args[0].error) {
656
- webSocket.close();
657
- reject(new SwapError({ message: msg.args[0].error }));
658
- }
659
- const status = msg.args[0].status;
660
- const negotiable = status === "transaction.lockupFailed" && msg.args[0].failureDetails?.actual !== void 0 && msg.args[0].failureDetails?.expected !== void 0;
718
+ const handleStatus = (status, data) => {
719
+ if (settled) return;
720
+ if (status === lastStatus) return;
721
+ lastStatus = status;
722
+ const negotiable = status === "transaction.lockupFailed" && data?.failureDetails?.actual !== void 0 && data?.failureDetails?.expected !== void 0;
661
723
  switch (status) {
662
724
  case "invoice.settled":
663
725
  case "transaction.claimed":
@@ -666,13 +728,13 @@ var BoltzSwapProvider = class {
666
728
  case "invoice.failedToPay":
667
729
  case "transaction.failed":
668
730
  case "swap.expired":
669
- webSocket.close();
670
- update(status, msg.args[0]);
671
- break;
731
+ update(status, data);
732
+ finish();
733
+ return;
672
734
  case "transaction.lockupFailed":
673
- if (!negotiable) webSocket.close();
674
- update(status, msg.args[0]);
675
- break;
735
+ update(status, data);
736
+ if (!negotiable) finish();
737
+ return;
676
738
  case "invoice.paid":
677
739
  case "invoice.pending":
678
740
  case "invoice.set":
@@ -682,9 +744,77 @@ var BoltzSwapProvider = class {
682
744
  case "transaction.claim.pending":
683
745
  case "transaction.server.mempool":
684
746
  case "transaction.server.confirmed":
685
- update(status, msg.args[0]);
747
+ update(status, data);
748
+ return;
686
749
  }
687
750
  };
751
+ const startPolling = () => {
752
+ if (settled || pollTimer) return;
753
+ const poll = async () => {
754
+ if (settled) return;
755
+ try {
756
+ const result = await this.getSwapStatus(swapId);
757
+ handleStatus(result.status, { ...result, id: swapId });
758
+ } catch {
759
+ }
760
+ };
761
+ pollTimer = setInterval(poll, 1e3);
762
+ poll();
763
+ };
764
+ try {
765
+ webSocket = new globalThis.WebSocket(this.wsUrl);
766
+ connectionTimeout = setTimeout(() => {
767
+ try {
768
+ webSocket?.close();
769
+ } catch {
770
+ }
771
+ webSocket = null;
772
+ startPolling();
773
+ }, 5e3);
774
+ webSocket.onerror = () => {
775
+ if (connectionTimeout) {
776
+ clearTimeout(connectionTimeout);
777
+ connectionTimeout = null;
778
+ }
779
+ webSocket = null;
780
+ startPolling();
781
+ };
782
+ webSocket.onopen = () => {
783
+ if (connectionTimeout) {
784
+ clearTimeout(connectionTimeout);
785
+ connectionTimeout = null;
786
+ }
787
+ webSocket?.send(
788
+ JSON.stringify({
789
+ op: "subscribe",
790
+ channel: "swap.update",
791
+ args: [swapId]
792
+ })
793
+ );
794
+ };
795
+ webSocket.onclose = () => {
796
+ if (connectionTimeout) {
797
+ clearTimeout(connectionTimeout);
798
+ connectionTimeout = null;
799
+ }
800
+ if (!settled && !pollTimer) startPolling();
801
+ };
802
+ webSocket.onmessage = (rawMsg) => {
803
+ try {
804
+ const msg = JSON.parse(rawMsg.data);
805
+ if (msg.event !== "update" || msg.args?.[0]?.id !== swapId) return;
806
+ if (msg.args[0].error) {
807
+ finish(new SwapError({ message: msg.args[0].error }));
808
+ return;
809
+ }
810
+ handleStatus(msg.args[0].status, msg.args[0]);
811
+ } catch {
812
+ }
813
+ };
814
+ } catch {
815
+ webSocket = null;
816
+ startPolling();
817
+ }
688
818
  });
689
819
  }
690
820
  /** Returns current chain swap fees for a given pair (e.g. ARK→BTC). */
@@ -692,10 +822,7 @@ var BoltzSwapProvider = class {
692
822
  if (from === to) {
693
823
  throw new SwapError({ message: "Invalid chain pair" });
694
824
  }
695
- const response = await this.request(
696
- "/v2/swap/chain",
697
- "GET"
698
- );
825
+ const response = await this.request("/v2/swap/chain", "GET");
699
826
  if (!isGetChainPairsResponse(response))
700
827
  throw new SchemaError({ message: "error fetching fees" });
701
828
  if (!response[from]?.[to]) {
@@ -710,10 +837,7 @@ var BoltzSwapProvider = class {
710
837
  if (from === to) {
711
838
  throw new SwapError({ message: "Invalid chain pair" });
712
839
  }
713
- const response = await this.request(
714
- "/v2/swap/chain",
715
- "GET"
716
- );
840
+ const response = await this.request("/v2/swap/chain", "GET");
717
841
  if (!isGetChainPairsResponse(response))
718
842
  throw new SchemaError({ message: "error fetching limits" });
719
843
  if (!response[from]?.[to]) {
@@ -842,9 +966,7 @@ var BoltzSwapProvider = class {
842
966
  return await response.json();
843
967
  } catch (error) {
844
968
  if (error instanceof NetworkError) throw error;
845
- throw new NetworkError(
846
- `Request to ${url} failed: ${error.message}`
847
- );
969
+ throw new NetworkError(`Request to ${url} failed: ${error.message}`);
848
970
  }
849
971
  }
850
972
  };
@@ -854,9 +976,7 @@ import bolt11 from "light-bolt11-decoder";
854
976
  import { ArkAddress } from "@arkade-os/sdk";
855
977
  var decodeInvoice = (invoice) => {
856
978
  const decoded = bolt11.decode(invoice);
857
- const millisats = Number(
858
- decoded.sections.find((s) => s.name === "amount")?.value ?? "0"
859
- );
979
+ const millisats = Number(decoded.sections.find((s) => s.name === "amount")?.value ?? "0");
860
980
  return {
861
981
  expiry: decoded.expiry ?? 3600,
862
982
  amountSats: Math.floor(millisats / 1e3),
@@ -925,6 +1045,13 @@ var SwapManager = class _SwapManager {
925
1045
  * enough that a real "swap unknown to this provider" surfaces quickly.
926
1046
  */
927
1047
  static NOT_FOUND_THRESHOLD = 10;
1048
+ /**
1049
+ * Delay between re-attempts of a chain refund that left VTXOs deferred
1050
+ * (e.g. pre-CLTV recoverable VTXO, or Boltz 3-of-3 rejected before CLTV
1051
+ * has elapsed). Boltz won't send another status update once the swap
1052
+ * is `swap.expired`, so the manager owns the local retry cadence.
1053
+ */
1054
+ static REFUND_RETRY_DELAY_MS = 6e4;
928
1055
  swapProvider;
929
1056
  config;
930
1057
  // Event listeners storage (supports multiple listeners per event)
@@ -943,6 +1070,11 @@ var SwapManager = class _SwapManager {
943
1070
  reconnectTimer = null;
944
1071
  initialPollTimer = null;
945
1072
  pollRetryTimers = /* @__PURE__ */ new Map();
1073
+ // Per-swap retry timers for chain refunds that left work undone
1074
+ // (refundArk returned `skipped > 0`). The swap is held in
1075
+ // `monitoredSwaps` past its terminal Boltz status until the local
1076
+ // refund completes or the manager stops.
1077
+ refundRetryTimers = /* @__PURE__ */ new Map();
946
1078
  // Per-swap counter of consecutive `SwapNotFoundError` responses from
947
1079
  // `getSwapStatus`. Reset on any successful poll. Once a swap reaches
948
1080
  // `NOT_FOUND_THRESHOLD` consecutive 404s the safety net trips and the
@@ -996,9 +1128,7 @@ var SwapManager = class _SwapManager {
996
1128
  this.wsConnectedListeners.add(config.events.onWebSocketConnected);
997
1129
  }
998
1130
  if (config.events?.onWebSocketDisconnected) {
999
- this.wsDisconnectedListeners.add(
1000
- config.events.onWebSocketDisconnected
1001
- );
1131
+ this.wsDisconnectedListeners.add(config.events.onWebSocketDisconnected);
1002
1132
  }
1003
1133
  this.currentReconnectDelay = this.config.reconnectDelayMs;
1004
1134
  this.currentPollRetryDelay = this.config.pollRetryDelayMs;
@@ -1141,6 +1271,10 @@ var SwapManager = class _SwapManager {
1141
1271
  clearTimeout(timer);
1142
1272
  }
1143
1273
  this.pollRetryTimers.clear();
1274
+ for (const timer of this.refundRetryTimers.values()) {
1275
+ clearTimeout(timer);
1276
+ }
1277
+ this.refundRetryTimers.clear();
1144
1278
  this.notFoundCounts.clear();
1145
1279
  }
1146
1280
  /**
@@ -1149,9 +1283,7 @@ var SwapManager = class _SwapManager {
1149
1283
  */
1150
1284
  setPollInterval(ms) {
1151
1285
  if (ms <= 0) {
1152
- throw new RangeError(
1153
- `setPollInterval: ms must be a positive number, got ${ms}`
1154
- );
1286
+ throw new RangeError(`setPollInterval: ms must be a positive number, got ${ms}`);
1155
1287
  }
1156
1288
  const cappedInterval = Math.min(ms, this.config.maxPollIntervalMs);
1157
1289
  if (cappedInterval !== ms) {
@@ -1160,10 +1292,7 @@ var SwapManager = class _SwapManager {
1160
1292
  );
1161
1293
  }
1162
1294
  this.config.pollInterval = cappedInterval;
1163
- this.currentPollRetryDelay = Math.min(
1164
- cappedInterval,
1165
- this.config.pollRetryDelayMs
1166
- );
1295
+ this.currentPollRetryDelay = Math.min(cappedInterval, this.config.pollRetryDelayMs);
1167
1296
  if (this.isRunning) {
1168
1297
  if (this.usePollingFallback) {
1169
1298
  this.startPollingFallback();
@@ -1202,6 +1331,11 @@ var SwapManager = class _SwapManager {
1202
1331
  clearTimeout(retryTimer);
1203
1332
  this.pollRetryTimers.delete(swapId);
1204
1333
  }
1334
+ const refundRetryTimer = this.refundRetryTimers.get(swapId);
1335
+ if (refundRetryTimer) {
1336
+ clearTimeout(refundRetryTimer);
1337
+ this.refundRetryTimers.delete(swapId);
1338
+ }
1205
1339
  this.notFoundCounts.delete(swapId);
1206
1340
  logger.log(`Removed swap ${swapId} from monitoring`);
1207
1341
  }
@@ -1248,9 +1382,7 @@ var SwapManager = class _SwapManager {
1248
1382
  }
1249
1383
  if (this.isFinalStatus(swap)) {
1250
1384
  if (isPendingReverseSwap(swap)) {
1251
- const response = await this.swapProvider.getReverseSwapTxId(
1252
- swap.id
1253
- );
1385
+ const response = await this.swapProvider.getReverseSwapTxId(swap.id);
1254
1386
  return { txid: response.id };
1255
1387
  }
1256
1388
  if (isPendingSubmarineSwap(swap)) {
@@ -1267,31 +1399,19 @@ var SwapManager = class _SwapManager {
1267
1399
  if (updatedSwap.status === "invoice.settled") {
1268
1400
  this.swapProvider.getReverseSwapTxId(updatedSwap.id).then((response) => resolve({ txid: response.id })).catch((error) => reject(error));
1269
1401
  } else {
1270
- reject(
1271
- new Error(
1272
- `Swap failed with status: ${updatedSwap.status}`
1273
- )
1274
- );
1402
+ reject(new Error(`Swap failed with status: ${updatedSwap.status}`));
1275
1403
  }
1276
1404
  } else if (isPendingSubmarineSwap(updatedSwap)) {
1277
1405
  if (updatedSwap.status === "transaction.claimed") {
1278
1406
  resolve({ txid: updatedSwap.id });
1279
1407
  } else {
1280
- reject(
1281
- new Error(
1282
- `Swap failed with status: ${updatedSwap.status}`
1283
- )
1284
- );
1408
+ reject(new Error(`Swap failed with status: ${updatedSwap.status}`));
1285
1409
  }
1286
1410
  } else if (isPendingChainSwap(updatedSwap)) {
1287
1411
  if (updatedSwap.status === "transaction.claimed") {
1288
1412
  resolve({ txid: updatedSwap.id });
1289
1413
  } else {
1290
- reject(
1291
- new Error(
1292
- `Swap failed with status: ${updatedSwap.status}`
1293
- )
1294
- );
1414
+ reject(new Error(`Swap failed with status: ${updatedSwap.status}`));
1295
1415
  }
1296
1416
  }
1297
1417
  };
@@ -1334,16 +1454,12 @@ var SwapManager = class _SwapManager {
1334
1454
  const connectionTimeout = setTimeout(() => {
1335
1455
  logger.error("WebSocket connection timeout");
1336
1456
  this.websocket?.close();
1337
- this.enterPollingFallback(
1338
- new NetworkError("WebSocket connection failed")
1339
- );
1457
+ this.enterPollingFallback(new NetworkError("WebSocket connection failed"));
1340
1458
  }, 1e4);
1341
1459
  this.websocket.onerror = (error) => {
1342
1460
  clearTimeout(connectionTimeout);
1343
1461
  logger.error("WebSocket error:", error);
1344
- this.enterPollingFallback(
1345
- new NetworkError("WebSocket connection failed")
1346
- );
1462
+ this.enterPollingFallback(new NetworkError("WebSocket connection failed"));
1347
1463
  };
1348
1464
  this.websocket.onopen = () => {
1349
1465
  clearTimeout(connectionTimeout);
@@ -1383,9 +1499,7 @@ var SwapManager = class _SwapManager {
1383
1499
  };
1384
1500
  } catch (error) {
1385
1501
  logger.error("Failed to create WebSocket:", error);
1386
- this.enterPollingFallback(
1387
- new NetworkError("WebSocket connection failed")
1388
- );
1502
+ this.enterPollingFallback(new NetworkError("WebSocket connection failed"));
1389
1503
  }
1390
1504
  }
1391
1505
  /**
@@ -1420,11 +1534,8 @@ var SwapManager = class _SwapManager {
1420
1534
  * Schedule WebSocket reconnection with exponential backoff
1421
1535
  */
1422
1536
  scheduleReconnect() {
1423
- if (this.reconnectTimer || this.webSocketUnavailable || !this.hasWebSocketSupport())
1424
- return;
1425
- logger.log(
1426
- `Scheduling WebSocket reconnect in ${this.currentReconnectDelay}ms`
1427
- );
1537
+ if (this.reconnectTimer || this.webSocketUnavailable || !this.hasWebSocketSupport()) return;
1538
+ logger.log(`Scheduling WebSocket reconnect in ${this.currentReconnectDelay}ms`);
1428
1539
  this.reconnectTimer = setTimeout(() => {
1429
1540
  this.reconnectTimer = null;
1430
1541
  this.isReconnecting = false;
@@ -1439,8 +1550,7 @@ var SwapManager = class _SwapManager {
1439
1550
  * Subscribe to a specific swap ID on the WebSocket
1440
1551
  */
1441
1552
  subscribeToSwap(swapId) {
1442
- if (!this.websocket || this.websocket.readyState !== WebSocket.OPEN)
1443
- return;
1553
+ if (!this.websocket || this.websocket.readyState !== WebSocket.OPEN) return;
1444
1554
  this.websocket.send(
1445
1555
  JSON.stringify({
1446
1556
  op: "subscribe",
@@ -1463,9 +1573,7 @@ var SwapManager = class _SwapManager {
1463
1573
  if (msg.args[0].error) {
1464
1574
  logger.error(`Swap ${swapId} error:`, msg.args[0].error);
1465
1575
  const error = new Error(msg.args[0].error);
1466
- this.swapFailedListeners.forEach(
1467
- (listener) => listener(swap, error)
1468
- );
1576
+ this.swapFailedListeners.forEach((listener) => listener(swap, error));
1469
1577
  return;
1470
1578
  }
1471
1579
  const newStatus = msg.args[0].status;
@@ -1483,19 +1591,14 @@ var SwapManager = class _SwapManager {
1483
1591
  const oldStatus = swap.status;
1484
1592
  if (oldStatus === newStatus) return;
1485
1593
  swap.status = newStatus;
1486
- this.swapUpdateListeners.forEach(
1487
- (listener) => listener(swap, oldStatus)
1488
- );
1594
+ this.swapUpdateListeners.forEach((listener) => listener(swap, oldStatus));
1489
1595
  const subscribers = this.swapSubscriptions.get(swap.id);
1490
1596
  if (subscribers) {
1491
1597
  subscribers.forEach((callback) => {
1492
1598
  try {
1493
1599
  callback(swap, oldStatus);
1494
1600
  } catch (error) {
1495
- logger.error(
1496
- `Error in swap subscription callback for ${swap.id}:`,
1497
- error
1498
- );
1601
+ logger.error(`Error in swap subscription callback for ${swap.id}:`, error);
1499
1602
  }
1500
1603
  });
1501
1604
  }
@@ -1504,15 +1607,57 @@ var SwapManager = class _SwapManager {
1504
1607
  await this.executeAutonomousAction(swap);
1505
1608
  }
1506
1609
  if (this.isFinalStatus(swap)) {
1507
- this.monitoredSwaps.delete(swap.id);
1508
- this.swapSubscriptions.delete(swap.id);
1509
- const retryTimer = this.pollRetryTimers.get(swap.id);
1510
- if (retryTimer) {
1511
- clearTimeout(retryTimer);
1512
- this.pollRetryTimers.delete(swap.id);
1610
+ if (this.refundRetryTimers.has(swap.id)) {
1611
+ return;
1513
1612
  }
1514
- this.swapCompletedListeners.forEach((listener) => listener(swap));
1613
+ this.finalizeMonitoredSwap(swap);
1614
+ }
1615
+ }
1616
+ /**
1617
+ * Drop a swap from monitoring and emit the terminal completion event.
1618
+ * Shared between the on-status-update finalization path and the
1619
+ * refund-retry finalization path (used when a previously-deferred
1620
+ * chain refund has finished its remaining work).
1621
+ */
1622
+ finalizeMonitoredSwap(swap) {
1623
+ if (!this.monitoredSwaps.has(swap.id)) return;
1624
+ this.monitoredSwaps.delete(swap.id);
1625
+ this.swapSubscriptions.delete(swap.id);
1626
+ const retryTimer = this.pollRetryTimers.get(swap.id);
1627
+ if (retryTimer) {
1628
+ clearTimeout(retryTimer);
1629
+ this.pollRetryTimers.delete(swap.id);
1515
1630
  }
1631
+ const refundRetry = this.refundRetryTimers.get(swap.id);
1632
+ if (refundRetry) {
1633
+ clearTimeout(refundRetry);
1634
+ this.refundRetryTimers.delete(swap.id);
1635
+ }
1636
+ this.swapCompletedListeners.forEach((listener) => listener(swap));
1637
+ }
1638
+ /**
1639
+ * Schedule another `executeAutonomousAction` run for a chain swap whose
1640
+ * refund left VTXOs deferred. After the retry completes, if no further
1641
+ * deferral was reported, finalize monitoring cleanup.
1642
+ */
1643
+ scheduleRefundRetry(swap, delayMs) {
1644
+ const existing = this.refundRetryTimers.get(swap.id);
1645
+ if (existing) clearTimeout(existing);
1646
+ this.refundRetryTimers.set(
1647
+ swap.id,
1648
+ setTimeout(async () => {
1649
+ this.refundRetryTimers.delete(swap.id);
1650
+ if (!this.isRunning) return;
1651
+ if (!this.monitoredSwaps.has(swap.id)) return;
1652
+ try {
1653
+ await this.executeAutonomousAction(swap);
1654
+ } finally {
1655
+ if (!this.refundRetryTimers.has(swap.id) && this.isFinalStatus(swap)) {
1656
+ this.finalizeMonitoredSwap(swap);
1657
+ }
1658
+ }
1659
+ }, delayMs)
1660
+ );
1516
1661
  }
1517
1662
  /**
1518
1663
  * Execute autonomous action based on swap status
@@ -1520,9 +1665,7 @@ var SwapManager = class _SwapManager {
1520
1665
  */
1521
1666
  async executeAutonomousAction(swap) {
1522
1667
  if (this.swapsInProgress.has(swap.id)) {
1523
- logger.log(
1524
- `Swap ${swap.id} is already being processed, skipping autonomous action`
1525
- );
1668
+ logger.log(`Swap ${swap.id} is already being processed, skipping autonomous action`);
1526
1669
  return;
1527
1670
  }
1528
1671
  try {
@@ -1537,9 +1680,7 @@ var SwapManager = class _SwapManager {
1537
1680
  if (isReverseClaimableStatus(swap.status)) {
1538
1681
  logger.log(`Auto-claiming reverse swap ${swap.id}`);
1539
1682
  await this.executeClaimAction(swap);
1540
- this.actionExecutedListeners.forEach(
1541
- (listener) => listener(swap, "claim")
1542
- );
1683
+ this.actionExecutedListeners.forEach((listener) => listener(swap, "claim"));
1543
1684
  }
1544
1685
  } else if (isPendingSubmarineSwap(swap)) {
1545
1686
  if (!swap.request?.invoice || swap.request.invoice.length === 0) {
@@ -1551,9 +1692,7 @@ var SwapManager = class _SwapManager {
1551
1692
  if (isSubmarineRefundableStatus(swap.status)) {
1552
1693
  logger.log(`Auto-refunding submarine swap ${swap.id}`);
1553
1694
  await this.executeRefundAction(swap);
1554
- this.actionExecutedListeners.forEach(
1555
- (listener) => listener(swap, "refund")
1556
- );
1695
+ this.actionExecutedListeners.forEach((listener) => listener(swap, "refund"));
1557
1696
  }
1558
1697
  } else if (isPendingChainSwap(swap)) {
1559
1698
  if (isChainClaimableStatus(swap.status)) {
@@ -1573,10 +1712,27 @@ var SwapManager = class _SwapManager {
1573
1712
  } else if (isChainRefundableStatus(swap.status)) {
1574
1713
  if (swap.request.from === "ARK") {
1575
1714
  logger.log(`Auto-refunding ARK chain swap ${swap.id}`);
1576
- await this.executeRefundArkAction(swap);
1577
- this.actionExecutedListeners.forEach(
1578
- (listener) => listener(swap, "refundArk")
1579
- );
1715
+ try {
1716
+ const outcome = await this.executeRefundArkAction(swap);
1717
+ if (outcome && outcome.skipped > 0) {
1718
+ logger.log(
1719
+ `Chain swap ${swap.id}: ${outcome.skipped} VTXO(s) deferred \u2014 scheduling refund retry`
1720
+ );
1721
+ this.scheduleRefundRetry(swap, _SwapManager.REFUND_RETRY_DELAY_MS);
1722
+ }
1723
+ this.actionExecutedListeners.forEach(
1724
+ (listener) => listener(swap, "refundArk")
1725
+ );
1726
+ } catch (error) {
1727
+ logger.error(
1728
+ `Auto-refunding ARK chain swap ${swap.id} failed; scheduling retry`,
1729
+ error
1730
+ );
1731
+ this.swapFailedListeners.forEach(
1732
+ (listener) => listener(swap, error)
1733
+ );
1734
+ this.scheduleRefundRetry(swap, _SwapManager.REFUND_RETRY_DELAY_MS);
1735
+ }
1580
1736
  }
1581
1737
  if (swap.request.from === "BTC") {
1582
1738
  logger.warn(
@@ -1603,13 +1759,8 @@ var SwapManager = class _SwapManager {
1603
1759
  }
1604
1760
  }
1605
1761
  } catch (error) {
1606
- logger.error(
1607
- `Failed to execute autonomous action for swap ${swap.id}:`,
1608
- error
1609
- );
1610
- this.swapFailedListeners.forEach(
1611
- (listener) => listener(swap, error)
1612
- );
1762
+ logger.error(`Failed to execute autonomous action for swap ${swap.id}:`, error);
1763
+ this.swapFailedListeners.forEach((listener) => listener(swap, error));
1613
1764
  } finally {
1614
1765
  this.swapsInProgress.delete(swap.id);
1615
1766
  }
@@ -1662,7 +1813,7 @@ var SwapManager = class _SwapManager {
1662
1813
  logger.error("refundArk callback not set");
1663
1814
  return;
1664
1815
  }
1665
- await this.refundArkCallback(swap);
1816
+ return this.refundArkCallback(swap);
1666
1817
  }
1667
1818
  /**
1668
1819
  * Execute sign server claim action for chain swap.
@@ -1710,9 +1861,7 @@ var SwapManager = class _SwapManager {
1710
1861
  logger.log(`Resuming chain refund for swap ${swap.id}`);
1711
1862
  await this.executeAutonomousAction(swap);
1712
1863
  } else if (isPendingChainSwap(swap) && swap.request.to === "ARK" && isChainSignableStatus(swap.status)) {
1713
- logger.log(
1714
- `Resuming server claim signing for swap ${swap.id}`
1715
- );
1864
+ logger.log(`Resuming server claim signing for swap ${swap.id}`);
1716
1865
  await this.executeAutonomousAction(swap);
1717
1866
  }
1718
1867
  } catch (error) {
@@ -1764,16 +1913,12 @@ var SwapManager = class _SwapManager {
1764
1913
  */
1765
1914
  async pollAllSwaps() {
1766
1915
  if (this.monitoredSwaps.size === 0) return;
1767
- const pollPromises = Array.from(this.monitoredSwaps.values()).map(
1768
- (swap) => this.pollSingleSwap(swap)
1769
- );
1916
+ const pollPromises = Array.from(this.monitoredSwaps.values()).filter((swap) => !this.refundRetryTimers.has(swap.id)).map((swap) => this.pollSingleSwap(swap));
1770
1917
  await Promise.allSettled(pollPromises);
1771
1918
  }
1772
1919
  async pollSingleSwap(swap) {
1773
1920
  try {
1774
- const statusResponse = await this.swapProvider.getSwapStatus(
1775
- swap.id
1776
- );
1921
+ const statusResponse = await this.swapProvider.getSwapStatus(swap.id);
1777
1922
  this.notFoundCounts.delete(swap.id);
1778
1923
  if (statusResponse.status !== swap.status) {
1779
1924
  await this.handleSwapStatusUpdate(swap, statusResponse.status);
@@ -1784,9 +1929,7 @@ var SwapManager = class _SwapManager {
1784
1929
  return;
1785
1930
  }
1786
1931
  if (error instanceof NetworkError && error.statusCode === 429) {
1787
- logger.warn(
1788
- `Rate-limited polling swap ${swap.id}, retrying in 2s`
1789
- );
1932
+ logger.warn(`Rate-limited polling swap ${swap.id}, retrying in 2s`);
1790
1933
  const existing = this.pollRetryTimers.get(swap.id);
1791
1934
  if (existing) clearTimeout(existing);
1792
1935
  this.pollRetryTimers.set(
@@ -1794,25 +1937,17 @@ var SwapManager = class _SwapManager {
1794
1937
  setTimeout(async () => {
1795
1938
  this.pollRetryTimers.delete(swap.id);
1796
1939
  try {
1797
- const retry = await this.swapProvider.getSwapStatus(
1798
- swap.id
1799
- );
1940
+ const retry = await this.swapProvider.getSwapStatus(swap.id);
1800
1941
  this.notFoundCounts.delete(swap.id);
1801
1942
  if (retry.status !== swap.status) {
1802
- await this.handleSwapStatusUpdate(
1803
- swap,
1804
- retry.status
1805
- );
1943
+ await this.handleSwapStatusUpdate(swap, retry.status);
1806
1944
  }
1807
1945
  } catch (retryError) {
1808
1946
  if (retryError instanceof SwapNotFoundError) {
1809
1947
  await this.handleSwapNotFound(swap);
1810
1948
  return;
1811
1949
  }
1812
- logger.error(
1813
- `Retry poll for swap ${swap.id} also failed:`,
1814
- retryError
1815
- );
1950
+ logger.error(`Retry poll for swap ${swap.id} also failed:`, retryError);
1816
1951
  }
1817
1952
  }, 2e3)
1818
1953
  );
@@ -1831,6 +1966,7 @@ var SwapManager = class _SwapManager {
1831
1966
  * Boltz endpoint).
1832
1967
  */
1833
1968
  async handleSwapNotFound(swap) {
1969
+ if (this.refundRetryTimers.has(swap.id)) return;
1834
1970
  const count = (this.notFoundCounts.get(swap.id) ?? 0) + 1;
1835
1971
  this.notFoundCounts.set(swap.id, count);
1836
1972
  logger.warn(
@@ -1851,6 +1987,7 @@ var SwapManager = class _SwapManager {
1851
1987
  * 404s without recovering anything.
1852
1988
  */
1853
1989
  async markSwapAsUnknownToProvider(swap) {
1990
+ if (this.refundRetryTimers.has(swap.id)) return;
1854
1991
  if (!this.monitoredSwaps.has(swap.id)) {
1855
1992
  this.notFoundCounts.delete(swap.id);
1856
1993
  return;
@@ -1863,10 +2000,13 @@ var SwapManager = class _SwapManager {
1863
2000
  clearTimeout(retryTimer);
1864
2001
  this.pollRetryTimers.delete(swap.id);
1865
2002
  }
2003
+ const refundRetryTimer = this.refundRetryTimers.get(swap.id);
2004
+ if (refundRetryTimer) {
2005
+ clearTimeout(refundRetryTimer);
2006
+ this.refundRetryTimers.delete(swap.id);
2007
+ }
1866
2008
  this.notFoundCounts.delete(swap.id);
1867
- this.swapUpdateListeners.forEach(
1868
- (listener) => listener(swap, oldStatus)
1869
- );
2009
+ this.swapUpdateListeners.forEach((listener) => listener(swap, oldStatus));
1870
2010
  const subscribers = this.swapSubscriptions.get(swap.id);
1871
2011
  if (subscribers) {
1872
2012
  subscribers.forEach((callback) => {
@@ -1931,9 +2071,7 @@ async function saveSwap(swap, saver) {
1931
2071
  if (saver.saveSubmarineSwap) {
1932
2072
  await saver.saveSubmarineSwap(swap);
1933
2073
  } else {
1934
- console.warn(
1935
- "No saveSubmarineSwap handler provided, swap not saved"
1936
- );
2074
+ console.warn("No saveSubmarineSwap handler provided, swap not saved");
1937
2075
  }
1938
2076
  } else if (isPendingChainSwap(swap)) {
1939
2077
  if (saver.saveChainSwap) {
@@ -2012,6 +2150,10 @@ function initDatabase(db) {
2012
2150
  swapStore.createIndex("createdAt", "createdAt", { unique: false });
2013
2151
  }
2014
2152
  }
2153
+ function asArray(v) {
2154
+ if (v === void 0) return void 0;
2155
+ return Array.isArray(v) ? v : [v];
2156
+ }
2015
2157
  var IndexedDbSwapRepository = class {
2016
2158
  constructor(dbName = DEFAULT_DB_NAME) {
2017
2159
  this.dbName = dbName;
@@ -2026,10 +2168,7 @@ var IndexedDbSwapRepository = class {
2026
2168
  async saveSwap(swap) {
2027
2169
  const db = await this.getDB();
2028
2170
  return new Promise((resolve, reject) => {
2029
- const transaction = db.transaction(
2030
- [STORE_SWAPS_STATE],
2031
- "readwrite"
2032
- );
2171
+ const transaction = db.transaction([STORE_SWAPS_STATE], "readwrite");
2033
2172
  const store = transaction.objectStore(STORE_SWAPS_STATE);
2034
2173
  const request = store.put(swap);
2035
2174
  request.onsuccess = () => resolve();
@@ -2039,10 +2178,7 @@ var IndexedDbSwapRepository = class {
2039
2178
  async deleteSwap(id) {
2040
2179
  const db = await this.getDB();
2041
2180
  return new Promise((resolve, reject) => {
2042
- const transaction = db.transaction(
2043
- [STORE_SWAPS_STATE],
2044
- "readwrite"
2045
- );
2181
+ const transaction = db.transaction([STORE_SWAPS_STATE], "readwrite");
2046
2182
  const store = transaction.objectStore(STORE_SWAPS_STATE);
2047
2183
  const request = store.delete(id);
2048
2184
  request.onsuccess = () => resolve();
@@ -2055,10 +2191,7 @@ var IndexedDbSwapRepository = class {
2055
2191
  async clear() {
2056
2192
  const db = await this.getDB();
2057
2193
  return new Promise((resolve, reject) => {
2058
- const transaction = db.transaction(
2059
- [STORE_SWAPS_STATE],
2060
- "readwrite"
2061
- );
2194
+ const transaction = db.transaction([STORE_SWAPS_STATE], "readwrite");
2062
2195
  const store = transaction.objectStore(STORE_SWAPS_STATE);
2063
2196
  const request = store.clear();
2064
2197
  request.onsuccess = () => resolve();
@@ -2075,11 +2208,10 @@ var IndexedDbSwapRepository = class {
2075
2208
  request.onsuccess = () => resolve(request.result ?? []);
2076
2209
  })
2077
2210
  );
2078
- return Promise.all(requests).then(
2079
- (results) => results.flatMap((result) => result)
2080
- );
2211
+ return Promise.all(requests).then((results) => results.flatMap((result) => result));
2081
2212
  }
2082
2213
  async getAllSwapsFromStore(filter) {
2214
+ if (hasImpossibleSwapsFilter(filter)) return [];
2083
2215
  const db = await this.getDB();
2084
2216
  const store = db.transaction([STORE_SWAPS_STATE], "readonly").objectStore(STORE_SWAPS_STATE);
2085
2217
  if (!filter || Object.keys(filter).length === 0) {
@@ -2089,9 +2221,8 @@ var IndexedDbSwapRepository = class {
2089
2221
  request.onerror = () => reject(request.error);
2090
2222
  });
2091
2223
  }
2092
- const normalizedFilter = normalizeFilter(filter);
2093
- if (normalizedFilter.has("id")) {
2094
- const ids = normalizedFilter.get("id");
2224
+ const ids = asArray(filter.id);
2225
+ if (ids) {
2095
2226
  const swaps = await Promise.all(
2096
2227
  ids.map(
2097
2228
  (id) => new Promise((resolve, reject) => {
@@ -2101,34 +2232,17 @@ var IndexedDbSwapRepository = class {
2101
2232
  })
2102
2233
  )
2103
2234
  );
2104
- return this.sortIfNeeded(
2105
- this.applySwapsFilter(swaps, normalizedFilter),
2106
- filter
2107
- );
2235
+ return applyCreatedAtOrder(applySwapsFilter(swaps, filter), filter);
2108
2236
  }
2109
- if (normalizedFilter.has("type")) {
2110
- const types = normalizedFilter.get("type");
2111
- const swaps = await this.getSwapsByIndexValues(
2112
- store,
2113
- "type",
2114
- types
2115
- );
2116
- return this.sortIfNeeded(
2117
- this.applySwapsFilter(swaps, normalizedFilter),
2118
- filter
2119
- );
2237
+ const types = asArray(filter.type);
2238
+ if (types) {
2239
+ const swaps = await this.getSwapsByIndexValues(store, "type", types);
2240
+ return applyCreatedAtOrder(applySwapsFilter(swaps, filter), filter);
2120
2241
  }
2121
- if (normalizedFilter.has("status")) {
2122
- const ids = normalizedFilter.get("status");
2123
- const swaps = await this.getSwapsByIndexValues(
2124
- store,
2125
- "status",
2126
- ids
2127
- );
2128
- return this.sortIfNeeded(
2129
- this.applySwapsFilter(swaps, normalizedFilter),
2130
- filter
2131
- );
2242
+ const statuses = asArray(filter.status);
2243
+ if (statuses) {
2244
+ const swaps = await this.getSwapsByIndexValues(store, "status", statuses);
2245
+ return applyCreatedAtOrder(applySwapsFilter(swaps, filter), filter);
2132
2246
  }
2133
2247
  if (filter.orderBy === "createdAt") {
2134
2248
  return this.getAllSwapsByCreatedAt(store, filter.orderDirection);
@@ -2138,22 +2252,7 @@ var IndexedDbSwapRepository = class {
2138
2252
  request.onsuccess = () => resolve(request.result ?? []);
2139
2253
  request.onerror = () => reject(request.error);
2140
2254
  });
2141
- return this.sortIfNeeded(
2142
- this.applySwapsFilter(allSwaps, normalizedFilter),
2143
- filter
2144
- );
2145
- }
2146
- applySwapsFilter(swaps, filter) {
2147
- return swaps.filter((swap) => {
2148
- if (swap === void 0) return false;
2149
- if (filter.has("id") && !filter.get("id")?.includes(swap.id))
2150
- return false;
2151
- if (filter.has("status") && !filter.get("status")?.includes(swap.status))
2152
- return false;
2153
- if (filter.has("type") && !filter.get("type")?.includes(swap.type))
2154
- return false;
2155
- return true;
2156
- });
2255
+ return applyCreatedAtOrder(applySwapsFilter(allSwaps, filter), filter);
2157
2256
  }
2158
2257
  async getAllSwapsByCreatedAt(store, orderDirection) {
2159
2258
  const index = store.index("createdAt");
@@ -2173,30 +2272,12 @@ var IndexedDbSwapRepository = class {
2173
2272
  };
2174
2273
  });
2175
2274
  }
2176
- sortIfNeeded(swaps, filter) {
2177
- if (filter?.orderBy !== "createdAt") return swaps;
2178
- const direction = filter.orderDirection === "asc" ? 1 : -1;
2179
- return swaps.slice().sort((a, b) => (a.createdAt - b.createdAt) * direction);
2180
- }
2181
2275
  async [Symbol.asyncDispose]() {
2182
2276
  if (!this.db) return;
2183
2277
  await closeDatabase(this.dbName);
2184
2278
  this.db = null;
2185
2279
  }
2186
2280
  };
2187
- var FILTER_FIELDS = ["id", "status", "type"];
2188
- function normalizeFilter(filter) {
2189
- const res = /* @__PURE__ */ new Map();
2190
- FILTER_FIELDS.forEach((current) => {
2191
- if (!filter?.[current]) return;
2192
- if (Array.isArray(filter[current])) {
2193
- res.set(current, filter[current]);
2194
- } else {
2195
- res.set(current, [filter[current]]);
2196
- }
2197
- });
2198
- return res;
2199
- }
2200
2281
 
2201
2282
  // src/arkade-swaps.ts
2202
2283
  import {
@@ -2225,8 +2306,7 @@ var findKeyIndex = (keys, target) => keys.findIndex((k) => equalBytes(target, k)
2225
2306
  var assertPublicKeys = (keys) => {
2226
2307
  const seen = /* @__PURE__ */ new Set();
2227
2308
  for (const key of keys) {
2228
- if (key.length !== 33)
2229
- throw new Error(`public key must be 33 bytes, got ${key.length}`);
2309
+ if (key.length !== 33) throw new Error(`public key must be 33 bytes, got ${key.length}`);
2230
2310
  const enc = hex3.encode(key);
2231
2311
  if (seen.has(enc)) throw new Error(`duplicate public key ${enc}`);
2232
2312
  seen.add(enc);
@@ -2234,9 +2314,7 @@ var assertPublicKeys = (keys) => {
2234
2314
  };
2235
2315
  var aggregateKeys = (publicKeys, tweak) => {
2236
2316
  assertPublicKeys([...publicKeys]);
2237
- return keyAggExport(
2238
- keyAggregate([...publicKeys], tweak ? [tweak] : [], tweak ? [true] : [])
2239
- );
2317
+ return keyAggExport(keyAggregate([...publicKeys], tweak ? [tweak] : [], tweak ? [true] : []));
2240
2318
  };
2241
2319
  var MusigKeyAgg = class _MusigKeyAgg {
2242
2320
  constructor(privateKey, myPublicKey, publicKeys, myIndex, aggPubkey, internalKey, _tweak) {
@@ -2283,18 +2361,12 @@ var MusigWithMessage = class {
2283
2361
  this.msg = msg;
2284
2362
  }
2285
2363
  generateNonce() {
2286
- const nonce = nonceGen(
2287
- this.myPublicKey,
2288
- this.privateKey,
2289
- this.aggPubkey,
2290
- this.msg
2291
- );
2364
+ const nonce = nonceGen(this.myPublicKey, this.privateKey, this.aggPubkey, this.msg);
2292
2365
  return new MusigWithNonce(
2293
2366
  this.privateKey,
2294
2367
  this.myPublicKey,
2295
2368
  this.publicKeys,
2296
2369
  this.myIndex,
2297
- this.aggPubkey,
2298
2370
  this.tweak,
2299
2371
  this.msg,
2300
2372
  nonce
@@ -2302,12 +2374,11 @@ var MusigWithMessage = class {
2302
2374
  }
2303
2375
  };
2304
2376
  var MusigWithNonce = class {
2305
- constructor(privateKey, myPublicKey, publicKeys, myIndex, aggPubkey, tweak, msg, nonce) {
2377
+ constructor(privateKey, myPublicKey, publicKeys, myIndex, tweak, msg, nonce) {
2306
2378
  this.privateKey = privateKey;
2307
2379
  this.myPublicKey = myPublicKey;
2308
2380
  this.publicKeys = publicKeys;
2309
2381
  this.myIndex = myIndex;
2310
- this.aggPubkey = aggPubkey;
2311
2382
  this.tweak = tweak;
2312
2383
  this.msg = msg;
2313
2384
  this.nonce = nonce;
@@ -2339,10 +2410,8 @@ var MusigWithNonce = class {
2339
2410
  const aggregatedNonce = nonceAggregate([...ordered]);
2340
2411
  return new MusigNoncesAggregated(
2341
2412
  this.privateKey,
2342
- this.myPublicKey,
2343
2413
  this.publicKeys,
2344
2414
  this.myIndex,
2345
- this.aggPubkey,
2346
2415
  this.tweak,
2347
2416
  this.msg,
2348
2417
  this.nonce,
@@ -2352,12 +2421,10 @@ var MusigWithNonce = class {
2352
2421
  }
2353
2422
  };
2354
2423
  var MusigNoncesAggregated = class {
2355
- constructor(privateKey, myPublicKey, publicKeys, myIndex, aggPubkey, tweak, msg, nonce, pubNonces, aggregatedNonce) {
2424
+ constructor(privateKey, publicKeys, myIndex, tweak, msg, nonce, pubNonces, aggregatedNonce) {
2356
2425
  this.privateKey = privateKey;
2357
- this.myPublicKey = myPublicKey;
2358
2426
  this.publicKeys = publicKeys;
2359
2427
  this.myIndex = myIndex;
2360
- this.aggPubkey = aggPubkey;
2361
2428
  this.tweak = tweak;
2362
2429
  this.msg = msg;
2363
2430
  this.nonce = nonce;
@@ -2403,11 +2470,7 @@ var MusigSession = class {
2403
2470
  const index = typeof publicKeyOrIndex === "number" ? publicKeyOrIndex : findKeyIndex(this.publicKeys, publicKeyOrIndex);
2404
2471
  if (index < 0 || index >= this.publicKeys.length)
2405
2472
  throw new Error("public key not found or index out of range");
2406
- if (!this.session.partialSigVerify(
2407
- signature,
2408
- [...this.pubNonces],
2409
- index
2410
- )) {
2473
+ if (!this.session.partialSigVerify(signature, [...this.pubNonces], index)) {
2411
2474
  throw new Error("invalid partial signature");
2412
2475
  }
2413
2476
  this.partialSignatures[index] = signature;
@@ -2416,12 +2479,7 @@ var MusigSession = class {
2416
2479
  signPartial() {
2417
2480
  const sig = this.session.sign(this.nonce.secret, this.privateKey, true);
2418
2481
  this.partialSignatures[this.myIndex] = sig;
2419
- return new MusigSigned(
2420
- this.session,
2421
- [...this.partialSignatures],
2422
- sig,
2423
- this.nonce.public
2424
- );
2482
+ return new MusigSigned(this.session, [...this.partialSignatures], sig, this.nonce.public);
2425
2483
  }
2426
2484
  };
2427
2485
  var MusigSigned = class {
@@ -2435,14 +2493,11 @@ var MusigSigned = class {
2435
2493
  if (this.partialSignatures.some((s) => s === null)) {
2436
2494
  throw new Error("not all partial signatures are set");
2437
2495
  }
2438
- return this.session.partialSigAgg(
2439
- this.partialSignatures
2440
- );
2496
+ return this.session.partialSigAgg(this.partialSignatures);
2441
2497
  }
2442
2498
  };
2443
2499
  var create = (privateKey, publicKeys) => {
2444
- if (publicKeys.length < 2)
2445
- throw new Error("need at least 2 keys to aggregate");
2500
+ if (publicKeys.length < 2) throw new Error("need at least 2 keys to aggregate");
2446
2501
  const keys = [...publicKeys];
2447
2502
  assertPublicKeys(keys);
2448
2503
  Object.freeze(keys);
@@ -2450,14 +2505,7 @@ var create = (privateKey, publicKeys) => {
2450
2505
  const myIndex = findKeyIndex(keys, myPublicKey);
2451
2506
  if (myIndex === -1) throw new Error("our key is not in publicKeys");
2452
2507
  const aggPubkey = aggregateKeys(keys);
2453
- return new MusigKeyAgg(
2454
- privateKey,
2455
- myPublicKey,
2456
- keys,
2457
- myIndex,
2458
- aggPubkey,
2459
- aggPubkey
2460
- );
2508
+ return new MusigKeyAgg(privateKey, myPublicKey, keys, myIndex, aggPubkey, aggPubkey);
2461
2509
  };
2462
2510
 
2463
2511
  // src/utils/boltz-swap-tx.ts
@@ -2533,9 +2581,7 @@ var taprootHashTree = (tree) => {
2533
2581
  };
2534
2582
  var tweakMusig = (musig, tree) => {
2535
2583
  const tweak = taprootHashTree(tree).hash;
2536
- return musig.xonlyTweakAdd(
2537
- schnorr.utils.taggedHash("TapTweak", musig.aggPubkey, tweak)
2538
- );
2584
+ return musig.xonlyTweakAdd(schnorr.utils.taggedHash("TapTweak", musig.aggPubkey, tweak));
2539
2585
  };
2540
2586
  var toXOnly = (pubKey) => {
2541
2587
  if (pubKey.length === 32) return pubKey;
@@ -2547,9 +2593,7 @@ var toXOnly = (pubKey) => {
2547
2593
  }
2548
2594
  return pubKey.subarray(1, 33);
2549
2595
  }
2550
- throw new Error(
2551
- `Invalid public key length: expected 32 or 33 bytes, got ${pubKey.length}`
2552
- );
2596
+ throw new Error(`Invalid public key length: expected 32 or 33 bytes, got ${pubKey.length}`);
2553
2597
  };
2554
2598
  var p2trScript = (publicKey) => Script.encode(["OP_1", toXOnly(publicKey)]);
2555
2599
  var detectSwapOutput = (tweakedKey, transaction) => {
@@ -2564,8 +2608,7 @@ var detectSwapOutput = (tweakedKey, transaction) => {
2564
2608
  };
2565
2609
  var DUMMY_TAPROOT_SIGNATURE = new Uint8Array(64);
2566
2610
  var constructClaimTransaction = (utxo, destinationScript, fee) => {
2567
- if (fee < BigInt(0) || fee >= utxo.amount)
2568
- throw new Error("fee exceeds utxo amount");
2611
+ if (fee < BigInt(0) || fee >= utxo.amount) throw new Error("fee exceeds utxo amount");
2569
2612
  const tx = new Transaction2({ version: 2 });
2570
2613
  tx.addOutput({
2571
2614
  amount: utxo.amount - fee,
@@ -2584,9 +2627,7 @@ var constructClaimTransaction = (utxo, destinationScript, fee) => {
2584
2627
  };
2585
2628
  var targetFee = (satPerVbyte, constructTx) => {
2586
2629
  const tx = constructTx(BigInt(1));
2587
- return constructTx(
2588
- BigInt(Math.ceil((tx.vsize + tx.inputsLength) * satPerVbyte))
2589
- );
2630
+ return constructTx(BigInt(Math.ceil((tx.vsize + tx.inputsLength) * satPerVbyte)));
2590
2631
  };
2591
2632
 
2592
2633
  // src/utils/restoration.ts
@@ -2610,10 +2651,7 @@ function extractTimeLockFromLeafOutput(scriptHex) {
2610
2651
  const data = opcodes[hasCSV - 1];
2611
2652
  if (data instanceof Uint8Array) {
2612
2653
  const dataBytes = new Uint8Array(data).reverse();
2613
- const {
2614
- blocks,
2615
- seconds
2616
- } = bip68.decode(
2654
+ const { blocks, seconds } = bip68.decode(
2617
2655
  parseInt(hex5.encode(dataBytes), 16)
2618
2656
  );
2619
2657
  return blocks ?? seconds ?? 0;
@@ -2634,11 +2672,7 @@ function extractInvoiceAmount(amountSats, fees) {
2634
2672
  }
2635
2673
 
2636
2674
  // src/utils/identity.ts
2637
- import {
2638
- ConditionWitness,
2639
- setArkPsbtField,
2640
- Transaction as Transaction3
2641
- } from "@arkade-os/sdk";
2675
+ import { ConditionWitness, setArkPsbtField, Transaction as Transaction3 } from "@arkade-os/sdk";
2642
2676
  function claimVHTLCIdentity(identity, preimage) {
2643
2677
  return {
2644
2678
  ...identity,
@@ -2647,13 +2681,8 @@ function claimVHTLCIdentity(identity, preimage) {
2647
2681
  let signedTx = await identity.sign(cpy, inputIndexes);
2648
2682
  signedTx = Transaction3.fromPSBT(signedTx.toPSBT());
2649
2683
  if (preimage) {
2650
- for (const inputIndex of inputIndexes || Array.from(
2651
- { length: signedTx.inputsLength },
2652
- (_, i) => i
2653
- )) {
2654
- setArkPsbtField(signedTx, inputIndex, ConditionWitness, [
2655
- preimage
2656
- ]);
2684
+ for (const inputIndex of inputIndexes || Array.from({ length: signedTx.inputsLength }, (_, i) => i)) {
2685
+ setArkPsbtField(signedTx, inputIndex, ConditionWitness, [preimage]);
2657
2686
  }
2658
2687
  }
2659
2688
  return signedTx;
@@ -2728,17 +2757,13 @@ function createVHTLCBatchHandler(intentId, vhtlc, arkProvider, identity, session
2728
2757
  if (!sweepTapTreeRoot) {
2729
2758
  throw new Error("Sweep tap tree root not set");
2730
2759
  }
2731
- const xOnlyPublicKeys = event.cosignersPublicKeys.map(
2732
- (k) => k.slice(2)
2733
- );
2760
+ const xOnlyPublicKeys = event.cosignersPublicKeys.map((k) => k.slice(2));
2734
2761
  const signerPublicKey = await session.getPublicKey();
2735
2762
  const xonlySignerPublicKey = signerPublicKey.subarray(1);
2736
2763
  if (!xOnlyPublicKeys.includes(hex6.encode(xonlySignerPublicKey))) {
2737
2764
  return { skip: true };
2738
2765
  }
2739
- const commitmentTx = Transaction4.fromPSBT(
2740
- base642.decode(event.unsignedCommitmentTx)
2741
- );
2766
+ const commitmentTx = Transaction4.fromPSBT(base642.decode(event.unsignedCommitmentTx));
2742
2767
  validateVtxoTxGraph(vtxoTree, commitmentTx, sweepTapTreeRoot);
2743
2768
  const sharedOutput = commitmentTx.getOutput(0);
2744
2769
  if (!sharedOutput?.amount) {
@@ -2754,18 +2779,11 @@ function createVHTLCBatchHandler(intentId, vhtlc, arkProvider, identity, session
2754
2779
  if (!session) {
2755
2780
  return { fullySigned: true };
2756
2781
  }
2757
- const { hasAllNonces } = await session.aggregatedNonces(
2758
- event.txid,
2759
- event.nonces
2760
- );
2782
+ const { hasAllNonces } = await session.aggregatedNonces(event.txid, event.nonces);
2761
2783
  if (!hasAllNonces) return { fullySigned: false };
2762
2784
  const signatures = await session.sign();
2763
2785
  const pubkey = hex6.encode(await session.getPublicKey());
2764
- await arkProvider.submitTreeSignatures(
2765
- event.id,
2766
- pubkey,
2767
- signatures
2768
- );
2786
+ await arkProvider.submitTreeSignatures(event.id, pubkey, signatures);
2769
2787
  return { fullySigned: true };
2770
2788
  },
2771
2789
  onBatchFinalization: async (event, _, connectorTree) => {
@@ -2773,9 +2791,7 @@ function createVHTLCBatchHandler(intentId, vhtlc, arkProvider, identity, session
2773
2791
  return;
2774
2792
  }
2775
2793
  if (!connectorTree) {
2776
- throw new Error(
2777
- "BatchFinalizationEvent: expected connector tree to be defined"
2778
- );
2794
+ throw new Error("BatchFinalizationEvent: expected connector tree to be defined");
2779
2795
  }
2780
2796
  validateConnectorsTxGraph(event.commitmentTx, connectorTree);
2781
2797
  const connectors = connectorTree.leaves();
@@ -2790,9 +2806,7 @@ function createVHTLCBatchHandler(intentId, vhtlc, arkProvider, identity, session
2790
2806
  connectors[connectorIndex]
2791
2807
  );
2792
2808
  const signedForfeitTx = await identity.sign(forfeitTx);
2793
- await arkProvider.submitSignedForfeitTxs([
2794
- base642.encode(signedForfeitTx.toPSBT())
2795
- ]);
2809
+ await arkProvider.submitSignedForfeitTxs([base642.encode(signedForfeitTx.toPSBT())]);
2796
2810
  }
2797
2811
  };
2798
2812
  }
@@ -2852,36 +2866,22 @@ var createVHTLCScript = (args) => {
2852
2866
  serverPubkey,
2853
2867
  timeoutBlockHeights: vhtlcTimeouts
2854
2868
  } = args;
2855
- const receiverXOnlyPublicKey = normalizeToXOnlyKey(
2856
- hex7.decode(receiverPubkey),
2857
- "receiver"
2858
- );
2859
- const senderXOnlyPublicKey = normalizeToXOnlyKey(
2860
- hex7.decode(senderPubkey),
2861
- "sender"
2862
- );
2863
- const serverXOnlyPublicKey = normalizeToXOnlyKey(
2864
- hex7.decode(serverPubkey),
2865
- "server"
2866
- );
2869
+ const receiverXOnlyPublicKey = normalizeToXOnlyKey(hex7.decode(receiverPubkey), "receiver");
2870
+ const senderXOnlyPublicKey = normalizeToXOnlyKey(hex7.decode(senderPubkey), "sender");
2871
+ const serverXOnlyPublicKey = normalizeToXOnlyKey(hex7.decode(serverPubkey), "server");
2867
2872
  const vhtlcScript = new VHTLC.Script({
2868
2873
  preimageHash: ripemd160(preimageHash),
2869
2874
  sender: senderXOnlyPublicKey,
2870
2875
  receiver: receiverXOnlyPublicKey,
2871
2876
  server: serverXOnlyPublicKey,
2872
2877
  refundLocktime: BigInt(vhtlcTimeouts.refund),
2873
- unilateralClaimDelay: toBip68RelativeTimelock(
2874
- vhtlcTimeouts.unilateralClaim
2875
- ),
2876
- unilateralRefundDelay: toBip68RelativeTimelock(
2877
- vhtlcTimeouts.unilateralRefund
2878
- ),
2878
+ unilateralClaimDelay: toBip68RelativeTimelock(vhtlcTimeouts.unilateralClaim),
2879
+ unilateralRefundDelay: toBip68RelativeTimelock(vhtlcTimeouts.unilateralRefund),
2879
2880
  unilateralRefundWithoutReceiverDelay: toBip68RelativeTimelock(
2880
2881
  vhtlcTimeouts.unilateralRefundWithoutReceiver
2881
2882
  )
2882
2883
  });
2883
- if (!vhtlcScript.claimScript)
2884
- throw new Error("Failed to create VHTLC script");
2884
+ if (!vhtlcScript.claimScript) throw new Error("Failed to create VHTLC script");
2885
2885
  const hrp = network === "bitcoin" ? "ark" : "tark";
2886
2886
  const vhtlcAddress = vhtlcScript.address(hrp, serverXOnlyPublicKey).encode();
2887
2887
  return { vhtlcScript, vhtlcAddress };
@@ -2915,11 +2915,7 @@ var joinBatch = async (arkProvider, identity, input, output, {
2915
2915
  unknown: [VtxoTaprootTree.encode(input.tapTree)],
2916
2916
  sequence: getSequence2(input.tapLeafScript)
2917
2917
  };
2918
- const registerIntent = Intent.create(
2919
- intentMessage,
2920
- [intentInput],
2921
- [output]
2922
- );
2918
+ const registerIntent = Intent.create(intentMessage, [intentInput], [output]);
2923
2919
  const deleteIntent = Intent.create(deleteMessage, [intentInput]);
2924
2920
  const [signedRegisterIntent, signedDeleteIntent] = await Promise.all([
2925
2921
  identity.sign(registerIntent),
@@ -2943,14 +2939,8 @@ var joinBatch = async (arkProvider, identity, input, output, {
2943
2939
  normalizeToXOnlyKey(forfeitPubkey, "forfeit"),
2944
2940
  isRecoverable2 ? void 0 : OutScript.encode(decodedAddress)
2945
2941
  );
2946
- const topics = [
2947
- hex7.encode(signerPublicKey),
2948
- `${input.txid}:${input.vout}`
2949
- ];
2950
- const eventStream = arkProvider.getEventStream(
2951
- abortController.signal,
2952
- topics
2953
- );
2942
+ const topics = [hex7.encode(signerPublicKey), `${input.txid}:${input.vout}`];
2943
+ const eventStream = arkProvider.getEventStream(abortController.signal, topics);
2954
2944
  const commitmentTxid = await Batch.join(eventStream, handler, {
2955
2945
  abortController
2956
2946
  });
@@ -2971,14 +2961,8 @@ var joinBatch = async (arkProvider, identity, input, output, {
2971
2961
  };
2972
2962
  var claimVHTLCwithOffchainTx = async (identity, vhtlcScript, serverXOnlyPublicKey, input, output, arkInfo, arkProvider) => {
2973
2963
  const rawCheckpointTapscript = hex7.decode(arkInfo.checkpointTapscript);
2974
- const serverUnrollScript = CSVMultisigTapscript2.decode(
2975
- rawCheckpointTapscript
2976
- );
2977
- const { arkTx, checkpoints } = buildOffchainTx(
2978
- [input],
2979
- [output],
2980
- serverUnrollScript
2981
- );
2964
+ const serverUnrollScript = CSVMultisigTapscript2.decode(rawCheckpointTapscript);
2965
+ const { arkTx, checkpoints } = buildOffchainTx([input], [output], serverUnrollScript);
2982
2966
  const signedArkTx = await identity.sign(arkTx);
2983
2967
  const { arkTxid, finalArkTx, signedCheckpointTxs } = await arkProvider.submitTx(
2984
2968
  base643.encode(signedArkTx.toPSBT()),
@@ -2986,9 +2970,7 @@ var claimVHTLCwithOffchainTx = async (identity, vhtlcScript, serverXOnlyPublicKe
2986
2970
  );
2987
2971
  const finalTx = Transaction5.fromPSBT(base643.decode(finalArkTx));
2988
2972
  const serverPubkeyHex = hex7.encode(serverXOnlyPublicKey);
2989
- const claimLeafHash = tapLeafHash3(
2990
- scriptFromTapLeafScript(vhtlcScript.claim())
2991
- );
2973
+ const claimLeafHash = tapLeafHash3(scriptFromTapLeafScript(vhtlcScript.claim()));
2992
2974
  for (let i = 0; i < finalTx.inputsLength; i++) {
2993
2975
  if (!verifySignatures(finalTx, i, [serverPubkeyHex], claimLeafHash)) {
2994
2976
  throw new Error("Invalid final Ark transaction");
@@ -2998,13 +2980,9 @@ var claimVHTLCwithOffchainTx = async (identity, vhtlcScript, serverXOnlyPublicKe
2998
2980
  signedCheckpointTxs.map(async (c, idx) => {
2999
2981
  const tx = Transaction5.fromPSBT(base643.decode(c));
3000
2982
  const checkpointLeaf = checkpoints[idx].getInput(0).tapLeafScript[0];
3001
- const cpLeafHash = tapLeafHash3(
3002
- scriptFromTapLeafScript(checkpointLeaf)
3003
- );
2983
+ const cpLeafHash = tapLeafHash3(scriptFromTapLeafScript(checkpointLeaf));
3004
2984
  if (!verifySignatures(tx, 0, [serverPubkeyHex], cpLeafHash)) {
3005
- throw new Error(
3006
- "Invalid server signature in checkpoint transaction"
3007
- );
2985
+ throw new Error("Invalid server signature in checkpoint transaction");
3008
2986
  }
3009
2987
  const signedCheckpoint = await identity.sign(tx, [0]);
3010
2988
  return base643.encode(signedCheckpoint.toPSBT());
@@ -3014,61 +2992,37 @@ var claimVHTLCwithOffchainTx = async (identity, vhtlcScript, serverXOnlyPublicKe
3014
2992
  };
3015
2993
  var refundVHTLCwithOffchainTx = async (swapId, identity, arkProvider, boltzXOnlyPublicKey, ourXOnlyPublicKey, serverXOnlyPublicKey, input, output, arkInfo, refundFunc) => {
3016
2994
  const rawCheckpointTapscript = hex7.decode(arkInfo.checkpointTapscript);
3017
- const serverUnrollScript = CSVMultisigTapscript2.decode(
3018
- rawCheckpointTapscript
2995
+ const serverUnrollScript = CSVMultisigTapscript2.decode(rawCheckpointTapscript);
2996
+ const { arkTx: unsignedRefundTx, checkpoints: checkpointPtxs } = buildOffchainTx(
2997
+ [input],
2998
+ [output],
2999
+ serverUnrollScript
3019
3000
  );
3020
- const { arkTx: unsignedRefundTx, checkpoints: checkpointPtxs } = buildOffchainTx([input], [output], serverUnrollScript);
3021
3001
  if (checkpointPtxs.length !== 1)
3022
- throw new Error(
3023
- `Expected one checkpoint transaction, got ${checkpointPtxs.length}`
3024
- );
3002
+ throw new Error(`Expected one checkpoint transaction, got ${checkpointPtxs.length}`);
3025
3003
  const unsignedCheckpointTx = checkpointPtxs[0];
3026
3004
  let boltzSignedRefundTx;
3027
3005
  let boltzSignedCheckpointTx;
3028
3006
  try {
3029
- const result = await refundFunc(
3030
- swapId,
3031
- unsignedRefundTx,
3032
- unsignedCheckpointTx
3033
- );
3007
+ const result = await refundFunc(swapId, unsignedRefundTx, unsignedCheckpointTx);
3034
3008
  boltzSignedRefundTx = result.transaction;
3035
3009
  boltzSignedCheckpointTx = result.checkpoint;
3036
3010
  } catch (error) {
3037
- throw new BoltzRefundError(
3038
- `Boltz rejected refund for swap ${swapId}`,
3039
- error
3040
- );
3011
+ throw new BoltzRefundError(`Boltz rejected refund for swap ${swapId}`, error);
3041
3012
  }
3042
3013
  const boltzXOnlyPublicKeyHex = hex7.encode(boltzXOnlyPublicKey);
3043
- const refundLeafHash = tapLeafHash3(
3044
- scriptFromTapLeafScript(input.tapLeafScript)
3045
- );
3046
- if (!verifySignatures(
3047
- boltzSignedRefundTx,
3048
- 0,
3049
- [boltzXOnlyPublicKeyHex],
3050
- refundLeafHash
3051
- )) {
3014
+ const refundLeafHash = tapLeafHash3(scriptFromTapLeafScript(input.tapLeafScript));
3015
+ if (!verifySignatures(boltzSignedRefundTx, 0, [boltzXOnlyPublicKeyHex], refundLeafHash)) {
3052
3016
  throw new Error("Invalid Boltz signature in refund transaction");
3053
3017
  }
3054
3018
  const checkpointLeaf = unsignedCheckpointTx.getInput(0).tapLeafScript[0];
3055
- const checkpointLeafHash = tapLeafHash3(
3056
- scriptFromTapLeafScript(checkpointLeaf)
3057
- );
3058
- if (!verifySignatures(
3059
- boltzSignedCheckpointTx,
3060
- 0,
3061
- [boltzXOnlyPublicKeyHex],
3062
- checkpointLeafHash
3063
- )) {
3019
+ const checkpointLeafHash = tapLeafHash3(scriptFromTapLeafScript(checkpointLeaf));
3020
+ if (!verifySignatures(boltzSignedCheckpointTx, 0, [boltzXOnlyPublicKeyHex], checkpointLeafHash)) {
3064
3021
  throw new Error("Invalid Boltz signature in checkpoint transaction");
3065
3022
  }
3066
3023
  const signedRefundTx = await identity.sign(unsignedRefundTx);
3067
3024
  const signedCheckpointTx = await identity.sign(unsignedCheckpointTx);
3068
- const combinedSignedRefundTx = combineTapscriptSigs(
3069
- boltzSignedRefundTx,
3070
- signedRefundTx
3071
- );
3025
+ const combinedSignedRefundTx = combineTapscriptSigs(boltzSignedRefundTx, signedRefundTx);
3072
3026
  const combinedSignedCheckpointTx = combineTapscriptSigs(
3073
3027
  boltzSignedCheckpointTx,
3074
3028
  signedCheckpointTx
@@ -3092,25 +3046,16 @@ var refundVHTLCwithOffchainTx = async (swapId, identity, arkProvider, boltzXOnly
3092
3046
  `Expected one signed checkpoint transaction, got ${signedCheckpointTxs.length}`
3093
3047
  );
3094
3048
  }
3095
- const serverSignedCheckpointTx = Transaction5.fromPSBT(
3096
- base643.decode(signedCheckpointTxs[0])
3097
- );
3049
+ const serverSignedCheckpointTx = Transaction5.fromPSBT(base643.decode(signedCheckpointTxs[0]));
3098
3050
  const serverPubkeyHex = hex7.encode(serverXOnlyPublicKey);
3099
- if (!verifySignatures(
3100
- serverSignedCheckpointTx,
3101
- 0,
3102
- [serverPubkeyHex],
3103
- checkpointLeafHash
3104
- )) {
3051
+ if (!verifySignatures(serverSignedCheckpointTx, 0, [serverPubkeyHex], checkpointLeafHash)) {
3105
3052
  throw new Error("Invalid server signature in checkpoint transaction");
3106
3053
  }
3107
3054
  const finalCheckpointTx = combineTapscriptSigs(
3108
3055
  combinedSignedCheckpointTx,
3109
3056
  serverSignedCheckpointTx
3110
3057
  );
3111
- await arkProvider.finalizeTx(arkTxid, [
3112
- base643.encode(finalCheckpointTx.toPSBT())
3113
- ]);
3058
+ await arkProvider.finalizeTx(arkTxid, [base643.encode(finalCheckpointTx.toPSBT())]);
3114
3059
  };
3115
3060
  function scriptFromTapLeafScript(leaf) {
3116
3061
  return leaf[1].subarray(0, leaf[1].length - 1);
@@ -3118,21 +3063,21 @@ function scriptFromTapLeafScript(leaf) {
3118
3063
 
3119
3064
  // src/arkade-swaps.ts
3120
3065
  var dedupeVtxos = (vtxos) => [
3121
- ...new Map(
3122
- vtxos.map((vtxo) => [`${vtxo.txid}:${vtxo.vout}`, vtxo])
3123
- ).values()
3066
+ ...new Map(vtxos.map((vtxo) => [`${vtxo.txid}:${vtxo.vout}`, vtxo])).values()
3124
3067
  ];
3125
3068
  var hasNonEmptyString = (value) => typeof value === "string" && value.length > 0;
3126
3069
  var canRecoverViaBoltz3of3 = (refundableVtxos, swap) => {
3127
3070
  const hasRequiredSwapMetadata = hasNonEmptyString(swap.id) && hasNonEmptyString(swap.request.refundPublicKey) && hasNonEmptyString(swap.response.address) && hasNonEmptyString(swap.response.claimPublicKey) && !!swap.response.timeoutBlockHeights;
3128
3071
  if (!hasRequiredSwapMetadata) return false;
3129
- return refundableVtxos.some(
3130
- (vtxo) => !vtxo.isSpent && !isRecoverable(vtxo)
3131
- );
3072
+ return refundableVtxos.some((vtxo) => !vtxo.isSpent && !isRecoverable(vtxo));
3132
3073
  };
3133
3074
  var isSubmarineRefundLocktimeReached = (refundTimestamp) => Math.floor(Date.now() / 1e3) >= refundTimestamp;
3134
3075
  var CLAIM_VTXO_RETRY_ATTEMPTS = 3;
3135
3076
  var CLAIM_VTXO_RETRY_DELAY_MS = 500;
3077
+ var quoteOptionsForSwap = (swap) => {
3078
+ const amount = swap.response?.claimDetails?.amount;
3079
+ return typeof amount === "number" ? { minAcceptableAmount: amount } : void 0;
3080
+ };
3136
3081
  var ArkadeSwaps = class _ArkadeSwaps {
3137
3082
  /** The Arkade wallet instance used for signing and address generation. */
3138
3083
  wallet;
@@ -3168,10 +3113,7 @@ var ArkadeSwaps = class _ArkadeSwaps {
3168
3113
  return new _ArkadeSwaps(config);
3169
3114
  }
3170
3115
  const arkProvider = config.arkProvider ?? config.wallet.arkProvider;
3171
- if (!arkProvider)
3172
- throw new Error(
3173
- "Ark provider is required either in wallet or config."
3174
- );
3116
+ if (!arkProvider) throw new Error("Ark provider is required either in wallet or config.");
3175
3117
  const arkInfo = await arkProvider.getInfo();
3176
3118
  const network = arkInfo.network;
3177
3119
  const swapProvider = new BoltzSwapProvider({ network });
@@ -3182,16 +3124,11 @@ var ArkadeSwaps = class _ArkadeSwaps {
3182
3124
  if (!config.swapProvider) throw new Error("Swap provider is required.");
3183
3125
  this.wallet = config.wallet;
3184
3126
  const arkProvider = config.arkProvider ?? config.wallet.arkProvider;
3185
- if (!arkProvider)
3186
- throw new Error(
3187
- "Ark provider is required either in wallet or config."
3188
- );
3127
+ if (!arkProvider) throw new Error("Ark provider is required either in wallet or config.");
3189
3128
  this.arkProvider = arkProvider;
3190
3129
  const indexerProvider = config.indexerProvider ?? config.wallet.indexerProvider;
3191
3130
  if (!indexerProvider)
3192
- throw new Error(
3193
- "Indexer provider is required either in wallet or config."
3194
- );
3131
+ throw new Error("Indexer provider is required either in wallet or config.");
3195
3132
  this.indexerProvider = indexerProvider;
3196
3133
  this.swapProvider = config.swapProvider;
3197
3134
  if (config.swapRepository) {
@@ -3202,10 +3139,7 @@ var ArkadeSwaps = class _ArkadeSwaps {
3202
3139
  if (config.swapManager !== false) {
3203
3140
  const swapManagerConfig = !config.swapManager || config.swapManager === true ? {} : config.swapManager;
3204
3141
  const shouldAutostart = swapManagerConfig.autoStart ?? true;
3205
- this.swapManager = new SwapManager(
3206
- this.swapProvider,
3207
- swapManagerConfig
3208
- );
3142
+ this.swapManager = new SwapManager(this.swapProvider, swapManagerConfig);
3209
3143
  this.swapManager.setCallbacks({
3210
3144
  claim: async (swap) => {
3211
3145
  await this.claimVHTLC(swap);
@@ -3220,7 +3154,7 @@ var ArkadeSwaps = class _ArkadeSwaps {
3220
3154
  await this.claimBtc(swap);
3221
3155
  },
3222
3156
  refundArk: async (swap) => {
3223
- await this.refundArk(swap);
3157
+ return this.refundArk(swap);
3224
3158
  },
3225
3159
  signServerClaim: async (swap) => {
3226
3160
  await this.signCooperativeClaimForServer(swap);
@@ -3353,19 +3287,15 @@ var ArkadeSwaps = class _ArkadeSwaps {
3353
3287
  * @throws {SwapError} If amount is <= 0 or key retrieval fails.
3354
3288
  */
3355
3289
  async createReverseSwap(args) {
3356
- if (args.amount <= 0)
3357
- throw new SwapError({ message: "Amount must be greater than 0" });
3358
- const claimPublicKey = hex8.encode(
3359
- await this.wallet.identity.compressedPublicKey()
3360
- );
3290
+ if (args.amount <= 0) throw new SwapError({ message: "Amount must be greater than 0" });
3291
+ const claimPublicKey = hex8.encode(await this.wallet.identity.compressedPublicKey());
3361
3292
  if (!claimPublicKey)
3362
3293
  throw new SwapError({
3363
3294
  message: "Failed to get claim public key from wallet"
3364
3295
  });
3365
3296
  const preimage = randomBytes(32);
3366
3297
  const preimageHash = hex8.encode(sha2563(preimage));
3367
- if (!preimageHash)
3368
- throw new SwapError({ message: "Failed to get preimage hash" });
3298
+ if (!preimageHash) throw new SwapError({ message: "Failed to get preimage hash" });
3369
3299
  const swapRequest = {
3370
3300
  invoiceAmount: args.amount,
3371
3301
  claimPublicKey,
@@ -3395,18 +3325,14 @@ var ArkadeSwaps = class _ArkadeSwaps {
3395
3325
  */
3396
3326
  async claimVHTLC(pendingSwap) {
3397
3327
  if (!pendingSwap.preimage)
3398
- throw new Error(
3399
- `Swap ${pendingSwap.id}: preimage is required to claim VHTLC`
3400
- );
3328
+ throw new Error(`Swap ${pendingSwap.id}: preimage is required to claim VHTLC`);
3401
3329
  const {
3402
3330
  refundPublicKey,
3403
3331
  lockupAddress,
3404
3332
  timeoutBlockHeights: vhtlcTimeouts
3405
3333
  } = pendingSwap.response;
3406
3334
  if (!refundPublicKey || !lockupAddress || !vhtlcTimeouts)
3407
- throw new Error(
3408
- `Swap ${pendingSwap.id}: incomplete reverse swap response`
3409
- );
3335
+ throw new Error(`Swap ${pendingSwap.id}: incomplete reverse swap response`);
3410
3336
  const preimage = hex8.decode(pendingSwap.preimage);
3411
3337
  const arkInfo = await this.arkProvider.getInfo();
3412
3338
  const address = await this.wallet.getAddress();
@@ -3441,58 +3367,67 @@ var ArkadeSwaps = class _ArkadeSwaps {
3441
3367
  throw new Error(
3442
3368
  `Swap ${pendingSwap.id}: VHTLC address mismatch. Expected ${lockupAddress}, got ${vhtlcAddress}`
3443
3369
  );
3444
- let vtxo;
3370
+ let unspentVtxos = [];
3371
+ let rawVtxos = [];
3445
3372
  for (let attempt = 1; attempt <= CLAIM_VTXO_RETRY_ATTEMPTS; attempt++) {
3446
- const { vtxos } = await this.indexerProvider.getVtxos({
3373
+ const result = await this.indexerProvider.getVtxos({
3447
3374
  scripts: [hex8.encode(vhtlcScript.pkScript)]
3448
3375
  });
3449
- if (vtxos.length > 0) {
3450
- vtxo = vtxos[0];
3376
+ rawVtxos = result.vtxos;
3377
+ unspentVtxos = result.vtxos.filter((vtxo) => !vtxo.isSpent);
3378
+ if (unspentVtxos.length > 0) {
3451
3379
  break;
3452
3380
  }
3453
3381
  if (attempt < CLAIM_VTXO_RETRY_ATTEMPTS) {
3454
- await new Promise(
3455
- (resolve) => setTimeout(resolve, CLAIM_VTXO_RETRY_DELAY_MS)
3456
- );
3382
+ await new Promise((resolve) => setTimeout(resolve, CLAIM_VTXO_RETRY_DELAY_MS));
3457
3383
  }
3458
3384
  }
3459
- if (!vtxo) {
3460
- throw new Error(
3461
- `Swap ${pendingSwap.id}: no spendable virtual coins found`
3462
- );
3463
- }
3464
- if (vtxo.isSpent) {
3385
+ if (unspentVtxos.length === 0) {
3386
+ if (rawVtxos.length === 0) {
3387
+ throw new Error(`Swap ${pendingSwap.id}: no spendable virtual coins found`);
3388
+ }
3465
3389
  throw new Error(`Swap ${pendingSwap.id}: VHTLC is already spent`);
3466
3390
  }
3467
- const input = {
3468
- ...vtxo,
3469
- tapLeafScript: vhtlcScript.claim(),
3470
- tapTree: vhtlcScript.encode()
3471
- };
3472
- const output = {
3473
- amount: BigInt(vtxo.value),
3474
- script: ArkAddress2.decode(address).pkScript
3475
- };
3476
- const vhtlcIdentity = claimVHTLCIdentity(
3477
- this.wallet.identity,
3478
- preimage
3479
- );
3480
- let finalStatus;
3481
- if (isRecoverable(vtxo)) {
3482
- await this.joinBatch(vhtlcIdentity, input, output, arkInfo);
3483
- finalStatus = "transaction.claimed";
3484
- } else {
3485
- await claimVHTLCwithOffchainTx(
3486
- vhtlcIdentity,
3487
- vhtlcScript,
3488
- serverXOnly,
3489
- input,
3490
- output,
3491
- arkInfo,
3492
- this.arkProvider
3391
+ const vhtlcIdentity = claimVHTLCIdentity(this.wallet.identity, preimage);
3392
+ const outputScript = ArkAddress2.decode(address).pkScript;
3393
+ const claimErrors = [];
3394
+ let usedOffchainClaim = false;
3395
+ for (const vtxo of unspentVtxos) {
3396
+ const input = {
3397
+ ...vtxo,
3398
+ tapLeafScript: vhtlcScript.claim(),
3399
+ tapTree: vhtlcScript.encode()
3400
+ };
3401
+ const output = {
3402
+ amount: BigInt(vtxo.value),
3403
+ script: outputScript
3404
+ };
3405
+ try {
3406
+ if (isRecoverable(vtxo)) {
3407
+ await this.joinBatch(vhtlcIdentity, input, output, arkInfo);
3408
+ } else {
3409
+ await claimVHTLCwithOffchainTx(
3410
+ vhtlcIdentity,
3411
+ vhtlcScript,
3412
+ serverXOnly,
3413
+ input,
3414
+ output,
3415
+ arkInfo,
3416
+ this.arkProvider
3417
+ );
3418
+ usedOffchainClaim = true;
3419
+ }
3420
+ } catch (error) {
3421
+ claimErrors.push({ vtxo, error });
3422
+ }
3423
+ }
3424
+ if (claimErrors.length > 0) {
3425
+ const details = claimErrors.map(({ vtxo, error }) => `${vtxo.txid}:${vtxo.vout} (${error.message})`).join("; ");
3426
+ throw new Error(
3427
+ `Swap ${pendingSwap.id}: failed to claim ${claimErrors.length}/${unspentVtxos.length} VTXOs: ${details}`
3493
3428
  );
3494
- finalStatus = (await this.getSwapStatus(pendingSwap.id)).status;
3495
3429
  }
3430
+ const finalStatus = usedOffchainClaim ? (await this.getSwapStatus(pendingSwap.id)).status : "transaction.claimed";
3496
3431
  await updateReverseSwapStatus(
3497
3432
  pendingSwap,
3498
3433
  finalStatus,
@@ -3595,9 +3530,7 @@ var ArkadeSwaps = class _ArkadeSwaps {
3595
3530
  async sendLightningPayment(args) {
3596
3531
  const pendingSwap = await this.createSubmarineSwap(args);
3597
3532
  if (!pendingSwap.response.address)
3598
- throw new Error(
3599
- `Swap ${pendingSwap.id}: missing address in submarine swap response`
3600
- );
3533
+ throw new Error(`Swap ${pendingSwap.id}: missing address in submarine swap response`);
3601
3534
  await this.savePendingSubmarineSwap(pendingSwap);
3602
3535
  const txid = await this.wallet.send({
3603
3536
  address: pendingSwap.response.address,
@@ -3630,9 +3563,7 @@ var ArkadeSwaps = class _ArkadeSwaps {
3630
3563
  * @throws {SwapError} If invoice is missing or key retrieval fails.
3631
3564
  */
3632
3565
  async createSubmarineSwap(args) {
3633
- const refundPublicKey = hex8.encode(
3634
- await this.wallet.identity.compressedPublicKey()
3635
- );
3566
+ const refundPublicKey = hex8.encode(await this.wallet.identity.compressedPublicKey());
3636
3567
  if (!refundPublicKey)
3637
3568
  throw new SwapError({
3638
3569
  message: "Failed to get refund public key from wallet"
@@ -3670,9 +3601,7 @@ var ArkadeSwaps = class _ArkadeSwaps {
3670
3601
  async buildSubmarineVHTLCContext(swap, arkInfo) {
3671
3602
  const preimageHash = swap.request.invoice ? getInvoicePaymentHash(swap.request.invoice) : swap.preimageHash;
3672
3603
  if (!preimageHash)
3673
- throw new Error(
3674
- `Swap ${swap.id}: preimage hash is required to refund VHTLC`
3675
- );
3604
+ throw new Error(`Swap ${swap.id}: preimage hash is required to refund VHTLC`);
3676
3605
  const resolvedArkInfo = arkInfo ?? await this.arkProvider.getInfo();
3677
3606
  const ourXOnlyPublicKey = normalizeToXOnlyKey(
3678
3607
  await this.wallet.identity.xOnlyPublicKey(),
@@ -3686,9 +3615,7 @@ var ArkadeSwaps = class _ArkadeSwaps {
3686
3615
  );
3687
3616
  const { claimPublicKey, timeoutBlockHeights: vhtlcTimeouts } = swap.response;
3688
3617
  if (!claimPublicKey || !vhtlcTimeouts)
3689
- throw new Error(
3690
- `Swap ${swap.id}: incomplete submarine swap response`
3691
- );
3618
+ throw new Error(`Swap ${swap.id}: incomplete submarine swap response`);
3692
3619
  const boltzXOnlyPublicKey = normalizeToXOnlyKey(
3693
3620
  hex8.decode(claimPublicKey),
3694
3621
  "boltz",
@@ -3703,9 +3630,7 @@ var ArkadeSwaps = class _ArkadeSwaps {
3703
3630
  timeoutBlockHeights: vhtlcTimeouts
3704
3631
  });
3705
3632
  if (!vhtlcScript.claimScript)
3706
- throw new Error(
3707
- `Swap ${swap.id}: failed to create VHTLC script for submarine swap`
3708
- );
3633
+ throw new Error(`Swap ${swap.id}: failed to create VHTLC script for submarine swap`);
3709
3634
  if (vhtlcAddress !== swap.response.address)
3710
3635
  throw new Error(
3711
3636
  `VHTLC address mismatch for swap ${swap.id}: expected ${swap.response.address}, got ${vhtlcAddress}`
@@ -3744,10 +3669,7 @@ var ArkadeSwaps = class _ArkadeSwaps {
3744
3669
  recoverableOnly: true
3745
3670
  })
3746
3671
  ]);
3747
- const refundableVtxos = dedupeVtxos([
3748
- ...spendableResult.vtxos,
3749
- ...recoverableResult.vtxos
3750
- ]);
3672
+ const refundableVtxos = dedupeVtxos([...spendableResult.vtxos, ...recoverableResult.vtxos]);
3751
3673
  let diagnostic;
3752
3674
  if (refundableVtxos.length === 0) {
3753
3675
  const { vtxos: allVtxos } = await this.indexerProvider.getVtxos({
@@ -3767,13 +3689,8 @@ var ArkadeSwaps = class _ArkadeSwaps {
3767
3689
  submarineRecoveryInfoFromLookup(swap, lookup) {
3768
3690
  const { refundableVtxos, diagnostic, vhtlcTimeouts } = lookup;
3769
3691
  if (refundableVtxos.length > 0) {
3770
- const cltvSatisfied = isSubmarineRefundLocktimeReached(
3771
- vhtlcTimeouts.refund
3772
- );
3773
- const amountSats = refundableVtxos.reduce(
3774
- (sum, vtxo) => sum + Number(vtxo.value),
3775
- 0
3776
- );
3692
+ const cltvSatisfied = isSubmarineRefundLocktimeReached(vhtlcTimeouts.refund);
3693
+ const amountSats = refundableVtxos.reduce((sum, vtxo) => sum + Number(vtxo.value), 0);
3777
3694
  const isRecoverable2 = cltvSatisfied || canRecoverViaBoltz3of3(refundableVtxos, swap);
3778
3695
  return {
3779
3696
  swap,
@@ -3837,19 +3754,13 @@ var ArkadeSwaps = class _ArkadeSwaps {
3837
3754
  );
3838
3755
  }
3839
3756
  if (diagnostic.allSpent) {
3840
- throw new Error(
3841
- `Swap ${pendingSwap.id}: VHTLC is already spent`
3842
- );
3757
+ throw new Error(`Swap ${pendingSwap.id}: VHTLC is already spent`);
3843
3758
  }
3844
- throw new Error(
3845
- `Swap ${pendingSwap.id}: VHTLC has no refundable VTXOs yet`
3846
- );
3759
+ throw new Error(`Swap ${pendingSwap.id}: VHTLC has no refundable VTXOs yet`);
3847
3760
  }
3848
3761
  const outputScript = ArkAddress2.decode(address).pkScript;
3849
3762
  const refundWithoutReceiverLeaf = vhtlcScript.refundWithoutReceiver();
3850
- const cltvSatisfied = isSubmarineRefundLocktimeReached(
3851
- vhtlcTimeouts.refund
3852
- );
3763
+ const cltvSatisfied = isSubmarineRefundLocktimeReached(vhtlcTimeouts.refund);
3853
3764
  let boltzCallCount = 0;
3854
3765
  let sweptCount = 0;
3855
3766
  let skippedCount = 0;
@@ -3891,6 +3802,7 @@ var ArkadeSwaps = class _ArkadeSwaps {
3891
3802
  if (boltzCallCount > 0) {
3892
3803
  await new Promise((r) => setTimeout(r, 2e3));
3893
3804
  }
3805
+ boltzCallCount++;
3894
3806
  await refundVHTLCwithOffchainTx(
3895
3807
  pendingSwap.id,
3896
3808
  this.wallet.identity,
@@ -3901,11 +3813,8 @@ var ArkadeSwaps = class _ArkadeSwaps {
3901
3813
  input,
3902
3814
  output,
3903
3815
  arkInfo,
3904
- this.swapProvider.refundSubmarineSwap.bind(
3905
- this.swapProvider
3906
- )
3816
+ this.swapProvider.refundSubmarineSwap.bind(this.swapProvider)
3907
3817
  );
3908
- boltzCallCount++;
3909
3818
  sweptCount++;
3910
3819
  } catch (error) {
3911
3820
  if (!(error instanceof BoltzRefundError)) {
@@ -3926,13 +3835,7 @@ var ArkadeSwaps = class _ArkadeSwaps {
3926
3835
  tapLeafScript: refundWithoutReceiverLeaf,
3927
3836
  tapTree: vhtlcScript.encode()
3928
3837
  };
3929
- await this.joinBatch(
3930
- this.wallet.identity,
3931
- fallbackInput,
3932
- output,
3933
- arkInfo,
3934
- false
3935
- );
3838
+ await this.joinBatch(this.wallet.identity, fallbackInput, output, arkInfo, false);
3936
3839
  sweptCount++;
3937
3840
  }
3938
3841
  }
@@ -4028,10 +3931,7 @@ var ArkadeSwaps = class _ArkadeSwaps {
4028
3931
  try {
4029
3932
  return {
4030
3933
  swap,
4031
- context: await this.buildSubmarineVHTLCContext(
4032
- swap,
4033
- arkInfo
4034
- )
3934
+ context: await this.buildSubmarineVHTLCContext(swap, arkInfo)
4035
3935
  };
4036
3936
  } catch (err) {
4037
3937
  return {
@@ -4044,9 +3944,7 @@ var ArkadeSwaps = class _ArkadeSwaps {
4044
3944
  const valid = prepared.filter(
4045
3945
  (item) => "context" in item
4046
3946
  );
4047
- const scripts = [
4048
- ...new Set(valid.map(({ context }) => context.vhtlcPkScriptHex))
4049
- ];
3947
+ const scripts = [...new Set(valid.map(({ context }) => context.vhtlcPkScriptHex))];
4050
3948
  const refundableByScript = /* @__PURE__ */ new Map();
4051
3949
  if (scripts.length > 0) {
4052
3950
  const [spendableResult, recoverableResult] = await Promise.all([
@@ -4081,9 +3979,7 @@ var ArkadeSwaps = class _ArkadeSwaps {
4081
3979
  error: item.error
4082
3980
  };
4083
3981
  }
4084
- const refundableVtxos = refundableByScript.get(
4085
- item.context.vhtlcPkScriptHex.toLowerCase()
4086
- ) ?? [];
3982
+ const refundableVtxos = refundableByScript.get(item.context.vhtlcPkScriptHex.toLowerCase()) ?? [];
4087
3983
  return this.submarineRecoveryInfoFromLookup(item.swap, {
4088
3984
  ...item.context,
4089
3985
  refundableVtxos
@@ -4262,9 +4158,7 @@ var ArkadeSwaps = class _ArkadeSwaps {
4262
4158
  */
4263
4159
  async waitAndClaimBtc(pendingSwap) {
4264
4160
  if (this.swapManager && await this.swapManager.hasSwap(pendingSwap.id)) {
4265
- const { txid } = await this.swapManager.waitForSwapCompletion(
4266
- pendingSwap.id
4267
- );
4161
+ const { txid } = await this.swapManager.waitForSwapCompletion(pendingSwap.id);
4268
4162
  return { txid };
4269
4163
  }
4270
4164
  return new Promise((resolve, reject) => {
@@ -4291,24 +4185,25 @@ var ArkadeSwaps = class _ArkadeSwaps {
4291
4185
  }
4292
4186
  case "transaction.claimed":
4293
4187
  await updateSwapStatus();
4294
- const claimedStatus = await this.getSwapStatus(
4295
- pendingSwap.id
4296
- );
4188
+ const claimedStatus = await this.getSwapStatus(pendingSwap.id);
4297
4189
  resolve({
4298
4190
  txid: claimedStatus.transaction?.id ?? ""
4299
4191
  });
4300
4192
  break;
4301
4193
  case "transaction.lockupFailed":
4302
4194
  await updateSwapStatus();
4303
- await this.quoteSwap(swap.response.id).catch((err) => {
4304
- reject(
4305
- new SwapError({
4306
- message: `Failed to renegotiate quote: ${err.message}`,
4307
- isRefundable: true,
4308
- pendingSwap: swap
4309
- })
4310
- );
4311
- });
4195
+ await this.quoteSwap(swap.response.id, quoteOptionsForSwap(swap)).catch(
4196
+ (err) => {
4197
+ reject(
4198
+ new SwapError({
4199
+ message: `Failed to renegotiate quote: ${err.message}`,
4200
+ isRefundable: true,
4201
+ pendingSwap: swap,
4202
+ cause: err
4203
+ })
4204
+ );
4205
+ }
4206
+ );
4312
4207
  break;
4313
4208
  case "swap.expired":
4314
4209
  await updateSwapStatus();
@@ -4346,30 +4241,18 @@ var ArkadeSwaps = class _ArkadeSwaps {
4346
4241
  */
4347
4242
  async claimBtc(pendingSwap) {
4348
4243
  if (!pendingSwap.toAddress)
4349
- throw new Error(
4350
- `Swap ${pendingSwap.id}: destination address is required`
4351
- );
4244
+ throw new Error(`Swap ${pendingSwap.id}: destination address is required`);
4352
4245
  if (!pendingSwap.response.claimDetails.swapTree)
4353
- throw new Error(
4354
- `Swap ${pendingSwap.id}: missing swap tree in claim details`
4355
- );
4246
+ throw new Error(`Swap ${pendingSwap.id}: missing swap tree in claim details`);
4356
4247
  if (!pendingSwap.response.claimDetails.serverPublicKey)
4357
- throw new Error(
4358
- `Swap ${pendingSwap.id}: missing server public key in claim details`
4359
- );
4248
+ throw new Error(`Swap ${pendingSwap.id}: missing server public key in claim details`);
4360
4249
  const swapStatus = await this.getSwapStatus(pendingSwap.id);
4361
4250
  if (!swapStatus.transaction?.hex)
4362
- throw new Error(
4363
- `Swap ${pendingSwap.id}: BTC transaction hex is required`
4364
- );
4365
- const lockupTx = Transaction6.fromRaw(
4366
- hex8.decode(swapStatus.transaction.hex)
4367
- );
4251
+ throw new Error(`Swap ${pendingSwap.id}: BTC transaction hex is required`);
4252
+ const lockupTx = Transaction6.fromRaw(hex8.decode(swapStatus.transaction.hex));
4368
4253
  const arkInfo = await this.arkProvider.getInfo();
4369
4254
  const network = arkInfo.network === "bitcoin" ? NETWORK : arkInfo.network === "mutinynet" ? MUTINYNET_NETWORK : REGTEST_NETWORK;
4370
- const swapTree = deserializeSwapTree(
4371
- pendingSwap.response.claimDetails.swapTree
4372
- );
4255
+ const swapTree = deserializeSwapTree(pendingSwap.response.claimDetails.swapTree);
4373
4256
  const musig = tweakMusig(
4374
4257
  create(hex8.decode(pendingSwap.ephemeralKey), [
4375
4258
  hex8.decode(pendingSwap.response.claimDetails.serverPublicKey),
@@ -4390,19 +4273,14 @@ var ArkadeSwaps = class _ArkadeSwaps {
4390
4273
  vout: swapOutput.vout,
4391
4274
  transactionId: lockupTx.id
4392
4275
  },
4393
- OutScript2.encode(
4394
- Address2(network).decode(pendingSwap.toAddress)
4395
- ),
4276
+ OutScript2.encode(Address2(network).decode(pendingSwap.toAddress)),
4396
4277
  feeToDeliverExactAmount > fee ? feeToDeliverExactAmount : fee
4397
4278
  )
4398
4279
  );
4399
4280
  const musigMessage = musig.message(
4400
- claimTx.preimageWitnessV1(
4401
- 0,
4402
- [swapOutput.script],
4403
- SigHash2.DEFAULT,
4404
- [swapOutput.amount]
4405
- )
4281
+ claimTx.preimageWitnessV1(0, [swapOutput.script], SigHash2.DEFAULT, [
4282
+ swapOutput.amount
4283
+ ])
4406
4284
  ).generateNonce();
4407
4285
  const signedTxData = await this.swapProvider.postChainClaimDetails(
4408
4286
  pendingSwap.response.id,
@@ -4416,14 +4294,10 @@ var ArkadeSwaps = class _ArkadeSwaps {
4416
4294
  }
4417
4295
  );
4418
4296
  if (!signedTxData.pubNonce || !signedTxData.partialSignature)
4419
- throw new Error(
4420
- `Swap ${pendingSwap.id}: invalid signature data from server`
4421
- );
4297
+ throw new Error(`Swap ${pendingSwap.id}: invalid signature data from server`);
4422
4298
  const musigSession = musigMessage.aggregateNonces([
4423
4299
  [
4424
- hex8.decode(
4425
- pendingSwap.response.claimDetails.serverPublicKey
4426
- ),
4300
+ hex8.decode(pendingSwap.response.claimDetails.serverPublicKey),
4427
4301
  hex8.decode(signedTxData.pubNonce)
4428
4302
  ]
4429
4303
  ]).initializeSession();
@@ -4438,18 +4312,23 @@ var ArkadeSwaps = class _ArkadeSwaps {
4438
4312
  await this.swapProvider.postBtcTransaction(claimTx.hex);
4439
4313
  }
4440
4314
  /**
4441
- * When an ARK to BTC swap fails, refund sats on ARK chain by claiming the VHTLC.
4315
+ * When an ARK to BTC swap fails, refund every unspent VTXO at the chain
4316
+ * swap's ARK lockup address.
4317
+ *
4318
+ * Path selection per VTXO:
4319
+ * - CLTV has elapsed → `refundWithoutReceiver` via `joinBatch` (no Boltz).
4320
+ * - Pre-CLTV recoverable → skipped (Boltz can't co-sign swept-batch refund).
4321
+ * - Pre-CLTV non-recoverable → cooperative 3-of-3 refund via Boltz.
4322
+ *
4442
4323
  * @param pendingSwap - The pending chain swap to refund.
4324
+ * @returns Counts of VTXOs swept vs. deferred. A `swept: 0` outcome means
4325
+ * the call was a no-op — callers should retry after CLTV.
4443
4326
  */
4444
4327
  async refundArk(pendingSwap) {
4445
4328
  if (!pendingSwap.response.lockupDetails.serverPublicKey)
4446
- throw new Error(
4447
- `Swap ${pendingSwap.id}: missing server public key in lockup details`
4448
- );
4329
+ throw new Error(`Swap ${pendingSwap.id}: missing server public key in lockup details`);
4449
4330
  if (!pendingSwap.response.lockupDetails.timeouts)
4450
- throw new Error(
4451
- `Swap ${pendingSwap.id}: missing timeouts in lockup details`
4452
- );
4331
+ throw new Error(`Swap ${pendingSwap.id}: missing timeouts in lockup details`);
4453
4332
  const arkInfo = await this.arkProvider.getInfo();
4454
4333
  const address = await this.wallet.getAddress();
4455
4334
  const ourXOnlyPublicKey = normalizeToXOnlyKey(
@@ -4467,21 +4346,6 @@ var ArkadeSwaps = class _ArkadeSwaps {
4467
4346
  "boltz",
4468
4347
  pendingSwap.id
4469
4348
  );
4470
- const vhtlcPkScript = ArkAddress2.decode(
4471
- pendingSwap.response.lockupDetails.lockupAddress
4472
- ).pkScript;
4473
- const { vtxos } = await this.indexerProvider.getVtxos({
4474
- scripts: [hex8.encode(vhtlcPkScript)]
4475
- });
4476
- if (vtxos.length === 0) {
4477
- throw new Error(
4478
- `Swap ${pendingSwap.id}: VHTLC not found for address ${pendingSwap.response.lockupDetails.lockupAddress}`
4479
- );
4480
- }
4481
- const vtxo = vtxos[0];
4482
- if (vtxo.isSpent) {
4483
- throw new Error(`Swap ${pendingSwap.id}: VHTLC is already spent`);
4484
- }
4485
4349
  const { vhtlcAddress, vhtlcScript } = this.createVHTLCScript({
4486
4350
  network: arkInfo.network,
4487
4351
  preimageHash: hex8.decode(pendingSwap.request.preimageHash),
@@ -4491,45 +4355,111 @@ var ArkadeSwaps = class _ArkadeSwaps {
4491
4355
  timeoutBlockHeights: pendingSwap.response.lockupDetails.timeouts
4492
4356
  });
4493
4357
  if (!vhtlcScript.refundScript)
4494
- throw new Error(
4495
- `Swap ${pendingSwap.id}: failed to create VHTLC script for chain swap`
4496
- );
4358
+ throw new Error(`Swap ${pendingSwap.id}: failed to create VHTLC script for chain swap`);
4497
4359
  if (pendingSwap.response.lockupDetails.lockupAddress !== vhtlcAddress) {
4498
4360
  throw new SwapError({
4499
4361
  message: "Unable to claim: invalid VHTLC address"
4500
4362
  });
4501
4363
  }
4502
- const isRecoverableVtxo = isRecoverable(vtxo);
4503
- const input = {
4504
- ...vtxo,
4505
- tapLeafScript: isRecoverableVtxo ? vhtlcScript.refundWithoutReceiver() : vhtlcScript.refund(),
4506
- tapTree: vhtlcScript.encode()
4507
- };
4508
- const output = {
4509
- amount: BigInt(vtxo.value),
4510
- script: ArkAddress2.decode(address).pkScript
4511
- };
4512
- if (isRecoverableVtxo) {
4513
- await this.joinBatch(this.wallet.identity, input, output, arkInfo);
4514
- } else {
4515
- await refundVHTLCwithOffchainTx(
4516
- pendingSwap.id,
4517
- this.wallet.identity,
4518
- this.arkProvider,
4519
- boltzXOnlyPublicKey,
4520
- ourXOnlyPublicKey,
4521
- serverXOnlyPublicKey,
4522
- input,
4523
- output,
4524
- arkInfo,
4525
- this.swapProvider.refundChainSwap.bind(this.swapProvider)
4364
+ const { vtxos } = await this.indexerProvider.getVtxos({
4365
+ scripts: [hex8.encode(vhtlcScript.pkScript)]
4366
+ });
4367
+ if (vtxos.length === 0) {
4368
+ throw new Error(
4369
+ `Swap ${pendingSwap.id}: VHTLC not found for address ${pendingSwap.response.lockupDetails.lockupAddress}`
4526
4370
  );
4527
4371
  }
4372
+ const unspentVtxos = vtxos.filter((vtxo) => !vtxo.isSpent);
4373
+ if (unspentVtxos.length === 0) {
4374
+ throw new Error(`Swap ${pendingSwap.id}: VHTLC is already spent`);
4375
+ }
4376
+ const outputScript = ArkAddress2.decode(address).pkScript;
4377
+ const refundWithoutReceiverLeaf = vhtlcScript.refundWithoutReceiver();
4378
+ const refundLocktime = pendingSwap.response.lockupDetails.timeouts.refund;
4379
+ let boltzCallCount = 0;
4380
+ let sweptCount = 0;
4381
+ let skippedCount = 0;
4382
+ for (const vtxo of unspentVtxos) {
4383
+ const isRecoverableVtxo = isRecoverable(vtxo);
4384
+ const output = {
4385
+ amount: BigInt(vtxo.value),
4386
+ script: outputScript
4387
+ };
4388
+ if (isSubmarineRefundLocktimeReached(refundLocktime)) {
4389
+ const input2 = {
4390
+ ...vtxo,
4391
+ tapLeafScript: refundWithoutReceiverLeaf,
4392
+ tapTree: vhtlcScript.encode()
4393
+ };
4394
+ await this.joinBatch(
4395
+ this.wallet.identity,
4396
+ input2,
4397
+ output,
4398
+ arkInfo,
4399
+ isRecoverableVtxo
4400
+ );
4401
+ sweptCount++;
4402
+ continue;
4403
+ }
4404
+ if (isRecoverableVtxo) {
4405
+ logger.error(
4406
+ `Swap ${pendingSwap.id}: recoverable VTXO ${vtxo.txid}:${vtxo.vout} cannot be refunded yet \u2014 refundWithoutReceiver locktime has not passed (refundLocktime=${refundLocktime}, currentTimestamp=${Math.floor(Date.now() / 1e3)}). Refund will be retried after locktime.`
4407
+ );
4408
+ skippedCount++;
4409
+ continue;
4410
+ }
4411
+ const input = {
4412
+ ...vtxo,
4413
+ tapLeafScript: vhtlcScript.refund(),
4414
+ tapTree: vhtlcScript.encode()
4415
+ };
4416
+ try {
4417
+ if (boltzCallCount > 0) {
4418
+ await new Promise((r) => setTimeout(r, 2e3));
4419
+ }
4420
+ boltzCallCount++;
4421
+ await refundVHTLCwithOffchainTx(
4422
+ pendingSwap.id,
4423
+ this.wallet.identity,
4424
+ this.arkProvider,
4425
+ boltzXOnlyPublicKey,
4426
+ ourXOnlyPublicKey,
4427
+ serverXOnlyPublicKey,
4428
+ input,
4429
+ output,
4430
+ arkInfo,
4431
+ this.swapProvider.refundChainSwap.bind(this.swapProvider)
4432
+ );
4433
+ sweptCount++;
4434
+ } catch (error) {
4435
+ if (!(error instanceof BoltzRefundError)) {
4436
+ throw error;
4437
+ }
4438
+ if (!isSubmarineRefundLocktimeReached(refundLocktime)) {
4439
+ logger.error(
4440
+ `Swap ${pendingSwap.id}: Boltz rejected VTXO outpoint and refundWithoutReceiver locktime has not passed yet (currentTimestamp=${Math.floor(Date.now() / 1e3)}, locktime=${refundLocktime}). Refund will be retried after locktime.`
4441
+ );
4442
+ skippedCount++;
4443
+ continue;
4444
+ }
4445
+ logger.warn(
4446
+ `Swap ${pendingSwap.id}: Boltz rejected VTXO outpoint, falling back to refundWithoutReceiver via joinBatch`
4447
+ );
4448
+ const fallbackInput = {
4449
+ ...vtxo,
4450
+ tapLeafScript: refundWithoutReceiverLeaf,
4451
+ tapTree: vhtlcScript.encode()
4452
+ };
4453
+ await this.joinBatch(this.wallet.identity, fallbackInput, output, arkInfo, false);
4454
+ sweptCount++;
4455
+ }
4456
+ }
4528
4457
  const finalStatus = await this.getSwapStatus(pendingSwap.id);
4529
4458
  await this.savePendingChainSwap({
4530
4459
  ...pendingSwap,
4531
4460
  status: finalStatus.status
4532
4461
  });
4462
+ return { swept: sweptCount, skipped: skippedCount };
4533
4463
  }
4534
4464
  // =========================================================================
4535
4465
  // Chain swaps: BTC -> ARK
@@ -4574,9 +4504,7 @@ var ArkadeSwaps = class _ArkadeSwaps {
4574
4504
  */
4575
4505
  async waitAndClaimArk(pendingSwap) {
4576
4506
  if (this.swapManager && await this.swapManager.hasSwap(pendingSwap.id)) {
4577
- const { txid } = await this.swapManager.waitForSwapCompletion(
4578
- pendingSwap.id
4579
- );
4507
+ const { txid } = await this.swapManager.waitForSwapCompletion(pendingSwap.id);
4580
4508
  return { txid };
4581
4509
  }
4582
4510
  return new Promise((resolve, reject) => {
@@ -4597,37 +4525,33 @@ var ArkadeSwaps = class _ArkadeSwaps {
4597
4525
  break;
4598
4526
  case "transaction.claimed":
4599
4527
  await updateSwapStatus();
4600
- const claimedStatus = await this.getSwapStatus(
4601
- pendingSwap.id
4602
- );
4528
+ const claimedStatus = await this.getSwapStatus(pendingSwap.id);
4603
4529
  resolve({
4604
4530
  txid: claimedStatus.transaction?.id ?? ""
4605
4531
  });
4606
4532
  break;
4607
4533
  case "transaction.claim.pending":
4608
4534
  await updateSwapStatus();
4609
- await this.signCooperativeClaimForServer(swap).catch(
4535
+ await this.signCooperativeClaimForServer(swap).catch((err) => {
4536
+ logger.error(`Failed to sign cooperative claim for ${swap.id}:`, err);
4537
+ });
4538
+ break;
4539
+ case "transaction.lockupFailed":
4540
+ await updateSwapStatus();
4541
+ await this.quoteSwap(swap.response.id, quoteOptionsForSwap(swap)).catch(
4610
4542
  (err) => {
4611
- logger.error(
4612
- `Failed to sign cooperative claim for ${swap.id}:`,
4613
- err
4543
+ reject(
4544
+ new SwapError({
4545
+ message: `Failed to renegotiate quote: ${err.message}`,
4546
+ isRefundable: false,
4547
+ // TODO btc refund not implemented yet
4548
+ pendingSwap: swap,
4549
+ cause: err
4550
+ })
4614
4551
  );
4615
4552
  }
4616
4553
  );
4617
4554
  break;
4618
- case "transaction.lockupFailed":
4619
- await updateSwapStatus();
4620
- await this.quoteSwap(swap.response.id).catch((err) => {
4621
- reject(
4622
- new SwapError({
4623
- message: `Failed to renegotiate quote: ${err.message}`,
4624
- isRefundable: false,
4625
- // TODO btc refund not implemented yet
4626
- pendingSwap: swap
4627
- })
4628
- );
4629
- });
4630
- break;
4631
4555
  case "swap.expired":
4632
4556
  await updateSwapStatus();
4633
4557
  reject(
@@ -4667,17 +4591,11 @@ var ArkadeSwaps = class _ArkadeSwaps {
4667
4591
  */
4668
4592
  async claimArk(pendingSwap) {
4669
4593
  if (!pendingSwap.toAddress)
4670
- throw new Error(
4671
- `Swap ${pendingSwap.id}: destination address is required`
4672
- );
4594
+ throw new Error(`Swap ${pendingSwap.id}: destination address is required`);
4673
4595
  if (!pendingSwap.response.claimDetails.serverPublicKey)
4674
- throw new Error(
4675
- `Swap ${pendingSwap.id}: missing server public key in claim details`
4676
- );
4596
+ throw new Error(`Swap ${pendingSwap.id}: missing server public key in claim details`);
4677
4597
  if (!pendingSwap.response.claimDetails.timeouts)
4678
- throw new Error(
4679
- `Swap ${pendingSwap.id}: missing timeouts in claim details`
4680
- );
4598
+ throw new Error(`Swap ${pendingSwap.id}: missing timeouts in claim details`);
4681
4599
  const arkInfo = await this.arkProvider.getInfo();
4682
4600
  const preimage = hex8.decode(pendingSwap.preimage);
4683
4601
  const address = await this.wallet.getAddress();
@@ -4689,10 +4607,7 @@ var ArkadeSwaps = class _ArkadeSwaps {
4689
4607
  pendingSwap.response.claimDetails.serverPublicKey,
4690
4608
  "sender"
4691
4609
  );
4692
- const serverXOnlyPublicKey = normalizeToXOnlyKey(
4693
- arkInfo.signerPubkey,
4694
- "server"
4695
- );
4610
+ const serverXOnlyPublicKey = normalizeToXOnlyKey(arkInfo.signerPubkey, "server");
4696
4611
  const { vhtlcAddress, vhtlcScript } = this.createVHTLCScript({
4697
4612
  network: arkInfo.network,
4698
4613
  preimageHash: hex8.decode(pendingSwap.request.preimageHash),
@@ -4702,9 +4617,7 @@ var ArkadeSwaps = class _ArkadeSwaps {
4702
4617
  timeoutBlockHeights: pendingSwap.response.claimDetails.timeouts
4703
4618
  });
4704
4619
  if (!vhtlcScript.claimScript)
4705
- throw new Error(
4706
- `Swap ${pendingSwap.id}: failed to create VHTLC script for chain swap`
4707
- );
4620
+ throw new Error(`Swap ${pendingSwap.id}: failed to create VHTLC script for chain swap`);
4708
4621
  if (pendingSwap.response.claimDetails.lockupAddress !== vhtlcAddress) {
4709
4622
  throw new SwapError({
4710
4623
  message: "Unable to claim: invalid VHTLC address"
@@ -4721,15 +4634,11 @@ var ArkadeSwaps = class _ArkadeSwaps {
4721
4634
  break;
4722
4635
  }
4723
4636
  if (attempt < CLAIM_VTXO_RETRY_ATTEMPTS) {
4724
- await new Promise(
4725
- (resolve) => setTimeout(resolve, CLAIM_VTXO_RETRY_DELAY_MS)
4726
- );
4637
+ await new Promise((resolve) => setTimeout(resolve, CLAIM_VTXO_RETRY_DELAY_MS));
4727
4638
  }
4728
4639
  }
4729
4640
  if (!vtxo) {
4730
- throw new Error(
4731
- `Swap ${pendingSwap.id}: no spendable virtual coins found`
4732
- );
4641
+ throw new Error(`Swap ${pendingSwap.id}: no spendable virtual coins found`);
4733
4642
  }
4734
4643
  const input = {
4735
4644
  ...vtxo,
@@ -4740,10 +4649,7 @@ var ArkadeSwaps = class _ArkadeSwaps {
4740
4649
  amount: BigInt(vtxo.value),
4741
4650
  script: ArkAddress2.decode(address).pkScript
4742
4651
  };
4743
- const vhtlcIdentity = claimVHTLCIdentity(
4744
- this.wallet.identity,
4745
- preimage
4746
- );
4652
+ const vhtlcIdentity = claimVHTLCIdentity(this.wallet.identity, preimage);
4747
4653
  if (isRecoverable(vtxo)) {
4748
4654
  await this.joinBatch(vhtlcIdentity, input, output, arkInfo);
4749
4655
  } else {
@@ -4769,16 +4675,10 @@ var ArkadeSwaps = class _ArkadeSwaps {
4769
4675
  */
4770
4676
  async signCooperativeClaimForServer(pendingSwap) {
4771
4677
  if (!pendingSwap.response.lockupDetails.swapTree)
4772
- throw new Error(
4773
- `Swap ${pendingSwap.id}: missing swap tree in lockup details`
4774
- );
4678
+ throw new Error(`Swap ${pendingSwap.id}: missing swap tree in lockup details`);
4775
4679
  if (!pendingSwap.response.lockupDetails.serverPublicKey)
4776
- throw new Error(
4777
- `Swap ${pendingSwap.id}: missing server public key in lockup details`
4778
- );
4779
- const claimDetails = await this.swapProvider.getChainClaimDetails(
4780
- pendingSwap.id
4781
- );
4680
+ throw new Error(`Swap ${pendingSwap.id}: missing server public key in lockup details`);
4681
+ const claimDetails = await this.swapProvider.getChainClaimDetails(pendingSwap.id);
4782
4682
  const serverPubKey = pendingSwap.response.lockupDetails.serverPublicKey;
4783
4683
  if (claimDetails.publicKey !== serverPubKey) {
4784
4684
  throw new Error(
@@ -4794,9 +4694,7 @@ var ArkadeSwaps = class _ArkadeSwaps {
4794
4694
  );
4795
4695
  const musigNonces = musig.message(hex8.decode(claimDetails.transactionHash)).generateNonce().aggregateNonces([
4796
4696
  [
4797
- hex8.decode(
4798
- pendingSwap.response.lockupDetails.serverPublicKey
4799
- ),
4697
+ hex8.decode(pendingSwap.response.lockupDetails.serverPublicKey),
4800
4698
  hex8.decode(claimDetails.pubNonce)
4801
4699
  ]
4802
4700
  ]).initializeSession();
@@ -4815,10 +4713,8 @@ var ArkadeSwaps = class _ArkadeSwaps {
4815
4713
  * @returns The transaction ID of the claim.
4816
4714
  */
4817
4715
  async waitAndClaimChain(pendingSwap) {
4818
- if (pendingSwap.request.to === "ARK")
4819
- return this.waitAndClaimArk(pendingSwap);
4820
- if (pendingSwap.request.to === "BTC")
4821
- return this.waitAndClaimBtc(pendingSwap);
4716
+ if (pendingSwap.request.to === "ARK") return this.waitAndClaimArk(pendingSwap);
4717
+ if (pendingSwap.request.to === "BTC") return this.waitAndClaimBtc(pendingSwap);
4822
4718
  throw new SwapError({
4823
4719
  message: `Unsupported swap destination: ${pendingSwap.request.to}`
4824
4720
  });
@@ -4833,11 +4729,9 @@ var ArkadeSwaps = class _ArkadeSwaps {
4833
4729
  */
4834
4730
  async createChainSwap(args) {
4835
4731
  const { to, from, receiverLockAmount, senderLockAmount, toAddress } = args;
4836
- if (!toAddress)
4837
- throw new SwapError({ message: "Destination address is required" });
4732
+ if (!toAddress) throw new SwapError({ message: "Destination address is required" });
4838
4733
  const feeSatsPerByte = args.feeSatsPerByte ?? 1;
4839
- if (feeSatsPerByte <= 0)
4840
- throw new SwapError({ message: "Invalid feeSatsPerByte" });
4734
+ if (feeSatsPerByte <= 0) throw new SwapError({ message: "Invalid feeSatsPerByte" });
4841
4735
  let amount, serverLockAmount, userLockAmount;
4842
4736
  if (receiverLockAmount) {
4843
4737
  amount = receiverLockAmount;
@@ -4852,8 +4746,7 @@ var ArkadeSwaps = class _ArkadeSwaps {
4852
4746
  }
4853
4747
  const preimage = randomBytes(32);
4854
4748
  const preimageHash = hex8.encode(sha2563(preimage));
4855
- if (!preimageHash)
4856
- throw new SwapError({ message: "Failed to get preimage hash" });
4749
+ if (!preimageHash) throw new SwapError({ message: "Failed to get preimage hash" });
4857
4750
  const ephemeralKey = secp256k12.utils.randomSecretKey();
4858
4751
  const refundPublicKey = to === "ARK" ? hex8.encode(secp256k12.getPublicKey(ephemeralKey)) : hex8.encode(await this.wallet.identity.compressedPublicKey());
4859
4752
  if (!refundPublicKey)
@@ -4902,30 +4795,20 @@ var ArkadeSwaps = class _ArkadeSwaps {
4902
4795
  const { to, from, swap, arkInfo } = args;
4903
4796
  if (from === "ARK") {
4904
4797
  if (!swap.response.lockupDetails.serverPublicKey)
4905
- throw new Error(
4906
- `Swap ${swap.id}: missing serverPublicKey in lockup details`
4907
- );
4798
+ throw new Error(`Swap ${swap.id}: missing serverPublicKey in lockup details`);
4908
4799
  if (!swap.response.lockupDetails.timeouts)
4909
- throw new Error(
4910
- `Swap ${swap.id}: missing timeouts in lockup details`
4911
- );
4800
+ throw new Error(`Swap ${swap.id}: missing timeouts in lockup details`);
4912
4801
  }
4913
4802
  if (to === "ARK") {
4914
4803
  if (!swap.response.claimDetails.serverPublicKey)
4915
- throw new Error(
4916
- `Swap ${swap.id}: missing serverPublicKey in claim details`
4917
- );
4804
+ throw new Error(`Swap ${swap.id}: missing serverPublicKey in claim details`);
4918
4805
  if (!swap.response.claimDetails.timeouts)
4919
- throw new Error(
4920
- `Swap ${swap.id}: missing timeouts in claim details`
4921
- );
4806
+ throw new Error(`Swap ${swap.id}: missing timeouts in claim details`);
4922
4807
  }
4923
4808
  const lockupAddress = to === "ARK" ? swap.response.claimDetails.lockupAddress : swap.response.lockupDetails.lockupAddress;
4924
4809
  const receiverPubkey = to === "ARK" ? swap.request.claimPublicKey : swap.response.lockupDetails.serverPublicKey;
4925
4810
  const senderPubkey = to === "ARK" ? swap.response.claimDetails.serverPublicKey : swap.request.refundPublicKey;
4926
- const serverPubkey = hex8.encode(
4927
- normalizeToXOnlyKey(arkInfo.signerPubkey, "server")
4928
- );
4811
+ const serverPubkey = hex8.encode(normalizeToXOnlyKey(arkInfo.signerPubkey, "server"));
4929
4812
  const vhtlcTimeouts = to === "ARK" ? swap.response.claimDetails.timeouts : swap.response.lockupDetails.timeouts;
4930
4813
  const { vhtlcAddress } = this.createVHTLCScript({
4931
4814
  network: arkInfo.network,
@@ -4943,15 +4826,122 @@ var ArkadeSwaps = class _ArkadeSwaps {
4943
4826
  return true;
4944
4827
  }
4945
4828
  /**
4946
- * Renegotiates the quote for an existing swap.
4829
+ * Renegotiates the quote for an existing chain swap. Convenience wrapper
4830
+ * over `getSwapQuote` + `acceptSwapQuote` with a safety floor.
4831
+ *
4832
+ * The floor is resolved in order:
4833
+ * 1. `options.minAcceptableAmount` if provided.
4834
+ * 2. The original `response.claimDetails.amount` of the stored
4835
+ * pending swap (Boltz-confirmed server-lock amount at creation).
4836
+ * 3. Otherwise throws `QuoteRejectedError({ reason: "no_baseline" })`.
4837
+ *
4838
+ * `options.maxSlippageBps` (default 0) relaxes the floor by basis points.
4839
+ * Quotes ≤ 0 are always rejected. On rejection the acceptance is NOT
4840
+ * posted to Boltz.
4841
+ *
4842
+ * Prefer `getSwapQuote` / `acceptSwapQuote` for callers that want to
4843
+ * inspect the quote before committing.
4844
+ *
4947
4845
  * @param swapId - The ID of the swap.
4846
+ * @param options - Optional floor and slippage configuration.
4948
4847
  * @returns The accepted quote amount.
4848
+ * @throws QuoteRejectedError if the quote is non-positive, below the
4849
+ * effective floor, or no baseline is available.
4949
4850
  */
4950
- async quoteSwap(swapId) {
4851
+ async quoteSwap(swapId, options) {
4852
+ const effectiveFloor = await this.resolveEffectiveFloor(swapId, options);
4853
+ const amount = await this.getSwapQuote(swapId);
4854
+ this.validateQuote(amount, effectiveFloor);
4855
+ await this.swapProvider.postChainQuote(swapId, { amount });
4856
+ return amount;
4857
+ }
4858
+ /**
4859
+ * Fetches a renegotiated quote from Boltz without accepting it.
4860
+ * Pair with `acceptSwapQuote` to commit a specific value.
4861
+ */
4862
+ async getSwapQuote(swapId) {
4951
4863
  const { amount } = await this.swapProvider.getChainQuote(swapId);
4864
+ return amount;
4865
+ }
4866
+ /**
4867
+ * Accepts a quote amount for an existing chain swap, after validating it
4868
+ * against the configured floor. See `quoteSwap` for floor-resolution rules.
4869
+ *
4870
+ * @throws QuoteRejectedError if `amount` ≤ 0, below the effective floor,
4871
+ * or no baseline is available.
4872
+ */
4873
+ async acceptSwapQuote(swapId, amount, options) {
4874
+ const effectiveFloor = await this.resolveEffectiveFloor(swapId, options);
4875
+ this.validateQuote(amount, effectiveFloor);
4952
4876
  await this.swapProvider.postChainQuote(swapId, { amount });
4953
4877
  return amount;
4954
4878
  }
4879
+ async resolveEffectiveFloor(swapId, options) {
4880
+ this.validateQuoteOptions(options);
4881
+ const floor = await this.resolveQuoteFloor(swapId, options);
4882
+ const slippageBps = options?.maxSlippageBps ?? 0;
4883
+ const effectiveFloor = Math.floor(floor - floor * slippageBps / 1e4);
4884
+ if (effectiveFloor < 1) {
4885
+ throw new TypeError(
4886
+ `Invalid quote configuration: maxSlippageBps=${slippageBps} reduces floor ${floor} below 1 sat`
4887
+ );
4888
+ }
4889
+ return effectiveFloor;
4890
+ }
4891
+ async resolveQuoteFloor(swapId, options) {
4892
+ if (options?.minAcceptableAmount !== void 0) {
4893
+ return options.minAcceptableAmount;
4894
+ }
4895
+ const swaps = await this.swapRepository.getAllSwaps({
4896
+ id: swapId,
4897
+ type: "chain"
4898
+ });
4899
+ const stored = swaps[0];
4900
+ const amount = stored?.response?.claimDetails?.amount;
4901
+ if (typeof amount !== "number") {
4902
+ throw new QuoteRejectedError({ reason: "no_baseline" });
4903
+ }
4904
+ return amount;
4905
+ }
4906
+ validateQuoteOptions(options) {
4907
+ if (options?.minAcceptableAmount !== void 0) {
4908
+ const v = options.minAcceptableAmount;
4909
+ if (!Number.isInteger(v) || v <= 0) {
4910
+ throw new TypeError(
4911
+ `Invalid minAcceptableAmount: ${v} \u2014 must be a positive integer`
4912
+ );
4913
+ }
4914
+ }
4915
+ if (options?.maxSlippageBps !== void 0) {
4916
+ const v = options.maxSlippageBps;
4917
+ if (!Number.isInteger(v) || v < 0 || v > 1e4) {
4918
+ throw new TypeError(
4919
+ `Invalid maxSlippageBps: ${v} \u2014 must be an integer in [0, 10000]`
4920
+ );
4921
+ }
4922
+ }
4923
+ }
4924
+ validateQuote(amount, effectiveFloor) {
4925
+ if (!Number.isSafeInteger(amount)) {
4926
+ throw new QuoteRejectedError({
4927
+ reason: "non_safe_integer",
4928
+ quotedAmount: amount
4929
+ });
4930
+ }
4931
+ if (amount <= 0) {
4932
+ throw new QuoteRejectedError({
4933
+ reason: "non_positive",
4934
+ quotedAmount: amount
4935
+ });
4936
+ }
4937
+ if (amount < effectiveFloor) {
4938
+ throw new QuoteRejectedError({
4939
+ reason: "below_floor",
4940
+ quotedAmount: amount,
4941
+ floor: effectiveFloor
4942
+ });
4943
+ }
4944
+ }
4955
4945
  // =========================================================================
4956
4946
  // Shared utilities
4957
4947
  // =========================================================================
@@ -4965,14 +4955,7 @@ var ArkadeSwaps = class _ArkadeSwaps {
4965
4955
  * @returns The commitment transaction ID.
4966
4956
  */
4967
4957
  async joinBatch(identity, input, output, arkInfo, isRecoverable2 = true) {
4968
- return joinBatch(
4969
- this.arkProvider,
4970
- identity,
4971
- input,
4972
- output,
4973
- arkInfo,
4974
- isRecoverable2
4975
- );
4958
+ return joinBatch(this.arkProvider, identity, input, output, arkInfo, isRecoverable2);
4976
4959
  }
4977
4960
  /**
4978
4961
  * Creates a VHTLC script for the swap.
@@ -5010,9 +4993,7 @@ var ArkadeSwaps = class _ArkadeSwaps {
5010
4993
  async getPendingSubmarineSwaps() {
5011
4994
  const swaps = await this.getPendingSubmarineSwapsFromStorage();
5012
4995
  if (!swaps) return [];
5013
- return swaps.filter(
5014
- (swap) => swap.status === "invoice.set"
5015
- );
4996
+ return swaps.filter((swap) => swap.status === "invoice.set");
5016
4997
  }
5017
4998
  /**
5018
4999
  * Returns pending reverse swaps (those with status `swap.created`).
@@ -5020,9 +5001,7 @@ var ArkadeSwaps = class _ArkadeSwaps {
5020
5001
  async getPendingReverseSwaps() {
5021
5002
  const swaps = await this.getPendingReverseSwapsFromStorage();
5022
5003
  if (!swaps) return [];
5023
- return swaps.filter(
5024
- (swap) => swap.status === "swap.created"
5025
- );
5004
+ return swaps.filter((swap) => swap.status === "swap.created");
5026
5005
  }
5027
5006
  /**
5028
5007
  * Returns pending chain swaps (those with status `swap.created`).
@@ -5061,10 +5040,7 @@ var ArkadeSwaps = class _ArkadeSwaps {
5061
5040
  this.savePendingReverseSwap.bind(this)
5062
5041
  )
5063
5042
  ).catch((error) => {
5064
- logger.error(
5065
- `Failed to refresh swap status for ${swap.id}:`,
5066
- error
5067
- );
5043
+ logger.error(`Failed to refresh swap status for ${swap.id}:`, error);
5068
5044
  })
5069
5045
  );
5070
5046
  }
@@ -5078,23 +5054,15 @@ var ArkadeSwaps = class _ArkadeSwaps {
5078
5054
  this.savePendingSubmarineSwap.bind(this)
5079
5055
  )
5080
5056
  ).catch((error) => {
5081
- logger.error(
5082
- `Failed to refresh swap status for ${swap.id}:`,
5083
- error
5084
- );
5057
+ logger.error(`Failed to refresh swap status for ${swap.id}:`, error);
5085
5058
  })
5086
5059
  );
5087
5060
  }
5088
5061
  for (const swap of await this.getPendingChainSwapsFromStorage()) {
5089
5062
  if (isChainFinalStatus(swap.status)) continue;
5090
5063
  promises.push(
5091
- this.getSwapStatus(swap.id).then(
5092
- ({ status }) => this.savePendingChainSwap({ ...swap, status })
5093
- ).catch((error) => {
5094
- logger.error(
5095
- `Failed to refresh swap status for ${swap.id}:`,
5096
- error
5097
- );
5064
+ this.getSwapStatus(swap.id).then(({ status }) => this.savePendingChainSwap({ ...swap, status })).catch((error) => {
5065
+ logger.error(`Failed to refresh swap status for ${swap.id}:`, error);
5098
5066
  })
5099
5067
  );
5100
5068
  }
@@ -5111,9 +5079,7 @@ var ArkadeSwaps = class _ArkadeSwaps {
5111
5079
  * display/monitoring and are not automatically wired into the SwapManager.
5112
5080
  */
5113
5081
  async restoreSwaps(boltzFees) {
5114
- const publicKey = hex8.encode(
5115
- await this.wallet.identity.compressedPublicKey()
5116
- );
5082
+ const publicKey = hex8.encode(await this.wallet.identity.compressedPublicKey());
5117
5083
  if (!publicKey) throw new Error("Failed to get public key from wallet");
5118
5084
  const fees = boltzFees ?? await this.swapProvider.getFees();
5119
5085
  const chainSwaps = [];
@@ -5165,25 +5131,14 @@ var ArkadeSwaps = class _ArkadeSwaps {
5165
5131
  preimage: ""
5166
5132
  });
5167
5133
  } else if (isRestoredSubmarineSwap(swap)) {
5168
- const {
5169
- amount,
5170
- lockupAddress,
5171
- serverPublicKey,
5172
- tree,
5173
- timeoutBlockHeights
5174
- } = swap.refundDetails;
5134
+ const { amount, lockupAddress, serverPublicKey, tree, timeoutBlockHeights } = swap.refundDetails;
5175
5135
  let preimage = "";
5176
5136
  if (!isSubmarineFinalStatus(status)) {
5177
5137
  try {
5178
- const data = await this.swapProvider.getSwapPreimage(
5179
- swap.id
5180
- );
5138
+ const data = await this.swapProvider.getSwapPreimage(swap.id);
5181
5139
  preimage = data.preimage;
5182
5140
  } catch (error) {
5183
- logger.warn(
5184
- `Failed to restore preimage for submarine swap ${id}`,
5185
- error
5186
- );
5141
+ logger.warn(`Failed to restore preimage for submarine swap ${id}`, error);
5187
5142
  }
5188
5143
  }
5189
5144
  submarineSwaps.push({
@@ -5221,12 +5176,7 @@ var ArkadeSwaps = class _ArkadeSwaps {
5221
5176
  } else if (isRestoredChainSwap(swap)) {
5222
5177
  const refundDetails = swap.refundDetails;
5223
5178
  if (!refundDetails) continue;
5224
- const {
5225
- amount,
5226
- lockupAddress,
5227
- serverPublicKey,
5228
- timeoutBlockHeight
5229
- } = refundDetails;
5179
+ const { amount, lockupAddress, serverPublicKey, timeoutBlockHeight } = refundDetails;
5230
5180
  chainSwaps.push({
5231
5181
  id,
5232
5182
  type: "chain",
@@ -5285,6 +5235,7 @@ export {
5285
5235
  SwapExpiredError,
5286
5236
  TransactionFailedError,
5287
5237
  PreimageFetchError,
5238
+ QuoteRejectedError,
5288
5239
  BoltzRefundError,
5289
5240
  isSubmarineFailedStatus,
5290
5241
  isSubmarineFinalStatus,