@arkade-os/boltz-swap 0.3.22 → 0.3.24

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/README.md CHANGED
@@ -191,6 +191,28 @@ const unsubscribe = manager.subscribeToSwapUpdates(
191
191
  );
192
192
  ```
193
193
 
194
+ ### Submarine Fund Recovery
195
+
196
+ If a Lightning payment fails and funds get stranded in a VHTLC, you can recover them manually:
197
+
198
+ ```typescript
199
+ // Scan all local swaps for recoverable funds
200
+ const candidates = await swaps.scanRecoverableSubmarineSwaps();
201
+ // candidates[i].status: "recoverable" | "pre_cltv" | "none" | "already_spent" | "invalid_swap"
202
+
203
+ // Recover all at once
204
+ const results = await swaps.recoverAllSubmarineFunds(candidates.map(c => c.swap));
205
+
206
+ // Or inspect / recover a single swap
207
+ const info = await swaps.inspectSubmarineRecovery(swap);
208
+ if (info.status === 'recoverable') {
209
+ await swaps.recoverSubmarineFunds(swap);
210
+ }
211
+ ```
212
+
213
+ > [!NOTE]
214
+ > This only scans swaps stored in your local repository. It does not discover swaps that exist on Boltz but are missing locally.
215
+
194
216
  ### Cleanup
195
217
 
196
218
  ```typescript
@@ -1,7 +1,25 @@
1
1
  import { IWallet, ArkProvider, IndexerProvider, ArkInfo, Identity, ArkTxInput, VHTLC } from '@arkade-os/sdk';
2
- import { m as BoltzSwapProvider, V as SwapManager, j as SwapRepository, X as ArkadeSwapsCreateConfig, A as ArkadeSwapsConfig, k as SwapManagerClient, C as CreateLightningInvoiceRequest, e as CreateLightningInvoiceResponse, b as BoltzReverseSwap, S as SendLightningPaymentRequest, f as SendLightningPaymentResponse, c as BoltzSubmarineSwap, h as ArkToBtcResponse, a as BoltzChainSwap, i as BtcToArkResponse, d as Chain, F as FeesResponse, g as ChainFeesResponse, L as LimitsResponse, G as GetSwapStatusResponse, B as BoltzSwap } from './types-OyAdK824.js';
2
+ import { p as BoltzSwapProvider, Y as SwapManager, m as SwapRepository, _ as ArkadeSwapsCreateConfig, A as ArkadeSwapsConfig, n as SwapManagerClient, C as CreateLightningInvoiceRequest, e as CreateLightningInvoiceResponse, b as BoltzReverseSwap, S as SendLightningPaymentRequest, f as SendLightningPaymentResponse, c as BoltzSubmarineSwap, g as SubmarineRefundOutcome, h as SubmarineRecoveryInfo, i as SubmarineRecoveryResult, k as ArkToBtcResponse, a as BoltzChainSwap, l as BtcToArkResponse, d as Chain, F as FeesResponse, j as ChainFeesResponse, L as LimitsResponse, G as GetSwapStatusResponse, B as BoltzSwap } from './types-LCXS1AVA.cjs';
3
3
  import { TransactionOutput } from '@scure/btc-signer/psbt.js';
4
4
 
5
+ /**
6
+ * Boltz-Ark VHTLC timeouts. The shape matches Boltz's `timeoutBlockHeights`
7
+ * API field, but the legacy name is misleading — these are not all block
8
+ * heights:
9
+ * - `refund` is an absolute Unix timestamp in seconds (CLTV with timestamp
10
+ * semantics, BIP65 threshold ≥ 500_000_000).
11
+ * - `unilateralClaim`, `unilateralRefund`, `unilateralRefundWithoutReceiver`
12
+ * are BIP68 *relative* delays measured from the lockup confirmation. Values
13
+ * ≥ 512 are interpreted as seconds (BIP68 type-flag). For Boltz Ark mainnet
14
+ * they're always seconds.
15
+ */
16
+ type VhtlcTimeouts = {
17
+ refund: number;
18
+ unilateralClaim: number;
19
+ unilateralRefund: number;
20
+ unilateralRefundWithoutReceiver: number;
21
+ };
22
+
5
23
  /**
6
24
  * Unified entry point for Lightning and chain swaps between Arkade, Lightning Network, and Bitcoin.
7
25
  *
@@ -129,13 +147,86 @@ declare class ArkadeSwaps {
129
147
  * @throws {SwapError} If invoice is missing or key retrieval fails.
130
148
  */
