@arkade-os/boltz-swap 0.3.34 → 0.3.36

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 { r as BoltzSwapProvider, x as SwapManager, n as SwapRepository, q as ArkadeSwapsCreateConfig, A as ArkadeSwapsConfig, o 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, m as ChainArkRefundOutcome, l as BtcToArkResponse, d as Chain, F as FeesResponse, j as ChainFeesResponse, L as LimitsResponse, G as GetSwapStatusResponse, B as BoltzSwap } from './types--axEWA8c.cjs';
2
+ import { r as BoltzSwapProvider, x as SwapManager, n as SwapRepository, q as ArkadeSwapsCreateConfig, A as ArkadeSwapsConfig, o 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, m as ChainArkRefundOutcome, l as BtcToArkResponse, d as Chain, F as FeesResponse, j as ChainFeesResponse, L as LimitsResponse, G as GetSwapStatusResponse, B as BoltzSwap } from './types-D97i1LFu.cjs';
3
3
  import { TransactionOutput } from '@scure/btc-signer/psbt.js';
4
4
 
5
5
  /**
@@ -264,8 +264,11 @@ declare class ArkadeSwaps {
264
264
  /**
265
265
  * Claim sats on BTC chain by claiming the HTLC.
266
266
  * @param pendingSwap - The pending chain swap with BTC transaction hex.
267
+ * @returns The BTC transaction ID of the claim.
267
268
  */
268
- claimBtc(pendingSwap: BoltzChainSwap): Promise<void>;
269
+ claimBtc(pendingSwap: BoltzChainSwap): Promise<{
270
+ txid: string;
271
+ }>;
269
272
  /**
270
273
  * When an ARK to BTC swap fails, refund every unspent VTXO at the chain
271
274
  * swap's ARK lockup address.
@@ -305,8 +308,22 @@ declare class ArkadeSwaps {
305
308
  * Claim sats on ARK chain by claiming the VHTLC.
306
309
  * Refactored to use claimVHTLCIdentity + claimVHTLCwithOffchainTx utilities.
307
310
  * @param pendingSwap - The pending chain swap.
311
+ * @returns The Ark transaction ID of the claim.
308
312
  */
309
- claimArk(pendingSwap: BoltzChainSwap): Promise<void>;
313
+ claimArk(pendingSwap: BoltzChainSwap): Promise<{
314
+ txid: string;
315
+ }>;
316
+ /**
317
+ * Resolve the on-chain txid for a chain swap at completion.
318
+ *
319
+ * The claim transaction we broadcast (claimBtc/claimArk) carries the real
320
+ * on-chain txid; getSwapStatus does not surface it at transaction.claimed.
321
+ * Prefer the claim's txid and fall back to the provider status only for
322
+ * resumed swaps where we never ran the claim ourselves (claimPromise is
323
+ * undefined). Returns undefined when no usable txid is available, so
324
+ * callers never resolve with the Boltz swap id in place of a real txid.
325
+ */
326
+ private resolveChainClaimTxid;
310
327
  /**
311
328
  * Sign a cooperative claim for the server in BTC => ARK swaps.
312
329
  * @param pendingSwap - The pending chain swap.
@@ -523,7 +540,9 @@ interface IArkadeSwaps extends AsyncDisposable {
523
540
  waitAndClaimBtc(pendingSwap: BoltzChainSwap): Promise<{
524
541
  txid: string;
525
542
  }>;
526
- claimBtc(pendingSwap: BoltzChainSwap): Promise<void>;
543
+ claimBtc(pendingSwap: BoltzChainSwap): Promise<{
544
+ txid: string;
545
+ }>;
527
546
  refundArk(pendingSwap: BoltzChainSwap): Promise<ChainArkRefundOutcome>;
528
547
  btcToArk(args: {
529
548
  feeSatsPerByte?: number;
@@ -533,7 +552,9 @@ interface IArkadeSwaps extends AsyncDisposable {
533
552
  waitAndClaimArk(pendingSwap: BoltzChainSwap): Promise<{
534
553
  txid: string;
535
554
  }>;
536
- claimArk(pendingSwap: BoltzChainSwap): Promise<void>;
555
+ claimArk(pendingSwap: BoltzChainSwap): Promise<{
556
+ txid: string;
557
+ }>;
537
558
  signCooperativeClaimForServer(pendingSwap: BoltzChainSwap): Promise<void>;
538
559
  waitAndClaimChain(pendingSwap: BoltzChainSwap): Promise<{
539
560
  txid: string;
@@ -1,5 +1,5 @@
1
1
  import { IWallet, ArkProvider, IndexerProvider, ArkInfo, Identity, ArkTxInput, VHTLC } from '@arkade-os/sdk';
2
- import { r as BoltzSwapProvider, x as SwapManager, n as SwapRepository, q as ArkadeSwapsCreateConfig, A as ArkadeSwapsConfig, o 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, m as ChainArkRefundOutcome, l as BtcToArkResponse, d as Chain, F as FeesResponse, j as ChainFeesResponse, L as LimitsResponse, G as GetSwapStatusResponse, B as BoltzSwap } from './types--axEWA8c.js';
2
+ import { r as BoltzSwapProvider, x as SwapManager, n as SwapRepository, q as ArkadeSwapsCreateConfig, A as ArkadeSwapsConfig, o 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, m as ChainArkRefundOutcome, l as BtcToArkResponse, d as Chain, F as FeesResponse, j as ChainFeesResponse, L as LimitsResponse, G as GetSwapStatusResponse, B as BoltzSwap } from './types-D97i1LFu.js';
3
3
  import { TransactionOutput } from '@scure/btc-signer/psbt.js';
4
4
 
5
5
  /**
@@ -264,8 +264,11 @@ declare class ArkadeSwaps {
264
264
  /**
265
265
  * Claim sats on BTC chain by claiming the HTLC.
266
266
  * @param pendingSwap - The pending chain swap with BTC transaction hex.
267
+ * @returns The BTC transaction ID of the claim.
267
268
  */
