@arkade-os/boltz-swap 0.3.35 → 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.
@@ -1448,6 +1448,13 @@ var SwapManager = class _SwapManager {
1448
1448
  swapsInProgress = /* @__PURE__ */ new Set();
1449
1449
  // Per-swap subscriptions for UI hooks
1450
1450
  swapSubscriptions = /* @__PURE__ */ new Map();
1451
+ // In-flight (or settled) chain-swap claim promises, keyed by swap id. The
1452
+ // manager performs chain claims itself, so the claim tx it broadcasts is
1453
+ // the swap's on-chain completion; getSwapStatus does not surface that txid
1454
+ // at transaction.claimed. resolveClaimedTxid awaits the stored promise so a
1455
+ // transaction.claimed update that races an in-flight claim still resolves
1456
+ // the real txid instead of falling back to the provider and failing.
1457
+ chainClaimPromises = /* @__PURE__ */ new Map();
1451
1458
  // Action callbacks (injected via setCallbacks)
1452
1459
  claimCallback = null;
1453
1460
  refundCallback = null;
@@ -1589,6 +1596,7 @@ var SwapManager = class _SwapManager {
1589
1596
  }
1590
1597
  this.isRunning = true;
1591
1598
  this.initialSwaps.clear();
1599
+ this.chainClaimPromises.clear();
1592
1600
  for (const swap of pendingSwaps) {
1593
1601
  this.initialSwaps.set(swap.id, swap);
1594
1602
  }
@@ -1682,6 +1690,7 @@ var SwapManager = class _SwapManager {
1682
1690
  async removeSwap(swapId) {
1683
1691
  this.monitoredSwaps.delete(swapId);
1684
1692
  this.swapSubscriptions.delete(swapId);
1693
+ this.chainClaimPromises.delete(swapId);
1685
1694
  const retryTimer = this.pollRetryTimers.get(swapId);
1686
1695
  if (retryTimer) {
1687
1696
  clearTimeout(retryTimer);
@@ -1724,9 +1733,14 @@ var SwapManager = class _SwapManager {
1724
1733
  * Blocks until the swap reaches a final status or fails.
1725
1734
  * Useful when you want blocking behavior even with SwapManager enabled.
1726
1735
  *
1727
- * @throws If the swap is already in a final status (submarine/chain swaps throw immediately;
1728
- * reverse swaps return the existing txid).
1736
+ * If the swap is already in a final status, resolves immediately with the
1737
+ * on-chain txid of the successfully claimed swap (reverse: from
1738
+ * getReverseSwapTxId; submarine/chain: from getSwapStatus) and throws for a
1739
+ * failed final status.
1740
+ *
1741
+ * @throws If the swap is already in a failed final status.
1729
1742
  * @throws If the swap is not found in the manager.
1743
+ * @throws If a completed swap has no transaction id available yet.
1730
1744
  */
1731
1745
  async waitForSwapCompletion(swapId) {
1732
1746
  let swap = this.monitoredSwaps.get(swapId);
@@ -1741,10 +1755,12 @@ var SwapManager = class _SwapManager {
1741
1755
  const response = await this.swapProvider.getReverseSwapTxId(swap.id);
1742
1756
  return { txid: response.id };
1743
1757
  }
1744
- if (isPendingSubmarineSwap(swap)) {
1745
- throw new Error("Submarine swap already completed");
1758
+ if (isPendingSubmarineSwap(swap) || isPendingChainSwap(swap)) {
1759
+ if (swap.status === "transaction.claimed") {
1760
+ return this.resolveClaimedTxid(swap.id);
1761
+ }
1762
+ throw new Error(`Swap ${swap.id} already in final status: ${swap.status}`);
1746
1763
  }
1747
- throw new Error("Chain swap already completed");
1748
1764
  }
1749
1765
  return new Promise((resolve, reject) => {
1750
1766
  let unsubscribe = null;
@@ -1759,13 +1775,13 @@ var SwapManager = class _SwapManager {
1759
1775
  }
1760
1776
  } else if (isPendingSubmarineSwap(updatedSwap)) {
1761
1777
  if (updatedSwap.status === "transaction.claimed") {
1762
- resolve({ txid: updatedSwap.id });
1778
+ this.resolveClaimedTxid(updatedSwap.id).then(resolve).catch(reject);
1763
1779
  } else {
1764
1780
  reject(new Error(`Swap failed with status: ${updatedSwap.status}`));
1765
1781
  }
1766
1782
  } else if (isPendingChainSwap(updatedSwap)) {
1767
1783
  if (updatedSwap.status === "transaction.claimed") {
1768
- resolve({ txid: updatedSwap.id });
1784
+ this.resolveClaimedTxid(updatedSwap.id).then(resolve).catch(reject);
1769
1785
  } else {
1770
1786
  reject(new Error(`Swap failed with status: ${updatedSwap.status}`));
1771
1787
  }
@@ -1776,6 +1792,37 @@ var SwapManager = class _SwapManager {
1776
1792
  }).catch(reject);
1777
1793
  });
1778
1794
  }
1795
+ /**
1796
+ * Resolve the on-chain txid for a claimed submarine/chain swap.
1797
+ *
1798
+ * Chain swaps are claimed by the manager itself, so the claim txid captured
1799
+ * from the claimArk/claimBtc callback is the swap's on-chain completion and
1800
+ * is preferred — Boltz does not surface it via getSwapStatus at
1801
+ * transaction.claimed. Submarine swaps (claimed by Boltz) and chain swaps
1802
+ * we never claimed in this session (e.g. restored already-claimed) fall
1803
+ * back to the provider status. Rejects when no transaction id is available,
1804
+ * so callers never receive the Boltz swap id in place of a real txid.
1805
+ */
1806
+ async resolveClaimedTxid(swapId) {
1807
+ const claimPromise = this.chainClaimPromises.get(swapId);
1808
+ if (claimPromise) {
1809
+ const claimTxid = await claimPromise.then(
1810
+ (result) => result?.txid,
1811
+ () => void 0
1812
+ );
1813
+ if (claimTxid && claimTxid.trim() !== "") {
1814
+ return { txid: claimTxid };
1815
+ }
1816
+ }
1817
+ const status = await this.swapProvider.getSwapStatus(swapId);
1818
+ const txid = status.transaction?.id;
1819
+ if (!txid || txid.trim() === "") {
1820
+ throw new SwapError({
1821
+ message: `Transaction ID not available for completed swap ${swapId}`
1822
+ });
1823
+ }
1824
+ return { txid };
1825
+ }
1779
1826
  /**
1780
1827
  * Check if a swap is currently being processed
1781
1828
  * Useful for preventing race conditions
@@ -1979,6 +2026,7 @@ var SwapManager = class _SwapManager {
1979
2026
  if (!this.monitoredSwaps.has(swap.id)) return;
1980
2027
  this.monitoredSwaps.delete(swap.id);
1981
2028
  this.swapSubscriptions.delete(swap.id);
2029
+ this.chainClaimPromises.delete(swap.id);
1982
2030
  const retryTimer = this.pollRetryTimers.get(swap.id);
1983
2031
  if (retryTimer) {
1984
2032
  clearTimeout(retryTimer);
@@ -2149,7 +2197,9 @@ var SwapManager = class _SwapManager {
2149
2197
  logger.error("claimArk callback not set");
2150
2198
  return;
2151
2199
  }
2152
- await this.claimArkCallback(swap);
2200
+ const claimPromise = this.claimArkCallback(swap);
2201
+ this.rememberChainClaim(swap.id, claimPromise);
2202
+ await claimPromise;
2153
2203
  }
2154
2204
  /**
2155
2205
  * Execute claim action for chain swap Ark to Btc
@@ -2159,7 +2209,23 @@ var SwapManager = class _SwapManager {
2159
2209
  logger.error("claimBtc callback not set");
2160
2210
  return;
2161
2211
  }
2162
- await this.claimBtcCallback(swap);
2212
+ const claimPromise = this.claimBtcCallback(swap);
2213
+ this.rememberChainClaim(swap.id, claimPromise);
2214
+ await claimPromise;
2215
+ }
2216
+ /**
2217
+ * Store the in-flight claim promise returned by a claim callback so
2218
+ * {@link resolveClaimedTxid} can await it and surface the real on-chain
2219
+ * txid at transaction.claimed — even when that update races the still
2220
+ * in-flight claim. Storing the promise (not the resolved txid) closes the
2221
+ * window where the txid is not yet captured when transaction.claimed
2222
+ * arrives. Defensive against callbacks that don't return a promise (older
2223
+ * integrations, test doubles): those simply fall back to getSwapStatus.
2224
+ */
2225
+ rememberChainClaim(swapId, claimPromise) {
2226
+ if (claimPromise) {
2227
+ this.chainClaimPromises.set(swapId, claimPromise);
2228
+ }
2163
2229
  }
2164
2230
  /**
2165
2231
  * Execute refund action for chain swap Ark to Btc
@@ -2946,6 +3012,7 @@ var claimVHTLCwithOffchainTx = async (identity, vhtlcScript, serverXOnlyPublicKe
2946
3012
  })
2947
3013
  );
2948
3014
  await arkProvider.finalizeTx(arkTxid, finalCheckpoints);
3015
+ return arkTxid;
2949
3016
  };
2950
3017
  var refundVHTLCwithOffchainTx = async (swapId, identity, arkProvider, boltzXOnlyPublicKey, ourXOnlyPublicKey, serverXOnlyPublicKey, input, output, arkInfo, refundFunc) => {
2951
3018
  const rawCheckpointTapscript = import_base8.hex.decode(arkInfo.checkpointTapscript);
@@ -3104,12 +3171,8 @@ var ArkadeSwaps = class _ArkadeSwaps {
3104
3171
  refund: async (swap) => {
3105
3172
  await this.refundVHTLC(swap);
3106
3173
  },
3107
- claimArk: async (swap) => {
3108
- await this.claimArk(swap);
3109
- },
3110
- claimBtc: async (swap) => {
3111
- await this.claimBtc(swap);
3112
- },
3174
+ claimArk: (swap) => this.claimArk(swap),
3175
+ claimBtc: (swap) => this.claimBtc(swap),
3113
3176
  refundArk: async (swap) => {
3114
3177
  return this.refundArk(swap);
3115
3178
  },
@@ -4120,6 +4183,7 @@ var ArkadeSwaps = class _ArkadeSwaps {
4120
4183
  }
4121
4184
  return new Promise((resolve, reject) => {
4122
4185
  let claimStarted = false;
4186
+ let claimPromise;
4123
4187
  const swap = { ...pendingSwap };
4124
4188
  const onStatusUpdate = async (status, data) => {
4125
4189
  const updateSwapStatus = async () => {
@@ -4137,16 +4201,24 @@ var ArkadeSwaps = class _ArkadeSwaps {
4137
4201
  const updatedSwap = await updateSwapStatus();
4138
4202
  if (claimStarted) return;
4139
4203
  claimStarted = true;
4140
- this.claimBtc(updatedSwap).catch(reject);
4204
+ claimPromise = this.claimBtc(updatedSwap);
4205
+ claimPromise.catch(reject);
4141
4206
  break;
4142
4207
  }
4143
- case "transaction.claimed":
4208
+ case "transaction.claimed": {
4144
4209
  await updateSwapStatus();
4145
- const claimedStatus = await this.getSwapStatus(pendingSwap.id);
4146
- resolve({
4147
- txid: claimedStatus.transaction?.id ?? ""
4148
- });
4210
+ const txid = await this.resolveChainClaimTxid(pendingSwap.id, claimPromise);
4211
+ if (!txid) {
4212
+ reject(
4213
+ new SwapError({
4214
+ message: `Transaction ID not available for claimed swap ${pendingSwap.id}.`
4215
+ })
4216
+ );
4217
+ break;
4218
+ }
4219
+ resolve({ txid });
4149
4220
  break;
4221
+ }
4150
4222
  case "transaction.lockupFailed":
4151
4223
  await updateSwapStatus();
4152
4224
  await this.quoteSwap(swap.response.id, quoteOptionsForSwap(swap)).catch(
@@ -4195,6 +4267,7 @@ var ArkadeSwaps = class _ArkadeSwaps {
4195
4267
  /**
4196
4268
  * Claim sats on BTC chain by claiming the HTLC.
4197
4269
  * @param pendingSwap - The pending chain swap with BTC transaction hex.
4270
+ * @returns The BTC transaction ID of the claim.
4198
4271
  */
4199
4272
  async claimBtc(pendingSwap) {
4200
4273
  if (!pendingSwap.toAddress)
@@ -4267,6 +4340,7 @@ var ArkadeSwaps = class _ArkadeSwaps {
4267
4340
  finalScriptWitness: [musigSigned.aggregatePartials()]
4268
4341
  });
4269
4342
  await this.swapProvider.postBtcTransaction(claimTx.hex);
4343
+ return { txid: claimTx.id };
4270
4344
  }
4271
4345
  /**
4272
4346
  * When an ARK to BTC swap fails, refund every unspent VTXO at the chain
@@ -4466,6 +4540,7 @@ var ArkadeSwaps = class _ArkadeSwaps {
4466
4540
  }
4467
4541
  return new Promise((resolve, reject) => {
4468
4542
  let claimStarted = false;
4543
+ let claimPromise;
4469
4544
  const swap = { ...pendingSwap };
4470
4545
  const onStatusUpdate = async (status, data) => {
4471
4546
  const updateSwapStatus = () => {
@@ -4478,15 +4553,23 @@ var ArkadeSwaps = class _ArkadeSwaps {
4478
4553
  await updateSwapStatus();
4479
4554
  if (claimStarted) return;
4480
4555
  claimStarted = true;
4481
- this.claimArk(swap).catch(reject);
4556
+ claimPromise = this.claimArk(swap);
4557
+ claimPromise.catch(reject);
4482
4558
  break;
4483
- case "transaction.claimed":
4559
+ case "transaction.claimed": {
4484
4560
  await updateSwapStatus();
4485
- const claimedStatus = await this.getSwapStatus(pendingSwap.id);
4486
- resolve({
4487
- txid: claimedStatus.transaction?.id ?? ""
4488
- });
4561
+ const txid = await this.resolveChainClaimTxid(pendingSwap.id, claimPromise);
4562
+ if (!txid) {
4563
+ reject(
4564
+ new SwapError({
4565
+ message: `Transaction ID not available for claimed swap ${pendingSwap.id}.`
4566
+ })
4567
+ );
4568
+ break;
4569
+ }
4570
+ resolve({ txid });
4489
4571
  break;
4572
+ }
4490
4573
  case "transaction.claim.pending":
4491
4574
  await updateSwapStatus();
4492
4575
  await this.signCooperativeClaimForServer(swap).catch((err) => {
@@ -4545,6 +4628,7 @@ var ArkadeSwaps = class _ArkadeSwaps {
4545
4628
  * Claim sats on ARK chain by claiming the VHTLC.
4546
4629
  * Refactored to use claimVHTLCIdentity + claimVHTLCwithOffchainTx utilities.
4547
4630
  * @param pendingSwap - The pending chain swap.
4631
+ * @returns The Ark transaction ID of the claim.
4548
4632
  */
4549
4633
  async claimArk(pendingSwap) {
4550
4634
  if (!pendingSwap.toAddress)
@@ -4607,24 +4691,42 @@ var ArkadeSwaps = class _ArkadeSwaps {
4607
4691
  script: import_sdk8.ArkAddress.decode(address).pkScript
4608
4692
  };
4609
4693
  const vhtlcIdentity = claimVHTLCIdentity(this.wallet.identity, preimage);
4610
- if ((0, import_sdk8.isRecoverable)(vtxo)) {
4611
- await this.joinBatch(vhtlcIdentity, input, output, arkInfo);
4612
- } else {
4613
- await claimVHTLCwithOffchainTx(
4614
- vhtlcIdentity,
4615
- vhtlcScript,
4616
- serverXOnlyPublicKey,
4617
- input,
4618
- output,
4619
- arkInfo,
4620
- this.arkProvider
4621
- );
4622
- }
4694
+ const txid = (0, import_sdk8.isRecoverable)(vtxo) ? await this.joinBatch(vhtlcIdentity, input, output, arkInfo) : await claimVHTLCwithOffchainTx(
4695
+ vhtlcIdentity,
4696
+ vhtlcScript,
4697
+ serverXOnlyPublicKey,
4698
+ input,
4699
+ output,
4700
+ arkInfo,
4701
+ this.arkProvider
4702
+ );
4623
4703
  const finalStatus = await this.getSwapStatus(pendingSwap.id);
4624
4704
  await this.savePendingChainSwap({
4625
4705
  ...pendingSwap,
4626
4706
  status: finalStatus.status
4627
4707
  });
4708
+ return { txid };
4709
+ }
4710
+ /**
4711
+ * Resolve the on-chain txid for a chain swap at completion.
4712
+ *
4713
+ * The claim transaction we broadcast (claimBtc/claimArk) carries the real
4714
+ * on-chain txid; getSwapStatus does not surface it at transaction.claimed.
4715
+ * Prefer the claim's txid and fall back to the provider status only for
4716
+ * resumed swaps where we never ran the claim ourselves (claimPromise is
4717
+ * undefined). Returns undefined when no usable txid is available, so
4718
+ * callers never resolve with the Boltz swap id in place of a real txid.
4719
+ */
4720
+ async resolveChainClaimTxid(swapId, claimPromise) {
4721
+ if (claimPromise) {
4722
+ const claimTxid = await claimPromise.then(
4723
+ (res) => res.txid,
4724
+ () => void 0
4725
+ );
4726
+ if (claimTxid && claimTxid.trim() !== "") return claimTxid;
4727
+ }
4728
+ const txid = (await this.getSwapStatus(swapId)).transaction?.id;
4729
+ return txid && txid.trim() !== "" ? txid : void 0;
4628
4730
  }
4629
4731
  /**
4630
4732
  * Sign a cooperative claim for the server in BTC => ARK swaps.
@@ -1,8 +1,8 @@
1
- import { D as DefineSwapBackgroundTaskOptions } from '../swapsPollProcessor-BpAqG0V6.cjs';
2
- export { P as PersistedSwapBackgroundConfig, S as SWAP_POLL_TASK_TYPE, a as SwapTaskDependencies, s as swapsPollProcessor } from '../swapsPollProcessor-BpAqG0V6.cjs';
1
+ import { D as DefineSwapBackgroundTaskOptions } from '../swapsPollProcessor-BlyUrhtO.cjs';
2
+ export { P as PersistedSwapBackgroundConfig, S as SWAP_POLL_TASK_TYPE, a as SwapTaskDependencies, s as swapsPollProcessor } from '../swapsPollProcessor-BlyUrhtO.cjs';
3
3
  import '@arkade-os/sdk/worker/expo';
4
4
  import '@arkade-os/sdk';
5
- import '../types--axEWA8c.cjs';
5
+ import '../types-D97i1LFu.cjs';
6
6
 
7
7
  /**
8
8
  * Define the Expo background task handler for swap polling.
@@ -1,8 +1,8 @@
1
- import { D as DefineSwapBackgroundTaskOptions } from '../swapsPollProcessor-DFVOAy_-.js';
2
- export { P as PersistedSwapBackgroundConfig, S as SWAP_POLL_TASK_TYPE, a as SwapTaskDependencies, s as swapsPollProcessor } from '../swapsPollProcessor-DFVOAy_-.js';
1
+ import { D as DefineSwapBackgroundTaskOptions } from '../swapsPollProcessor-Bv4Z2R7g.js';
2
+ export { P as PersistedSwapBackgroundConfig, S as SWAP_POLL_TASK_TYPE, a as SwapTaskDependencies, s as swapsPollProcessor } from '../swapsPollProcessor-Bv4Z2R7g.js';
3
3
  import '@arkade-os/sdk/worker/expo';
4
4
  import '@arkade-os/sdk';
5
- import '../types--axEWA8c.js';
5
+ import '../types-D97i1LFu.js';
6
6
 
7
7
  /**
8
8
  * Define the Expo background task handler for swap polling.
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  SWAP_POLL_TASK_TYPE,
3
3
  swapsPollProcessor
4
- } from "../chunk-H6F67K2A.js";
4
+ } from "../chunk-DNCIVDU5.js";
5
5
  import {
6
6
  BoltzSwapProvider
7
- } from "../chunk-B4CYBKFJ.js";
7
+ } from "../chunk-CFB2NNGT.js";
8
8
  import "../chunk-SJQJQO7P.js";
9
9
 
10
10
  // src/expo/background.ts