131
149
  createSubmarineSwap(args: SendLightningPaymentRequest): Promise<BoltzSubmarineSwap>;
150
+ /**
151
+ * Reconstruct a submarine swap's VHTLC script from stored data. This does
152
+ * not query the indexer, so bulk scans can build every script first and
153
+ * then use batched VTXO lookups.
154
+ *
155
+ * @throws {Error} If preimage hash is unavailable, the swap response is
156
+ * incomplete, the script can't be built, or the reconstructed address
157
+ * doesn't match the one Boltz returned.
158
+ */
159
+ private buildSubmarineVHTLCContext;
160
+ /**
161
+ * Reconstruct a submarine swap's VHTLC script from stored data and look
162
+ * up its VTXOs at the indexer. Side-effect free; shared by `refundVHTLC`
163
+ * (spending path) and `inspectSubmarineRecovery` (diagnostic path).
164
+ *
165
+ * `refundableVtxos` merges spendable + recoverable indexer queries
166
+ * (deduped by outpoint). When that set is empty, a third query
167
+ * populates `diagnostic` so callers can distinguish "never funded",
168
+ * "already spent", and "preconfirmed-only".
169
+ */
170
+ private lookupSubmarineVHTLC;
171
+ private submarineRecoveryInfoFromLookup;
132
172
  /**
133
173
  * Refunds the VHTLC for a failed submarine swap, returning locked funds to the wallet.
134
174
  * Uses multi-party signatures (user + Boltz + server) for non-recoverable VTXOs.
135
175
  * @param pendingSwap - The submarine swap to refund.
176
+ * @returns Counts of VTXOs swept vs. deferred. A return value of `{ swept: 0, skipped: N }`
177
+ * means the call was a no-op — callers should not treat it as a successful refund.
136
178
  * @throws {Error} If preimage hash is unavailable, VHTLC not found, or already spent.
137
179
  */
