@arkade-os/boltz-swap 0.3.7 → 0.3.9

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.
@@ -1,5 +1,5 @@
1
1
  import { IWallet, ArkProvider, IndexerProvider, ArkInfo, Identity, ArkTxInput, VHTLC } from '@arkade-os/sdk';
2
- import { l as BoltzSwapProvider, V as SwapManager, i as SwapRepository, X as ArkadeSwapsCreateConfig, A as ArkadeSwapsConfig, j as SwapManagerClient, C as CreateLightningInvoiceRequest, e as CreateLightningInvoiceResponse, b as PendingReverseSwap, S as SendLightningPaymentRequest, f as SendLightningPaymentResponse, c as PendingSubmarineSwap, h as ArkToBtcResponse, a as PendingChainSwap, B as BtcToArkResponse, d as Chain, F as FeesResponse, g as ChainFeesResponse, L as LimitsResponse, G as GetSwapStatusResponse, P as PendingSwap } from './types-DCCYGYCD.js';
2
+ import { l as BoltzSwapProvider, V as SwapManager, i as SwapRepository, X as ArkadeSwapsCreateConfig, A as ArkadeSwapsConfig, j as SwapManagerClient, C as CreateLightningInvoiceRequest, e as CreateLightningInvoiceResponse, b as PendingReverseSwap, S as SendLightningPaymentRequest, f as SendLightningPaymentResponse, c as PendingSubmarineSwap, h as ArkToBtcResponse, a as PendingChainSwap, B as BtcToArkResponse, d as Chain, F as FeesResponse, g as ChainFeesResponse, L as LimitsResponse, G as GetSwapStatusResponse, P as PendingSwap } from './types-CEmBBA71.cjs';
3
3
  import { TransactionOutput } from '@scure/btc-signer/psbt.js';
4
4
 
5
5
  /**
@@ -68,6 +68,13 @@ declare class ArkadeSwaps {
68
68
  * Dispose of resources (stops SwapManager and cleans up).
69
69
  * Can be called manually or automatically with `await using` syntax (TypeScript 5.2+).
70
70
  */
71
+ /**
72
+ * Reset all swap state: stops the SwapManager and clears the swap repository.
73
+ *
74
+ * **Destructive** — any swap in a non-terminal state will lose its
75
+ * refund/claim path. Intended for wallet-reset / dev / test scenarios only.
76
+ */
77
+ reset(): Promise<void>;
71
78
  dispose(): Promise<void>;
72
79
  /**
73
80
  * Symbol.asyncDispose for automatic cleanup with `await using` syntax.
@@ -426,6 +433,13 @@ interface IArkadeSwaps extends AsyncDisposable {
426
433
  getSwapStatus(swapId: string): Promise<GetSwapStatusResponse>;
427
434
  enrichReverseSwapPreimage(swap: PendingReverseSwap, preimage: string): PendingReverseSwap;
428
435
  enrichSubmarineSwapInvoice(swap: PendingSubmarineSwap, invoice: string): PendingSubmarineSwap;
436
+ /**
437
+ * Reset all swap state: stops the SwapManager and clears the swap repository.
438
+ *
439
+ * **Destructive** — any swap in a non-terminal state will lose its
440
+ * refund/claim path. Intended for wallet-reset / dev / test scenarios only.
441
+ */
442
+ reset(): Promise<void>;
429
443
  dispose(): Promise<void>;
430
444
  }
431
445
 
@@ -1,5 +1,5 @@
1
1
  import { IWallet, ArkProvider, IndexerProvider, ArkInfo, Identity, ArkTxInput, VHTLC } from '@arkade-os/sdk';
2
- import { l as BoltzSwapProvider, V as SwapManager, i as SwapRepository, X as ArkadeSwapsCreateConfig, A as ArkadeSwapsConfig, j as SwapManagerClient, C as CreateLightningInvoiceRequest, e as CreateLightningInvoiceResponse, b as PendingReverseSwap, S as SendLightningPaymentRequest, f as SendLightningPaymentResponse, c as PendingSubmarineSwap, h as ArkToBtcResponse, a as PendingChainSwap, B as BtcToArkResponse, d as Chain, F as FeesResponse, g as ChainFeesResponse, L as LimitsResponse, G as GetSwapStatusResponse, P as PendingSwap } from './types-DCCYGYCD.cjs';
2
+ import { l as BoltzSwapProvider, V as SwapManager, i as SwapRepository, X as ArkadeSwapsCreateConfig, A as ArkadeSwapsConfig, j as SwapManagerClient, C as CreateLightningInvoiceRequest, e as CreateLightningInvoiceResponse, b as PendingReverseSwap, S as SendLightningPaymentRequest, f as SendLightningPaymentResponse, c as PendingSubmarineSwap, h as ArkToBtcResponse, a as PendingChainSwap, B as BtcToArkResponse, d as Chain, F as FeesResponse, g as ChainFeesResponse, L as LimitsResponse, G as GetSwapStatusResponse, P as PendingSwap } from './types-CEmBBA71.js';
3
3
  import { TransactionOutput } from '@scure/btc-signer/psbt.js';
4
4
 
5
5
  /**
@@ -68,6 +68,13 @@ declare class ArkadeSwaps {
68
68
  * Dispose of resources (stops SwapManager and cleans up).
69
69
  * Can be called manually or automatically with `await using` syntax (TypeScript 5.2+).
70
70
  */
71
+ /**
72
+ * Reset all swap state: stops the SwapManager and clears the swap repository.
73
+ *
74
+ * **Destructive** — any swap in a non-terminal state will lose its
75
+ * refund/claim path. Intended for wallet-reset / dev / test scenarios only.
76
+ */
77
+ reset(): Promise<void>;
71
78
  dispose(): Promise<void>;
72
79
  /**
73
80
  * Symbol.asyncDispose for automatic cleanup with `await using` syntax.
@@ -426,6 +433,13 @@ interface IArkadeSwaps extends AsyncDisposable {
426
433
  getSwapStatus(swapId: string): Promise<GetSwapStatusResponse>;
427
434
  enrichReverseSwapPreimage(swap: PendingReverseSwap, preimage: string): PendingReverseSwap;
428
435
  enrichSubmarineSwapInvoice(swap: PendingSubmarineSwap, invoice: string): PendingSubmarineSwap;
436
+ /**
437
+ * Reset all swap state: stops the SwapManager and clears the swap repository.
438
+ *
439
+ * **Destructive** — any swap in a non-terminal state will lose its
440
+ * refund/claim path. Intended for wallet-reset / dev / test scenarios only.
441
+ */
442
+ reset(): Promise<void>;
429
443
  dispose(): Promise<void>;
430
444
  }
431
445
 
@@ -2,8 +2,8 @@ import {
2
2
  defineExpoSwapBackgroundTask,
3
3
  registerExpoSwapBackgroundTask,
4
4
  unregisterExpoSwapBackgroundTask
5
- } from "./chunk-XUBVFCQZ.js";
6
- import "./chunk-ZJ2Q5QSA.js";
5
+ } from "./chunk-3XJR3RIH.js";
6
+ import "./chunk-NUR5GPRK.js";
7
7
  import "./chunk-3RG5ZIWI.js";
8
8
  export {
9
9
  defineExpoSwapBackgroundTask,
@@ -8,7 +8,7 @@ import {
8
8
  isSubmarineFinalStatus,
9
9
  isSubmarineSwapRefundable,
10
10
  logger
11
- } from "./chunk-ZJ2Q5QSA.js";
11
+ } from "./chunk-NUR5GPRK.js";
12
12
  import {
13
13
  __require
14
14
  } from "./chunk-3RG5ZIWI.js";
@@ -865,6 +865,8 @@ var SwapManager = class {
865
865
  // All swaps passed to start(), including completed ones
866
866
  pollTimer = null;
867
867
  reconnectTimer = null;
868
+ initialPollTimer = null;
869
+ pollRetryTimers = /* @__PURE__ */ new Map();
868
870
  isRunning = false;
869
871
  currentReconnectDelay;
870
872
  currentPollRetryDelay;
@@ -1048,6 +1050,14 @@ var SwapManager = class {
1048
1050
  clearTimeout(this.reconnectTimer);
1049
1051
  this.reconnectTimer = null;
1050
1052
  }
1053
+ if (this.initialPollTimer) {
1054
+ clearTimeout(this.initialPollTimer);
1055
+ this.initialPollTimer = null;
1056
+ }
1057
+ for (const timer of this.pollRetryTimers.values()) {
1058
+ clearTimeout(timer);
1059
+ }
1060
+ this.pollRetryTimers.clear();
1051
1061
  }
