@arkade-os/boltz-swap 0.3.50 → 0.3.52
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-A180YN8g.d.ts → arkade-swaps-CoJKHu2Y.d.ts} +19 -4
- package/dist/{arkade-swaps-BG3xEK31.d.cts → arkade-swaps-qZmYvnVy.d.cts} +19 -4
- package/dist/{chunk-HIG2OOAN.js → chunk-76V3TKDL.js} +1 -1
- package/dist/{chunk-VKHFXGKL.js → chunk-CZTZTYZU.js} +224 -47
- package/dist/expo/background.cjs +222 -47
- package/dist/expo/background.d.cts +3 -3
- package/dist/expo/background.d.ts +3 -3
- package/dist/expo/background.js +2 -2
- package/dist/expo/index.cjs +224 -47
- package/dist/expo/index.d.cts +4 -4
- package/dist/expo/index.d.ts +4 -4
- package/dist/expo/index.js +4 -2
- package/dist/index.cjs +228 -48
- package/dist/index.d.cts +4 -4
- package/dist/index.d.ts +4 -4
- package/dist/index.js +7 -2
- package/dist/repositories/realm/index.cjs +1 -0
- package/dist/repositories/realm/index.d.cts +1 -1
- package/dist/repositories/realm/index.d.ts +1 -1
- package/dist/repositories/realm/index.js +1 -0
- package/dist/repositories/sqlite/index.cjs +1 -0
- package/dist/repositories/sqlite/index.d.cts +1 -1
- package/dist/repositories/sqlite/index.d.ts +1 -1
- package/dist/repositories/sqlite/index.js +1 -0
- package/dist/{swapsPollProcessor-C4HQHP9Z.d.ts → swapsPollProcessor--BPV48QB.d.ts} +1 -1
- package/dist/{swapsPollProcessor-CIqTFkV1.d.cts → swapsPollProcessor-DCtJYSbJ.d.cts} +1 -1
- package/dist/{types-BKEkNZxK.d.cts → types-Db4JSdjk.d.cts} +69 -8
- package/dist/{types-BKEkNZxK.d.ts → types-Db4JSdjk.d.ts} +69 -8
- package/package.json +3 -3
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { IWallet, ArkProvider, IndexerProvider, ArkInfo, Identity, ArkTxInput, VHTLC } from '@arkade-os/sdk';
|
|
2
|
-
import { r as BoltzSwapProvider, y as SwapManager, m as SwapRepository, q as ArkadeSwapsCreateConfig, A as ArkadeSwapsConfig, n as SwapManagerClient, C as CreateLightningInvoiceRequest, e as CreateLightningInvoiceResponse, b as BoltzReverseSwap, S as SendLightningPaymentRequest, o as SendLightningPaymentResponse, O as OptimisticSendLightningPaymentResponse, c as BoltzSubmarineSwap, f as SubmarineRefundOutcome, g as SubmarineRecoveryInfo, h as SubmarineRecoveryResult, j as ArkToBtcResponse, a as BoltzChainSwap, l as ChainArkRefundOutcome, k as BtcToArkResponse, d as Chain, F as FeesResponse, i as ChainFeesResponse, L as LimitsResponse, G as GetSwapStatusResponse, B as BoltzSwap } from './types-
|
|
2
|
+
import { r as BoltzSwapProvider, y as SwapManager, m as SwapRepository, q as ArkadeSwapsCreateConfig, A as ArkadeSwapsConfig, n as SwapManagerClient, C as CreateLightningInvoiceRequest, e as CreateLightningInvoiceResponse, b as BoltzReverseSwap, S as SendLightningPaymentRequest, o as SendLightningPaymentResponse, O as OptimisticSendLightningPaymentResponse, c as BoltzSubmarineSwap, f as SubmarineRefundOutcome, g as SubmarineRecoveryInfo, h as SubmarineRecoveryResult, j as ArkToBtcResponse, a as BoltzChainSwap, l as ChainArkRefundOutcome, k as BtcToArkResponse, d as Chain, F as FeesResponse, i as ChainFeesResponse, L as LimitsResponse, G as GetSwapStatusResponse, B as BoltzSwap } from './types-Db4JSdjk.js';
|
|
3
3
|
import { TransactionOutput } from '@scure/btc-signer/psbt.js';
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -485,18 +485,33 @@ declare class ArkadeSwaps {
|
|
|
485
485
|
* reclaimed by re-registering it into a batch.
|
|
486
486
|
*/
|
|
487
487
|
private settleRefundWithoutReceiver;
|
|
488
|
+
/**
|
|
489
|
+
* {@link settleRefundWithoutReceiver}, deferring instead of failing when the server
|
|
490
|
+
* rejects the spend as CLTV-immature ({@link ArkErrorName.FORFEIT_CLOSURE_LOCKED}):
|
|
491
|
+
* we gate on the local wall clock, the server on the chain tip's, which lags. The
|
|
492
|
+
* server-authoritative sibling of the wall-clock deferral the pre-CLTV branches
|
|
493
|
+
* already implement. Anything else propagates.
|
|
494
|
+
*
|
|
495
|
+
* Only `submitTx` can raise it, so this is a pass-through for recoverable VTXOs,
|
|
496
|
+
* which settle via a batch round.
|
|
497
|
+
*
|
|
498
|
+
* @returns `true` if settled, `false` if the server deferred it.
|
|
499
|
+
*/
|
|
500
|
+
private trySettleRefundWithoutReceiver;
|
|
488
501
|
/**
|
|
489
502
|
* Refund every VTXO at a swap's VHTLC address back to the wallet, shared by
|
|
490
503
|
* {@link ArkadeSwaps.refundVHTLC} (submarine) and {@link ArkadeSwaps.refundArk}
|
|
491
504
|
* (chain). Path selection per VTXO:
|
|
492
|
-
* - CLTV elapsed → `refundWithoutReceiver` (offchain for a
|
|
493
|
-
* batch round for a swept one — see
|
|
505
|
+
* - CLTV elapsed by our wall clock → `refundWithoutReceiver` (offchain for a
|
|
506
|
+
* live VTXO, via a batch round for a swept one — see
|
|
507
|
+
* {@link settleRefundWithoutReceiver}), or skipped if the server defers it as
|
|
508
|
+
* still immature (see {@link trySettleRefundWithoutReceiver}).
|
|
494
509
|
* - Pre-CLTV recoverable → skipped (Boltz can't co-sign a swept-batch refund).
|
|
495
510
|
* - Pre-CLTV non-recoverable → cooperative 3-of-3 refund via Boltz, falling
|
|
496
511
|
* back to `refundWithoutReceiver` offchain if Boltz rejects after the
|
|
497
512
|
* locktime has since elapsed.
|
|
498
513
|
*
|
|
499
|
-
* @returns Counts of VTXOs swept vs. deferred.
|
|
514
|
+
* @returns Counts of VTXOs swept vs. deferred, and when to retry the latter.
|
|
500
515
|
*/
|
|
501
516
|
private refundVtxos;
|
|
502
517
|
/**
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { IWallet, ArkProvider, IndexerProvider, ArkInfo, Identity, ArkTxInput, VHTLC } from '@arkade-os/sdk';
|
|
2
|
-
import { r as BoltzSwapProvider, y as SwapManager, m as SwapRepository, q as ArkadeSwapsCreateConfig, A as ArkadeSwapsConfig, n as SwapManagerClient, C as CreateLightningInvoiceRequest, e as CreateLightningInvoiceResponse, b as BoltzReverseSwap, S as SendLightningPaymentRequest, o as SendLightningPaymentResponse, O as OptimisticSendLightningPaymentResponse, c as BoltzSubmarineSwap, f as SubmarineRefundOutcome, g as SubmarineRecoveryInfo, h as SubmarineRecoveryResult, j as ArkToBtcResponse, a as BoltzChainSwap, l as ChainArkRefundOutcome, k as BtcToArkResponse, d as Chain, F as FeesResponse, i as ChainFeesResponse, L as LimitsResponse, G as GetSwapStatusResponse, B as BoltzSwap } from './types-
|
|
2
|
+
import { r as BoltzSwapProvider, y as SwapManager, m as SwapRepository, q as ArkadeSwapsCreateConfig, A as ArkadeSwapsConfig, n as SwapManagerClient, C as CreateLightningInvoiceRequest, e as CreateLightningInvoiceResponse, b as BoltzReverseSwap, S as SendLightningPaymentRequest, o as SendLightningPaymentResponse, O as OptimisticSendLightningPaymentResponse, c as BoltzSubmarineSwap, f as SubmarineRefundOutcome, g as SubmarineRecoveryInfo, h as SubmarineRecoveryResult, j as ArkToBtcResponse, a as BoltzChainSwap, l as ChainArkRefundOutcome, k as BtcToArkResponse, d as Chain, F as FeesResponse, i as ChainFeesResponse, L as LimitsResponse, G as GetSwapStatusResponse, B as BoltzSwap } from './types-Db4JSdjk.cjs';
|
|
3
3
|
import { TransactionOutput } from '@scure/btc-signer/psbt.js';
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -485,18 +485,33 @@ declare class ArkadeSwaps {
|
|
|
485
485
|
* reclaimed by re-registering it into a batch.
|
|
486
486
|
*/
|
|
487
487
|
private settleRefundWithoutReceiver;
|
|
488
|
+
/**
|
|
489
|
+
* {@link settleRefundWithoutReceiver}, deferring instead of failing when the server
|
|
490
|
+
* rejects the spend as CLTV-immature ({@link ArkErrorName.FORFEIT_CLOSURE_LOCKED}):
|
|
491
|
+
* we gate on the local wall clock, the server on the chain tip's, which lags. The
|
|
492
|
+
* server-authoritative sibling of the wall-clock deferral the pre-CLTV branches
|
|
493
|
+
* already implement. Anything else propagates.
|
|
494
|
+
*
|
|
495
|
+
* Only `submitTx` can raise it, so this is a pass-through for recoverable VTXOs,
|
|
496
|
+
* which settle via a batch round.
|
|
497
|
+
*
|
|
498
|
+
* @returns `true` if settled, `false` if the server deferred it.
|
|
499
|
+
*/
|
|
500
|
+
private trySettleRefundWithoutReceiver;
|
|
488
501
|
/**
|
|
489
502
|
* Refund every VTXO at a swap's VHTLC address back to the wallet, shared by
|
|
490
503
|
* {@link ArkadeSwaps.refundVHTLC} (submarine) and {@link ArkadeSwaps.refundArk}
|
|
491
504
|
* (chain). Path selection per VTXO:
|
|
492
|
-
* - CLTV elapsed → `refundWithoutReceiver` (offchain for a
|
|
493
|
-
* batch round for a swept one — see
|
|
505
|
+
* - CLTV elapsed by our wall clock → `refundWithoutReceiver` (offchain for a
|
|
506
|
+
* live VTXO, via a batch round for a swept one — see
|
|
507
|
+
* {@link settleRefundWithoutReceiver}), or skipped if the server defers it as
|
|
508
|
+
* still immature (see {@link trySettleRefundWithoutReceiver}).
|
|
494
509
|
* - Pre-CLTV recoverable → skipped (Boltz can't co-sign a swept-batch refund).
|
|
495
510
|
* - Pre-CLTV non-recoverable → cooperative 3-of-3 refund via Boltz, falling
|
|
496
511
|
* back to `refundWithoutReceiver` offchain if Boltz rejects after the
|
|
497
512
|
* locktime has since elapsed.
|
|
498
513
|
*
|
|
499
|
-
* @returns Counts of VTXOs swept vs. deferred.
|
|
514
|
+
* @returns Counts of VTXOs swept vs. deferred, and when to retry the latter.
|
|
500
515
|
*/
|
|
501
516
|
private refundVtxos;
|
|
502
517
|
/**
|
|
@@ -208,6 +208,7 @@ var BoltzRefundError = class extends Error {
|
|
|
208
208
|
this.cause = cause;
|
|
209
209
|
this.name = "BoltzRefundError";
|
|
210
210
|
}
|
|
211
|
+
cause;
|
|
211
212
|
};
|
|
212
213
|
|
|
213
214
|
// src/boltz-swap-provider.ts
|
|
@@ -1077,12 +1078,20 @@ var SwapManager = class _SwapManager {
|
|
|
1077
1078
|
*/
|
|
1078
1079
|
static NOT_FOUND_THRESHOLD = 10;
|
|
1079
1080
|
/**
|
|
1080
|
-
*
|
|
1081
|
-
*
|
|
1082
|
-
*
|
|
1083
|
-
*
|
|
1081
|
+
* Floor on the delay between re-attempts of a refund that left VTXOs
|
|
1082
|
+
* deferred, and the delay used when the outcome names no retry time. Boltz
|
|
1083
|
+
* won't send another status update once the swap is refundable-and-final,
|
|
1084
|
+
* so the manager owns the local retry cadence.
|
|
1084
1085
|
*/
|
|
1085
1086
|
static REFUND_RETRY_DELAY_MS = 6e4;
|
|
1087
|
+
/**
|
|
1088
|
+
* Ceiling on that delay. A deferral can be hours out (a pre-CLTV VTXO waits
|
|
1089
|
+
* out the whole refund locktime), and `setTimeout` fires *immediately* past
|
|
1090
|
+
* its 32-bit millisecond range, so long waits are broken into re-armed
|
|
1091
|
+
* hops. Re-attempting costs an indexer lookup and re-defers, which also lets
|
|
1092
|
+
* the cadence recover from a suspended device or a clock jump.
|
|
1093
|
+
*/
|
|
1094
|
+
static MAX_REFUND_RETRY_DELAY_MS = 60 * 6e4;
|
|
1086
1095
|
swapProvider;
|
|
1087
1096
|
config;
|
|
1088
1097
|
// Event listeners storage (supports multiple listeners per event)
|
|
@@ -1101,10 +1110,9 @@ var SwapManager = class _SwapManager {
|
|
|
1101
1110
|
reconnectTimer = null;
|
|
1102
1111
|
initialPollTimer = null;
|
|
1103
1112
|
pollRetryTimers = /* @__PURE__ */ new Map();
|
|
1104
|
-
// Per-swap retry timers for
|
|
1105
|
-
//
|
|
1106
|
-
//
|
|
1107
|
-
// refund completes or the manager stops.
|
|
1113
|
+
// Per-swap retry timers for deferred refunds that left local work undone.
|
|
1114
|
+
// The swap is held in `monitoredSwaps` past its terminal Boltz status
|
|
1115
|
+
// until the local refund completes or the manager stops.
|
|
1108
1116
|
refundRetryTimers = /* @__PURE__ */ new Map();
|
|
1109
1117
|
// Per-swap counter of consecutive `SwapNotFoundError` responses from
|
|
1110
1118
|
// `getSwapStatus`. Reset on any successful poll. Once a swap reaches
|
|
@@ -1276,11 +1284,12 @@ var SwapManager = class _SwapManager {
|
|
|
1276
1284
|
this.initialSwaps.set(swap.id, swap);
|
|
1277
1285
|
}
|
|
1278
1286
|
for (const swap of pendingSwaps) {
|
|
1279
|
-
if (
|
|
1287
|
+
if (this.shouldMonitorOnStart(swap)) {
|
|
1280
1288
|
this.monitoredSwaps.set(swap.id, swap);
|
|
1281
1289
|
}
|
|
1282
1290
|
}
|
|
1283
1291
|
await this.tryConnectWebSocket();
|
|
1292
|
+
this.resumePersistedRefundRetries();
|
|
1284
1293
|
await this.resumeActionableSwaps();
|
|
1285
1294
|
}
|
|
1286
1295
|
/**
|
|
@@ -1363,6 +1372,7 @@ var SwapManager = class _SwapManager {
|
|
|
1363
1372
|
* Remove a swap from monitoring
|
|
1364
1373
|
*/
|
|
1365
1374
|
async removeSwap(swapId) {
|
|
1375
|
+
const swap = this.monitoredSwaps.get(swapId);
|
|
1366
1376
|
this.monitoredSwaps.delete(swapId);
|
|
1367
1377
|
this.swapSubscriptions.delete(swapId);
|
|
1368
1378
|
this.chainClaimPromises.delete(swapId);
|
|
@@ -1376,6 +1386,9 @@ var SwapManager = class _SwapManager {
|
|
|
1376
1386
|
clearTimeout(refundRetryTimer);
|
|
1377
1387
|
this.refundRetryTimers.delete(swapId);
|
|
1378
1388
|
}
|
|
1389
|
+
if (swap) {
|
|
1390
|
+
await this.clearRefundRetry(swap);
|
|
1391
|
+
}
|
|
1379
1392
|
this.notFoundCounts.delete(swapId);
|
|
1380
1393
|
logger.log(`Removed swap ${swapId} from monitoring`);
|
|
1381
1394
|
}
|
|
@@ -1714,12 +1727,69 @@ var SwapManager = class _SwapManager {
|
|
|
1714
1727
|
}
|
|
1715
1728
|
this.swapCompletedListeners.forEach((listener) => listener(swap));
|
|
1716
1729
|
}
|
|
1730
|
+
static isRefundRetryableSwap(swap) {
|
|
1731
|
+
return isPendingSubmarineSwap(swap) || isPendingChainSwap(swap);
|
|
1732
|
+
}
|
|
1733
|
+
static hasPendingRefundRetry(swap) {
|
|
1734
|
+
if (!_SwapManager.isRefundRetryableSwap(swap)) return false;
|
|
1735
|
+
return swap.refundRetry?.pending === true && Number.isFinite(swap.refundRetry.nextRetryAt);
|
|
1736
|
+
}
|
|
1737
|
+
shouldMonitorOnStart(swap) {
|
|
1738
|
+
if (!this.isFinalStatus(swap)) return true;
|
|
1739
|
+
return this.config.enableAutoActions === true && _SwapManager.hasPendingRefundRetry(swap);
|
|
1740
|
+
}
|
|
1741
|
+
/**
|
|
1742
|
+
* How long to wait before re-attempting a refund, given the `retryAt`
|
|
1743
|
+
* (Unix seconds) the outcome reported. Clamped at both ends: see
|
|
1744
|
+
* {@link SwapManager.REFUND_RETRY_DELAY_MS} and
|
|
1745
|
+
* {@link SwapManager.MAX_REFUND_RETRY_DELAY_MS}.
|
|
1746
|
+
*/
|
|
1747
|
+
static refundRetryDelayMs(retryAt) {
|
|
1748
|
+
if (retryAt === void 0) return _SwapManager.REFUND_RETRY_DELAY_MS;
|
|
1749
|
+
return Math.min(
|
|
1750
|
+
Math.max(retryAt * 1e3 - Date.now(), _SwapManager.REFUND_RETRY_DELAY_MS),
|
|
1751
|
+
_SwapManager.MAX_REFUND_RETRY_DELAY_MS
|
|
1752
|
+
);
|
|
1753
|
+
}
|
|
1754
|
+
static persistedRefundRetryDelayMs(nextRetryAt) {
|
|
1755
|
+
return Math.min(
|
|
1756
|
+
Math.max(nextRetryAt * 1e3 - Date.now(), 0),
|
|
1757
|
+
_SwapManager.MAX_REFUND_RETRY_DELAY_MS
|
|
1758
|
+
);
|
|
1759
|
+
}
|
|
1760
|
+
static nextRefundRetryAt(delayMs) {
|
|
1761
|
+
return Math.ceil((Date.now() + delayMs) / 1e3);
|
|
1762
|
+
}
|
|
1763
|
+
async clearRefundRetry(swap) {
|
|
1764
|
+
if (!_SwapManager.isRefundRetryableSwap(swap) || swap.refundRetry === void 0) return;
|
|
1765
|
+
delete swap.refundRetry;
|
|
1766
|
+
await this.saveSwap(swap);
|
|
1767
|
+
}
|
|
1768
|
+
resumePersistedRefundRetries() {
|
|
1769
|
+
if (!this.config.enableAutoActions) return;
|
|
1770
|
+
for (const swap of this.monitoredSwaps.values()) {
|
|
1771
|
+
if (!_SwapManager.hasPendingRefundRetry(swap)) continue;
|
|
1772
|
+
const delayMs = _SwapManager.persistedRefundRetryDelayMs(swap.refundRetry.nextRetryAt);
|
|
1773
|
+
logger.log(
|
|
1774
|
+
`Swap ${swap.id}: resuming deferred refund retry in ${Math.round(delayMs / 1e3)}s`
|
|
1775
|
+
);
|
|
1776
|
+
this.armRefundRetryTimer(swap, delayMs);
|
|
1777
|
+
}
|
|
1778
|
+
}
|
|
1717
1779
|
/**
|
|
1718
|
-
* Schedule another `executeAutonomousAction` run for a
|
|
1719
|
-
*
|
|
1720
|
-
*
|
|
1780
|
+
* Schedule another `executeAutonomousAction` run for a swap whose refund
|
|
1781
|
+
* left VTXOs deferred. The pending retry and its next attempt time are saved
|
|
1782
|
+
* with the swap so stop/start and application restart can re-arm it.
|
|
1721
1783
|
*/
|
|
1722
|
-
scheduleRefundRetry(swap, delayMs) {
|
|
1784
|
+
async scheduleRefundRetry(swap, delayMs) {
|
|
1785
|
+
swap.refundRetry = {
|
|
1786
|
+
pending: true,
|
|
1787
|
+
nextRetryAt: _SwapManager.nextRefundRetryAt(delayMs)
|
|
1788
|
+
};
|
|
1789
|
+
await this.saveSwap(swap);
|
|
1790
|
+
this.armRefundRetryTimer(swap, delayMs);
|
|
1791
|
+
}
|
|
1792
|
+
armRefundRetryTimer(swap, delayMs) {
|
|
1723
1793
|
const existing = this.refundRetryTimers.get(swap.id);
|
|
1724
1794
|
if (existing) clearTimeout(existing);
|
|
1725
1795
|
this.refundRetryTimers.set(
|
|
@@ -1727,12 +1797,14 @@ var SwapManager = class _SwapManager {
|
|
|
1727
1797
|
setTimeout(async () => {
|
|
1728
1798
|
this.refundRetryTimers.delete(swap.id);
|
|
1729
1799
|
if (!this.isRunning) return;
|
|
1730
|
-
|
|
1800
|
+
const current = this.monitoredSwaps.get(swap.id);
|
|
1801
|
+
if (!current) return;
|
|
1802
|
+
let ran = false;
|
|
1731
1803
|
try {
|
|
1732
|
-
await this.executeAutonomousAction(
|
|
1804
|
+
ran = await this.executeAutonomousAction(current);
|
|
1733
1805
|
} finally {
|
|
1734
|
-
if (!this.refundRetryTimers.has(
|
|
1735
|
-
this.finalizeMonitoredSwap(
|
|
1806
|
+
if (ran && !this.refundRetryTimers.has(current.id) && this.isFinalStatus(current)) {
|
|
1807
|
+
this.finalizeMonitoredSwap(current);
|
|
1736
1808
|
}
|
|
1737
1809
|
}
|
|
1738
1810
|
}, delayMs)
|
|
@@ -1741,11 +1813,16 @@ var SwapManager = class _SwapManager {
|
|
|
1741
1813
|
/**
|
|
1742
1814
|
* Execute autonomous action based on swap status
|
|
1743
1815
|
* Uses locking to prevent race conditions with manual operations
|
|
1816
|
+
*
|
|
1817
|
+
* @returns `false` when another action for this swap already held the lock
|
|
1818
|
+
* and this call did nothing, `true` when it ran. Callers that finalize on
|
|
1819
|
+
* the result must not act on `false`: the in-flight action owns the swap's
|
|
1820
|
+
* outcome and will re-arm or finalize from its own result.
|
|
1744
1821
|
*/
|
|
1745
1822
|
async executeAutonomousAction(swap) {
|
|
1746
1823
|
if (this.swapsInProgress.has(swap.id)) {
|
|
1747
1824
|
logger.log(`Swap ${swap.id} is already being processed, skipping autonomous action`);
|
|
1748
|
-
return;
|
|
1825
|
+
return false;
|
|
1749
1826
|
}
|
|
1750
1827
|
try {
|
|
1751
1828
|
this.swapsInProgress.add(swap.id);
|
|
@@ -1754,7 +1831,7 @@ var SwapManager = class _SwapManager {
|
|
|
1754
1831
|
logger.log(
|
|
1755
1832
|
`Skipping claim for swap ${swap.id}: missing preimage (restored swap)`
|
|
1756
1833
|
);
|
|
1757
|
-
return;
|
|
1834
|
+
return true;
|
|
1758
1835
|
}
|
|
1759
1836
|
if (isReverseClaimableStatus(swap.status)) {
|
|
1760
1837
|
logger.log(`Auto-claiming reverse swap ${swap.id}`);
|
|
@@ -1766,11 +1843,26 @@ var SwapManager = class _SwapManager {
|
|
|
1766
1843
|
logger.log(
|
|
1767
1844
|
`Skipping refund for swap ${swap.id}: missing invoice (restored swap)`
|
|
1768
1845
|
);
|
|
1769
|
-
return;
|
|
1846
|
+
return true;
|
|
1770
1847
|
}
|
|
1771
1848
|
if (isSubmarineRefundableStatus(swap.status)) {
|
|
1772
1849
|
logger.log(`Auto-refunding submarine swap ${swap.id}`);
|
|
1773
|
-
|
|
1850
|
+
let outcome;
|
|
1851
|
+
try {
|
|
1852
|
+
outcome = await this.executeRefundAction(swap);
|
|
1853
|
+
} catch (error) {
|
|
1854
|
+
await this.clearRefundRetry(swap);
|
|
1855
|
+
throw error;
|
|
1856
|
+
}
|
|
1857
|
+
if (outcome && outcome.skipped > 0) {
|
|
1858
|
+
const delayMs = _SwapManager.refundRetryDelayMs(outcome.retryAt);
|
|
1859
|
+
logger.log(
|
|
1860
|
+
`Submarine swap ${swap.id}: ${outcome.skipped} VTXO(s) deferred \u2014 scheduling refund retry in ${Math.round(delayMs / 1e3)}s`
|
|
1861
|
+
);
|
|
1862
|
+
await this.scheduleRefundRetry(swap, delayMs);
|
|
1863
|
+
} else if (outcome) {
|
|
1864
|
+
await this.clearRefundRetry(swap);
|
|
1865
|
+
}
|
|
1774
1866
|
this.actionExecutedListeners.forEach((listener) => listener(swap, "refund"));
|
|
1775
1867
|
}
|
|
1776
1868
|
} else if (isPendingChainSwap(swap)) {
|
|
@@ -1794,10 +1886,13 @@ var SwapManager = class _SwapManager {
|
|
|
1794
1886
|
try {
|
|
1795
1887
|
const outcome = await this.executeRefundArkAction(swap);
|
|
1796
1888
|
if (outcome && outcome.skipped > 0) {
|
|
1889
|
+
const delayMs = _SwapManager.refundRetryDelayMs(outcome.retryAt);
|
|
1797
1890
|
logger.log(
|
|
1798
|
-
`Chain swap ${swap.id}: ${outcome.skipped} VTXO(s) deferred \u2014 scheduling refund retry`
|
|
1891
|
+
`Chain swap ${swap.id}: ${outcome.skipped} VTXO(s) deferred \u2014 scheduling refund retry in ${Math.round(delayMs / 1e3)}s`
|
|
1799
1892
|
);
|
|
1800
|
-
this.scheduleRefundRetry(swap,
|
|
1893
|
+
await this.scheduleRefundRetry(swap, delayMs);
|
|
1894
|
+
} else if (outcome) {
|
|
1895
|
+
await this.clearRefundRetry(swap);
|
|
1801
1896
|
}
|
|
1802
1897
|
this.actionExecutedListeners.forEach(
|
|
1803
1898
|
(listener) => listener(swap, "refundArk")
|
|
@@ -1810,7 +1905,7 @@ var SwapManager = class _SwapManager {
|
|
|
1810
1905
|
this.swapFailedListeners.forEach(
|
|
1811
1906
|
(listener) => listener(swap, error)
|
|
1812
1907
|
);
|
|
1813
|
-
this.scheduleRefundRetry(swap, _SwapManager.REFUND_RETRY_DELAY_MS);
|
|
1908
|
+
await this.scheduleRefundRetry(swap, _SwapManager.REFUND_RETRY_DELAY_MS);
|
|
1814
1909
|
}
|
|
1815
1910
|
}
|
|
1816
1911
|
if (swap.request.from === "BTC") {
|
|
@@ -1843,6 +1938,7 @@ var SwapManager = class _SwapManager {
|
|
|
1843
1938
|
} finally {
|
|
1844
1939
|
this.swapsInProgress.delete(swap.id);
|
|
1845
1940
|
}
|
|
1941
|
+
return true;
|
|
1846
1942
|
}
|
|
1847
1943
|
/**
|
|
1848
1944
|
* Execute claim action for reverse swap
|
|
@@ -1856,13 +1952,15 @@ var SwapManager = class _SwapManager {
|
|
|
1856
1952
|
}
|
|
1857
1953
|
/**
|
|
1858
1954
|
* Execute refund action for submarine swap
|
|
1955
|
+
*
|
|
1956
|
+
* @returns The refund outcome, or `undefined` if no callback is wired.
|
|
1859
1957
|
*/
|
|
1860
1958
|
async executeRefundAction(swap) {
|
|
1861
1959
|
if (!this.refundCallback) {
|
|
1862
1960
|
logger.error("Refund callback not set");
|
|
1863
1961
|
return;
|
|
1864
1962
|
}
|
|
1865
|
-
|
|
1963
|
+
return this.refundCallback(swap);
|
|
1866
1964
|
}
|
|
1867
1965
|
/**
|
|
1868
1966
|
* Execute claim action for chain swap Btc to Ark
|
|
@@ -1944,6 +2042,7 @@ var SwapManager = class _SwapManager {
|
|
|
1944
2042
|
return;
|
|
1945
2043
|
}
|
|
1946
2044
|
for (const swap of this.monitoredSwaps.values()) {
|
|
2045
|
+
if (this.refundRetryTimers.has(swap.id)) continue;
|
|
1947
2046
|
try {
|
|
1948
2047
|
if (isPendingReverseSwap(swap) && isReverseClaimableStatus(swap.status)) {
|
|
1949
2048
|
logger.log(`Resuming claim for swap ${swap.id}`);
|
|
@@ -2255,6 +2354,7 @@ var IndexedDbSwapRepository = class {
|
|
|
2255
2354
|
constructor(dbName = DEFAULT_DB_NAME) {
|
|
2256
2355
|
this.dbName = dbName;
|
|
2257
2356
|
}
|
|
2357
|
+
dbName;
|
|
2258
2358
|
version = 1;
|
|
2259
2359
|
db = null;
|
|
2260
2360
|
async getDB() {
|
|
@@ -2379,6 +2479,8 @@ var IndexedDbSwapRepository = class {
|
|
|
2379
2479
|
// src/arkade-swaps.ts
|
|
2380
2480
|
import {
|
|
2381
2481
|
ArkAddress as ArkAddress2,
|
|
2482
|
+
ArkErrorName,
|
|
2483
|
+
isArkError,
|
|
2382
2484
|
isRecoverable,
|
|
2383
2485
|
getNetwork as getNetwork2
|
|
2384
2486
|
} from "@arkade-os/sdk";
|
|
@@ -2425,6 +2527,13 @@ var MusigKeyAgg = class _MusigKeyAgg {
|
|
|
2425
2527
|
this.internalKey = internalKey;
|
|
2426
2528
|
this._tweak = _tweak;
|
|
2427
2529
|
}
|
|
2530
|
+
privateKey;
|
|
2531
|
+
myPublicKey;
|
|
2532
|
+
publicKeys;
|
|
2533
|
+
myIndex;
|
|
2534
|
+
aggPubkey;
|
|
2535
|
+
internalKey;
|
|
2536
|
+
_tweak;
|
|
2428
2537
|
xonlyTweakAdd(tweak) {
|
|
2429
2538
|
if (this._tweak) throw new Error("musig key already tweaked");
|
|
2430
2539
|
return new _MusigKeyAgg(
|
|
@@ -2459,6 +2568,13 @@ var MusigWithMessage = class {
|
|
|
2459
2568
|
this.tweak = tweak;
|
|
2460
2569
|
this.msg = msg;
|
|
2461
2570
|
}
|
|
2571
|
+
privateKey;
|
|
2572
|
+
myPublicKey;
|
|
2573
|
+
publicKeys;
|
|
2574
|
+
myIndex;
|
|
2575
|
+
aggPubkey;
|
|
2576
|
+
tweak;
|
|
2577
|
+
msg;
|
|
2462
2578
|
generateNonce() {
|
|
2463
2579
|
const nonce = nonceGen(this.myPublicKey, this.privateKey, this.aggPubkey, this.msg);
|
|
2464
2580
|
return new MusigWithNonce(
|
|
@@ -2482,6 +2598,13 @@ var MusigWithNonce = class {
|
|
|
2482
2598
|
this.msg = msg;
|
|
2483
2599
|
this.nonce = nonce;
|
|
2484
2600
|
}
|
|
2601
|
+
privateKey;
|
|
2602
|
+
myPublicKey;
|
|
2603
|
+
publicKeys;
|
|
2604
|
+
myIndex;
|
|
2605
|
+
tweak;
|
|
2606
|
+
msg;
|
|
2607
|
+
nonce;
|
|
2485
2608
|
get publicNonce() {
|
|
2486
2609
|
return this.nonce.public;
|
|
2487
2610
|
}
|
|
@@ -2530,6 +2653,14 @@ var MusigNoncesAggregated = class {
|
|
|
2530
2653
|
this.pubNonces = pubNonces;
|
|
2531
2654
|
this.aggregatedNonce = aggregatedNonce;
|
|
2532
2655
|
}
|
|
2656
|
+
privateKey;
|
|
2657
|
+
publicKeys;
|
|
2658
|
+
myIndex;
|
|
2659
|
+
tweak;
|
|
2660
|
+
msg;
|
|
2661
|
+
nonce;
|
|
2662
|
+
pubNonces;
|
|
2663
|
+
aggregatedNonce;
|
|
2533
2664
|
get publicNonce() {
|
|
2534
2665
|
return this.nonce.public;
|
|
2535
2666
|
}
|
|
@@ -2561,6 +2692,12 @@ var MusigSession = class {
|
|
|
2561
2692
|
this.session = session;
|
|
2562
2693
|
this.partialSignatures = Array(publicKeys.length).fill(null);
|
|
2563
2694
|
}
|
|
2695
|
+
privateKey;
|
|
2696
|
+
publicKeys;
|
|
2697
|
+
myIndex;
|
|
2698
|
+
nonce;
|
|
2699
|
+
pubNonces;
|
|
2700
|
+
session;
|
|
2564
2701
|
partialSignatures;
|
|
2565
2702
|
get publicNonce() {
|
|
2566
2703
|
return this.nonce.public;
|
|
@@ -2588,6 +2725,10 @@ var MusigSigned = class {
|
|
|
2588
2725
|
this.ourPartialSignature = ourPartialSignature;
|
|
2589
2726
|
this.publicNonce = publicNonce;
|
|
2590
2727
|
}
|
|
2728
|
+
session;
|
|
2729
|
+
partialSignatures;
|
|
2730
|
+
ourPartialSignature;
|
|
2731
|
+
publicNonce;
|
|
2591
2732
|
aggregatePartials() {
|
|
2592
2733
|
if (this.partialSignatures.some((s) => s === null)) {
|
|
2593
2734
|
throw new Error("not all partial signatures are set");
|
|
@@ -3235,6 +3376,7 @@ var canRecoverViaBoltz3of3 = (refundableVtxos, swap) => {
|
|
|
3235
3376
|
return refundableVtxos.some((vtxo) => !vtxo.isSpent && !isRecoverable(vtxo));
|
|
3236
3377
|
};
|
|
3237
3378
|
var isSubmarineRefundLocktimeReached = (refundTimestamp) => Math.floor(Date.now() / 1e3) >= refundTimestamp;
|
|
3379
|
+
var CLTV_IMMATURE_RETRY_SEC = 60;
|
|
3238
3380
|
var CLAIM_VTXO_RETRY_ATTEMPTS = 3;
|
|
3239
3381
|
var CLAIM_VTXO_RETRY_DELAY_MS = 500;
|
|
3240
3382
|
var quoteOptionsForSwap = (swap) => {
|
|
@@ -3307,9 +3449,7 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
3307
3449
|
claim: async (swap) => {
|
|
3308
3450
|
await this.claimVHTLC(swap);
|
|
3309
3451
|
},
|
|
3310
|
-
refund:
|
|
3311
|
-
await this.refundVHTLC(swap);
|
|
3312
|
-
},
|
|
3452
|
+
refund: (swap) => this.refundVHTLC(swap),
|
|
3313
3453
|
claimArk: (swap) => this.claimArk(swap),
|
|
3314
3454
|
claimBtc: (swap) => this.claimBtc(swap),
|
|
3315
3455
|
refundArk: async (swap) => {
|
|
@@ -3915,7 +4055,7 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
3915
4055
|
refundWithoutReceiverLeaf,
|
|
3916
4056
|
outputScript
|
|
3917
4057
|
};
|
|
3918
|
-
const
|
|
4058
|
+
const outcome = await this.refundVtxos({
|
|
3919
4059
|
swapId: pendingSwap.id,
|
|
3920
4060
|
vtxos: refundableVtxos,
|
|
3921
4061
|
refundLocktime: vhtlcTimeouts.refund,
|
|
@@ -3925,7 +4065,9 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
3925
4065
|
refundViaBoltz: this.swapProvider.refundSubmarineSwap.bind(this.swapProvider)
|
|
3926
4066
|
});
|
|
3927
4067
|
if (!isSubmarineSuccessStatus(pendingSwap.status)) {
|
|
3928
|
-
const fullyRefunded =
|
|
4068
|
+
const fullyRefunded = outcome.skipped === 0;
|
|
4069
|
+
pendingSwap.refundable = true;
|
|
4070
|
+
pendingSwap.refunded = fullyRefunded;
|
|
3929
4071
|
await updateSubmarineSwapStatus(
|
|
3930
4072
|
pendingSwap,
|
|
3931
4073
|
pendingSwap.status,
|
|
@@ -3934,7 +4076,7 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
3934
4076
|
{ refundable: true, refunded: fullyRefunded }
|
|
3935
4077
|
);
|
|
3936
4078
|
}
|
|
3937
|
-
return
|
|
4079
|
+
return outcome;
|
|
3938
4080
|
}
|
|
3939
4081
|
/**
|
|
3940
4082
|
* Inspect a submarine swap's lockup address for recoverable funds.
|
|
@@ -4551,7 +4693,7 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
4551
4693
|
refundWithoutReceiverLeaf,
|
|
4552
4694
|
outputScript
|
|
4553
4695
|
};
|
|
4554
|
-
const
|
|
4696
|
+
const outcome = await this.refundVtxos({
|
|
4555
4697
|
swapId: pendingSwap.id,
|
|
4556
4698
|
vtxos: unspentVtxos,
|
|
4557
4699
|
refundLocktime,
|
|
@@ -4561,11 +4703,9 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
4561
4703
|
refundViaBoltz: this.swapProvider.refundChainSwap.bind(this.swapProvider)
|
|
4562
4704
|
});
|
|
4563
4705
|
const finalStatus = await this.getSwapStatus(pendingSwap.id);
|
|
4564
|
-
|
|
4565
|
-
|
|
4566
|
-
|
|
4567
|
-
});
|
|
4568
|
-
return { swept: sweptCount, skipped: skippedCount };
|
|
4706
|
+
pendingSwap.status = finalStatus.status;
|
|
4707
|
+
await this.savePendingChainSwap(pendingSwap);
|
|
4708
|
+
return outcome;
|
|
4569
4709
|
}
|
|
4570
4710
|
// =========================================================================
|
|
4571
4711
|
// Chain swaps: BTC -> ARK
|
|
@@ -5169,18 +5309,44 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
5169
5309
|
);
|
|
5170
5310
|
}
|
|
5171
5311
|
}
|
|
5312
|
+
/**
|
|
5313
|
+
* {@link settleRefundWithoutReceiver}, deferring instead of failing when the server
|
|
5314
|
+
* rejects the spend as CLTV-immature ({@link ArkErrorName.FORFEIT_CLOSURE_LOCKED}):
|
|
5315
|
+
* we gate on the local wall clock, the server on the chain tip's, which lags. The
|
|
5316
|
+
* server-authoritative sibling of the wall-clock deferral the pre-CLTV branches
|
|
5317
|
+
* already implement. Anything else propagates.
|
|
5318
|
+
*
|
|
5319
|
+
* Only `submitTx` can raise it, so this is a pass-through for recoverable VTXOs,
|
|
5320
|
+
* which settle via a batch round.
|
|
5321
|
+
*
|
|
5322
|
+
* @returns `true` if settled, `false` if the server deferred it.
|
|
5323
|
+
*/
|
|
5324
|
+
async trySettleRefundWithoutReceiver(swapId, ctx, vtxo) {
|
|
5325
|
+
try {
|
|
5326
|
+
await this.settleRefundWithoutReceiver(ctx, vtxo);
|
|
5327
|
+
return true;
|
|
5328
|
+
} catch (error) {
|
|
5329
|
+
if (!isArkError(error, ArkErrorName.FORFEIT_CLOSURE_LOCKED)) throw error;
|
|
5330
|
+
logger.warn(
|
|
5331
|
+
`Swap ${swapId}: server deferred refundWithoutReceiver for VTXO ${vtxo.txid}:${vtxo.vout} \u2014 refund locktime has not matured against the chain tip (locktime=${error.metadata?.locktime}, currentLocktime=${error.metadata?.current_locktime}, type=${error.metadata?.type}). Refund will be retried once a later block carries the locktime.`
|
|
5332
|
+
);
|
|
5333
|
+
return false;
|
|
5334
|
+
}
|
|
5335
|
+
}
|
|
5172
5336
|
/**
|
|
5173
5337
|
* Refund every VTXO at a swap's VHTLC address back to the wallet, shared by
|
|
5174
5338
|
* {@link ArkadeSwaps.refundVHTLC} (submarine) and {@link ArkadeSwaps.refundArk}
|
|
5175
5339
|
* (chain). Path selection per VTXO:
|
|
5176
|
-
* - CLTV elapsed → `refundWithoutReceiver` (offchain for a
|
|
5177
|
-
* batch round for a swept one — see
|
|
5340
|
+
* - CLTV elapsed by our wall clock → `refundWithoutReceiver` (offchain for a
|
|
5341
|
+
* live VTXO, via a batch round for a swept one — see
|
|
5342
|
+
* {@link settleRefundWithoutReceiver}), or skipped if the server defers it as
|
|
5343
|
+
* still immature (see {@link trySettleRefundWithoutReceiver}).
|
|
5178
5344
|
* - Pre-CLTV recoverable → skipped (Boltz can't co-sign a swept-batch refund).
|
|
5179
5345
|
* - Pre-CLTV non-recoverable → cooperative 3-of-3 refund via Boltz, falling
|
|
5180
5346
|
* back to `refundWithoutReceiver` offchain if Boltz rejects after the
|
|
5181
5347
|
* locktime has since elapsed.
|
|
5182
5348
|
*
|
|
5183
|
-
* @returns Counts of VTXOs swept vs. deferred.
|
|
5349
|
+
* @returns Counts of VTXOs swept vs. deferred, and when to retry the latter.
|
|
5184
5350
|
*/
|
|
5185
5351
|
async refundVtxos(params) {
|
|
5186
5352
|
const {
|
|
@@ -5196,19 +5362,27 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
5196
5362
|
let boltzCallCount = 0;
|
|
5197
5363
|
let sweptCount = 0;
|
|
5198
5364
|
let skippedCount = 0;
|
|
5365
|
+
let retryAt;
|
|
5366
|
+
const defer = (candidate) => {
|
|
5367
|
+
skippedCount++;
|
|
5368
|
+
retryAt = retryAt === void 0 ? candidate : Math.min(retryAt, candidate);
|
|
5369
|
+
};
|
|
5199
5370
|
for (const vtxo of vtxos) {
|
|
5200
5371
|
const isRecoverableVtxo = isRecoverable(vtxo);
|
|
5201
5372
|
const output = { amount: BigInt(vtxo.value), script: outputScript };
|
|
5202
5373
|
if (isSubmarineRefundLocktimeReached(refundLocktime)) {
|
|
5203
|
-
await this.
|
|
5204
|
-
|
|
5374
|
+
if (await this.trySettleRefundWithoutReceiver(swapId, refundContext, vtxo)) {
|
|
5375
|
+
sweptCount++;
|
|
5376
|
+
} else {
|
|
5377
|
+
defer(Math.floor(Date.now() / 1e3) + CLTV_IMMATURE_RETRY_SEC);
|
|
5378
|
+
}
|
|
5205
5379
|
continue;
|
|
5206
5380
|
}
|
|
5207
5381
|
if (isRecoverableVtxo) {
|
|
5208
5382
|
logger.error(
|
|
5209
5383
|
`Swap ${swapId}: 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.`
|
|
5210
5384
|
);
|
|
5211
|
-
|
|
5385
|
+
defer(refundLocktime);
|
|
5212
5386
|
continue;
|
|
5213
5387
|
}
|
|
5214
5388
|
const input = {
|
|
@@ -5242,17 +5416,20 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
5242
5416
|
logger.error(
|
|
5243
5417
|
`Swap ${swapId}: 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.`
|
|
5244
5418
|
);
|
|
5245
|
-
|
|
5419
|
+
defer(refundLocktime);
|
|
5246
5420
|
continue;
|
|
5247
5421
|
}
|
|
5248
5422
|
logger.warn(
|
|
5249
5423
|
`Swap ${swapId}: Boltz rejected VTXO outpoint, falling back to refundWithoutReceiver offchain`
|
|
5250
5424
|
);
|
|
5251
|
-
await this.
|
|
5252
|
-
|
|
5425
|
+
if (await this.trySettleRefundWithoutReceiver(swapId, refundContext, vtxo)) {
|
|
5426
|
+
sweptCount++;
|
|
5427
|
+
} else {
|
|
5428
|
+
defer(Math.floor(Date.now() / 1e3) + CLTV_IMMATURE_RETRY_SEC);
|
|
5429
|
+
}
|
|
5253
5430
|
}
|
|
5254
5431
|
}
|
|
5255
|
-
return { swept: sweptCount, skipped: skippedCount };
|
|
5432
|
+
return { swept: sweptCount, skipped: skippedCount, retryAt };
|
|
5256
5433
|
}
|
|
5257
5434
|
/**
|
|
5258
5435
|
* Creates a VHTLC script for the swap.
|