138
- refundVHTLC(pendingSwap: BoltzSubmarineSwap): Promise<void>;
180
+ refundVHTLC(pendingSwap: BoltzSubmarineSwap, cachedArkInfo?: ArkInfo): Promise<SubmarineRefundOutcome>;
181
+ /**
182
+ * Inspect a submarine swap's lockup address for recoverable funds.
183
+ *
184
+ * Side-effect free. Returns a structured snapshot the UI can use to
185
+ * decide whether to offer the user a recovery action — it will not
186
+ * trigger any signing or persistence.
187
+ *
188
+ * Only `transaction.claimed` (success with possible stranded extras)
189
+ * and refundable failure statuses are recovery candidates. Pending
190
+ * statuses (`invoice.set`, `transaction.mempool`, …) are returned as
191
+ * `invalid_swap`; this API is for recovery, not a generic VTXO probe.
192
+ *
193
+ * @param swap - The submarine swap to inspect.
194
+ */
195
+ inspectSubmarineRecovery(swap: BoltzSubmarineSwap): Promise<SubmarineRecoveryInfo>;
196
+ /**
197
+ * Scan all locally-known submarine swaps for recoverable VHTLC funds.
198
+ *
199
+ * Loads submarine swaps from the repository, filters to recovery
200
+ * candidates (`transaction.claimed` plus refundable failure
201
+ * statuses), reconstructs their scripts, and performs one batched
202
+ * spendable query plus one batched recoverable query. Pending swaps are
203
+ * skipped entirely — they appear in the local repository but cannot
204
+ * be in a recovery state yet.
205
+ *
206
+ * Side-effect free: does not mutate the repository, does not sign,
207
+ * and does not query Boltz swap status.
208
+ */
209
+ scanRecoverableSubmarineSwaps(): Promise<SubmarineRecoveryInfo[]>;
210
+ /**
211
+ * Recover funds locked at a single submarine swap's VHTLC address.
212
+ *
213
+ * Thin wrapper around `refundVHTLC` for callers that have already
214
+ * confirmed (e.g. via `inspectSubmarineRecovery`) that funds are
215
+ * present. Centralises the spending logic in one place — flag-write
216
+ * behavior matches `refundVHTLC` (no-op for `transaction.claimed`,
217
+ * normal flag updates for failure statuses).
218
+ */
219
+ recoverSubmarineFunds(swap: BoltzSubmarineSwap, arkInfo?: ArkInfo): Promise<SubmarineRefundOutcome>;
220
+ /**
221
+ * Recover funds for a batch of submarine swaps.
222
+ *
223
+ * Each swap's recovery is independent — a failure on one swap does
224
+ * not abort the rest, and the caller receives a per-swap result so
225
+ * they can present partial outcomes in the UI. Recovery runs
226
+ * sequentially to avoid hammering Boltz / the indexer with parallel
227
+ * batch joins.
228
+ */
229
+ recoverAllSubmarineFunds(swaps: BoltzSubmarineSwap[]): Promise<SubmarineRecoveryResult[]>;
139
230
  /**
140
231
  * Waits for a submarine swap's Lightning payment to settle.
141
232
  * @param pendingSwap - The submarine swap to monitor.
@@ -271,12 +362,7 @@ declare class ArkadeSwaps {
271
362
  receiverPubkey: string;
272
363
  senderPubkey: string;
273
364
  serverPubkey: string;
274
- timeoutBlockHeights: {
275
- refund: number;
276
- unilateralClaim: number;
277
- unilateralRefund: number;
278
- unilateralRefundWithoutReceiver: number;
279
- };
365
+ timeoutBlockHeights: VhtlcTimeouts;
280
366
  }): {
281
367
  vhtlcScript: VHTLC.Script;
282
368
  vhtlcAddress: string;
@@ -353,7 +439,11 @@ interface IArkadeSwaps extends AsyncDisposable {
353
439
  createSubmarineSwap(args: SendLightningPaymentRequest): Promise<BoltzSubmarineSwap>;
354
440
  createReverseSwap(args: CreateLightningInvoiceRequest): Promise<BoltzReverseSwap>;
355
441
  claimVHTLC(pendingSwap: BoltzReverseSwap): Promise<void>;
356
- refundVHTLC(pendingSwap: BoltzSubmarineSwap): Promise<void>;
442
+ refundVHTLC(pendingSwap: BoltzSubmarineSwap): Promise<SubmarineRefundOutcome>;
443
+ inspectSubmarineRecovery(swap: BoltzSubmarineSwap): Promise<SubmarineRecoveryInfo>;
444
+ scanRecoverableSubmarineSwaps(): Promise<SubmarineRecoveryInfo[]>;
445
+ recoverSubmarineFunds(swap: BoltzSubmarineSwap): Promise<SubmarineRefundOutcome>;
446
+ recoverAllSubmarineFunds(swaps: BoltzSubmarineSwap[]): Promise<SubmarineRecoveryResult[]>;
357
447
  waitAndClaim(pendingSwap: BoltzReverseSwap): Promise<{
358
448
  txid: string;
359
449
  }>;
@@ -411,12 +501,7 @@ interface IArkadeSwaps extends AsyncDisposable {
411
501
  receiverPubkey: string;
412
502
  senderPubkey: string;
413
503
  serverPubkey: string;
414
- timeoutBlockHeights: {
415
- refund: number;
416
- unilateralClaim: number;
417
- unilateralRefund: number;
418
- unilateralRefundWithoutReceiver: number;
419
- };
504
+ timeoutBlockHeights: VhtlcTimeouts;
420
505
  }): {
421
506
  vhtlcScript: VHTLC.Script;
422
507
  vhtlcAddress: string;
@@ -443,4 +528,4 @@ interface IArkadeSwaps extends AsyncDisposable {
443
528
  dispose(): Promise<void>;
444
529
  }
445
530
 
446
- export { ArkadeSwaps as A, type IArkadeSwaps as I };
531
+ export { ArkadeSwaps as A, type IArkadeSwaps as I, type VhtlcTimeouts as V };
@@ -1,7 +1,25 @@
1
1
  import { IWallet, ArkProvider, IndexerProvider, ArkInfo, Identity, ArkTxInput, VHTLC } from '@arkade-os/sdk';
2
- import { m as BoltzSwapProvider, V as SwapManager, j as SwapRepository, X as ArkadeSwapsCreateConfig, A as ArkadeSwapsConfig, k as SwapManagerClient, C as CreateLightningInvoiceRequest, e as CreateLightningInvoiceResponse, b as BoltzReverseSwap, S as SendLightningPaymentRequest, f as SendLightningPaymentResponse, c as BoltzSubmarineSwap, h as ArkToBtcResponse, a as BoltzChainSwap, i as BtcToArkResponse, d as Chain, F as FeesResponse, g as ChainFeesResponse, L as LimitsResponse, G as GetSwapStatusResponse, B as BoltzSwap } from './types-OyAdK824.cjs';
2
+ import { p as BoltzSwapProvider, Y as SwapManager, m as SwapRepository, _ as ArkadeSwapsCreateConfig, A as ArkadeSwapsConfig, n as SwapManagerClient, C as CreateLightningInvoiceRequest, e as CreateLightningInvoiceResponse, b as BoltzReverseSwap, S as SendLightningPaymentRequest, f as SendLightningPaymentResponse, c as BoltzSubmarineSwap, g as SubmarineRefundOutcome, h as SubmarineRecoveryInfo, i as SubmarineRecoveryResult, k as ArkToBtcResponse, a as BoltzChainSwap, l as BtcToArkResponse, d as Chain, F as FeesResponse, j as ChainFeesResponse, L as LimitsResponse, G as GetSwapStatusResponse, B as BoltzSwap } from './types-LCXS1AVA.js';
3
3
  import { TransactionOutput } from '@scure/btc-signer/psbt.js';
4
4
 
5
+ /**
6
+ * Boltz-Ark VHTLC timeouts. The shape matches Boltz's `timeoutBlockHeights`
7
+ * API field, but the legacy name is misleading — these are not all block
8
+ * heights:
9
+ * - `refund` is an absolute Unix timestamp in seconds (CLTV with timestamp
10
+ * semantics, BIP65 threshold ≥ 500_000_000).
11
+ * - `unilateralClaim`, `unilateralRefund`, `unilateralRefundWithoutReceiver`
12
+ * are BIP68 *relative* delays measured from the lockup confirmation. Values
13
+ * ≥ 512 are interpreted as seconds (BIP68 type-flag). For Boltz Ark mainnet
14
+ * they're always seconds.
15
+ */
16
+ type VhtlcTimeouts = {
17
+ refund: number;
18
+ unilateralClaim: number;
19
+ unilateralRefund: number;
20
+ unilateralRefundWithoutReceiver: number;
21
+ };
22
+
5
23
  /**
6
24
  * Unified entry point for Lightning and chain swaps between Arkade, Lightning Network, and Bitcoin.
7
25
  *
@@ -129,13 +147,86 @@ declare class ArkadeSwaps {
129
147
  * @throws {SwapError} If invoice is missing or key retrieval fails.
130
148
  */