1052
1062
  /**
1053
1063
  * Set the polling interval (ms).
@@ -1103,6 +1113,11 @@ var SwapManager = class {
1103
1113
  async removeSwap(swapId) {
1104
1114
  this.monitoredSwaps.delete(swapId);
1105
1115
  this.swapSubscriptions.delete(swapId);
1116
+ const retryTimer = this.pollRetryTimers.get(swapId);
1117
+ if (retryTimer) {
1118
+ clearTimeout(retryTimer);
1119
+ this.pollRetryTimers.delete(swapId);
1120
+ }
1106
1121
  logger.log(`Removed swap ${swapId} from monitoring`);
1107
1122
  }
1108
1123
  /**
@@ -1253,12 +1268,25 @@ var SwapManager = class {
1253
1268
  for (const swapId of this.monitoredSwaps.keys()) {
1254
1269
  this.subscribeToSwap(swapId);
1255
1270
  }
1256
- this.pollAllSwaps();
1271
+ if (this.initialPollTimer) {
1272
+ clearTimeout(this.initialPollTimer);
1273
+ this.initialPollTimer = null;
1274
+ }
1275
+ this.initialPollTimer = setTimeout(() => {
1276
+ this.initialPollTimer = null;
1277
+ if (this.isRunning) {
1278
+ this.pollAllSwaps();
1279
+ }
1280
+ }, 2e3);
1257
1281
  this.startPolling();
1258
1282
  this.wsConnectedListeners.forEach((listener) => listener());
1259
1283
  };
1260
1284
  this.websocket.onclose = () => {
1261
1285
  clearTimeout(connectionTimeout);
1286
+ if (this.initialPollTimer) {
1287
+ clearTimeout(this.initialPollTimer);
1288
+ this.initialPollTimer = null;
1289
+ }
1262
1290
  this.websocket = null;
1263
1291
  if (this.isRunning) {
1264
1292
  this.scheduleReconnect();
@@ -1287,6 +1315,10 @@ var SwapManager = class {
1287
1315
  */
1288
1316
  enterPollingFallback(error) {
1289
1317
  if (!this.isRunning) return;
1318
+ if (this.initialPollTimer) {
1319
+ clearTimeout(this.initialPollTimer);
1320
+ this.initialPollTimer = null;
1321
+ }
1290
1322
  this.isReconnecting = false;
1291
1323
  this.websocket = null;
1292
1324
  this.usePollingFallback = true;
@@ -1388,6 +1420,11 @@ var SwapManager = class {
1388
1420
  if (this.isFinalStatus(swap)) {
1389
1421
  this.monitoredSwaps.delete(swap.id);
1390
1422
  this.swapSubscriptions.delete(swap.id);
1423
+ const retryTimer = this.pollRetryTimers.get(swap.id);
1424
+ if (retryTimer) {
1425
+ clearTimeout(retryTimer);
1426
+ this.pollRetryTimers.delete(swap.id);
1427
+ }
1391
1428
  this.swapCompletedListeners.forEach((listener) => listener(swap));
1392
1429
  }
1393
1430
  }
@@ -1652,7 +1689,37 @@ var SwapManager = class {
1652
1689
  );
1653
1690
  }
1654
1691
  } catch (error) {
1655
- logger.error(`Failed to poll swap ${swap.id}:`, error);
1692
+ if (error instanceof NetworkError && error.statusCode === 429) {
1693
+ logger.warn(
1694
+ `Rate-limited polling swap ${swap.id}, retrying in 2s`
1695
+ );
1696
+ const existing = this.pollRetryTimers.get(swap.id);
1697
+ if (existing) clearTimeout(existing);
1698
+ this.pollRetryTimers.set(
1699
+ swap.id,
1700
+ setTimeout(async () => {
1701
+ this.pollRetryTimers.delete(swap.id);
1702
+ try {
1703
+ const retry = await this.swapProvider.getSwapStatus(
1704
+ swap.id
1705
+ );
1706
+ if (retry.status !== swap.status) {
1707
+ await this.handleSwapStatusUpdate(
1708
+ swap,
1709
+ retry.status
1710
+ );
1711
+ }
1712
+ } catch (retryError) {
1713
+ logger.error(
1714
+ `Retry poll for swap ${swap.id} also failed:`,
1715
+ retryError
1716
+ );
1717
+ }
1718
+ }, 2e3)
1719
+ );
1720
+ } else {
1721
+ logger.error(`Failed to poll swap ${swap.id}:`, error);
1722
+ }
1656
1723
  }
1657
1724
  }
1658
1725
  );