268
- claimBtc(pendingSwap: BoltzChainSwap): Promise<void>;
269
+ claimBtc(pendingSwap: BoltzChainSwap): Promise<{
270
+ txid: string;
271
+ }>;
269
272
  /**
270
273
  * When an ARK to BTC swap fails, refund every unspent VTXO at the chain
271
274
  * swap's ARK lockup address.
@@ -305,8 +308,22 @@ declare class ArkadeSwaps {
305
308
  * Claim sats on ARK chain by claiming the VHTLC.
306
309
  * Refactored to use claimVHTLCIdentity + claimVHTLCwithOffchainTx utilities.
307
310
  * @param pendingSwap - The pending chain swap.
311
+ * @returns The Ark transaction ID of the claim.
308
312
  */
309
- claimArk(pendingSwap: BoltzChainSwap): Promise<void>;
313
+ claimArk(pendingSwap: BoltzChainSwap): Promise<{
314
+ txid: string;
315
+ }>;
316
+ /**
317
+ * Resolve the on-chain txid for a chain swap at completion.
318
+ *
319
+ * The claim transaction we broadcast (claimBtc/claimArk) carries the real
320
+ * on-chain txid; getSwapStatus does not surface it at transaction.claimed.
321
+ * Prefer the claim's txid and fall back to the provider status only for
322
+ * resumed swaps where we never ran the claim ourselves (claimPromise is
323
+ * undefined). Returns undefined when no usable txid is available, so
324
+ * callers never resolve with the Boltz swap id in place of a real txid.
325
+ */
326
+ private resolveChainClaimTxid;
310
327
  /**
311
328
  * Sign a cooperative claim for the server in BTC => ARK swaps.
312
329
  * @param pendingSwap - The pending chain swap.
@@ -523,7 +540,9 @@ interface IArkadeSwaps extends AsyncDisposable {
523
540
  waitAndClaimBtc(pendingSwap: BoltzChainSwap): Promise<{
524
541
  txid: string;
525
542
  }>;
526
- claimBtc(pendingSwap: BoltzChainSwap): Promise<void>;
543
+ claimBtc(pendingSwap: BoltzChainSwap): Promise<{
544
+ txid: string;
545
+ }>;
527
546
  refundArk(pendingSwap: BoltzChainSwap): Promise<ChainArkRefundOutcome>;
528
547
  btcToArk(args: {
529
548
  feeSatsPerByte?: number;
@@ -533,7 +552,9 @@ interface IArkadeSwaps extends AsyncDisposable {
533
552
  waitAndClaimArk(pendingSwap: BoltzChainSwap): Promise<{
534
553
  txid: string;
535
554
  }>;
536
- claimArk(pendingSwap: BoltzChainSwap): Promise<void>;
555
+ claimArk(pendingSwap: BoltzChainSwap): Promise<{
556
+ txid: string;
557
+ }>;
537
558
  signCooperativeClaimForServer(pendingSwap: BoltzChainSwap): Promise<void>;
538
559
  waitAndClaimChain(pendingSwap: BoltzChainSwap): Promise<{
539
560
  txid: string;
@@ -1092,6 +1092,13 @@ var SwapManager = class _SwapManager {
1092
1092
  swapsInProgress = /* @__PURE__ */ new Set();