131
149
  createSubmarineSwap(args: SendLightningPaymentRequest): Promise<BoltzSubmarineSwap>;
150
+ /**
151
+ * Reconstruct a submarine swap's VHTLC script from stored data. This does
152
+ * not query the indexer, so bulk scans can build every script first and
153
+ * then use batched VTXO lookups.
154
+ *
155
+ * @throws {Error} If preimage hash is unavailable, the swap response is
156
+ * incomplete, the script can't be built, or the reconstructed address
157
+ * doesn't match the one Boltz returned.
158
+ */
159
+ private buildSubmarineVHTLCContext;
160
+ /**
161
+ * Reconstruct a submarine swap's VHTLC script from stored data and look
162
+ * up its VTXOs at the indexer. Side-effect free; shared by `refundVHTLC`
163
+ * (spending path) and `inspectSubmarineRecovery` (diagnostic path).
164
+ *
165
+ * `refundableVtxos` merges spendable + recoverable indexer queries
166
+ * (deduped by outpoint). When that set is empty, a third query
167
+ * populates `diagnostic` so callers can distinguish "never funded",
168
+ * "already spent", and "preconfirmed-only".
169
+ */
170
+ private lookupSubmarineVHTLC;
171
+ private submarineRecoveryInfoFromLookup;
132
172
  /**
133
173
  * Refunds the VHTLC for a failed submarine swap, returning locked funds to the wallet.
134
174
  * Uses multi-party signatures (user + Boltz + server) for non-recoverable VTXOs.
135
175
  * @param pendingSwap - The submarine swap to refund.
176
+ * @returns Counts of VTXOs swept vs. deferred. A return value of `{ swept: 0, skipped: N }`
177
+ * means the call was a no-op — callers should not treat it as a successful refund.
136
178
  * @throws {Error} If preimage hash is unavailable, VHTLC not found, or already spent.
137
179
  */
