@arkade-os/boltz-swap 0.3.25 → 0.3.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{arkade-swaps-CZF9XoFR.d.cts → arkade-swaps-CS8FZSVL.d.cts} +1 -1
- package/dist/{arkade-swaps-pfAgQUMP.d.ts → arkade-swaps-WiKCanCL.d.ts} +1 -1
- package/dist/{background-GCBCKLGY.js → background-UQDFQCGM.js} +2 -2
- package/dist/{chunk-XC2ARJZO.js → chunk-LWUXSE5N.js} +187 -49
- package/dist/{chunk-FEXQELYZ.js → chunk-X3JNWDAR.js} +1 -1
- package/dist/expo/index.cjs +188 -51
- package/dist/expo/index.d.cts +2 -2
- package/dist/expo/index.d.ts +2 -2
- package/dist/expo/index.js +4 -4
- package/dist/index.cjs +188 -49
- package/dist/index.d.cts +19 -5
- package/dist/index.d.ts +19 -5
- package/dist/index.js +3 -1
- package/dist/repositories/realm/index.d.cts +1 -1
- package/dist/repositories/realm/index.d.ts +1 -1
- package/dist/repositories/sqlite/index.d.cts +1 -1
- package/dist/repositories/sqlite/index.d.ts +1 -1
- package/dist/{types-LCXS1AVA.d.cts → types-BBI7-KJ0.d.cts} +39 -1
- package/dist/{types-LCXS1AVA.d.ts → types-BBI7-KJ0.d.ts} +39 -1
- package/package.json +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { IWallet, ArkProvider, IndexerProvider, ArkInfo, Identity, ArkTxInput, VHTLC } from '@arkade-os/sdk';
|
|
2
|
-
import { p as BoltzSwapProvider, Y as SwapManager, m as SwapRepository, _ as ArkadeSwapsCreateConfig, A as ArkadeSwapsConfig, n as SwapManagerClient, C as CreateLightningInvoiceRequest, e as CreateLightningInvoiceResponse, b as BoltzReverseSwap, S as SendLightningPaymentRequest, f as SendLightningPaymentResponse, c as BoltzSubmarineSwap, g as SubmarineRefundOutcome, h as SubmarineRecoveryInfo, i as SubmarineRecoveryResult, k as ArkToBtcResponse, a as BoltzChainSwap, l as BtcToArkResponse, d as Chain, F as FeesResponse, j as ChainFeesResponse, L as LimitsResponse, G as GetSwapStatusResponse, B as BoltzSwap } from './types-
|
|
2
|
+
import { p as BoltzSwapProvider, Y as SwapManager, m as SwapRepository, _ as ArkadeSwapsCreateConfig, A as ArkadeSwapsConfig, n as SwapManagerClient, C as CreateLightningInvoiceRequest, e as CreateLightningInvoiceResponse, b as BoltzReverseSwap, S as SendLightningPaymentRequest, f as SendLightningPaymentResponse, c as BoltzSubmarineSwap, g as SubmarineRefundOutcome, h as SubmarineRecoveryInfo, i as SubmarineRecoveryResult, k as ArkToBtcResponse, a as BoltzChainSwap, l as BtcToArkResponse, d as Chain, F as FeesResponse, j as ChainFeesResponse, L as LimitsResponse, G as GetSwapStatusResponse, B as BoltzSwap } from './types-BBI7-KJ0.cjs';
|
|
3
3
|
import { TransactionOutput } from '@scure/btc-signer/psbt.js';
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { IWallet, ArkProvider, IndexerProvider, ArkInfo, Identity, ArkTxInput, VHTLC } from '@arkade-os/sdk';
|
|
2
|
-
import { p as BoltzSwapProvider, Y as SwapManager, m as SwapRepository, _ as ArkadeSwapsCreateConfig, A as ArkadeSwapsConfig, n as SwapManagerClient, C as CreateLightningInvoiceRequest, e as CreateLightningInvoiceResponse, b as BoltzReverseSwap, S as SendLightningPaymentRequest, f as SendLightningPaymentResponse, c as BoltzSubmarineSwap, g as SubmarineRefundOutcome, h as SubmarineRecoveryInfo, i as SubmarineRecoveryResult, k as ArkToBtcResponse, a as BoltzChainSwap, l as BtcToArkResponse, d as Chain, F as FeesResponse, j as ChainFeesResponse, L as LimitsResponse, G as GetSwapStatusResponse, B as BoltzSwap } from './types-
|
|
2
|
+
import { p as BoltzSwapProvider, Y as SwapManager, m as SwapRepository, _ as ArkadeSwapsCreateConfig, A as ArkadeSwapsConfig, n as SwapManagerClient, C as CreateLightningInvoiceRequest, e as CreateLightningInvoiceResponse, b as BoltzReverseSwap, S as SendLightningPaymentRequest, f as SendLightningPaymentResponse, c as BoltzSubmarineSwap, g as SubmarineRefundOutcome, h as SubmarineRecoveryInfo, i as SubmarineRecoveryResult, k as ArkToBtcResponse, a as BoltzChainSwap, l as BtcToArkResponse, d as Chain, F as FeesResponse, j as ChainFeesResponse, L as LimitsResponse, G as GetSwapStatusResponse, B as BoltzSwap } from './types-BBI7-KJ0.js';
|
|
3
3
|
import { TransactionOutput } from '@scure/btc-signer/psbt.js';
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -2,8 +2,8 @@ import {
|
|
|
2
2
|
defineExpoSwapBackgroundTask,
|
|
3
3
|
registerExpoSwapBackgroundTask,
|
|
4
4
|
unregisterExpoSwapBackgroundTask
|
|
5
|
-
} from "./chunk-
|
|
6
|
-
import "./chunk-
|
|
5
|
+
} from "./chunk-X3JNWDAR.js";
|
|
6
|
+
import "./chunk-LWUXSE5N.js";
|
|
7
7
|
import "./chunk-3RG5ZIWI.js";
|
|
8
8
|
export {
|
|
9
9
|
defineExpoSwapBackgroundTask,
|
|
@@ -47,6 +47,19 @@ var NetworkError = class extends Error {
|
|
|
47
47
|
this.errorData = errorData;
|
|
48
48
|
}
|
|
49
49
|
};
|
|
50
|
+
var SwapNotFoundError = class extends NetworkError {
|
|
51
|
+
/** The swap ID Boltz did not recognise. */
|
|
52
|
+
swapId;
|
|
53
|
+
constructor(swapId, errorData) {
|
|
54
|
+
super(
|
|
55
|
+
`Boltz returned 404 for swap '${swapId}': swap unknown to this Boltz instance`,
|
|
56
|
+
404,
|
|
57
|
+
errorData
|
|
58
|
+
);
|
|
59
|
+
this.name = "SwapNotFoundError";
|
|
60
|
+
this.swapId = swapId;
|
|
61
|
+
}
|
|
62
|
+
};
|
|
50
63
|
var SchemaError = class extends SwapError {
|
|
51
64
|
constructor(options = {}) {
|
|
52
65
|
super({ message: "Invalid API response", ...options });
|
|
@@ -302,10 +315,17 @@ var isCreateSwapsRestoreResponse = (data) => {
|
|
|
302
315
|
);
|
|
303
316
|
};
|
|
304
317
|
var BASE_URLS = {
|
|
305
|
-
bitcoin: "https://api.
|
|
318
|
+
bitcoin: "https://api.boltz.exchange",
|
|
306
319
|
mutinynet: "https://api.boltz.mutinynet.arkade.sh",
|
|
307
320
|
regtest: "http://localhost:9069"
|
|
308
321
|
};
|
|
322
|
+
var isSwapNotFoundBody = (error) => {
|
|
323
|
+
const needle = "could not find swap";
|
|
324
|
+
const fromJson = error.errorData?.error;
|
|
325
|
+
if (typeof fromJson === "string" && fromJson.toLowerCase().includes(needle))
|
|
326
|
+
return true;
|
|
327
|
+
return error.message.toLowerCase().includes(needle);
|
|
328
|
+
};
|
|
309
329
|
var BoltzSwapProvider = class {
|
|
310
330
|
wsUrl;
|
|
311
331
|
apiUrl;
|
|
@@ -397,12 +417,27 @@ var BoltzSwapProvider = class {
|
|
|
397
417
|
});
|
|
398
418
|
return res;
|
|
399
419
|
}
|
|
400
|
-
/**
|
|
420
|
+
/**
|
|
421
|
+
* Queries the current status of a swap by ID.
|
|
422
|
+
*
|
|
423
|
+
* @throws {SwapNotFoundError} when Boltz responds with HTTP 404 and a body
|
|
424
|
+
* matching the "could not find swap" pattern. Distinct from a generic 404
|
|
425
|
+
* so callers (e.g. SwapManager polling) can drive a per-swap unknown-to-
|
|
426
|
+
* provider counter without tripping on transient route or proxy errors.
|
|
427
|
+
*/
|
|
401
428
|
async getSwapStatus(id) {
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
429
|
+
let response;
|
|
430
|
+
try {
|
|
431
|
+
response = await this.request(
|
|
432
|
+
`/v2/swap/${id}`,
|
|
433
|
+
"GET"
|
|
434
|
+
);
|
|
435
|
+
} catch (error) {
|
|
436
|
+
if (error instanceof NetworkError && error.statusCode === 404 && isSwapNotFoundBody(error)) {
|
|
437
|
+
throw new SwapNotFoundError(id, error.errorData);
|
|
438
|
+
}
|
|
439
|
+
throw error;
|
|
440
|
+
}
|
|
406
441
|
if (!isGetSwapStatusResponse(response))
|
|
407
442
|
throw new SchemaError({
|
|
408
443
|
message: `error fetching status for swap: ${id}`
|
|
@@ -881,7 +916,15 @@ function setLogger(customLogger) {
|
|
|
881
916
|
}
|
|
882
917
|
|
|
883
918
|
// src/swap-manager.ts
|
|
884
|
-
var SwapManager = class {
|
|
919
|
+
var SwapManager = class _SwapManager {
|
|
920
|
+
/**
|
|
921
|
+
* Number of consecutive Boltz 404s for a single swap ID before the
|
|
922
|
+
* polling loop gives up and transitions the swap to a terminal state.
|
|
923
|
+
* At the default 30s poll cadence this is roughly a 5-minute grace
|
|
924
|
+
* window — long enough to ride out a transient Boltz blip, short
|
|
925
|
+
* enough that a real "swap unknown to this provider" surfaces quickly.
|
|
926
|
+
*/
|
|
927
|
+
static NOT_FOUND_THRESHOLD = 10;
|
|
885
928
|
swapProvider;
|
|
886
929
|
config;
|
|
887
930
|
// Event listeners storage (supports multiple listeners per event)
|
|
@@ -900,6 +943,13 @@ var SwapManager = class {
|
|
|
900
943
|
reconnectTimer = null;
|
|
901
944
|
initialPollTimer = null;
|
|
902
945
|
pollRetryTimers = /* @__PURE__ */ new Map();
|
|
946
|
+
// Per-swap counter of consecutive `SwapNotFoundError` responses from
|
|
947
|
+
// `getSwapStatus`. Reset on any successful poll. Once a swap reaches
|
|
948
|
+
// `NOT_FOUND_THRESHOLD` consecutive 404s the safety net trips and the
|
|
949
|
+
// swap is transitioned to `swap.expired` (terminal) and dropped from
|
|
950
|
+
// monitoring — typically the canonical failure mode after a Boltz
|
|
951
|
+
// endpoint switch, where old swap IDs are unknown to the new instance.
|
|
952
|
+
notFoundCounts = /* @__PURE__ */ new Map();
|
|
903
953
|
isRunning = false;
|
|
904
954
|
currentReconnectDelay;
|
|
905
955
|
currentPollRetryDelay;
|
|
@@ -1091,6 +1141,7 @@ var SwapManager = class {
|
|
|
1091
1141
|
clearTimeout(timer);
|
|
1092
1142
|
}
|
|
1093
1143
|
this.pollRetryTimers.clear();
|
|
1144
|
+
this.notFoundCounts.clear();
|
|
1094
1145
|
}
|
|
1095
1146
|
/**
|
|
1096
1147
|
* Set the polling interval (ms).
|
|
@@ -1151,6 +1202,7 @@ var SwapManager = class {
|
|
|
1151
1202
|
clearTimeout(retryTimer);
|
|
1152
1203
|
this.pollRetryTimers.delete(swapId);
|
|
1153
1204
|
}
|
|
1205
|
+
this.notFoundCounts.delete(swapId);
|
|
1154
1206
|
logger.log(`Removed swap ${swapId} from monitoring`);
|
|
1155
1207
|
}
|
|
1156
1208
|
/**
|
|
@@ -1427,6 +1479,7 @@ var SwapManager = class {
|
|
|
1427
1479
|
* This is the core logic that determines what actions to take
|
|
1428
1480
|
*/
|
|
1429
1481
|
async handleSwapStatusUpdate(swap, newStatus) {
|
|
1482
|
+
this.notFoundCounts.delete(swap.id);
|
|
1430
1483
|
const oldStatus = swap.status;
|
|
1431
1484
|
if (oldStatus === newStatus) return;
|
|
1432
1485
|
swap.status = newStatus;
|
|
@@ -1712,51 +1765,135 @@ var SwapManager = class {
|
|
|
1712
1765
|
async pollAllSwaps() {
|
|
1713
1766
|
if (this.monitoredSwaps.size === 0) return;
|
|
1714
1767
|
const pollPromises = Array.from(this.monitoredSwaps.values()).map(
|
|
1715
|
-
|
|
1768
|
+
(swap) => this.pollSingleSwap(swap)
|
|
1769
|
+
);
|
|
1770
|
+
await Promise.allSettled(pollPromises);
|
|
1771
|
+
}
|
|
1772
|
+
async pollSingleSwap(swap) {
|
|
1773
|
+
try {
|
|
1774
|
+
const statusResponse = await this.swapProvider.getSwapStatus(
|
|
1775
|
+
swap.id
|
|
1776
|
+
);
|
|
1777
|
+
this.notFoundCounts.delete(swap.id);
|
|
1778
|
+
if (statusResponse.status !== swap.status) {
|
|
1779
|
+
await this.handleSwapStatusUpdate(swap, statusResponse.status);
|
|
1780
|
+
}
|
|
1781
|
+
} catch (error) {
|
|
1782
|
+
if (error instanceof SwapNotFoundError) {
|
|
1783
|
+
await this.handleSwapNotFound(swap);
|
|
1784
|
+
return;
|
|
1785
|
+
}
|
|
1786
|
+
if (error instanceof NetworkError && error.statusCode === 429) {
|
|
1787
|
+
logger.warn(
|
|
1788
|
+
`Rate-limited polling swap ${swap.id}, retrying in 2s`
|
|
1789
|
+
);
|
|
1790
|
+
const existing = this.pollRetryTimers.get(swap.id);
|
|
1791
|
+
if (existing) clearTimeout(existing);
|
|
1792
|
+
this.pollRetryTimers.set(
|
|
1793
|
+
swap.id,
|
|
1794
|
+
setTimeout(async () => {
|
|
1795
|
+
this.pollRetryTimers.delete(swap.id);
|
|
1796
|
+
try {
|
|
1797
|
+
const retry = await this.swapProvider.getSwapStatus(
|
|
1798
|
+
swap.id
|
|
1799
|
+
);
|
|
1800
|
+
this.notFoundCounts.delete(swap.id);
|
|
1801
|
+
if (retry.status !== swap.status) {
|
|
1802
|
+
await this.handleSwapStatusUpdate(
|
|
1803
|
+
swap,
|
|
1804
|
+
retry.status
|
|
1805
|
+
);
|
|
1806
|
+
}
|
|
1807
|
+
} catch (retryError) {
|
|
1808
|
+
if (retryError instanceof SwapNotFoundError) {
|
|
1809
|
+
await this.handleSwapNotFound(swap);
|
|
1810
|
+
return;
|
|
1811
|
+
}
|
|
1812
|
+
logger.error(
|
|
1813
|
+
`Retry poll for swap ${swap.id} also failed:`,
|
|
1814
|
+
retryError
|
|
1815
|
+
);
|
|
1816
|
+
}
|
|
1817
|
+
}, 2e3)
|
|
1818
|
+
);
|
|
1819
|
+
} else {
|
|
1820
|
+
logger.error(`Failed to poll swap ${swap.id}:`, error);
|
|
1821
|
+
}
|
|
1822
|
+
}
|
|
1823
|
+
}
|
|
1824
|
+
/**
|
|
1825
|
+
* Increment the consecutive-not-found counter and, once the threshold is
|
|
1826
|
+
* reached, transition the swap to a terminal state and stop polling it.
|
|
1827
|
+
* Driven from {@link pollSingleSwap} when `getSwapStatus` throws
|
|
1828
|
+
* {@link SwapNotFoundError}. The threshold rides out a transient blip
|
|
1829
|
+
* but ensures we stop hammering Boltz with requests for swap IDs the
|
|
1830
|
+
* server has no record of (e.g. after switching the configured
|
|
1831
|
+
* Boltz endpoint).
|
|
1832
|
+
*/
|
|
1833
|
+
async handleSwapNotFound(swap) {
|
|
1834
|
+
const count = (this.notFoundCounts.get(swap.id) ?? 0) + 1;
|
|
1835
|
+
this.notFoundCounts.set(swap.id, count);
|
|
1836
|
+
logger.warn(
|
|
1837
|
+
`Swap ${swap.id}: unknown to Boltz (${count}/${_SwapManager.NOT_FOUND_THRESHOLD} consecutive)`
|
|
1838
|
+
);
|
|
1839
|
+
if (count >= _SwapManager.NOT_FOUND_THRESHOLD) {
|
|
1840
|
+
await this.markSwapAsUnknownToProvider(swap);
|
|
1841
|
+
}
|
|
1842
|
+
}
|
|
1843
|
+
/**
|
|
1844
|
+
* Transition a swap to {@code swap.expired} (terminal for all swap types)
|
|
1845
|
+
* after Boltz has consistently reported it unknown for
|
|
1846
|
+
* {@link SwapManager.NOT_FOUND_THRESHOLD} consecutive polls. The swap is
|
|
1847
|
+
* persisted, removed from monitoring, and reported via `onSwapFailed`.
|
|
1848
|
+
* Bypasses {@link handleSwapStatusUpdate} on purpose: we don't want to
|
|
1849
|
+
* trigger autonomous claim/refund actions against a Boltz instance that
|
|
1850
|
+
* has no record of this swap — the requests would just generate more
|
|
1851
|
+
* 404s without recovering anything.
|
|
1852
|
+
*/
|
|
1853
|
+
async markSwapAsUnknownToProvider(swap) {
|
|
1854
|
+
if (!this.monitoredSwaps.has(swap.id)) {
|
|
1855
|
+
this.notFoundCounts.delete(swap.id);
|
|
1856
|
+
return;
|
|
1857
|
+
}
|
|
1858
|
+
const oldStatus = swap.status;
|
|
1859
|
+
swap.status = "swap.expired";
|
|
1860
|
+
this.monitoredSwaps.delete(swap.id);
|
|
1861
|
+
const retryTimer = this.pollRetryTimers.get(swap.id);
|
|
1862
|
+
if (retryTimer) {
|
|
1863
|
+
clearTimeout(retryTimer);
|
|
1864
|
+
this.pollRetryTimers.delete(swap.id);
|
|
1865
|
+
}
|
|
1866
|
+
this.notFoundCounts.delete(swap.id);
|
|
1867
|
+
this.swapUpdateListeners.forEach(
|
|
1868
|
+
(listener) => listener(swap, oldStatus)
|
|
1869
|
+
);
|
|
1870
|
+
const subscribers = this.swapSubscriptions.get(swap.id);
|
|
1871
|
+
if (subscribers) {
|
|
1872
|
+
subscribers.forEach((callback) => {
|
|
1716
1873
|
try {
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
}
|
|
1724
|
-
} catch (error) {
|
|
1725
|
-
if (error instanceof NetworkError && error.statusCode === 429) {
|
|
1726
|
-
logger.warn(
|
|
1727
|
-
`Rate-limited polling swap ${swap.id}, retrying in 2s`
|
|
1728
|
-
);
|
|
1729
|
-
const existing = this.pollRetryTimers.get(swap.id);
|
|
1730
|
-
if (existing) clearTimeout(existing);
|
|
1731
|
-
this.pollRetryTimers.set(
|
|
1732
|
-
swap.id,
|
|
1733
|
-
setTimeout(async () => {
|
|
1734
|
-
this.pollRetryTimers.delete(swap.id);
|
|
1735
|
-
try {
|
|
1736
|
-
const retry = await this.swapProvider.getSwapStatus(
|
|
1737
|
-
swap.id
|
|
1738
|
-
);
|
|
1739
|
-
if (retry.status !== swap.status) {
|
|
1740
|
-
await this.handleSwapStatusUpdate(
|
|
1741
|
-
swap,
|
|
1742
|
-
retry.status
|
|
1743
|
-
);
|
|
1744
|
-
}
|
|
1745
|
-
} catch (retryError) {
|
|
1746
|
-
logger.error(
|
|
1747
|
-
`Retry poll for swap ${swap.id} also failed:`,
|
|
1748
|
-
retryError
|
|
1749
|
-
);
|
|
1750
|
-
}
|
|
1751
|
-
}, 2e3)
|
|
1752
|
-
);
|
|
1753
|
-
} else {
|
|
1754
|
-
logger.error(`Failed to poll swap ${swap.id}:`, error);
|
|
1755
|
-
}
|
|
1874
|
+
callback(swap, oldStatus);
|
|
1875
|
+
} catch (subscriberError) {
|
|
1876
|
+
logger.error(
|
|
1877
|
+
`Error in swap subscription callback for ${swap.id}:`,
|
|
1878
|
+
subscriberError
|
|
1879
|
+
);
|
|
1756
1880
|
}
|
|
1757
|
-
}
|
|
1881
|
+
});
|
|
1882
|
+
}
|
|
1883
|
+
try {
|
|
1884
|
+
await this.saveSwap(swap);
|
|
1885
|
+
} catch (saveError) {
|
|
1886
|
+
logger.error(
|
|
1887
|
+
`Failed to persist unknown-to-provider state for swap ${swap.id}:`,
|
|
1888
|
+
saveError
|
|
1889
|
+
);
|
|
1890
|
+
}
|
|
1891
|
+
logger.warn(
|
|
1892
|
+
`Swap ${swap.id}: marked failed after ${_SwapManager.NOT_FOUND_THRESHOLD} consecutive Boltz 404s \u2014 swap is unknown to the configured Boltz instance`
|
|
1758
1893
|
);
|
|
1759
|
-
|
|
1894
|
+
const error = new SwapNotFoundError(swap.id);
|
|
1895
|
+
this.swapFailedListeners.forEach((listener) => listener(swap, error));
|
|
1896
|
+
this.swapSubscriptions.delete(swap.id);
|
|
1760
1897
|
}
|
|
1761
1898
|
/**
|
|
1762
1899
|
* Check if a status is final (no more updates expected)
|
|
@@ -5143,6 +5280,7 @@ export {
|
|
|
5143
5280
|
InvoiceFailedToPayError,
|
|
5144
5281
|
InsufficientFundsError,
|
|
5145
5282
|
NetworkError,
|
|
5283
|
+
SwapNotFoundError,
|
|
5146
5284
|
SchemaError,
|
|
5147
5285
|
SwapExpiredError,
|
|
5148
5286
|
TransactionFailedError,
|
package/dist/expo/index.cjs
CHANGED
|
@@ -31,7 +31,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
31
31
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
32
32
|
|
|
33
33
|
// src/errors.ts
|
|
34
|
-
var SwapError, InvoiceExpiredError, InvoiceFailedToPayError, NetworkError, SchemaError, SwapExpiredError, TransactionFailedError, TransactionLockupFailedError, TransactionRefundedError, BoltzRefundError;
|
|
34
|
+
var SwapError, InvoiceExpiredError, InvoiceFailedToPayError, NetworkError, SwapNotFoundError, SchemaError, SwapExpiredError, TransactionFailedError, TransactionLockupFailedError, TransactionRefundedError, BoltzRefundError;
|
|
35
35
|
var init_errors = __esm({
|
|
36
36
|
"src/errors.ts"() {
|
|
37
37
|
"use strict";
|
|
@@ -77,6 +77,19 @@ var init_errors = __esm({
|
|
|
77
77
|
this.errorData = errorData;
|
|
78
78
|
}
|
|
79
79
|
};
|
|
80
|
+
SwapNotFoundError = class extends NetworkError {
|
|
81
|
+
/** The swap ID Boltz did not recognise. */
|
|
82
|
+
swapId;
|
|
83
|
+
constructor(swapId, errorData) {
|
|
84
|
+
super(
|
|
85
|
+
`Boltz returned 404 for swap '${swapId}': swap unknown to this Boltz instance`,
|
|
86
|
+
404,
|
|
87
|
+
errorData
|
|
88
|
+
);
|
|
89
|
+
this.name = "SwapNotFoundError";
|
|
90
|
+
this.swapId = swapId;
|
|
91
|
+
}
|
|
92
|
+
};
|
|
80
93
|
SchemaError = class extends SwapError {
|
|
81
94
|
constructor(options = {}) {
|
|
82
95
|
super({ message: "Invalid API response", ...options });
|
|
@@ -118,7 +131,7 @@ var init_errors = __esm({
|
|
|
118
131
|
});
|
|
119
132
|
|
|
120
133
|
// src/boltz-swap-provider.ts
|
|
121
|
-
var import_sdk, import_base, isSubmarineFinalStatus, isSubmarineRefundableStatus, isSubmarineSuccessStatus, isReverseFinalStatus, isReverseClaimableStatus, isChainClaimableStatus, isChainFinalStatus, isChainRefundableStatus, isChainSignableStatus, isPendingReverseSwap, isPendingSubmarineSwap, isPendingChainSwap, isSubmarineSwapRefundable, isTimeoutBlockHeights, isGetReverseSwapTxIdResponse, isGetSwapStatusResponse, isGetSubmarinePairsResponse, isGetReversePairsResponse, isCreateSubmarineSwapResponse, isGetSwapPreimageResponse, isCreateReverseSwapResponse, isRefundSubmarineSwapResponse, isRefundChainSwapResponse, isGetChainPairsResponse, isSwapTree, isChainSwapDetailsResponse, isCreateChainSwapResponse, isGetChainClaimDetailsResponse, isPostChainClaimDetailsResponse, isGetChainQuoteResponse, isPostChainQuoteResponse, isPostBtcTransactionResponse, isLeaf, isTree, isDetails, isRestoredChainSwap, isRestoredSubmarineSwap, isRestoredReverseSwap, isCreateSwapsRestoreResponse, BASE_URLS, BoltzSwapProvider;
|
|
134
|
+
var import_sdk, import_base, isSubmarineFinalStatus, isSubmarineRefundableStatus, isSubmarineSuccessStatus, isReverseFinalStatus, isReverseClaimableStatus, isChainClaimableStatus, isChainFinalStatus, isChainRefundableStatus, isChainSignableStatus, isPendingReverseSwap, isPendingSubmarineSwap, isPendingChainSwap, isSubmarineSwapRefundable, isTimeoutBlockHeights, isGetReverseSwapTxIdResponse, isGetSwapStatusResponse, isGetSubmarinePairsResponse, isGetReversePairsResponse, isCreateSubmarineSwapResponse, isGetSwapPreimageResponse, isCreateReverseSwapResponse, isRefundSubmarineSwapResponse, isRefundChainSwapResponse, isGetChainPairsResponse, isSwapTree, isChainSwapDetailsResponse, isCreateChainSwapResponse, isGetChainClaimDetailsResponse, isPostChainClaimDetailsResponse, isGetChainQuoteResponse, isPostChainQuoteResponse, isPostBtcTransactionResponse, isLeaf, isTree, isDetails, isRestoredChainSwap, isRestoredSubmarineSwap, isRestoredReverseSwap, isCreateSwapsRestoreResponse, BASE_URLS, isSwapNotFoundBody, BoltzSwapProvider;
|
|
122
135
|
var init_boltz_swap_provider = __esm({
|
|
123
136
|
"src/boltz-swap-provider.ts"() {
|
|
124
137
|
"use strict";
|
|
@@ -269,10 +282,17 @@ var init_boltz_swap_provider = __esm({
|
|
|
269
282
|
);
|
|
270
283
|
};
|
|
271
284
|
BASE_URLS = {
|
|
272
|
-
bitcoin: "https://api.
|
|
285
|
+
bitcoin: "https://api.boltz.exchange",
|
|
273
286
|
mutinynet: "https://api.boltz.mutinynet.arkade.sh",
|
|
274
287
|
regtest: "http://localhost:9069"
|
|
275
288
|
};
|
|
289
|
+
isSwapNotFoundBody = (error) => {
|
|
290
|
+
const needle = "could not find swap";
|
|
291
|
+
const fromJson = error.errorData?.error;
|
|
292
|
+
if (typeof fromJson === "string" && fromJson.toLowerCase().includes(needle))
|
|
293
|
+
return true;
|
|
294
|
+
return error.message.toLowerCase().includes(needle);
|
|
295
|
+
};
|
|
276
296
|
BoltzSwapProvider = class {
|
|
277
297
|
wsUrl;
|
|
278
298
|
apiUrl;
|
|
@@ -364,12 +384,27 @@ var init_boltz_swap_provider = __esm({
|
|
|
364
384
|
});
|
|
365
385
|
return res;
|
|
366
386
|
}
|
|
367
|
-
/**
|
|
387
|
+
/**
|
|
388
|
+
* Queries the current status of a swap by ID.
|
|
389
|
+
*
|
|
390
|
+
* @throws {SwapNotFoundError} when Boltz responds with HTTP 404 and a body
|
|
391
|
+
* matching the "could not find swap" pattern. Distinct from a generic 404
|
|
392
|
+
* so callers (e.g. SwapManager polling) can drive a per-swap unknown-to-
|
|
393
|
+
* provider counter without tripping on transient route or proxy errors.
|
|
394
|
+
*/
|
|
368
395
|
async getSwapStatus(id) {
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
396
|
+
let response;
|
|
397
|
+
try {
|
|
398
|
+
response = await this.request(
|
|
399
|
+
`/v2/swap/${id}`,
|
|
400
|
+
"GET"
|
|
401
|
+
);
|
|
402
|
+
} catch (error) {
|
|
403
|
+
if (error instanceof NetworkError && error.statusCode === 404 && isSwapNotFoundBody(error)) {
|
|
404
|
+
throw new SwapNotFoundError(id, error.errorData);
|
|
405
|
+
}
|
|
406
|
+
throw error;
|
|
407
|
+
}
|
|
373
408
|
if (!isGetSwapStatusResponse(response))
|
|
374
409
|
throw new SchemaError({
|
|
375
410
|
message: `error fetching status for swap: ${id}`
|
|
@@ -1296,7 +1331,15 @@ var init_swap_manager = __esm({
|
|
|
1296
1331
|
init_boltz_swap_provider();
|
|
1297
1332
|
init_errors();
|
|
1298
1333
|
init_logger();
|
|
1299
|
-
SwapManager = class {
|
|
1334
|
+
SwapManager = class _SwapManager {
|
|
1335
|
+
/**
|
|
1336
|
+
* Number of consecutive Boltz 404s for a single swap ID before the
|
|
1337
|
+
* polling loop gives up and transitions the swap to a terminal state.
|
|
1338
|
+
* At the default 30s poll cadence this is roughly a 5-minute grace
|
|
1339
|
+
* window — long enough to ride out a transient Boltz blip, short
|
|
1340
|
+
* enough that a real "swap unknown to this provider" surfaces quickly.
|
|
1341
|
+
*/
|
|
1342
|
+
static NOT_FOUND_THRESHOLD = 10;
|
|
1300
1343
|
swapProvider;
|
|
1301
1344
|
config;
|
|
1302
1345
|
// Event listeners storage (supports multiple listeners per event)
|
|
@@ -1315,6 +1358,13 @@ var init_swap_manager = __esm({
|
|
|
1315
1358
|
reconnectTimer = null;
|
|
1316
1359
|
initialPollTimer = null;
|
|
1317
1360
|
pollRetryTimers = /* @__PURE__ */ new Map();
|
|
1361
|
+
// Per-swap counter of consecutive `SwapNotFoundError` responses from
|
|
1362
|
+
// `getSwapStatus`. Reset on any successful poll. Once a swap reaches
|
|
1363
|
+
// `NOT_FOUND_THRESHOLD` consecutive 404s the safety net trips and the
|
|
1364
|
+
// swap is transitioned to `swap.expired` (terminal) and dropped from
|
|
1365
|
+
// monitoring — typically the canonical failure mode after a Boltz
|
|
1366
|
+
// endpoint switch, where old swap IDs are unknown to the new instance.
|
|
1367
|
+
notFoundCounts = /* @__PURE__ */ new Map();
|
|
1318
1368
|
isRunning = false;
|
|
1319
1369
|
currentReconnectDelay;
|
|
1320
1370
|
currentPollRetryDelay;
|
|
@@ -1506,6 +1556,7 @@ var init_swap_manager = __esm({
|
|
|
1506
1556
|
clearTimeout(timer);
|
|
1507
1557
|
}
|
|
1508
1558
|
this.pollRetryTimers.clear();
|
|
1559
|
+
this.notFoundCounts.clear();
|
|
1509
1560
|
}
|
|
1510
1561
|
/**
|
|
1511
1562
|
* Set the polling interval (ms).
|
|
@@ -1566,6 +1617,7 @@ var init_swap_manager = __esm({
|
|
|
1566
1617
|
clearTimeout(retryTimer);
|
|
1567
1618
|
this.pollRetryTimers.delete(swapId);
|
|
1568
1619
|
}
|
|
1620
|
+
this.notFoundCounts.delete(swapId);
|
|
1569
1621
|
logger.log(`Removed swap ${swapId} from monitoring`);
|
|
1570
1622
|
}
|
|
1571
1623
|
/**
|
|
@@ -1842,6 +1894,7 @@ var init_swap_manager = __esm({
|
|
|
1842
1894
|
* This is the core logic that determines what actions to take
|
|
1843
1895
|
*/
|
|
1844
1896
|
async handleSwapStatusUpdate(swap, newStatus) {
|
|
1897
|
+
this.notFoundCounts.delete(swap.id);
|
|
1845
1898
|
const oldStatus = swap.status;
|
|
1846
1899
|
if (oldStatus === newStatus) return;
|
|
1847
1900
|
swap.status = newStatus;
|
|
@@ -2127,51 +2180,135 @@ var init_swap_manager = __esm({
|
|
|
2127
2180
|
async pollAllSwaps() {
|
|
2128
2181
|
if (this.monitoredSwaps.size === 0) return;
|
|
2129
2182
|
const pollPromises = Array.from(this.monitoredSwaps.values()).map(
|
|
2130
|
-
|
|
2183
|
+
(swap) => this.pollSingleSwap(swap)
|
|
2184
|
+
);
|
|
2185
|
+
await Promise.allSettled(pollPromises);
|
|
2186
|
+
}
|
|
2187
|
+
async pollSingleSwap(swap) {
|
|
2188
|
+
try {
|
|
2189
|
+
const statusResponse = await this.swapProvider.getSwapStatus(
|
|
2190
|
+
swap.id
|
|
2191
|
+
);
|
|
2192
|
+
this.notFoundCounts.delete(swap.id);
|
|
2193
|
+
if (statusResponse.status !== swap.status) {
|
|
2194
|
+
await this.handleSwapStatusUpdate(swap, statusResponse.status);
|
|
2195
|
+
}
|
|
2196
|
+
} catch (error) {
|
|
2197
|
+
if (error instanceof SwapNotFoundError) {
|
|
2198
|
+
await this.handleSwapNotFound(swap);
|
|
2199
|
+
return;
|
|
2200
|
+
}
|
|
2201
|
+
if (error instanceof NetworkError && error.statusCode === 429) {
|
|
2202
|
+
logger.warn(
|
|
2203
|
+
`Rate-limited polling swap ${swap.id}, retrying in 2s`
|
|
2204
|
+
);
|
|
2205
|
+
const existing = this.pollRetryTimers.get(swap.id);
|
|
2206
|
+
if (existing) clearTimeout(existing);
|
|
2207
|
+
this.pollRetryTimers.set(
|
|
2208
|
+
swap.id,
|
|
2209
|
+
setTimeout(async () => {
|
|
2210
|
+
this.pollRetryTimers.delete(swap.id);
|
|
2211
|
+
try {
|
|
2212
|
+
const retry = await this.swapProvider.getSwapStatus(
|
|
2213
|
+
swap.id
|
|
2214
|
+
);
|
|
2215
|
+
this.notFoundCounts.delete(swap.id);
|
|
2216
|
+
if (retry.status !== swap.status) {
|
|
2217
|
+
await this.handleSwapStatusUpdate(
|
|
2218
|
+
swap,
|
|
2219
|
+
retry.status
|
|
2220
|
+
);
|
|
2221
|
+
}
|
|
2222
|
+
} catch (retryError) {
|
|
2223
|
+
if (retryError instanceof SwapNotFoundError) {
|
|
2224
|
+
await this.handleSwapNotFound(swap);
|
|
2225
|
+
return;
|
|
2226
|
+
}
|
|
2227
|
+
logger.error(
|
|
2228
|
+
`Retry poll for swap ${swap.id} also failed:`,
|
|
2229
|
+
retryError
|
|
2230
|
+
);
|
|
2231
|
+
}
|
|
2232
|
+
}, 2e3)
|
|
2233
|
+
);
|
|
2234
|
+
} else {
|
|
2235
|
+
logger.error(`Failed to poll swap ${swap.id}:`, error);
|
|
2236
|
+
}
|
|
2237
|
+
}
|
|
2238
|
+
}
|
|
2239
|
+
/**
|
|
2240
|
+
* Increment the consecutive-not-found counter and, once the threshold is
|
|
2241
|
+
* reached, transition the swap to a terminal state and stop polling it.
|
|
2242
|
+
* Driven from {@link pollSingleSwap} when `getSwapStatus` throws
|
|
2243
|
+
* {@link SwapNotFoundError}. The threshold rides out a transient blip
|
|
2244
|
+
* but ensures we stop hammering Boltz with requests for swap IDs the
|
|
2245
|
+
* server has no record of (e.g. after switching the configured
|
|
2246
|
+
* Boltz endpoint).
|
|
2247
|
+
*/
|
|
2248
|
+
async handleSwapNotFound(swap) {
|
|
2249
|
+
const count = (this.notFoundCounts.get(swap.id) ?? 0) + 1;
|
|
2250
|
+
this.notFoundCounts.set(swap.id, count);
|
|
2251
|
+
logger.warn(
|
|
2252
|
+
`Swap ${swap.id}: unknown to Boltz (${count}/${_SwapManager.NOT_FOUND_THRESHOLD} consecutive)`
|
|
2253
|
+
);
|
|
2254
|
+
if (count >= _SwapManager.NOT_FOUND_THRESHOLD) {
|
|
2255
|
+
await this.markSwapAsUnknownToProvider(swap);
|
|
2256
|
+
}
|
|
2257
|
+
}
|
|
2258
|
+
/**
|
|
2259
|
+
* Transition a swap to {@code swap.expired} (terminal for all swap types)
|
|
2260
|
+
* after Boltz has consistently reported it unknown for
|
|
2261
|
+
* {@link SwapManager.NOT_FOUND_THRESHOLD} consecutive polls. The swap is
|
|
2262
|
+
* persisted, removed from monitoring, and reported via `onSwapFailed`.
|
|
2263
|
+
* Bypasses {@link handleSwapStatusUpdate} on purpose: we don't want to
|
|
2264
|
+
* trigger autonomous claim/refund actions against a Boltz instance that
|
|
2265
|
+
* has no record of this swap — the requests would just generate more
|
|
2266
|
+
* 404s without recovering anything.
|
|
2267
|
+
*/
|
|
2268
|
+
async markSwapAsUnknownToProvider(swap) {
|
|
2269
|
+
if (!this.monitoredSwaps.has(swap.id)) {
|
|
2270
|
+
this.notFoundCounts.delete(swap.id);
|
|
2271
|
+
return;
|
|
2272
|
+
}
|
|
2273
|
+
const oldStatus = swap.status;
|
|
2274
|
+
swap.status = "swap.expired";
|
|
2275
|
+
this.monitoredSwaps.delete(swap.id);
|
|
2276
|
+
const retryTimer = this.pollRetryTimers.get(swap.id);
|
|
2277
|
+
if (retryTimer) {
|
|
2278
|
+
clearTimeout(retryTimer);
|
|
2279
|
+
this.pollRetryTimers.delete(swap.id);
|
|
2280
|
+
}
|
|
2281
|
+
this.notFoundCounts.delete(swap.id);
|
|
2282
|
+
this.swapUpdateListeners.forEach(
|
|
2283
|
+
(listener) => listener(swap, oldStatus)
|
|
2284
|
+
);
|
|
2285
|
+
const subscribers = this.swapSubscriptions.get(swap.id);
|
|
2286
|
+
if (subscribers) {
|
|
2287
|
+
subscribers.forEach((callback) => {
|
|
2131
2288
|
try {
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
}
|
|
2139
|
-
} catch (error) {
|
|
2140
|
-
if (error instanceof NetworkError && error.statusCode === 429) {
|
|
2141
|
-
logger.warn(
|
|
2142
|
-
`Rate-limited polling swap ${swap.id}, retrying in 2s`
|
|
2143
|
-
);
|
|
2144
|
-
const existing = this.pollRetryTimers.get(swap.id);
|
|
2145
|
-
if (existing) clearTimeout(existing);
|
|
2146
|
-
this.pollRetryTimers.set(
|
|
2147
|
-
swap.id,
|
|
2148
|
-
setTimeout(async () => {
|
|
2149
|
-
this.pollRetryTimers.delete(swap.id);
|
|
2150
|
-
try {
|
|
2151
|
-
const retry = await this.swapProvider.getSwapStatus(
|
|
2152
|
-
swap.id
|
|
2153
|
-
);
|
|
2154
|
-
if (retry.status !== swap.status) {
|
|
2155
|
-
await this.handleSwapStatusUpdate(
|
|
2156
|
-
swap,
|
|
2157
|
-
retry.status
|
|
2158
|
-
);
|
|
2159
|
-
}
|
|
2160
|
-
} catch (retryError) {
|
|
2161
|
-
logger.error(
|
|
2162
|
-
`Retry poll for swap ${swap.id} also failed:`,
|
|
2163
|
-
retryError
|
|
2164
|
-
);
|
|
2165
|
-
}
|
|
2166
|
-
}, 2e3)
|
|
2167
|
-
);
|
|
2168
|
-
} else {
|
|
2169
|
-
logger.error(`Failed to poll swap ${swap.id}:`, error);
|
|
2170
|
-
}
|
|
2289
|
+
callback(swap, oldStatus);
|
|
2290
|
+
} catch (subscriberError) {
|
|
2291
|
+
logger.error(
|
|
2292
|
+
`Error in swap subscription callback for ${swap.id}:`,
|
|
2293
|
+
subscriberError
|
|
2294
|
+
);
|
|
2171
2295
|
}
|
|
2172
|
-
}
|
|
2296
|
+
});
|
|
2297
|
+
}
|
|
2298
|
+
try {
|
|
2299
|
+
await this.saveSwap(swap);
|
|
2300
|
+
} catch (saveError) {
|
|
2301
|
+
logger.error(
|
|
2302
|
+
`Failed to persist unknown-to-provider state for swap ${swap.id}:`,
|
|
2303
|
+
saveError
|
|
2304
|
+
);
|
|
2305
|
+
}
|
|
2306
|
+
logger.warn(
|
|
2307
|
+
`Swap ${swap.id}: marked failed after ${_SwapManager.NOT_FOUND_THRESHOLD} consecutive Boltz 404s \u2014 swap is unknown to the configured Boltz instance`
|
|
2173
2308
|
);
|
|
2174
|
-
|
|
2309
|
+
const error = new SwapNotFoundError(swap.id);
|
|
2310
|
+
this.swapFailedListeners.forEach((listener) => listener(swap, error));
|
|
2311
|
+
this.swapSubscriptions.delete(swap.id);
|
|
2175
2312
|
}
|
|
2176
2313
|
/**
|
|
2177
2314
|
* Check if a status is final (no more updates expected)
|
package/dist/expo/index.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { I as IArkadeSwaps, A as ArkadeSwaps, V as VhtlcTimeouts } from '../arkade-swaps-
|
|
2
|
-
import { A as ArkadeSwapsConfig, m as SwapRepository, p as BoltzSwapProvider, N as Network, n as SwapManagerClient, C as CreateLightningInvoiceRequest, e as CreateLightningInvoiceResponse, S as SendLightningPaymentRequest, f as SendLightningPaymentResponse, c as BoltzSubmarineSwap, b as BoltzReverseSwap, g as SubmarineRefundOutcome, h as SubmarineRecoveryInfo, i as SubmarineRecoveryResult, F as FeesResponse, a as BoltzChainSwap, k as ArkToBtcResponse, l as BtcToArkResponse, d as Chain, j as ChainFeesResponse, L as LimitsResponse, G as GetSwapStatusResponse, B as BoltzSwap } from '../types-
|
|
1
|
+
import { I as IArkadeSwaps, A as ArkadeSwaps, V as VhtlcTimeouts } from '../arkade-swaps-CS8FZSVL.cjs';
|
|
2
|
+
import { A as ArkadeSwapsConfig, m as SwapRepository, p as BoltzSwapProvider, N as Network, n as SwapManagerClient, C as CreateLightningInvoiceRequest, e as CreateLightningInvoiceResponse, S as SendLightningPaymentRequest, f as SendLightningPaymentResponse, c as BoltzSubmarineSwap, b as BoltzReverseSwap, g as SubmarineRefundOutcome, h as SubmarineRecoveryInfo, i as SubmarineRecoveryResult, F as FeesResponse, a as BoltzChainSwap, k as ArkToBtcResponse, l as BtcToArkResponse, d as Chain, j as ChainFeesResponse, L as LimitsResponse, G as GetSwapStatusResponse, B as BoltzSwap } from '../types-BBI7-KJ0.cjs';
|
|
3
3
|
import { Identity, ArkProvider, IndexerProvider, IWallet, ArkInfo, ArkTxInput, VHTLC } from '@arkade-os/sdk';
|
|
4
4
|
import { AsyncStorageTaskQueue, TaskProcessor } from '@arkade-os/sdk/worker/expo';
|
|
5
5
|
import { TransactionOutput } from '@scure/btc-signer/psbt.js';
|