@@ -3039,6 +3106,16 @@ var ArkadeSwaps = class _ArkadeSwaps {
3039
3106
  * Dispose of resources (stops SwapManager and cleans up).
3040
3107
  * Can be called manually or automatically with `await using` syntax (TypeScript 5.2+).
3041
3108
  */
3109
+ /**
3110
+ * Reset all swap state: stops the SwapManager and clears the swap repository.
3111
+ *
3112
+ * **Destructive** — any swap in a non-terminal state will lose its
3113
+ * refund/claim path. Intended for wallet-reset / dev / test scenarios only.
3114
+ */
3115
+ async reset() {
3116
+ await this.dispose();
3117
+ await this.swapRepository.clear();
3118
+ }
3042
3119
  async dispose() {
3043
3120
  if (this.swapManager) {
3044
3121
  await this.stopSwapManager();
@@ -4409,16 +4486,18 @@ var ArkadeSwaps = class _ArkadeSwaps {
4409
4486
  } else if (isRestoredSubmarineSwap(swap)) {
4410
4487
  const { amount, lockupAddress, serverPublicKey, tree } = swap.refundDetails;
4411
4488
  let preimage = "";
4412
- try {
4413
- const data = await this.swapProvider.getSwapPreimage(
4414
- swap.id
4415
- );
4416
- preimage = data.preimage;
4417
- } catch (error) {
4418
- logger.warn(
4419
- `Failed to restore preimage for submarine swap ${id}`,
4420
- error
4421
- );
4489
+ if (!isSubmarineFinalStatus(status)) {
4490
+ try {
4491
+ const data = await this.swapProvider.getSwapPreimage(
4492
+ swap.id
4493
+ );
4494
+ preimage = data.preimage;
4495
+ } catch (error) {
4496
+ logger.warn(
4497
+ `Failed to restore preimage for submarine swap ${id}`,
4498
+ error
4499
+ );
4500
+ }
4422
4501
  }
4423
4502
  submarineSwaps.push({
4424
4503
  id,
@@ -1277,6 +1277,8 @@ var init_swap_manager = __esm({
1277
1277
  // All swaps passed to start(), including completed ones
1278
1278
  pollTimer = null;
1279
1279
  reconnectTimer = null;
1280
+ initialPollTimer = null;
1281
+ pollRetryTimers = /* @__PURE__ */ new Map();
1280
1282
  isRunning = false;
1281
1283
  currentReconnectDelay;
1282
1284
  currentPollRetryDelay;
@@ -1460,6 +1462,14 @@ var init_swap_manager = __esm({
1460
1462
  clearTimeout(this.reconnectTimer);
1461
1463
  this.reconnectTimer = null;
1462
1464
  }
1465
+ if (this.initialPollTimer) {
1466
+ clearTimeout(this.initialPollTimer);
1467
+ this.initialPollTimer = null;
1468
+ }
1469
+ for (const timer of this.pollRetryTimers.values()) {
1470
+ clearTimeout(timer);
1471
+ }
1472
+ this.pollRetryTimers.clear();
1463
1473
  }
1464
1474
  /**
1465
1475
  * Set the polling interval (ms).
@@ -1515,6 +1525,11 @@ var init_swap_manager = __esm({
1515
1525
  async removeSwap(swapId) {
1516
1526
  this.monitoredSwaps.delete(swapId);
1517
1527
  this.swapSubscriptions.delete(swapId);
1528
+ const retryTimer = this.pollRetryTimers.get(swapId);
1529
+ if (retryTimer) {
1530
+ clearTimeout(retryTimer);
1531
+ this.pollRetryTimers.delete(swapId);
1532
+ }
1518
1533
  logger.log(`Removed swap ${swapId} from monitoring`);
1519
1534
  }
1520
1535
  /**
@@ -1665,12 +1680,25 @@ var init_swap_manager = __esm({
1665
1680
  for (const swapId of this.monitoredSwaps.keys()) {
1666
1681
  this.subscribeToSwap(swapId);
1667
1682
  }
1668
- this.pollAllSwaps();
1683
+ if (this.initialPollTimer) {
1684
+ clearTimeout(this.initialPollTimer);
1685
+ this.initialPollTimer = null;
1686
+ }
1687
+ this.initialPollTimer = setTimeout(() => {
1688
+ this.initialPollTimer = null;
1689
+ if (this.isRunning) {
1690
+ this.pollAllSwaps();
1691
+ }
1692
+ }, 2e3);
1669
1693
  this.startPolling();
1670
1694
  this.wsConnectedListeners.forEach((listener) => listener());
1671
1695
  };
1672
1696
  this.websocket.onclose = () => {
1673
1697
  clearTimeout(connectionTimeout);
1698
+ if (this.initialPollTimer) {
1699
+ clearTimeout(this.initialPollTimer);
1700
+ this.initialPollTimer = null;
1701
+ }
1674
1702
  this.websocket = null;
1675
1703
  if (this.isRunning) {
1676
1704
  this.scheduleReconnect();
@@ -1699,6 +1727,10 @@ var init_swap_manager = __esm({
1699
1727
  */
1700
1728
  enterPollingFallback(error) {
1701
1729
  if (!this.isRunning) return;
1730
+ if (this.initialPollTimer) {
1731
+ clearTimeout(this.initialPollTimer);
1732
+ this.initialPollTimer = null;
1733
+ }
1702
1734
  this.isReconnecting = false;
1703
1735
  this.websocket = null;
1704
1736
  this.usePollingFallback = true;
@@ -1800,6 +1832,11 @@ var init_swap_manager = __esm({
1800
1832
  if (this.isFinalStatus(swap)) {
1801
1833
  this.monitoredSwaps.delete(swap.id);
1802
1834
  this.swapSubscriptions.delete(swap.id);
1835
+ const retryTimer = this.pollRetryTimers.get(swap.id);
1836
+ if (retryTimer) {
1837
+ clearTimeout(retryTimer);
1838
+ this.pollRetryTimers.delete(swap.id);
1839
+ }
1803
1840
  this.swapCompletedListeners.forEach((listener) => listener(swap));
1804
1841
  }
1805
1842
  }
@@ -2064,7 +2101,37 @@ var init_swap_manager = __esm({
2064
2101
  );
2065
2102
  }
2066
2103
  } catch (error) {
2067
- logger.error(`Failed to poll swap ${swap.id}:`, error);
2104
+ if (error instanceof NetworkError && error.statusCode === 429) {
2105
+ logger.warn(
2106
+ `Rate-limited polling swap ${swap.id}, retrying in 2s`
2107
+ );
2108
+ const existing = this.pollRetryTimers.get(swap.id);
2109
+ if (existing) clearTimeout(existing);
2110
+ this.pollRetryTimers.set(
2111
+ swap.id,
2112
+ setTimeout(async () => {
2113
+ this.pollRetryTimers.delete(swap.id);
2114
+ try {
2115
+ const retry = await this.swapProvider.getSwapStatus(
2116
+ swap.id
2117
+ );
2118
+ if (retry.status !== swap.status) {
2119
+ await this.handleSwapStatusUpdate(
2120
+ swap,
2121
+ retry.status
2122
+ );
2123
+ }
2124
+ } catch (retryError) {
2125
+ logger.error(
2126
+ `Retry poll for swap ${swap.id} also failed:`,
2127
+ retryError
2128
+ );
2129
+ }
2130
+ }, 2e3)
2131
+ );
2132
+ } else {
2133
+ logger.error(`Failed to poll swap ${swap.id}:`, error);
2134
+ }
2068
2135
  }
2069
2136
  }
2070
2137
  );
@@ -3044,6 +3111,16 @@ var init_arkade_swaps = __esm({
3044
3111
  * Dispose of resources (stops SwapManager and cleans up).
3045
3112
  * Can be called manually or automatically with `await using` syntax (TypeScript 5.2+).
3046
3113
  */
3114
+ /**
3115
+ * Reset all swap state: stops the SwapManager and clears the swap repository.
3116
+ *
3117
+ * **Destructive** — any swap in a non-terminal state will lose its
3118
+ * refund/claim path. Intended for wallet-reset / dev / test scenarios only.
3119
+ */
3120
+ async reset() {
3121
+ await this.dispose();
3122
+ await this.swapRepository.clear();
3123
+ }
3047
3124
  async dispose() {
3048
3125
  if (this.swapManager) {
3049
3126
  await this.stopSwapManager();
@@ -4414,16 +4491,18 @@ var init_arkade_swaps = __esm({
4414
4491
  } else if (isRestoredSubmarineSwap(swap)) {
4415
4492
  const { amount, lockupAddress, serverPublicKey, tree } = swap.refundDetails;
4416
4493
  let preimage = "";
4417
- try {
4418
- const data = await this.swapProvider.getSwapPreimage(
4419
- swap.id
4420
- );
4421
- preimage = data.preimage;
4422
- } catch (error) {
4423
- logger.warn(
4424
- `Failed to restore preimage for submarine swap ${id}`,
4425
- error
4426
- );
4494
+ if (!isSubmarineFinalStatus(status)) {
4495
+ try {
4496
+ const data = await this.swapProvider.getSwapPreimage(
4497
+ swap.id
4498
+ );
4499
+ preimage = data.preimage;
4500
+ } catch (error) {
4501
+ logger.warn(
4502
+ `Failed to restore preimage for submarine swap ${id}`,
4503
+ error
4504
+ );
4505
+ }
4427
4506
  }
4428
4507
  submarineSwaps.push({
4429
4508
  id,
@@ -4887,6 +4966,16 @@ var ExpoArkadeSwaps = class _ExpoArkadeSwaps {
4887
4966
  await taskQueue.addTask(task);
4888
4967
  }
4889
4968
  // ── Lifecycle ────────────────────────────────────────────────────
4969
+ /**
4970
+ * Reset all swap state: stops polling and clears the swap repository.
4971
+ *
4972
+ * **Destructive** — any swap in a non-terminal state will lose its
4973
+ * refund/claim path. Intended for wallet-reset / dev / test scenarios only.
4974
+ */
4975
+ async reset() {
4976
+ await this.dispose();
4977
+ await this.inner.swapRepository.clear();
4978
+ }
4890
4979
  async dispose() {
4891
4980
  if (this.foregroundIntervalId) {
4892
4981
  clearInterval(this.foregroundIntervalId);
@@ -1,5 +1,5 @@
1
- import { I as IArkadeSwaps, A as ArkadeSwaps } from '../arkade-swaps-DnhnGtTb.cjs';
2
- import { A as ArkadeSwapsConfig, i as SwapRepository, l as BoltzSwapProvider, N as Network, j as SwapManagerClient, C as CreateLightningInvoiceRequest, e as CreateLightningInvoiceResponse, S as SendLightningPaymentRequest, f as SendLightningPaymentResponse, c as PendingSubmarineSwap, b as PendingReverseSwap, F as FeesResponse, a as PendingChainSwap, h as ArkToBtcResponse, B as BtcToArkResponse, d as Chain, g as ChainFeesResponse, L as LimitsResponse, G as GetSwapStatusResponse, P as PendingSwap } from '../types-DCCYGYCD.cjs';
1
+ import { I as IArkadeSwaps, A as ArkadeSwaps } from '../arkade-swaps--vc0H2oD.cjs';
2
+ import { A as ArkadeSwapsConfig, i as SwapRepository, l as BoltzSwapProvider, N as Network, j as SwapManagerClient, C as CreateLightningInvoiceRequest, e as CreateLightningInvoiceResponse, S as SendLightningPaymentRequest, f as SendLightningPaymentResponse, c as PendingSubmarineSwap, b as PendingReverseSwap, F as FeesResponse, a as PendingChainSwap, h as ArkToBtcResponse, B as BtcToArkResponse, d as Chain, g as ChainFeesResponse, L as LimitsResponse, G as GetSwapStatusResponse, P as PendingSwap } from '../types-CEmBBA71.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';
@@ -124,6 +124,13 @@ declare class ExpoArkadeSwaps implements IArkadeSwaps {
124
124
  private startForegroundPolling;
125
125
  private runForegroundPoll;
126
126
  private seedSwapPollTask;
127
+ /**
128
+ * Reset all swap state: stops polling and clears the swap repository.
129
+ *
130
+ * **Destructive** — any swap in a non-terminal state will lose its
131
+ * refund/claim path. Intended for wallet-reset / dev / test scenarios only.
132
+ */
133
+ reset(): Promise<void>;
127
134
  dispose(): Promise<void>;
128
135
  [Symbol.asyncDispose](): Promise<void>;
129
136
  startSwapManager(): Promise<void>;
@@ -1,5 +1,5 @@
1
- import { I as IArkadeSwaps, A as ArkadeSwaps } from '../arkade-swaps-MmMjdK2G.js';
2
- import { A as ArkadeSwapsConfig, i as SwapRepository, l as BoltzSwapProvider, N as Network, j as SwapManagerClient, C as CreateLightningInvoiceRequest, e as CreateLightningInvoiceResponse, S as SendLightningPaymentRequest, f as SendLightningPaymentResponse, c as PendingSubmarineSwap, b as PendingReverseSwap, F as FeesResponse, a as PendingChainSwap, h as ArkToBtcResponse, B as BtcToArkResponse, d as Chain, g as ChainFeesResponse, L as LimitsResponse, G as GetSwapStatusResponse, P as PendingSwap } from '../types-DCCYGYCD.js';
1
+ import { I as IArkadeSwaps, A as ArkadeSwaps } from '../arkade-swaps-qQuX73sR.js';
2
+ import { A as ArkadeSwapsConfig, i as SwapRepository, l as BoltzSwapProvider, N as Network, j as SwapManagerClient, C as CreateLightningInvoiceRequest, e as CreateLightningInvoiceResponse, S as SendLightningPaymentRequest, f as SendLightningPaymentResponse, c as PendingSubmarineSwap, b as PendingReverseSwap, F as FeesResponse, a as PendingChainSwap, h as ArkToBtcResponse, B as BtcToArkResponse, d as Chain, g as ChainFeesResponse, L as LimitsResponse, G as GetSwapStatusResponse, P as PendingSwap } from '../types-CEmBBA71.js';
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';
@@ -124,6 +124,13 @@ declare class ExpoArkadeSwaps implements IArkadeSwaps {
124
124
  private startForegroundPolling;
125
125
  private runForegroundPoll;
126
126
  private seedSwapPollTask;
127
+ /**
128
+ * Reset all swap state: stops polling and clears the swap repository.
129
+ *
130
+ * **Destructive** — any swap in a non-terminal state will lose its
131
+ * refund/claim path. Intended for wallet-reset / dev / test scenarios only.
132
+ */
133
+ reset(): Promise<void>;
127
134
  dispose(): Promise<void>;
128
135
  [Symbol.asyncDispose](): Promise<void>;
129
136
  startSwapManager(): Promise<void>;
@@ -4,10 +4,10 @@ import {
4
4
  registerExpoSwapBackgroundTask,
5
5
  swapsPollProcessor,
6
6
  unregisterExpoSwapBackgroundTask
7
- } from "../chunk-XUBVFCQZ.js";
7
+ } from "../chunk-3XJR3RIH.js";
8
8
  import {
9
9
  ArkadeSwaps
10
- } from "../chunk-ZJ2Q5QSA.js";
10
+ } from "../chunk-NUR5GPRK.js";
11
11
  import "../chunk-3RG5ZIWI.js";
12
12
 
13
13
  // src/expo/arkade-lightning.ts
@@ -56,7 +56,7 @@ var ExpoArkadeSwaps = class _ExpoArkadeSwaps {
56
56
  await instance.seedSwapPollTask();
57
57
  if (config.background.minimumBackgroundInterval) {
58
58
  try {
59
- const { registerExpoSwapBackgroundTask: registerExpoSwapBackgroundTask2 } = await import("../background-RLZLTWCI.js");
59
+ const { registerExpoSwapBackgroundTask: registerExpoSwapBackgroundTask2 } = await import("../background-VM6FN7TU.js");
60
60
  await registerExpoSwapBackgroundTask2(
61
61
  config.background.taskName,
62
62
  {
@@ -112,6 +112,16 @@ var ExpoArkadeSwaps = class _ExpoArkadeSwaps {
112
112
  await taskQueue.addTask(task);
113
113
  }
114
114
  // ── Lifecycle ────────────────────────────────────────────────────
115
+ /**
116
+ * Reset all swap state: stops polling and clears the swap repository.
117
+ *
118
+ * **Destructive** — any swap in a non-terminal state will lose its
119
+ * refund/claim path. Intended for wallet-reset / dev / test scenarios only.
120
+ */
121
+ async reset() {
122
+ await this.dispose();
123
+ await this.inner.swapRepository.clear();
124
+ }
115
125
  async dispose() {
116
126
  if (this.foregroundIntervalId) {
117
127
  clearInterval(this.foregroundIntervalId);
@@ -119,7 +129,7 @@ var ExpoArkadeSwaps = class _ExpoArkadeSwaps {
119
129
  }
120
130
  await this.inner.dispose();
121
131
  try {
122
- const { unregisterExpoSwapBackgroundTask: unregisterExpoSwapBackgroundTask2 } = await import("../background-RLZLTWCI.js");
132
+ const { unregisterExpoSwapBackgroundTask: unregisterExpoSwapBackgroundTask2 } = await import("../background-VM6FN7TU.js");
123
133
  await unregisterExpoSwapBackgroundTask2(this.taskName);
124
134
  } catch (err) {
125
135
  const message = err instanceof Error ? err.message : String(err);
package/dist/index.cjs CHANGED
@@ -1383,6 +1383,8 @@ var SwapManager = class {
1383
1383
  // All swaps passed to start(), including completed ones
1384
1384
  pollTimer = null;
1385
1385
  reconnectTimer = null;
1386
+ initialPollTimer = null;
1387
+ pollRetryTimers = /* @__PURE__ */ new Map();
1386
1388
  isRunning = false;
1387
1389
  currentReconnectDelay;
1388
1390
  currentPollRetryDelay;
@@ -1566,6 +1568,14 @@ var SwapManager = class {
1566
1568
  clearTimeout(this.reconnectTimer);
1567
1569
  this.reconnectTimer = null;
1568
1570
  }
1571
+ if (this.initialPollTimer) {
1572
+ clearTimeout(this.initialPollTimer);
1573
+ this.initialPollTimer = null;
1574
+ }
1575
+ for (const timer of this.pollRetryTimers.values()) {
1576
+ clearTimeout(timer);
1577
+ }
1578
+ this.pollRetryTimers.clear();
1569
1579
  }
1570
1580
  /**
1571
1581
  * Set the polling interval (ms).
@@ -1621,6 +1631,11 @@ var SwapManager = class {
1621
1631
  async removeSwap(swapId) {
1622
1632
  this.monitoredSwaps.delete(swapId);
1623
1633
  this.swapSubscriptions.delete(swapId);
1634
+ const retryTimer = this.pollRetryTimers.get(swapId);
1635
+ if (retryTimer) {
1636
+ clearTimeout(retryTimer);
1637
+ this.pollRetryTimers.delete(swapId);
1638
+ }
1624
1639
  logger.log(`Removed swap ${swapId} from monitoring`);
1625
1640
  }
1626
1641
  /**
@@ -1771,12 +1786,25 @@ var SwapManager = class {
1771
1786
  for (const swapId of this.monitoredSwaps.keys()) {
1772
1787
  this.subscribeToSwap(swapId);
1773
1788
  }
1774
- this.pollAllSwaps();
1789
+ if (this.initialPollTimer) {
1790
+ clearTimeout(this.initialPollTimer);
1791
+ this.initialPollTimer = null;
1792
+ }
1793
+ this.initialPollTimer = setTimeout(() => {
1794
+ this.initialPollTimer = null;
1795
+ if (this.isRunning) {
1796
+ this.pollAllSwaps();
1797
+ }
1798
+ }, 2e3);
1775
1799
  this.startPolling();
1776
1800
  this.wsConnectedListeners.forEach((listener) => listener());
1777
1801
  };
1778
1802
  this.websocket.onclose = () => {
1779
1803
  clearTimeout(connectionTimeout);
1804
+ if (this.initialPollTimer) {
1805
+ clearTimeout(this.initialPollTimer);
1806
+ this.initialPollTimer = null;
1807
+ }
1780
1808
  this.websocket = null;
1781
1809
  if (this.isRunning) {
1782
1810
  this.scheduleReconnect();
@@ -1805,6 +1833,10 @@ var SwapManager = class {
1805
1833
  */
1806
1834
  enterPollingFallback(error) {
1807
1835
  if (!this.isRunning) return;
1836
+ if (this.initialPollTimer) {
1837
+ clearTimeout(this.initialPollTimer);
1838
+ this.initialPollTimer = null;
1839
+ }
1808
1840
  this.isReconnecting = false;
1809
1841
  this.websocket = null;
1810
1842
  this.usePollingFallback = true;
@@ -1906,6 +1938,11 @@ var SwapManager = class {
1906
1938
  if (this.isFinalStatus(swap)) {
1907
1939
  this.monitoredSwaps.delete(swap.id);
1908
1940
  this.swapSubscriptions.delete(swap.id);
1941
+ const retryTimer = this.pollRetryTimers.get(swap.id);
1942
+ if (retryTimer) {
1943
+ clearTimeout(retryTimer);
1944
+ this.pollRetryTimers.delete(swap.id);
1945
+ }
1909
1946
  this.swapCompletedListeners.forEach((listener) => listener(swap));
1910
1947
  }
1911
1948
  }
@@ -2170,7 +2207,37 @@ var SwapManager = class {
2170
2207
  );
2171
2208
  }
2172
2209
  } catch (error) {
2173
- logger.error(`Failed to poll swap ${swap.id}:`, error);
2210
+ if (error instanceof NetworkError && error.statusCode === 429) {
2211
+ logger.warn(
2212
+ `Rate-limited polling swap ${swap.id}, retrying in 2s`
2213
+ );
2214
+ const existing = this.pollRetryTimers.get(swap.id);
2215
+ if (existing) clearTimeout(existing);
2216
+ this.pollRetryTimers.set(
2217
+ swap.id,
2218
+ setTimeout(async () => {
2219
+ this.pollRetryTimers.delete(swap.id);
2220
+ try {
2221
+ const retry = await this.swapProvider.getSwapStatus(
2222
+ swap.id
2223
+ );
2224
+ if (retry.status !== swap.status) {
2225
+ await this.handleSwapStatusUpdate(
2226
+ swap,
2227
+ retry.status
2228
+ );
2229
+ }
2230
+ } catch (retryError) {
2231
+ logger.error(
2232
+ `Retry poll for swap ${swap.id} also failed:`,
2233
+ retryError
2234
+ );
2235
+ }
2236
+ }, 2e3)
2237
+ );
2238
+ } else {
2239
+ logger.error(`Failed to poll swap ${swap.id}:`, error);
2240
+ }
2174
2241
  }
2175
2242
  }
2176
2243
  );
@@ -3098,6 +3165,16 @@ var ArkadeSwaps = class _ArkadeSwaps {
3098
3165
  * Dispose of resources (stops SwapManager and cleans up).
3099
3166
  * Can be called manually or automatically with `await using` syntax (TypeScript 5.2+).
3100
3167
  */
3168
+ /**
3169
+ * Reset all swap state: stops the SwapManager and clears the swap repository.
3170
+ *
3171
+ * **Destructive** — any swap in a non-terminal state will lose its
3172
+ * refund/claim path. Intended for wallet-reset / dev / test scenarios only.
3173
+ */
3174
+ async reset() {
3175
+ await this.dispose();
3176
+ await this.swapRepository.clear();
3177
+ }
3101
3178
  async dispose() {
3102
3179
  if (this.swapManager) {
3103
3180
  await this.stopSwapManager();
@@ -4468,16 +4545,18 @@ var ArkadeSwaps = class _ArkadeSwaps {
4468
4545
  } else if (isRestoredSubmarineSwap(swap)) {
4469
4546
  const { amount, lockupAddress, serverPublicKey, tree } = swap.refundDetails;
4470
4547
  let preimage = "";
4471
- try {
4472
- const data = await this.swapProvider.getSwapPreimage(
4473
- swap.id
4474
- );
4475
- preimage = data.preimage;
4476
- } catch (error) {
4477
- logger.warn(
4478
- `Failed to restore preimage for submarine swap ${id}`,
4479
- error
4480
- );
4548
+ if (!isSubmarineFinalStatus(status)) {
4549
+ try {
4550
+ const data = await this.swapProvider.getSwapPreimage(
4551
+ swap.id
4552
+ );
4553
+ preimage = data.preimage;
4554
+ } catch (error) {
4555
+ logger.warn(
4556
+ `Failed to restore preimage for submarine swap ${id}`,
4557
+ error
4558
+ );
4559
+ }
4481
4560
  }
4482
4561
  submarineSwaps.push({
4483
4562
  id,
@@ -5036,6 +5115,28 @@ var ArkadeSwapsMessageHandler = class _ArkadeSwapsMessageHandler {
5036
5115
  };
5037
5116
 
5038
5117
  // src/serviceWorker/arkade-swaps-runtime.ts
5118
+ var import_sdk10 = require("@arkade-os/sdk");
5119
+ function isMessageBusNotInitializedError(error) {
5120
+ return error instanceof Error && error.message.includes(import_sdk10.MESSAGE_BUS_NOT_INITIALIZED);
5121
+ }
5122
+ var DEDUPABLE_REQUEST_TYPES = /* @__PURE__ */ new Set([
5123
+ "GET_FEES",
5124
+ "GET_LIMITS",
5125
+ "GET_SWAP_STATUS",
5126
+ "GET_PENDING_SUBMARINE_SWAPS",
5127
+ "GET_PENDING_REVERSE_SWAPS",
5128
+ "GET_PENDING_CHAIN_SWAPS",
5129
+ "GET_SWAP_HISTORY",
5130
+ "QUOTE_SWAP",
5131
+ "SM-GET_PENDING_SWAPS",
5132
+ "SM-HAS_SWAP",
5133
+ "SM-IS_PROCESSING",
5134
+ "SM-GET_STATS"
5135
+ ]);
5136
+ function getRequestDedupKey(request) {
5137
+ const { id, tag, ...rest } = request;
5138
+ return JSON.stringify(rest);
5139
+ }
5039
5140
  var ServiceWorkerArkadeSwaps = class _ServiceWorkerArkadeSwaps {
5040
5141
  constructor(messageTag, serviceWorker, swapRepository, withSwapManager) {
5041
5142
  this.messageTag = messageTag;
@@ -5050,6 +5151,10 @@ var ServiceWorkerArkadeSwaps = class _ServiceWorkerArkadeSwaps {
5050
5151
  actionExecutedListeners = /* @__PURE__ */ new Set();
5051
5152
  wsConnectedListeners = /* @__PURE__ */ new Set();
5052
5153
  wsDisconnectedListeners = /* @__PURE__ */ new Set();
5154
+ initPayload = null;
5155
+ reinitPromise = null;
5156
+ pingPromise = null;
5157
+ inflightRequests = /* @__PURE__ */ new Map();
5053
5158
  static async create(config) {
5054
5159
  const messageTag = config.messageTag ?? DEFAULT_MESSAGE_TAG;
5055
5160
  const swapRepository = config.swapRepository ?? new IndexedDbSwapRepository();
@@ -5059,18 +5164,20 @@ var ServiceWorkerArkadeSwaps = class _ServiceWorkerArkadeSwaps {
5059
5164
  swapRepository,
5060
5165
  Boolean(config.swapManager)
5061
5166
  );
5167
+ const initPayload = {
5168
+ network: config.network,
5169
+ arkServerUrl: config.arkServerUrl,
5170
+ swapProvider: { baseUrl: config.swapProvider.getApiUrl() },
5171
+ swapManager: config.swapManager
5172
+ };
5062
5173
  const initMessage = {
5063
5174
  tag: messageTag,
5064
5175
  id: getRandomId(),
5065
5176
  type: "INIT_ARKADE_SWAPS",
5066
- payload: {
5067
- network: config.network,
5068
- arkServerUrl: config.arkServerUrl,
5069
- swapProvider: { baseUrl: config.swapProvider.getApiUrl() },
5070
- swapManager: config.swapManager
5071
- }
5177
+ payload: initPayload
5072
5178
  };
5073
5179
  await svcArkadeSwaps.sendMessage(initMessage);
5180
+ svcArkadeSwaps.initPayload = initPayload;
5074
5181
  return svcArkadeSwaps;
5075
5182
  }
5076
5183
  async startSwapManager() {
@@ -5602,6 +5709,16 @@ var ServiceWorkerArkadeSwaps = class _ServiceWorkerArkadeSwaps {
5602
5709
  type: "REFRESH_SWAPS_STATUS"
5603
5710
  });
5604
5711
  }
5712
+ /**
5713
+ * Reset all swap state: stops the SwapManager and clears the swap repository.
5714
+ *
5715
+ * **Destructive** — any swap in a non-terminal state will lose its
5716
+ * refund/claim path. Intended for wallet-reset / dev / test scenarios only.
5717
+ */
5718
+ async reset() {
5719
+ await this.dispose();
5720
+ await this.swapRepository.clear();
5721
+ }
5605
5722
  async dispose() {
5606
5723
  if (this.withSwapManager) {
5607
5724
  await this.stopSwapManager().catch(() => {
@@ -5611,17 +5728,23 @@ var ServiceWorkerArkadeSwaps = class _ServiceWorkerArkadeSwaps {
5611
5728
  async [Symbol.asyncDispose]() {
5612
5729
  return this.dispose();
5613
5730
  }
5614
- async sendMessage(request) {
5731
+ sendMessageDirect(request) {
5615
5732
  return new Promise((resolve, reject) => {
5616
- const timeoutMs = 3e4;
5617
- let timeout;
5618
5733
  const cleanup = () => {
5619
- if (timeout) clearTimeout(timeout);
5734
+ clearTimeout(timeoutId);
5620
5735
  navigator.serviceWorker.removeEventListener(
5621
5736
  "message",
5622
5737
  messageHandler
5623
5738
  );
5624
5739
  };
5740
+ const timeoutId = setTimeout(() => {
5741
+ cleanup();
5742
+ reject(
5743
+ new import_sdk10.ServiceWorkerTimeoutError(
5744
+ `Service worker message timed out (${request.type})`
5745
+ )
5746
+ );
5747
+ }, 3e4);
5625
5748
  const messageHandler = (event) => {
5626
5749
  const response = event.data;
5627
5750
  if (!response || response.tag !== this.messageTag || response.id !== request.id) {
@@ -5634,17 +5757,97 @@ var ServiceWorkerArkadeSwaps = class _ServiceWorkerArkadeSwaps {
5634
5757
  resolve(response);
5635
5758
  }
5636
5759
  };
5637
- timeout = setTimeout(() => {
5760
+ navigator.serviceWorker.addEventListener("message", messageHandler);
5761
+ this.serviceWorker.postMessage(request);
5762
+ });
5763
+ }
5764
+ async sendMessage(request) {
5765
+ if (!DEDUPABLE_REQUEST_TYPES.has(request.type)) {
5766
+ return this.sendMessageWithRetry(request);
5767
+ }
5768
+ const key = getRequestDedupKey(request);
5769
+ const existing = this.inflightRequests.get(key);
5770
+ if (existing) return existing;
5771
+ const promise = this.sendMessageWithRetry(request).finally(() => {
5772
+ this.inflightRequests.delete(key);
5773
+ });
5774
+ this.inflightRequests.set(key, promise);
5775
+ return promise;
5776
+ }
5777
+ pingServiceWorker() {
5778
+ if (this.pingPromise) return this.pingPromise;
5779
+ this.pingPromise = new Promise((resolve, reject) => {
5780
+ const pingId = getRandomId();
5781
+ const cleanup = () => {
5782
+ clearTimeout(timeoutId);
5783
+ navigator.serviceWorker.removeEventListener(
5784
+ "message",
5785
+ onMessage
5786
+ );
5787
+ };
5788
+ const timeoutId = setTimeout(() => {
5638
5789
  cleanup();
5639
5790
  reject(
5640
- new Error(
5641
- `Timed out waiting for service worker response: ${request.type}`
5791
+ new import_sdk10.ServiceWorkerTimeoutError(
5792
+ "Service worker ping timed out"
5642
5793
  )
5643
5794
  );
5644
- }, timeoutMs);
5645
- navigator.serviceWorker.addEventListener("message", messageHandler);
5646
- this.serviceWorker.postMessage(request);
5795
+ }, 2e3);
5796
+ const onMessage = (event) => {
5797
+ if (event.data?.id === pingId && event.data?.tag === "PONG") {
5798
+ cleanup();
5799
+ resolve();
5800
+ }
5801
+ };
5802
+ navigator.serviceWorker.addEventListener("message", onMessage);
5803
+ this.serviceWorker.postMessage({
5804
+ id: pingId,
5805
+ tag: "PING"
5806
+ });
5807
+ }).finally(() => {
5808
+ this.pingPromise = null;
5809
+ });
5810
+ return this.pingPromise;
5811
+ }
5812
+ // Send a message, retrying up to 2 times if the service worker was
5813
+ // killed and restarted by the OS (mobile browsers do this aggressively).
5814
+ async sendMessageWithRetry(request) {
5815
+ if (this.initPayload) {
5816
+ try {
5817
+ await this.pingServiceWorker();
5818
+ } catch {
5819
+ await this.reinitialize();
5820
+ }
5821
+ }
5822
+ const maxRetries = 2;
5823
+ for (let attempt = 0; ; attempt++) {
5824
+ try {
5825
+ return await this.sendMessageDirect(request);
5826
+ } catch (error) {
5827
+ if (!isMessageBusNotInitializedError(error) || attempt >= maxRetries) {
5828
+ throw error;
5829
+ }
5830
+ await this.reinitialize();
5831
+ }
5832
+ }
5833
+ }
5834
+ async reinitialize() {
5835
+ if (this.reinitPromise) return this.reinitPromise;
5836
+ this.reinitPromise = (async () => {
5837
+ if (!this.initPayload) {
5838
+ throw new Error("Cannot re-initialize: missing configuration");
5839
+ }
5840
+ const initMessage = {
5841
+ tag: this.messageTag,
5842
+ type: "INIT_ARKADE_SWAPS",
5843
+ id: getRandomId(),
5844
+ payload: this.initPayload
5845
+ };
5846
+ await this.sendMessageDirect(initMessage);
5847
+ })().finally(() => {
5848
+ this.reinitPromise = null;
5647
5849
  });
5850
+ return this.reinitPromise;
5648
5851
  }
5649
5852
  initEventStream() {
5650
5853
  if (this.eventListenerInitialized) return;
package/dist/index.d.cts CHANGED
@@ -1,7 +1,7 @@
1
- import { I as IArkadeSwaps } from './arkade-swaps-DnhnGtTb.cjs';
2
- export { A as ArkadeSwaps } from './arkade-swaps-DnhnGtTb.cjs';
3
- import { P as PendingSwap, D as DecodedInvoice, a as PendingChainSwap, b as PendingReverseSwap, c as PendingSubmarineSwap, A as ArkadeSwapsConfig, N as Network, C as CreateLightningInvoiceRequest, S as SendLightningPaymentRequest, F as FeesResponse, d as Chain, e as CreateLightningInvoiceResponse, f as SendLightningPaymentResponse, g as ChainFeesResponse, L as LimitsResponse, G as GetSwapStatusResponse, h as ArkToBtcResponse, B as BtcToArkResponse, i as SwapRepository, j as SwapManagerClient, k as GetSwapsFilter } from './types-DCCYGYCD.cjs';
4
- export { X as ArkadeSwapsCreateConfig, l as BoltzSwapProvider, m as BoltzSwapStatus, W as IncomingPaymentSubscription, V as SwapManager, $ as SwapManagerCallbacks, Z as SwapManagerConfig, _ as SwapManagerEvents, Y as Vtxo, n as isChainClaimableStatus, o as isChainFailedStatus, p as isChainFinalStatus, q as isChainPendingStatus, r as isChainRefundableStatus, s as isChainSignableStatus, t as isChainSuccessStatus, u as isChainSwapClaimable, v as isChainSwapRefundable, w as isPendingChainSwap, x as isPendingReverseSwap, y as isPendingSubmarineSwap, z as isReverseClaimableStatus, E as isReverseFailedStatus, H as isReverseFinalStatus, I as isReversePendingStatus, J as isReverseSuccessStatus, K as isReverseSwapClaimable, M as isSubmarineFailedStatus, O as isSubmarineFinalStatus, Q as isSubmarinePendingStatus, T as isSubmarineRefundableStatus, R as isSubmarineSuccessStatus, U as isSubmarineSwapRefundable } from './types-DCCYGYCD.cjs';
1
+ import { I as IArkadeSwaps } from './arkade-swaps--vc0H2oD.cjs';
2
+ export { A as ArkadeSwaps } from './arkade-swaps--vc0H2oD.cjs';
3
+ import { P as PendingSwap, D as DecodedInvoice, a as PendingChainSwap, b as PendingReverseSwap, c as PendingSubmarineSwap, A as ArkadeSwapsConfig, N as Network, C as CreateLightningInvoiceRequest, S as SendLightningPaymentRequest, F as FeesResponse, d as Chain, e as CreateLightningInvoiceResponse, f as SendLightningPaymentResponse, g as ChainFeesResponse, L as LimitsResponse, G as GetSwapStatusResponse, h as ArkToBtcResponse, B as BtcToArkResponse, i as SwapRepository, j as SwapManagerClient, k as GetSwapsFilter } from './types-CEmBBA71.cjs';
4
+ export { X as ArkadeSwapsCreateConfig, l as BoltzSwapProvider, m as BoltzSwapStatus, W as IncomingPaymentSubscription, V as SwapManager, $ as SwapManagerCallbacks, Z as SwapManagerConfig, _ as SwapManagerEvents, Y as Vtxo, n as isChainClaimableStatus, o as isChainFailedStatus, p as isChainFinalStatus, q as isChainPendingStatus, r as isChainRefundableStatus, s as isChainSignableStatus, t as isChainSuccessStatus, u as isChainSwapClaimable, v as isChainSwapRefundable, w as isPendingChainSwap, x as isPendingReverseSwap, y as isPendingSubmarineSwap, z as isReverseClaimableStatus, E as isReverseFailedStatus, H as isReverseFinalStatus, I as isReversePendingStatus, J as isReverseSuccessStatus, K as isReverseSwapClaimable, M as isSubmarineFailedStatus, O as isSubmarineFinalStatus, Q as isSubmarinePendingStatus, T as isSubmarineRefundableStatus, R as isSubmarineSuccessStatus, U as isSubmarineSwapRefundable } from './types-CEmBBA71.cjs';
5
5
  import { Transaction } from '@scure/btc-signer';
6
6
  import { MessageHandler, RequestEnvelope, ArkInfo, ResponseEnvelope, IWallet, IReadonlyWallet, VHTLC, Identity, ArkTxInput } from '@arkade-os/sdk';
7
7
  import { TransactionOutput } from '@scure/btc-signer/psbt.js';
@@ -562,6 +562,10 @@ declare class ServiceWorkerArkadeSwaps implements IArkadeSwaps {
562
562
  private actionExecutedListeners;
563
563
  private wsConnectedListeners;
564
564
  private wsDisconnectedListeners;
565
+ private initPayload;
566
+ private reinitPromise;
567
+ private pingPromise;
568
+ private inflightRequests;
565
569
  private constructor();
566
570
  static create(config: SvcWrkArkadeSwapsConfig): Promise<ServiceWorkerArkadeSwaps>;
567
571
  startSwapManager(): Promise<void>;
@@ -652,9 +656,20 @@ declare class ServiceWorkerArkadeSwaps implements IArkadeSwaps {
652
656
  getPendingChainSwaps(): Promise<PendingChainSwap[]>;
653
657
  getSwapHistory(): Promise<(PendingReverseSwap | PendingSubmarineSwap | PendingChainSwap)[]>;
654
658
  refreshSwapsStatus(): Promise<void>;
659
+ /**
660
+ * Reset all swap state: stops the SwapManager and clears the swap repository.
661
+ *
662
+ * **Destructive** — any swap in a non-terminal state will lose its
663
+ * refund/claim path. Intended for wallet-reset / dev / test scenarios only.
664
+ */
665
+ reset(): Promise<void>;
655
666
  dispose(): Promise<void>;
656
667
  [Symbol.asyncDispose](): Promise<void>;
668
+ private sendMessageDirect;
657
669
  private sendMessage;
670
+ private pingServiceWorker;
671
+ private sendMessageWithRetry;
672
+ private reinitialize;
658
673
  private initEventStream;
659
674
  private handleEventMessage;
660
675
  }
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
- import { I as IArkadeSwaps } from './arkade-swaps-MmMjdK2G.js';
2
- export { A as ArkadeSwaps } from './arkade-swaps-MmMjdK2G.js';
3
- import { P as PendingSwap, D as DecodedInvoice, a as PendingChainSwap, b as PendingReverseSwap, c as PendingSubmarineSwap, A as ArkadeSwapsConfig, N as Network, C as CreateLightningInvoiceRequest, S as SendLightningPaymentRequest, F as FeesResponse, d as Chain, e as CreateLightningInvoiceResponse, f as SendLightningPaymentResponse, g as ChainFeesResponse, L as LimitsResponse, G as GetSwapStatusResponse, h as ArkToBtcResponse, B as BtcToArkResponse, i as SwapRepository, j as SwapManagerClient, k as GetSwapsFilter } from './types-DCCYGYCD.js';
4
- export { X as ArkadeSwapsCreateConfig, l as BoltzSwapProvider, m as BoltzSwapStatus, W as IncomingPaymentSubscription, V as SwapManager, $ as SwapManagerCallbacks, Z as SwapManagerConfig, _ as SwapManagerEvents, Y as Vtxo, n as isChainClaimableStatus, o as isChainFailedStatus, p as isChainFinalStatus, q as isChainPendingStatus, r as isChainRefundableStatus, s as isChainSignableStatus, t as isChainSuccessStatus, u as isChainSwapClaimable, v as isChainSwapRefundable, w as isPendingChainSwap, x as isPendingReverseSwap, y as isPendingSubmarineSwap, z as isReverseClaimableStatus, E as isReverseFailedStatus, H as isReverseFinalStatus, I as isReversePendingStatus, J as isReverseSuccessStatus, K as isReverseSwapClaimable, M as isSubmarineFailedStatus, O as isSubmarineFinalStatus, Q as isSubmarinePendingStatus, T as isSubmarineRefundableStatus, R as isSubmarineSuccessStatus, U as isSubmarineSwapRefundable } from './types-DCCYGYCD.js';
1
+ import { I as IArkadeSwaps } from './arkade-swaps-qQuX73sR.js';
2
+ export { A as ArkadeSwaps } from './arkade-swaps-qQuX73sR.js';
3
+ import { P as PendingSwap, D as DecodedInvoice, a as PendingChainSwap, b as PendingReverseSwap, c as PendingSubmarineSwap, A as ArkadeSwapsConfig, N as Network, C as CreateLightningInvoiceRequest, S as SendLightningPaymentRequest, F as FeesResponse, d as Chain, e as CreateLightningInvoiceResponse, f as SendLightningPaymentResponse, g as ChainFeesResponse, L as LimitsResponse, G as GetSwapStatusResponse, h as ArkToBtcResponse, B as BtcToArkResponse, i as SwapRepository, j as SwapManagerClient, k as GetSwapsFilter } from './types-CEmBBA71.js';
4
+ export { X as ArkadeSwapsCreateConfig, l as BoltzSwapProvider, m as BoltzSwapStatus, W as IncomingPaymentSubscription, V as SwapManager, $ as SwapManagerCallbacks, Z as SwapManagerConfig, _ as SwapManagerEvents, Y as Vtxo, n as isChainClaimableStatus, o as isChainFailedStatus, p as isChainFinalStatus, q as isChainPendingStatus, r as isChainRefundableStatus, s as isChainSignableStatus, t as isChainSuccessStatus, u as isChainSwapClaimable, v as isChainSwapRefundable, w as isPendingChainSwap, x as isPendingReverseSwap, y as isPendingSubmarineSwap, z as isReverseClaimableStatus, E as isReverseFailedStatus, H as isReverseFinalStatus, I as isReversePendingStatus, J as isReverseSuccessStatus, K as isReverseSwapClaimable, M as isSubmarineFailedStatus, O as isSubmarineFinalStatus, Q as isSubmarinePendingStatus, T as isSubmarineRefundableStatus, R as isSubmarineSuccessStatus, U as isSubmarineSwapRefundable } from './types-CEmBBA71.js';
5
5
  import { Transaction } from '@scure/btc-signer';
6
6
  import { MessageHandler, RequestEnvelope, ArkInfo, ResponseEnvelope, IWallet, IReadonlyWallet, VHTLC, Identity, ArkTxInput } from '@arkade-os/sdk';
7
7
  import { TransactionOutput } from '@scure/btc-signer/psbt.js';
@@ -562,6 +562,10 @@ declare class ServiceWorkerArkadeSwaps implements IArkadeSwaps {
562
562
  private actionExecutedListeners;
563
563
  private wsConnectedListeners;
564
564
  private wsDisconnectedListeners;
565
+ private initPayload;
566
+ private reinitPromise;
567
+ private pingPromise;
568
+ private inflightRequests;
565
569
  private constructor();
566
570
  static create(config: SvcWrkArkadeSwapsConfig): Promise<ServiceWorkerArkadeSwaps>;
567
571
  startSwapManager(): Promise<void>;
@@ -652,9 +656,20 @@ declare class ServiceWorkerArkadeSwaps implements IArkadeSwaps {
652
656
  getPendingChainSwaps(): Promise<PendingChainSwap[]>;
653
657
  getSwapHistory(): Promise<(PendingReverseSwap | PendingSubmarineSwap | PendingChainSwap)[]>;
654
658
  refreshSwapsStatus(): Promise<void>;
659
+ /**
660
+ * Reset all swap state: stops the SwapManager and clears the swap repository.
661
+ *
662
+ * **Destructive** — any swap in a non-terminal state will lose its
663
+ * refund/claim path. Intended for wallet-reset / dev / test scenarios only.
664
+ */
665
+ reset(): Promise<void>;
655
666
  dispose(): Promise<void>;
656
667
  [Symbol.asyncDispose](): Promise<void>;
668
+ private sendMessageDirect;
657
669
  private sendMessage;
670
+ private pingServiceWorker;
671
+ private sendMessageWithRetry;
672
+ private reinitialize;
658
673
  private initEventStream;
659
674
  private handleEventMessage;
660
675
  }
package/dist/index.js CHANGED
@@ -49,7 +49,7 @@ import {
49
49
  updateReverseSwapStatus,
50
50
  updateSubmarineSwapStatus,
51
51
  verifySignatures
52
- } from "./chunk-ZJ2Q5QSA.js";
52
+ } from "./chunk-NUR5GPRK.js";
53
53
  import "./chunk-3RG5ZIWI.js";
54
54
 
55
55
  // src/serviceWorker/arkade-swaps-message-handler.ts
@@ -526,6 +526,31 @@ var ArkadeSwapsMessageHandler = class _ArkadeSwapsMessageHandler {
526
526
  };
527
527
 
528
528
  // src/serviceWorker/arkade-swaps-runtime.ts
529
+ import {
530
+ MESSAGE_BUS_NOT_INITIALIZED,
531
+ ServiceWorkerTimeoutError
532
+ } from "@arkade-os/sdk";
533
+ function isMessageBusNotInitializedError(error) {
534
+ return error instanceof Error && error.message.includes(MESSAGE_BUS_NOT_INITIALIZED);
535
+ }
536
+ var DEDUPABLE_REQUEST_TYPES = /* @__PURE__ */ new Set([
537
+ "GET_FEES",
538
+ "GET_LIMITS",
539
+ "GET_SWAP_STATUS",
540
+ "GET_PENDING_SUBMARINE_SWAPS",
541
+ "GET_PENDING_REVERSE_SWAPS",
542
+ "GET_PENDING_CHAIN_SWAPS",
543
+ "GET_SWAP_HISTORY",
544
+ "QUOTE_SWAP",
545
+ "SM-GET_PENDING_SWAPS",
546
+ "SM-HAS_SWAP",
547
+ "SM-IS_PROCESSING",
548
+ "SM-GET_STATS"
549
+ ]);
550
+ function getRequestDedupKey(request) {
551
+ const { id, tag, ...rest } = request;
552
+ return JSON.stringify(rest);
553
+ }
529
554
  var ServiceWorkerArkadeSwaps = class _ServiceWorkerArkadeSwaps {
530
555
  constructor(messageTag, serviceWorker, swapRepository, withSwapManager) {
531
556
  this.messageTag = messageTag;
@@ -540,6 +565,10 @@ var ServiceWorkerArkadeSwaps = class _ServiceWorkerArkadeSwaps {
540
565
  actionExecutedListeners = /* @__PURE__ */ new Set();
541
566
  wsConnectedListeners = /* @__PURE__ */ new Set();
542
567
  wsDisconnectedListeners = /* @__PURE__ */ new Set();
568
+ initPayload = null;
569
+ reinitPromise = null;
570
+ pingPromise = null;
571
+ inflightRequests = /* @__PURE__ */ new Map();
543
572
  static async create(config) {
544
573
  const messageTag = config.messageTag ?? DEFAULT_MESSAGE_TAG;
545
574
  const swapRepository = config.swapRepository ?? new IndexedDbSwapRepository();
@@ -549,18 +578,20 @@ var ServiceWorkerArkadeSwaps = class _ServiceWorkerArkadeSwaps {
549
578
  swapRepository,
550
579
  Boolean(config.swapManager)
551
580
  );
581
+ const initPayload = {
582
+ network: config.network,
583
+ arkServerUrl: config.arkServerUrl,
584
+ swapProvider: { baseUrl: config.swapProvider.getApiUrl() },
585
+ swapManager: config.swapManager
586
+ };
552
587
  const initMessage = {
553
588
  tag: messageTag,
554
589
  id: getRandomId(),
555
590
  type: "INIT_ARKADE_SWAPS",
556
- payload: {
557
- network: config.network,
558
- arkServerUrl: config.arkServerUrl,
559
- swapProvider: { baseUrl: config.swapProvider.getApiUrl() },
560
- swapManager: config.swapManager
561
- }
591
+ payload: initPayload
562
592
  };
563
593
  await svcArkadeSwaps.sendMessage(initMessage);
594
+ svcArkadeSwaps.initPayload = initPayload;
564
595
  return svcArkadeSwaps;
565
596
  }
566
597
  async startSwapManager() {
@@ -1092,6 +1123,16 @@ var ServiceWorkerArkadeSwaps = class _ServiceWorkerArkadeSwaps {
1092
1123
  type: "REFRESH_SWAPS_STATUS"
1093
1124
  });
1094
1125
  }
1126
+ /**
1127
+ * Reset all swap state: stops the SwapManager and clears the swap repository.
1128
+ *
1129
+ * **Destructive** — any swap in a non-terminal state will lose its
1130
+ * refund/claim path. Intended for wallet-reset / dev / test scenarios only.
1131
+ */
1132
+ async reset() {
1133
+ await this.dispose();
1134
+ await this.swapRepository.clear();
1135
+ }
1095
1136
  async dispose() {
1096
1137
  if (this.withSwapManager) {
1097
1138
  await this.stopSwapManager().catch(() => {
@@ -1101,17 +1142,23 @@ var ServiceWorkerArkadeSwaps = class _ServiceWorkerArkadeSwaps {
1101
1142
  async [Symbol.asyncDispose]() {
1102
1143
  return this.dispose();
1103
1144
  }
1104
- async sendMessage(request) {
1145
+ sendMessageDirect(request) {
1105
1146
  return new Promise((resolve, reject) => {
1106
- const timeoutMs = 3e4;
1107
- let timeout;
1108
1147
  const cleanup = () => {
1109
- if (timeout) clearTimeout(timeout);
1148
+ clearTimeout(timeoutId);
1110
1149
  navigator.serviceWorker.removeEventListener(
1111
1150
  "message",
1112
1151
  messageHandler
1113
1152
  );
1114
1153
  };
1154
+ const timeoutId = setTimeout(() => {
1155
+ cleanup();
1156
+ reject(
1157
+ new ServiceWorkerTimeoutError(
1158
+ `Service worker message timed out (${request.type})`
1159
+ )
1160
+ );
1161
+ }, 3e4);
1115
1162
  const messageHandler = (event) => {
1116
1163
  const response = event.data;
1117
1164
  if (!response || response.tag !== this.messageTag || response.id !== request.id) {
@@ -1124,17 +1171,97 @@ var ServiceWorkerArkadeSwaps = class _ServiceWorkerArkadeSwaps {
1124
1171
  resolve(response);
1125
1172
  }
1126
1173
  };
1127
- timeout = setTimeout(() => {
1174
+ navigator.serviceWorker.addEventListener("message", messageHandler);
1175
+ this.serviceWorker.postMessage(request);
1176
+ });
1177
+ }
1178
+ async sendMessage(request) {
1179
+ if (!DEDUPABLE_REQUEST_TYPES.has(request.type)) {
1180
+ return this.sendMessageWithRetry(request);
1181
+ }
1182
+ const key = getRequestDedupKey(request);
1183
+ const existing = this.inflightRequests.get(key);
1184
+ if (existing) return existing;
1185
+ const promise = this.sendMessageWithRetry(request).finally(() => {
1186
+ this.inflightRequests.delete(key);
1187
+ });
1188
+ this.inflightRequests.set(key, promise);
1189
+ return promise;
1190
+ }
1191
+ pingServiceWorker() {
1192
+ if (this.pingPromise) return this.pingPromise;
1193
+ this.pingPromise = new Promise((resolve, reject) => {
1194
+ const pingId = getRandomId();
1195
+ const cleanup = () => {
1196
+ clearTimeout(timeoutId);
1197
+ navigator.serviceWorker.removeEventListener(
1198
+ "message",
1199
+ onMessage
1200
+ );
1201
+ };
1202
+ const timeoutId = setTimeout(() => {
1128
1203
  cleanup();
1129
1204
  reject(
1130
- new Error(
1131
- `Timed out waiting for service worker response: ${request.type}`
1205
+ new ServiceWorkerTimeoutError(
1206
+ "Service worker ping timed out"
1132
1207
  )
1133
1208
  );
1134
- }, timeoutMs);
1135
- navigator.serviceWorker.addEventListener("message", messageHandler);
1136
- this.serviceWorker.postMessage(request);
1209
+ }, 2e3);
1210
+ const onMessage = (event) => {
1211
+ if (event.data?.id === pingId && event.data?.tag === "PONG") {
1212
+ cleanup();
1213
+ resolve();
1214
+ }
1215
+ };
1216
+ navigator.serviceWorker.addEventListener("message", onMessage);
1217
+ this.serviceWorker.postMessage({
1218
+ id: pingId,
1219
+ tag: "PING"
1220
+ });
1221
+ }).finally(() => {
1222
+ this.pingPromise = null;
1223
+ });
1224
+ return this.pingPromise;
1225
+ }
1226
+ // Send a message, retrying up to 2 times if the service worker was
1227
+ // killed and restarted by the OS (mobile browsers do this aggressively).
1228
+ async sendMessageWithRetry(request) {
1229
+ if (this.initPayload) {
1230
+ try {
1231
+ await this.pingServiceWorker();
1232
+ } catch {
1233
+ await this.reinitialize();
1234
+ }
1235
+ }
1236
+ const maxRetries = 2;
1237
+ for (let attempt = 0; ; attempt++) {
1238
+ try {
1239
+ return await this.sendMessageDirect(request);
1240
+ } catch (error) {
1241
+ if (!isMessageBusNotInitializedError(error) || attempt >= maxRetries) {
1242
+ throw error;
1243
+ }
1244
+ await this.reinitialize();
1245
+ }
1246
+ }
1247
+ }
1248
+ async reinitialize() {
1249
+ if (this.reinitPromise) return this.reinitPromise;
1250
+ this.reinitPromise = (async () => {
1251
+ if (!this.initPayload) {
1252
+ throw new Error("Cannot re-initialize: missing configuration");
1253
+ }
1254
+ const initMessage = {
1255
+ tag: this.messageTag,
1256
+ type: "INIT_ARKADE_SWAPS",
1257
+ id: getRandomId(),
1258
+ payload: this.initPayload
1259
+ };
1260
+ await this.sendMessageDirect(initMessage);
1261
+ })().finally(() => {
1262
+ this.reinitPromise = null;
1137
1263
  });
1264
+ return this.reinitPromise;
1138
1265
  }
1139
1266
  initEventStream() {
1140
1267
  if (this.eventListenerInitialized) return;
@@ -1,4 +1,4 @@
1
- import { i as SwapRepository, P as PendingSwap, k as GetSwapsFilter } from '../../types-DCCYGYCD.cjs';
1
+ import { i as SwapRepository, P as PendingSwap, k as GetSwapsFilter } from '../../types-CEmBBA71.cjs';
2
2
  import '@arkade-os/sdk';
3
3
 
4
4
  /**
@@ -1,4 +1,4 @@
1
- import { i as SwapRepository, P as PendingSwap, k as GetSwapsFilter } from '../../types-DCCYGYCD.js';
1
+ import { i as SwapRepository, P as PendingSwap, k as GetSwapsFilter } from '../../types-CEmBBA71.js';
2
2
  import '@arkade-os/sdk';
3
3
 
4
4
  /**
@@ -1,5 +1,5 @@
1
1
  import { SQLExecutor } from '@arkade-os/sdk/repositories/sqlite';
2
- import { i as SwapRepository, P as PendingSwap, k as GetSwapsFilter } from '../../types-DCCYGYCD.cjs';
2
+ import { i as SwapRepository, P as PendingSwap, k as GetSwapsFilter } from '../../types-CEmBBA71.cjs';
3
3
  import '@arkade-os/sdk';
4
4
 
5
5
  /**
@@ -1,5 +1,5 @@
1
1
  import { SQLExecutor } from '@arkade-os/sdk/repositories/sqlite';
2
- import { i as SwapRepository, P as PendingSwap, k as GetSwapsFilter } from '../../types-DCCYGYCD.js';
2
+ import { i as SwapRepository, P as PendingSwap, k as GetSwapsFilter } from '../../types-CEmBBA71.js';
3
3
  import '@arkade-os/sdk';
4
4
 
5
5
  /**
@@ -489,6 +489,8 @@ declare class SwapManager implements SwapManagerClient {
489
489
  private initialSwaps;
490
490
  private pollTimer;
491
491
  private reconnectTimer;
492
+ private initialPollTimer;
493
+ private pollRetryTimers;
492
494
  private isRunning;
493
495
  private currentReconnectDelay;
494
496
  private currentPollRetryDelay;
@@ -489,6 +489,8 @@ declare class SwapManager implements SwapManagerClient {
489
489
  private initialSwaps;
490
490
  private pollTimer;
491
491
  private reconnectTimer;
492
+ private initialPollTimer;
493
+ private pollRetryTimers;
492
494
  private isRunning;
493
495
  private currentReconnectDelay;
494
496
  private currentPollRetryDelay;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arkade-os/boltz-swap",
3
- "version": "0.3.7",
3
+ "version": "0.3.9",
4
4
  "type": "module",
5
5
  "description": "A production-ready TypeScript package that brings Boltz submarine-swaps to Arkade.",
6
6
  "main": "./dist/index.js",
@@ -60,7 +60,7 @@
60
60
  "author": "Arkade-OS",
61
61
  "license": "MIT",
62
62
  "dependencies": {
63
- "@arkade-os/sdk": "0.4.9",
63
+ "@arkade-os/sdk": "0.4.10",
64
64
  "@noble/hashes": "2.0.1",
65
65
  "@scure/base": "2.0.0",
66
66
  "@scure/btc-signer": "2.0.1",