138
- refundVHTLC(pendingSwap: BoltzSubmarineSwap): Promise<void>;
180
+ refundVHTLC(pendingSwap: BoltzSubmarineSwap, cachedArkInfo?: ArkInfo): Promise<SubmarineRefundOutcome>;
181
+ /**
182
+ * Inspect a submarine swap's lockup address for recoverable funds.
183
+ *
184
+ * Side-effect free. Returns a structured snapshot the UI can use to
185
+ * decide whether to offer the user a recovery action — it will not
186
+ * trigger any signing or persistence.
187
+ *
188
+ * Only `transaction.claimed` (success with possible stranded extras)
189
+ * and refundable failure statuses are recovery candidates. Pending
190
+ * statuses (`invoice.set`, `transaction.mempool`, …) are returned as
191
+ * `invalid_swap`; this API is for recovery, not a generic VTXO probe.
192
+ *
193
+ * @param swap - The submarine swap to inspect.
194
+ */
195
+ inspectSubmarineRecovery(swap: BoltzSubmarineSwap): Promise<SubmarineRecoveryInfo>;
196
+ /**
197
+ * Scan all locally-known submarine swaps for recoverable VHTLC funds.
198
+ *
199
+ * Loads submarine swaps from the repository, filters to recovery
200
+ * candidates (`transaction.claimed` plus refundable failure
201
+ * statuses), reconstructs their scripts, and performs one batched
202
+ * spendable query plus one batched recoverable query. Pending swaps are
203
+ * skipped entirely — they appear in the local repository but cannot
204
+ * be in a recovery state yet.
205
+ *
206
+ * Side-effect free: does not mutate the repository, does not sign,
207
+ * and does not query Boltz swap status.
208
+ */
209
+ scanRecoverableSubmarineSwaps(): Promise<SubmarineRecoveryInfo[]>;
210
+ /**
211
+ * Recover funds locked at a single submarine swap's VHTLC address.
212
+ *
213
+ * Thin wrapper around `refundVHTLC` for callers that have already
214
+ * confirmed (e.g. via `inspectSubmarineRecovery`) that funds are
215
+ * present. Centralises the spending logic in one place — flag-write
216
+ * behavior matches `refundVHTLC` (no-op for `transaction.claimed`,
217
+ * normal flag updates for failure statuses).
218
+ */
219
+ recoverSubmarineFunds(swap: BoltzSubmarineSwap, arkInfo?: ArkInfo): Promise<SubmarineRefundOutcome>;
220
+ /**
221
+ * Recover funds for a batch of submarine swaps.
222
+ *
223
+ * Each swap's recovery is independent — a failure on one swap does
224
+ * not abort the rest, and the caller receives a per-swap result so
225
+ * they can present partial outcomes in the UI. Recovery runs
226
+ * sequentially to avoid hammering Boltz / the indexer with parallel
227
+ * batch joins.
228
+ */
229
+ recoverAllSubmarineFunds(swaps: BoltzSubmarineSwap[]): Promise<SubmarineRecoveryResult[]>;
139
230
  /**
140
231
  * Waits for a submarine swap's Lightning payment to settle.
141
232
  * @param pendingSwap - The submarine swap to monitor.
@@ -271,12 +362,7 @@ declare class ArkadeSwaps {
271
362
  receiverPubkey: string;
272
363
  senderPubkey: string;
273
364
  serverPubkey: string;
274
- timeoutBlockHeights: {
275
- refund: number;
276
- unilateralClaim: number;
277
- unilateralRefund: number;
278
- unilateralRefundWithoutReceiver: number;
279
- };
365
+ timeoutBlockHeights: VhtlcTimeouts;
280
366
  }): {
281
367
  vhtlcScript: VHTLC.Script;
282
368
  vhtlcAddress: string;
@@ -353,7 +439,11 @@ interface IArkadeSwaps extends AsyncDisposable {
353
439
  createSubmarineSwap(args: SendLightningPaymentRequest): Promise<BoltzSubmarineSwap>;
354
440
  createReverseSwap(args: CreateLightningInvoiceRequest): Promise<BoltzReverseSwap>;
355
441
  claimVHTLC(pendingSwap: BoltzReverseSwap): Promise<void>;
356
- refundVHTLC(pendingSwap: BoltzSubmarineSwap): Promise<void>;
442
+ refundVHTLC(pendingSwap: BoltzSubmarineSwap): Promise<SubmarineRefundOutcome>;
443
+ inspectSubmarineRecovery(swap: BoltzSubmarineSwap): Promise<SubmarineRecoveryInfo>;
444
+ scanRecoverableSubmarineSwaps(): Promise<SubmarineRecoveryInfo[]>;
445
+ recoverSubmarineFunds(swap: BoltzSubmarineSwap): Promise<SubmarineRefundOutcome>;
446
+ recoverAllSubmarineFunds(swaps: BoltzSubmarineSwap[]): Promise<SubmarineRecoveryResult[]>;
357
447
  waitAndClaim(pendingSwap: BoltzReverseSwap): Promise<{
358
448
  txid: string;
359
449
  }>;
@@ -411,12 +501,7 @@ interface IArkadeSwaps extends AsyncDisposable {
411
501
  receiverPubkey: string;
412
502
  senderPubkey: string;
413
503
  serverPubkey: string;
414
- timeoutBlockHeights: {
415
- refund: number;
416
- unilateralClaim: number;
417
- unilateralRefund: number;
418
- unilateralRefundWithoutReceiver: number;
419
- };
504
+ timeoutBlockHeights: VhtlcTimeouts;
420
505
  }): {
421
506
  vhtlcScript: VHTLC.Script;
422
507
  vhtlcAddress: string;
@@ -443,4 +528,4 @@ interface IArkadeSwaps extends AsyncDisposable {
443
528
  dispose(): Promise<void>;
444
529
  }
445
530
 
446
- export { ArkadeSwaps as A, type IArkadeSwaps as I };
531
+ export { ArkadeSwaps as A, type IArkadeSwaps as I, type VhtlcTimeouts as V };
@@ -2,8 +2,8 @@ import {
2
2
  defineExpoSwapBackgroundTask,
3
3
  registerExpoSwapBackgroundTask,
4
4
  unregisterExpoSwapBackgroundTask
5
- } from "./chunk-EQK2BJQC.js";
6
- import "./chunk-K3QEFL7D.js";
5
+ } from "./chunk-FEXQELYZ.js";
6
+ import "./chunk-XC2ARJZO.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-K3QEFL7D.js";
11
+ } from "./chunk-XC2ARJZO.js";
12
12
  import {
13
13
  __require
14
14
  } from "./chunk-3RG5ZIWI.js";