1093
1093
  // Per-swap subscriptions for UI hooks
1094
1094
  swapSubscriptions = /* @__PURE__ */ new Map();
1095
+ // In-flight (or settled) chain-swap claim promises, keyed by swap id. The
1096
+ // manager performs chain claims itself, so the claim tx it broadcasts is
1097
+ // the swap's on-chain completion; getSwapStatus does not surface that txid
1098
+ // at transaction.claimed. resolveClaimedTxid awaits the stored promise so a
1099
+ // transaction.claimed update that races an in-flight claim still resolves
1100
+ // the real txid instead of falling back to the provider and failing.
1101
+ chainClaimPromises = /* @__PURE__ */ new Map();
1095
1102
  // Action callbacks (injected via setCallbacks)
1096
1103
  claimCallback = null;
1097
1104
  refundCallback = null;
@@ -1233,6 +1240,7 @@ var SwapManager = class _SwapManager {
1233
1240
  }
1234
1241
  this.isRunning = true;
1235
1242
  this.initialSwaps.clear();
1243
+ this.chainClaimPromises.clear();
1236
1244
  for (const swap of pendingSwaps) {
1237
1245
  this.initialSwaps.set(swap.id, swap);
1238
1246
  }
@@ -1326,6 +1334,7 @@ var SwapManager = class _SwapManager {
1326
1334
  async removeSwap(swapId) {
1327
1335
  this.monitoredSwaps.delete(swapId);
1328
1336
  this.swapSubscriptions.delete(swapId);
1337
+ this.chainClaimPromises.delete(swapId);
1329
1338
  const retryTimer = this.pollRetryTimers.get(swapId);
1330
1339
  if (retryTimer) {
1331
1340
  clearTimeout(retryTimer);
@@ -1368,9 +1377,14 @@ var SwapManager = class _SwapManager {
1368
1377
  * Blocks until the swap reaches a final status or fails.
1369
1378
  * Useful when you want blocking behavior even with SwapManager enabled.
1370
1379
  *
1371
- * @throws If the swap is already in a final status (submarine/chain swaps throw immediately;
1372
- * reverse swaps return the existing txid).
1380
+ * If the swap is already in a final status, resolves immediately with the
1381
+ * on-chain txid of the successfully claimed swap (reverse: from
1382
+ * getReverseSwapTxId; submarine/chain: from getSwapStatus) and throws for a
1383
+ * failed final status.
1384
+ *
1385
+ * @throws If the swap is already in a failed final status.
1373
1386
  * @throws If the swap is not found in the manager.
1387
+ * @throws If a completed swap has no transaction id available yet.
1374
1388
  */
1375
1389
  async waitForSwapCompletion(swapId) {
1376
1390
  let swap = this.monitoredSwaps.get(swapId);
@@ -1385,10 +1399,12 @@ var SwapManager = class _SwapManager {
1385
1399
  const response = await this.swapProvider.getReverseSwapTxId(swap.id);
1386
1400
  return { txid: response.id };
1387
1401
  }
1388
- if (isPendingSubmarineSwap(swap)) {
1389
- throw new Error("Submarine swap already completed");
1402
+ if (isPendingSubmarineSwap(swap) || isPendingChainSwap(swap)) {
1403
+ if (swap.status === "transaction.claimed") {
1404
+ return this.resolveClaimedTxid(swap.id);
1405
+ }
1406
+ throw new Error(`Swap ${swap.id} already in final status: ${swap.status}`);
1390
1407
  }
1391
- throw new Error("Chain swap already completed");
1392
1408
  }
1393
1409
  return new Promise((resolve, reject) => {
1394
1410
  let unsubscribe = null;
@@ -1403,13 +1419,13 @@ var SwapManager = class _SwapManager {
1403
1419
  }
1404
1420
  } else if (isPendingSubmarineSwap(updatedSwap)) {
1405
1421
  if (updatedSwap.status === "transaction.claimed") {
1406
- resolve({ txid: updatedSwap.id });
1422
+ this.resolveClaimedTxid(updatedSwap.id).then(resolve).catch(reject);
1407
1423
  } else {
1408
1424
  reject(new Error(`Swap failed with status: ${updatedSwap.status}`));
1409
1425
  }
1410
1426
  } else if (isPendingChainSwap(updatedSwap)) {
1411
1427
  if (updatedSwap.status === "transaction.claimed") {
1412
- resolve({ txid: updatedSwap.id });
1428
+ this.resolveClaimedTxid(updatedSwap.id).then(resolve).catch(reject);
1413
1429
  } else {
1414
1430
  reject(new Error(`Swap failed with status: ${updatedSwap.status}`));
1415
1431
  }
@@ -1420,6 +1436,37 @@ var SwapManager = class _SwapManager {
1420
1436
  }).catch(reject);
1421
1437
  });
1422
1438
  }
1439
+ /**
1440
+ * Resolve the on-chain txid for a claimed submarine/chain swap.
1441
+ *
1442
+ * Chain swaps are claimed by the manager itself, so the claim txid captured
1443
+ * from the claimArk/claimBtc callback is the swap's on-chain completion and
1444
+ * is preferred — Boltz does not surface it via getSwapStatus at
1445
+ * transaction.claimed. Submarine swaps (claimed by Boltz) and chain swaps
1446
+ * we never claimed in this session (e.g. restored already-claimed) fall
1447
+ * back to the provider status. Rejects when no transaction id is available,
1448
+ * so callers never receive the Boltz swap id in place of a real txid.
1449
+ */
1450
+ async resolveClaimedTxid(swapId) {
1451
+ const claimPromise = this.chainClaimPromises.get(swapId);
1452
+ if (claimPromise) {
1453
+ const claimTxid = await claimPromise.then(
1454
+ (result) => result?.txid,
1455
+ () => void 0
1456
+ );
1457
+ if (claimTxid && claimTxid.trim() !== "") {
1458
+ return { txid: claimTxid };
1459
+ }
1460
+ }
1461
+ const status = await this.swapProvider.getSwapStatus(swapId);
1462
+ const txid = status.transaction?.id;
1463
+ if (!txid || txid.trim() === "") {
1464
+ throw new SwapError({
1465
+ message: `Transaction ID not available for completed swap ${swapId}`
1466
+ });
1467
+ }
1468
+ return { txid };
1469
+ }
1423
1470
  /**
1424
1471
  * Check if a swap is currently being processed
1425
1472
  * Useful for preventing race conditions
@@ -1623,6 +1670,7 @@ var SwapManager = class _SwapManager {
1623
1670
  if (!this.monitoredSwaps.has(swap.id)) return;
1624
1671
  this.monitoredSwaps.delete(swap.id);
1625
1672
  this.swapSubscriptions.delete(swap.id);
1673
+ this.chainClaimPromises.delete(swap.id);
1626
1674
  const retryTimer = this.pollRetryTimers.get(swap.id);
1627
1675
  if (retryTimer) {
1628
1676
  clearTimeout(retryTimer);
@@ -1793,7 +1841,9 @@ var SwapManager = class _SwapManager {
1793
1841
  logger.error("claimArk callback not set");
1794
1842
  return;
1795
1843
  }
1796
- await this.claimArkCallback(swap);
1844
+ const claimPromise = this.claimArkCallback(swap);
1845
+ this.rememberChainClaim(swap.id, claimPromise);
1846
+ await claimPromise;
1797
1847
  }
1798
1848
  /**
1799
1849
  * Execute claim action for chain swap Ark to Btc
@@ -1803,7 +1853,23 @@ var SwapManager = class _SwapManager {
1803
1853
  logger.error("claimBtc callback not set");
1804
1854
  return;
1805
1855
  }
1806
- await this.claimBtcCallback(swap);
1856
+ const claimPromise = this.claimBtcCallback(swap);
1857
+ this.rememberChainClaim(swap.id, claimPromise);
1858
+ await claimPromise;
1859
+ }
1860
+ /**
1861
+ * Store the in-flight claim promise returned by a claim callback so
1862
+ * {@link resolveClaimedTxid} can await it and surface the real on-chain
1863
+ * txid at transaction.claimed — even when that update races the still
1864
+ * in-flight claim. Storing the promise (not the resolved txid) closes the
1865
+ * window where the txid is not yet captured when transaction.claimed
1866
+ * arrives. Defensive against callbacks that don't return a promise (older
1867
+ * integrations, test doubles): those simply fall back to getSwapStatus.
1868
+ */
1869
+ rememberChainClaim(swapId, claimPromise) {
1870
+ if (claimPromise) {
1871
+ this.chainClaimPromises.set(swapId, claimPromise);
1872
+ }
1807
1873
  }
1808
1874
  /**
1809
1875
  * Execute refund action for chain swap Ark to Btc
@@ -2670,6 +2736,20 @@ function extractInvoiceAmount(amountSats, fees) {
2670
2736
  if (miner >= amountSats) return 0;
2671
2737
  return Math.ceil((amountSats - miner) / (1 - percentage / 100));
2672
2738
  }
2739
+ function resolveVhtlcTimeouts(tree, timeoutBlockHeights) {
2740
+ const resolved = timeoutBlockHeights ?? {
2741
+ refund: extractTimeLockFromLeafOutput(tree.refundWithoutBoltzLeaf?.output ?? ""),
2742
+ unilateralClaim: extractTimeLockFromLeafOutput(tree.unilateralClaimLeaf?.output ?? ""),
2743
+ unilateralRefund: extractTimeLockFromLeafOutput(tree.unilateralRefundLeaf?.output ?? ""),
2744
+ unilateralRefundWithoutReceiver: extractTimeLockFromLeafOutput(
2745
+ tree.unilateralRefundWithoutBoltzLeaf?.output ?? ""
2746
+ )
2747
+ };
2748
+ if (!resolved.refund || !resolved.unilateralClaim || !resolved.unilateralRefund || !resolved.unilateralRefundWithoutReceiver) {
2749
+ return void 0;
2750
+ }
2751
+ return resolved;
2752
+ }
2673
2753
 
2674
2754
  // src/utils/identity.ts
2675
2755
  import { ConditionWitness, setArkPsbtField, Transaction as Transaction3 } from "@arkade-os/sdk";
@@ -2989,6 +3069,7 @@ var claimVHTLCwithOffchainTx = async (identity, vhtlcScript, serverXOnlyPublicKe
2989
3069
  })
2990
3070
  );
2991
3071
  await arkProvider.finalizeTx(arkTxid, finalCheckpoints);
3072
+ return arkTxid;
2992
3073
  };
2993
3074
  var refundVHTLCwithOffchainTx = async (swapId, identity, arkProvider, boltzXOnlyPublicKey, ourXOnlyPublicKey, serverXOnlyPublicKey, input, output, arkInfo, refundFunc) => {
2994
3075
  const rawCheckpointTapscript = hex7.decode(arkInfo.checkpointTapscript);
@@ -3147,12 +3228,8 @@ var ArkadeSwaps = class _ArkadeSwaps {
3147
3228
  refund: async (swap) => {
3148
3229
  await this.refundVHTLC(swap);
3149
3230
  },
3150
- claimArk: async (swap) => {
3151
- await this.claimArk(swap);
3152
- },
3153
- claimBtc: async (swap) => {
3154
- await this.claimBtc(swap);
3155
- },
3231
+ claimArk: (swap) => this.claimArk(swap),
3232
+ claimBtc: (swap) => this.claimBtc(swap),
3156
3233
  refundArk: async (swap) => {
3157
3234
  return this.refundArk(swap);
3158
3235
  },
@@ -4163,6 +4240,7 @@ var ArkadeSwaps = class _ArkadeSwaps {
4163
4240
  }
4164
4241
  return new Promise((resolve, reject) => {
4165
4242
  let claimStarted = false;
4243
+ let claimPromise;
4166
4244
  const swap = { ...pendingSwap };
4167
4245
  const onStatusUpdate = async (status, data) => {
4168
4246
  const updateSwapStatus = async () => {
@@ -4180,16 +4258,24 @@ var ArkadeSwaps = class _ArkadeSwaps {
4180
4258
  const updatedSwap = await updateSwapStatus();
4181
4259
  if (claimStarted) return;
4182
4260
  claimStarted = true;
4183
- this.claimBtc(updatedSwap).catch(reject);
4261
+ claimPromise = this.claimBtc(updatedSwap);
4262
+ claimPromise.catch(reject);
4184
4263
  break;
4185
4264
  }
4186
- case "transaction.claimed":
4265
+ case "transaction.claimed": {
4187
4266
  await updateSwapStatus();
4188
- const claimedStatus = await this.getSwapStatus(pendingSwap.id);
4189
- resolve({
4190
- txid: claimedStatus.transaction?.id ?? ""
4191
- });
4267
+ const txid = await this.resolveChainClaimTxid(pendingSwap.id, claimPromise);
4268
+ if (!txid) {
4269
+ reject(
4270
+ new SwapError({
4271
+ message: `Transaction ID not available for claimed swap ${pendingSwap.id}.`
4272
+ })
4273
+ );
4274
+ break;
4275
+ }
4276
+ resolve({ txid });
4192
4277
  break;
4278
+ }
4193
4279
  case "transaction.lockupFailed":
4194
4280
  await updateSwapStatus();
4195
4281
  await this.quoteSwap(swap.response.id, quoteOptionsForSwap(swap)).catch(
@@ -4238,6 +4324,7 @@ var ArkadeSwaps = class _ArkadeSwaps {
4238
4324
  /**
4239
4325
  * Claim sats on BTC chain by claiming the HTLC.
4240
4326
  * @param pendingSwap - The pending chain swap with BTC transaction hex.
4327
+ * @returns The BTC transaction ID of the claim.
4241
4328
  */
4242
4329
  async claimBtc(pendingSwap) {
4243
4330
  if (!pendingSwap.toAddress)
@@ -4310,6 +4397,7 @@ var ArkadeSwaps = class _ArkadeSwaps {
4310
4397
  finalScriptWitness: [musigSigned.aggregatePartials()]
4311
4398
  });
4312
4399
  await this.swapProvider.postBtcTransaction(claimTx.hex);
4400
+ return { txid: claimTx.id };
4313
4401
  }
4314
4402
  /**
4315
4403
  * When an ARK to BTC swap fails, refund every unspent VTXO at the chain
@@ -4509,6 +4597,7 @@ var ArkadeSwaps = class _ArkadeSwaps {
4509
4597
  }
4510
4598
  return new Promise((resolve, reject) => {
4511
4599
  let claimStarted = false;
4600
+ let claimPromise;
4512
4601
  const swap = { ...pendingSwap };
4513
4602
  const onStatusUpdate = async (status, data) => {
4514
4603
  const updateSwapStatus = () => {
@@ -4521,15 +4610,23 @@ var ArkadeSwaps = class _ArkadeSwaps {
4521
4610
  await updateSwapStatus();
4522
4611
  if (claimStarted) return;
4523
4612
  claimStarted = true;
4524
- this.claimArk(swap).catch(reject);
4613
+ claimPromise = this.claimArk(swap);
4614
+ claimPromise.catch(reject);
4525
4615
  break;
4526
- case "transaction.claimed":
4616
+ case "transaction.claimed": {
4527
4617
  await updateSwapStatus();
4528
- const claimedStatus = await this.getSwapStatus(pendingSwap.id);
4529
- resolve({
4530
- txid: claimedStatus.transaction?.id ?? ""
4531
- });
4618
+ const txid = await this.resolveChainClaimTxid(pendingSwap.id, claimPromise);
4619
+ if (!txid) {
4620
+ reject(
4621
+ new SwapError({
4622
+ message: `Transaction ID not available for claimed swap ${pendingSwap.id}.`
4623
+ })
4624
+ );
4625
+ break;
4626
+ }
4627
+ resolve({ txid });
4532
4628
  break;
4629
+ }
4533
4630
  case "transaction.claim.pending":
4534
4631
  await updateSwapStatus();
4535
4632
  await this.signCooperativeClaimForServer(swap).catch((err) => {
@@ -4588,6 +4685,7 @@ var ArkadeSwaps = class _ArkadeSwaps {
4588
4685
  * Claim sats on ARK chain by claiming the VHTLC.
4589
4686
  * Refactored to use claimVHTLCIdentity + claimVHTLCwithOffchainTx utilities.
4590
4687
  * @param pendingSwap - The pending chain swap.
4688
+ * @returns The Ark transaction ID of the claim.
4591
4689
  */
4592
4690
  async claimArk(pendingSwap) {
4593
4691
  if (!pendingSwap.toAddress)
@@ -4650,24 +4748,42 @@ var ArkadeSwaps = class _ArkadeSwaps {
4650
4748
  script: ArkAddress2.decode(address).pkScript
4651
4749
  };
4652
4750
  const vhtlcIdentity = claimVHTLCIdentity(this.wallet.identity, preimage);
4653
- if (isRecoverable(vtxo)) {
4654
- await this.joinBatch(vhtlcIdentity, input, output, arkInfo);
4655
- } else {
4656
- await claimVHTLCwithOffchainTx(
4657
- vhtlcIdentity,
4658
- vhtlcScript,
4659
- serverXOnlyPublicKey,
4660
- input,
4661
- output,
4662
- arkInfo,
4663
- this.arkProvider
4664
- );
4665
- }
4751
+ const txid = isRecoverable(vtxo) ? await this.joinBatch(vhtlcIdentity, input, output, arkInfo) : await claimVHTLCwithOffchainTx(
4752
+ vhtlcIdentity,
4753
+ vhtlcScript,
4754
+ serverXOnlyPublicKey,
4755
+ input,
4756
+ output,
4757
+ arkInfo,
4758
+ this.arkProvider
4759
+ );
4666
4760
  const finalStatus = await this.getSwapStatus(pendingSwap.id);
4667
4761
  await this.savePendingChainSwap({
4668
4762
  ...pendingSwap,
4669
4763
  status: finalStatus.status
4670
4764
  });
4765
+ return { txid };
4766
+ }
4767
+ /**
4768
+ * Resolve the on-chain txid for a chain swap at completion.
4769
+ *
4770
+ * The claim transaction we broadcast (claimBtc/claimArk) carries the real
4771
+ * on-chain txid; getSwapStatus does not surface it at transaction.claimed.
4772
+ * Prefer the claim's txid and fall back to the provider status only for
4773
+ * resumed swaps where we never ran the claim ourselves (claimPromise is
4774
+ * undefined). Returns undefined when no usable txid is available, so
4775
+ * callers never resolve with the Boltz swap id in place of a real txid.
4776
+ */
4777
+ async resolveChainClaimTxid(swapId, claimPromise) {
4778
+ if (claimPromise) {
4779
+ const claimTxid = await claimPromise.then(
4780
+ (res) => res.txid,
4781
+ () => void 0
4782
+ );
4783
+ if (claimTxid && claimTxid.trim() !== "") return claimTxid;
4784
+ }
4785
+ const txid = (await this.getSwapStatus(swapId)).transaction?.id;
4786
+ return txid && txid.trim() !== "" ? txid : void 0;
4671
4787
  }
4672
4788
  /**
4673
4789
  * Sign a cooperative claim for the server in BTC => ARK swaps.
@@ -5111,20 +5227,7 @@ var ArkadeSwaps = class _ArkadeSwaps {
5111
5227
  onchainAmount: amount,
5112
5228
  lockupAddress,
5113
5229
  refundPublicKey: serverPublicKey,
5114
- timeoutBlockHeights: timeoutBlockHeights ?? {
5115
- refund: extractTimeLockFromLeafOutput(
5116
- tree.refundWithoutBoltzLeaf?.output ?? ""
5117
- ),
5118
- unilateralClaim: extractTimeLockFromLeafOutput(
5119
- tree.unilateralClaimLeaf?.output ?? ""
5120
- ),
5121
- unilateralRefund: extractTimeLockFromLeafOutput(
5122
- tree.unilateralRefundLeaf?.output ?? ""
5123
- ),
5124
- unilateralRefundWithoutReceiver: extractTimeLockFromLeafOutput(
5125
- tree.unilateralRefundWithoutBoltzLeaf?.output ?? ""
5126
- )
5127
- }
5230
+ timeoutBlockHeights: resolveVhtlcTimeouts(tree, timeoutBlockHeights)
5128
5231
  },
5129
5232
  status,
5130
5233
  type: "reverse",
@@ -5157,26 +5260,20 @@ var ArkadeSwaps = class _ArkadeSwaps {
5157
5260
  address: lockupAddress,
5158
5261
  expectedAmount: amount,
5159
5262
  claimPublicKey: serverPublicKey,
5160
- timeoutBlockHeights: timeoutBlockHeights ?? {
5161
- refund: extractTimeLockFromLeafOutput(
5162
- tree.refundWithoutBoltzLeaf?.output ?? ""
5163
- ),
5164
- unilateralClaim: extractTimeLockFromLeafOutput(
5165
- tree.unilateralClaimLeaf?.output ?? ""
5166
- ),
5167
- unilateralRefund: extractTimeLockFromLeafOutput(
5168
- tree.unilateralRefundLeaf?.output ?? ""
5169
- ),
5170
- unilateralRefundWithoutReceiver: extractTimeLockFromLeafOutput(
5171
- tree.unilateralRefundWithoutBoltzLeaf?.output ?? ""
5172
- )
5173
- }
5263
+ timeoutBlockHeights: resolveVhtlcTimeouts(tree, timeoutBlockHeights)
5174
5264
  }
5175
5265
  });
5176
5266
  } else if (isRestoredChainSwap(swap)) {
5177
5267
  const refundDetails = swap.refundDetails;
5178
5268
  if (!refundDetails) continue;
5179
- const { amount, lockupAddress, serverPublicKey, timeoutBlockHeight } = refundDetails;
5269
+ const {
5270
+ amount,
5271
+ lockupAddress,
5272
+ serverPublicKey,
5273
+ timeoutBlockHeight,
5274
+ tree,
5275
+ timeoutBlockHeights
5276
+ } = refundDetails;
5180
5277
  chainSwaps.push({
5181
5278
  id,
5182
5279
  type: "chain",
@@ -5202,7 +5299,8 @@ var ArkadeSwaps = class _ArkadeSwaps {
5202
5299
  amount,
5203
5300
  lockupAddress,
5204
5301
  serverPublicKey,
5205
- timeoutBlockHeight
5302
+ timeoutBlockHeight,
5303
+ timeouts: resolveVhtlcTimeouts(tree, timeoutBlockHeights)
5206
5304
  }
5207
5305
  }
5208
5306
  });
@@ -7,7 +7,7 @@ import {
7
7
  isSubmarineFinalStatus,
8
8
  isSubmarineSwapRefundable,
9
9
  logger
10
- } from "./chunk-TDBUZE4N.js";
10
+ } from "./chunk-CFB2NNGT.js";
11
11
 
12
12
  // src/expo/swapsPollProcessor.ts
13
13
  var SWAP_POLL_TASK_TYPE = "swap-poll";