@arkade-os/boltz-swap 0.2.13 → 0.2.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -89,7 +89,7 @@ import {
89
89
  getSequence as getSequence2
90
90
  } from "@arkade-os/sdk";
91
91
  import { sha256 as sha2562 } from "@noble/hashes/sha2.js";
92
- import { base64 as base643, hex as hex2 } from "@scure/base";
92
+ import { base64 as base643, hex as hex3 } from "@scure/base";
93
93
  import { randomBytes } from "@noble/hashes/utils.js";
94
94
 
95
95
  // src/boltz-swap-provider.ts
@@ -154,6 +154,26 @@ var isCreateReverseSwapResponse = (data) => {
154
154
  var isRefundSubmarineSwapResponse = (data) => {
155
155
  return data && typeof data === "object" && typeof data.transaction === "string" && typeof data.checkpoint === "string";
156
156
  };
157
+ var isLeaf = (data) => {
158
+ return data && typeof data === "object" && typeof data.version === "number" && typeof data.output === "string";
159
+ };
160
+ var isTree = (data) => {
161
+ return data && typeof data === "object" && isLeaf(data.claimLeaf) && isLeaf(data.refundLeaf) && isLeaf(data.refundWithoutBoltzLeaf) && isLeaf(data.unilateralClaimLeaf) && isLeaf(data.unilateralRefundLeaf) && isLeaf(data.unilateralRefundWithoutBoltzLeaf);
162
+ };
163
+ var isDetails = (data) => {
164
+ return data && typeof data === "object" && isTree(data.tree) && (data.amount === void 0 || typeof data.amount === "number") && typeof data.keyIndex === "number" && (data.transaction === void 0 || data.transaction && typeof data.transaction === "object" && typeof data.transaction.id === "string" && typeof data.transaction.vout === "number") && typeof data.lockupAddress === "string" && typeof data.serverPublicKey === "string" && typeof data.timeoutBlockHeight === "number" && (data.preimageHash === void 0 || typeof data.preimageHash === "string");
165
+ };
166
+ var isRestoredSubmarineSwap = (data) => {
167
+ return data && typeof data === "object" && data.to === "BTC" && typeof data.id === "string" && data.from === "ARK" && data.type === "submarine" && typeof data.createdAt === "number" && typeof data.preimageHash === "string" && typeof data.status === "string" && isDetails(data.refundDetails);
168
+ };
169
+ var isRestoredReverseSwap = (data) => {
170
+ return data && typeof data === "object" && data.to === "ARK" && typeof data.id === "string" && data.from === "BTC" && data.type === "reverse" && typeof data.createdAt === "number" && typeof data.preimageHash === "string" && typeof data.status === "string" && isDetails(data.claimDetails);
171
+ };
172
+ var isCreateSwapsRestoreResponse = (data) => {
173
+ return Array.isArray(data) && data.every(
174
+ (item) => isRestoredReverseSwap(item) || isRestoredSubmarineSwap(item)
175
+ );
176
+ };
157
177
  var BASE_URLS = {
158
178
  mutinynet: "https://api.boltz.mutinynet.arkade.sh",
159
179
  regtest: "http://localhost:9069"
@@ -384,6 +404,21 @@ var BoltzSwapProvider = class {
384
404
  };
385
405
  });
386
406
  }
407
+ async restoreSwaps(publicKey) {
408
+ const requestBody = {
409
+ publicKey
410
+ };
411
+ const response = await this.request(
412
+ "/v2/swap/restore",
413
+ "POST",
414
+ requestBody
415
+ );
416
+ if (!isCreateSwapsRestoreResponse(response))
417
+ throw new SchemaError({
418
+ message: "Invalid schema in response for swap restoration"
419
+ });
420
+ return response;
421
+ }
387
422
  async request(path, method, body) {
388
423
  const url = `${this.apiUrl}${path}`;
389
424
  try {
@@ -451,6 +486,50 @@ var verifySignatures = (tx, inputIndex, requiredSigners) => {
451
486
  }
452
487
  };
453
488
 
489
+ // src/utils/restoration.ts
490
+ import { hex } from "@scure/base";
491
+ import { Script } from "@scure/btc-signer";
492
+ import bip68 from "bip68";
493
+ function extractTimeLockFromLeafOutput(scriptHex) {
494
+ if (!scriptHex) return 0;
495
+ try {
496
+ const opcodes = Script.decode(hex.decode(scriptHex));
497
+ const hasCLTV = opcodes.findIndex((op) => op === "CHECKLOCKTIMEVERIFY");
498
+ if (hasCLTV > 0) {
499
+ const data = opcodes[hasCLTV - 1];
500
+ if (data instanceof Uint8Array) {
501
+ const dataBytes = new Uint8Array(data).reverse();
502
+ return parseInt(hex.encode(dataBytes), 16);
503
+ }
504
+ }
505
+ const hasCSV = opcodes.findIndex((op) => op === "CHECKSEQUENCEVERIFY");
506
+ if (hasCSV > 0) {
507
+ const data = opcodes[hasCSV - 1];
508
+ if (data instanceof Uint8Array) {
509
+ const dataBytes = new Uint8Array(data).reverse();
510
+ const {
511
+ blocks,
512
+ seconds
513
+ } = bip68.decode(
514
+ parseInt(hex.encode(dataBytes), 16)
515
+ );
516
+ return blocks ?? seconds ?? 0;
517
+ }
518
+ }
519
+ } catch (error) {
520
+ return 0;
521
+ }
522
+ return 0;
523
+ }
524
+ function extractInvoiceAmount(amountSats, fees) {
525
+ if (!amountSats) return 0;
526
+ const { percentage, minerFees } = fees.reverse;
527
+ const miner = minerFees.lockup + minerFees.claim;
528
+ if (percentage >= 100 || percentage < 0) return 0;
529
+ if (miner >= amountSats) return 0;
530
+ return Math.ceil((amountSats - miner) / (1 - percentage / 100));
531
+ }
532
+
454
533
  // src/logger.ts
455
534
  var logger = console;
456
535
  function setLogger(customLogger) {
@@ -948,6 +1027,12 @@ var SwapManager = class {
948
1027
  try {
949
1028
  this.swapsInProgress.add(swap.id);
950
1029
  if (isPendingReverseSwap(swap)) {
1030
+ if (!swap.preimage || swap.preimage.length === 0) {
1031
+ logger.log(
1032
+ `Skipping claim for swap ${swap.id}: missing preimage (restored swap)`
1033
+ );
1034
+ return;
1035
+ }
951
1036
  if (isReverseClaimableStatus(swap.status)) {
952
1037
  logger.log(`Auto-claiming reverse swap ${swap.id}`);
953
1038
  await this.executeClaimAction(swap);
@@ -956,6 +1041,12 @@ var SwapManager = class {
956
1041
  );
957
1042
  }
958
1043
  } else if (isPendingSubmarineSwap(swap)) {
1044
+ if (!swap.request?.invoice || swap.request.invoice.length === 0) {
1045
+ logger.log(
1046
+ `Skipping refund for swap ${swap.id}: missing invoice (restored swap)`
1047
+ );
1048
+ return;
1049
+ }
959
1050
  if (isSubmarineRefundableStatus(swap.status)) {
960
1051
  logger.log(`Auto-refunding submarine swap ${swap.id}`);
961
1052
  await this.executeRefundAction(swap);
@@ -1175,13 +1266,13 @@ import {
1175
1266
  getSequence
1176
1267
  } from "@arkade-os/sdk";
1177
1268
  import { sha256 } from "@noble/hashes/sha2.js";
1178
- import { base64 as base642, hex } from "@scure/base";
1269
+ import { base64 as base642, hex as hex2 } from "@scure/base";
1179
1270
  import { SigHash } from "@scure/btc-signer";
1180
1271
  import { tapLeafHash } from "@scure/btc-signer/payment.js";
1181
1272
  function createVHTLCBatchHandler(intentId, vhtlc, arkProvider, identity, session, sweepPublicKey, forfeitOutputScript, connectorIndex = 0) {
1182
1273
  const utf8IntentId = new TextEncoder().encode(intentId);
1183
1274
  const intentIdHash = sha256(utf8IntentId);
1184
- const intentIdHashStr = hex.encode(intentIdHash);
1275
+ const intentIdHashStr = hex2.encode(intentIdHash);
1185
1276
  let sweepTapTreeRoot;
1186
1277
  return {
1187
1278
  onBatchStarted: async (event) => {
@@ -1220,7 +1311,7 @@ function createVHTLCBatchHandler(intentId, vhtlc, arkProvider, identity, session
1220
1311
  );
1221
1312
  const signerPublicKey = await session.getPublicKey();
1222
1313
  const xonlySignerPublicKey = signerPublicKey.subarray(1);
1223
- if (!xOnlyPublicKeys.includes(hex.encode(xonlySignerPublicKey))) {
1314
+ if (!xOnlyPublicKeys.includes(hex2.encode(xonlySignerPublicKey))) {
1224
1315
  return { skip: true };
1225
1316
  }
1226
1317
  const commitmentTx = Transaction3.fromPSBT(
@@ -1232,7 +1323,7 @@ function createVHTLCBatchHandler(intentId, vhtlc, arkProvider, identity, session
1232
1323
  throw new Error("Shared output not found");
1233
1324
  }
1234
1325
  await session.init(vtxoTree, sweepTapTreeRoot, sharedOutput.amount);
1235
- const pubkey = hex.encode(await session.getPublicKey());
1326
+ const pubkey = hex2.encode(await session.getPublicKey());
1236
1327
  const nonces = await session.getNonces();
1237
1328
  await arkProvider.submitTreeNonces(event.id, pubkey, nonces);
1238
1329
  return { skip: false };
@@ -1247,7 +1338,7 @@ function createVHTLCBatchHandler(intentId, vhtlc, arkProvider, identity, session
1247
1338
  );
1248
1339
  if (!hasAllNonces) return { fullySigned: false };
1249
1340
  const signatures = await session.sign();
1250
- const pubkey = hex.encode(await session.getPublicKey());
1341
+ const pubkey = hex2.encode(await session.getPublicKey());
1251
1342
  await arkProvider.submitTreeSignatures(
1252
1343
  event.id,
1253
1344
  pubkey,
@@ -1500,7 +1591,7 @@ var ArkadeLightning = class {
1500
1591
  * @returns The created pending submarine swap.
1501
1592
  */
1502
1593
  async createSubmarineSwap(args) {
1503
- const refundPublicKey = hex2.encode(
1594
+ const refundPublicKey = hex3.encode(
1504
1595
  await this.wallet.identity.compressedPublicKey()
1505
1596
  );
1506
1597
  if (!refundPublicKey)
@@ -1536,7 +1627,7 @@ var ArkadeLightning = class {
1536
1627
  async createReverseSwap(args) {
1537
1628
  if (args.amount <= 0)
1538
1629
  throw new SwapError({ message: "Amount must be greater than 0" });
1539
- const claimPublicKey = hex2.encode(
1630
+ const claimPublicKey = hex3.encode(
1540
1631
  await this.wallet.identity.compressedPublicKey()
1541
1632
  );
1542
1633
  if (!claimPublicKey)
@@ -1544,7 +1635,7 @@ var ArkadeLightning = class {
1544
1635
  message: "Failed to get claim public key from wallet"
1545
1636
  });
1546
1637
  const preimage = randomBytes(32);
1547
- const preimageHash = hex2.encode(sha2562(preimage));
1638
+ const preimageHash = hex3.encode(sha2562(preimage));
1548
1639
  if (!preimageHash)
1549
1640
  throw new SwapError({ message: "Failed to get preimage hash" });
1550
1641
  const swapRequest = {
@@ -1558,7 +1649,7 @@ var ArkadeLightning = class {
1558
1649
  id: swapResponse.id,
1559
1650
  type: "reverse",
1560
1651
  createdAt: Math.floor(Date.now() / 1e3),
1561
- preimage: hex2.encode(preimage),
1652
+ preimage: hex3.encode(preimage),
1562
1653
  request: swapRequest,
1563
1654
  response: swapResponse,
1564
1655
  status: "swap.created"
@@ -1575,7 +1666,9 @@ var ArkadeLightning = class {
1575
1666
  * @param pendingSwap - The pending reverse swap to claim the VHTLC.
1576
1667
  */
1577
1668
  async claimVHTLC(pendingSwap) {
1578
- const preimage = hex2.decode(pendingSwap.preimage);
1669
+ if (!pendingSwap.preimage)
1670
+ throw new Error("Preimage is required to claim VHTLC");
1671
+ const preimage = hex3.decode(pendingSwap.preimage);
1579
1672
  const aspInfo = await this.arkProvider.getInfo();
1580
1673
  const address = await this.wallet.getAddress();
1581
1674
  const ourXOnlyPublicKey = this.normalizeToXOnlyPublicKey(
@@ -1584,21 +1677,21 @@ var ArkadeLightning = class {
1584
1677
  pendingSwap.id
1585
1678
  );
1586
1679
  const boltzXOnlyPublicKey = this.normalizeToXOnlyPublicKey(
1587
- hex2.decode(pendingSwap.response.refundPublicKey),
1680
+ hex3.decode(pendingSwap.response.refundPublicKey),
1588
1681
  "boltz",
1589
1682
  pendingSwap.id
1590
1683
  );
1591
1684
  const serverXOnlyPublicKey = this.normalizeToXOnlyPublicKey(
1592
- hex2.decode(aspInfo.signerPubkey),
1685
+ hex3.decode(aspInfo.signerPubkey),
1593
1686
  "server",
1594
1687
  pendingSwap.id
1595
1688
  );
1596
1689
  const { vhtlcScript, vhtlcAddress } = this.createVHTLCScript({
1597
1690
  network: aspInfo.network,
1598
1691
  preimageHash: sha2562(preimage),
1599
- receiverPubkey: hex2.encode(ourXOnlyPublicKey),
1600
- senderPubkey: hex2.encode(boltzXOnlyPublicKey),
1601
- serverPubkey: hex2.encode(serverXOnlyPublicKey),
1692
+ receiverPubkey: hex3.encode(ourXOnlyPublicKey),
1693
+ senderPubkey: hex3.encode(boltzXOnlyPublicKey),
1694
+ serverPubkey: hex3.encode(serverXOnlyPublicKey),
1602
1695
  timeoutBlockHeights: pendingSwap.response.timeoutBlockHeights
1603
1696
  });
1604
1697
  if (!vhtlcScript)
@@ -1606,7 +1699,7 @@ var ArkadeLightning = class {
1606
1699
  if (vhtlcAddress !== pendingSwap.response.lockupAddress)
1607
1700
  throw new Error("Boltz is trying to scam us");
1608
1701
  const { vtxos } = await this.indexerProvider.getVtxos({
1609
- scripts: [hex2.encode(vhtlcScript.pkScript)]
1702
+ scripts: [hex3.encode(vhtlcScript.pkScript)]
1610
1703
  });
1611
1704
  if (vtxos.length === 0)
1612
1705
  throw new Error("No spendable virtual coins found");
@@ -1654,11 +1747,13 @@ var ArkadeLightning = class {
1654
1747
  * @param pendingSwap - The pending submarine swap to refund the VHTLC.
1655
1748
  */
1656
1749
  async refundVHTLC(pendingSwap) {
1750
+ if (!pendingSwap.request.invoice)
1751
+ throw new Error("Invoice is required to refund VHTLC");
1657
1752
  const vhtlcPkScript = ArkAddress.decode(
1658
1753
  pendingSwap.response.address
1659
1754
  ).pkScript;
1660
1755
  const { vtxos } = await this.indexerProvider.getVtxos({
1661
- scripts: [hex2.encode(vhtlcPkScript)]
1756
+ scripts: [hex3.encode(vhtlcPkScript)]
1662
1757
  });
1663
1758
  if (vtxos.length === 0) {
1664
1759
  throw new Error(
@@ -1678,23 +1773,23 @@ var ArkadeLightning = class {
1678
1773
  pendingSwap.id
1679
1774
  );
1680
1775
  const serverXOnlyPublicKey = this.normalizeToXOnlyPublicKey(
1681
- hex2.decode(aspInfo.signerPubkey),
1776
+ hex3.decode(aspInfo.signerPubkey),
1682
1777
  "server",
1683
1778
  pendingSwap.id
1684
1779
  );
1685
1780
  const boltzXOnlyPublicKey = this.normalizeToXOnlyPublicKey(
1686
- hex2.decode(pendingSwap.response.claimPublicKey),
1781
+ hex3.decode(pendingSwap.response.claimPublicKey),
1687
1782
  "boltz",
1688
1783
  pendingSwap.id
1689
1784
  );
1690
1785
  const { vhtlcScript } = this.createVHTLCScript({
1691
1786
  network: aspInfo.network,
1692
- preimageHash: hex2.decode(
1787
+ preimageHash: hex3.decode(
1693
1788
  getInvoicePaymentHash(pendingSwap.request.invoice)
1694
1789
  ),
1695
- receiverPubkey: hex2.encode(boltzXOnlyPublicKey),
1696
- senderPubkey: hex2.encode(ourXOnlyPublicKey),
1697
- serverPubkey: hex2.encode(serverXOnlyPublicKey),
1790
+ receiverPubkey: hex3.encode(boltzXOnlyPublicKey),
1791
+ senderPubkey: hex3.encode(ourXOnlyPublicKey),
1792
+ serverPubkey: hex3.encode(serverXOnlyPublicKey),
1698
1793
  timeoutBlockHeights: pendingSwap.response.timeoutBlockHeights
1699
1794
  });
1700
1795
  if (!vhtlcScript)
@@ -1750,14 +1845,14 @@ var ArkadeLightning = class {
1750
1845
  onchain_output_indexes: [],
1751
1846
  valid_at: 0,
1752
1847
  expire_at: 0,
1753
- cosigners_public_keys: [hex2.encode(signerPublicKey)]
1848
+ cosigners_public_keys: [hex3.encode(signerPublicKey)]
1754
1849
  };
1755
1850
  const deleteMessage = {
1756
1851
  type: "delete",
1757
1852
  expire_at: 0
1758
1853
  };
1759
1854
  const intentInput = {
1760
- txid: hex2.decode(input.txid),
1855
+ txid: hex3.decode(input.txid),
1761
1856
  index: input.vout,
1762
1857
  witnessUtxo: {
1763
1858
  amount: BigInt(input.value),
@@ -1792,11 +1887,11 @@ var ArkadeLightning = class {
1792
1887
  this.arkProvider,
1793
1888
  identity,
1794
1889
  signerSession,
1795
- hex2.decode(forfeitPubkey).slice(1),
1890
+ hex3.decode(forfeitPubkey).slice(1),
1796
1891
  isRecoverable2 ? void 0 : OutScript.encode(decodedAddress)
1797
1892
  );
1798
1893
  const topics = [
1799
- hex2.encode(signerPublicKey),
1894
+ hex3.encode(signerPublicKey),
1800
1895
  `${input.txid}:${input.vout}`
1801
1896
  ];
1802
1897
  const eventStream = this.arkProvider.getEventStream(
@@ -1969,8 +2064,179 @@ var ArkadeLightning = class {
1969
2064
  });
1970
2065
  });
1971
2066
  }
2067
+ /**
2068
+ * Restore swaps from Boltz API.
2069
+ *
2070
+ * Note: restored swaps may lack local-only data such as the original
2071
+ * Lightning invoice or preimage. They are intended primarily for
2072
+ * display/monitoring and are not automatically wired into the SwapManager.
2073
+ * Do not call `claimVHTLC` / `refundVHTLC` on them unless you have
2074
+ * enriched the objects with the missing fields.
2075
+ *
2076
+ * @param boltzFees - Optional fees response to use for restoration.
2077
+ * @returns An object containing arrays of restored reverse and submarine swaps.
2078
+ */
2079
+ async restoreSwaps(boltzFees) {
2080
+ const publicKey = hex3.encode(
2081
+ await this.wallet.identity.compressedPublicKey()
2082
+ );
2083
+ if (!publicKey) throw new Error("Failed to get public key from wallet");
2084
+ const fees = boltzFees ?? await this.swapProvider.getFees();
2085
+ const reverseSwaps = [];
2086
+ const submarineSwaps = [];
2087
+ const restoredSwaps = await this.swapProvider.restoreSwaps(publicKey);
2088
+ for (const swap of restoredSwaps) {
2089
+ const { id, createdAt, status } = swap;
2090
+ if (isRestoredReverseSwap(swap)) {
2091
+ const {
2092
+ amount,
2093
+ lockupAddress,
2094
+ preimageHash,
2095
+ serverPublicKey,
2096
+ tree
2097
+ } = swap.claimDetails;
2098
+ reverseSwaps.push({
2099
+ id,
2100
+ createdAt,
2101
+ request: {
2102
+ invoiceAmount: extractInvoiceAmount(amount, fees),
2103
+ claimPublicKey: publicKey,
2104
+ preimageHash
2105
+ },
2106
+ response: {
2107
+ id,
2108
+ invoice: "",
2109
+ // TODO check if we can get the invoice from boltz
2110
+ onchainAmount: amount,
2111
+ lockupAddress,
2112
+ refundPublicKey: serverPublicKey,
2113
+ timeoutBlockHeights: {
2114
+ refund: extractTimeLockFromLeafOutput(
2115
+ tree.refundWithoutBoltzLeaf.output
2116
+ ),
2117
+ unilateralClaim: extractTimeLockFromLeafOutput(
2118
+ tree.unilateralClaimLeaf.output
2119
+ ),
2120
+ unilateralRefund: extractTimeLockFromLeafOutput(
2121
+ tree.unilateralRefundLeaf.output
2122
+ ),
2123
+ unilateralRefundWithoutReceiver: extractTimeLockFromLeafOutput(
2124
+ tree.unilateralRefundWithoutBoltzLeaf.output
2125
+ )
2126
+ }
2127
+ },
2128
+ status,
2129
+ type: "reverse",
2130
+ preimage: ""
2131
+ });
2132
+ } else if (isRestoredSubmarineSwap(swap)) {
2133
+ const { amount, lockupAddress, serverPublicKey, tree } = swap.refundDetails;
2134
+ let preimage = "";
2135
+ try {
2136
+ const data = await this.swapProvider.getSwapPreimage(
2137
+ swap.id
2138
+ );
2139
+ preimage = data.preimage;
2140
+ } catch (error) {
2141
+ logger.warn(
2142
+ `Failed to restore preimage for submarine swap ${id}`,
2143
+ error
2144
+ );
2145
+ }
2146
+ submarineSwaps.push({
2147
+ id,
2148
+ type: "submarine",
2149
+ createdAt,
2150
+ preimage,
2151
+ preimageHash: swap.preimageHash,
2152
+ status,
2153
+ request: {
2154
+ invoice: "",
2155
+ // TODO check if we can get the invoice from boltz
2156
+ refundPublicKey: publicKey
2157
+ },
2158
+ response: {
2159
+ id,
2160
+ address: lockupAddress,
2161
+ expectedAmount: amount,
2162
+ claimPublicKey: serverPublicKey,
2163
+ timeoutBlockHeights: {
2164
+ refund: extractTimeLockFromLeafOutput(
2165
+ tree.refundWithoutBoltzLeaf.output
2166
+ ),
2167
+ unilateralClaim: extractTimeLockFromLeafOutput(
2168
+ tree.unilateralClaimLeaf.output
2169
+ ),
2170
+ unilateralRefund: extractTimeLockFromLeafOutput(
2171
+ tree.unilateralRefundLeaf.output
2172
+ ),
2173
+ unilateralRefundWithoutReceiver: extractTimeLockFromLeafOutput(
2174
+ tree.unilateralRefundWithoutBoltzLeaf.output
2175
+ )
2176
+ }
2177
+ }
2178
+ });
2179
+ }
2180
+ }
2181
+ return { reverseSwaps, submarineSwaps };
2182
+ }
2183
+ // Swap enrichment and validation helpers
2184
+ /**
2185
+ * Enrich a restored reverse swap with its preimage.
2186
+ * This makes the swap claimable via `claimVHTLC`.
2187
+ * Validates that the preimage hash matches the swap's expected preimageHash.
2188
+ *
2189
+ * @param swap - The restored reverse swap to enrich.
2190
+ * @param preimage - The preimage (hex-encoded) for the swap.
2191
+ * @returns The enriched swap object (same reference, mutated).
2192
+ * @throws Error if the preimage does not match the swap's preimageHash.
2193
+ */
2194
+ enrichReverseSwapPreimage(swap, preimage) {
2195
+ const computedHash = hex3.encode(sha2562(hex3.decode(preimage)));
2196
+ if (computedHash !== swap.request.preimageHash) {
2197
+ throw new Error(
2198
+ `Preimage does not match swap: expected hash ${swap.request.preimageHash}, got ${computedHash}`
2199
+ );
2200
+ }
2201
+ swap.preimage = preimage;
2202
+ return swap;
2203
+ }
2204
+ /**
2205
+ * Enrich a restored submarine swap with its invoice.
2206
+ * This makes the swap refundable via `refundVHTLC`.
2207
+ * Validates that the invoice is well-formed and its payment hash can be extracted.
2208
+ * If the swap has a preimageHash (from restoration), validates that the invoice's
2209
+ * payment hash matches.
2210
+ *
2211
+ * @param swap - The restored submarine swap to enrich.
2212
+ * @param invoice - The Lightning invoice for the swap.
2213
+ * @returns The enriched swap object (same reference, mutated).
2214
+ * @throws Error if the invoice is invalid, cannot be decoded, or payment hash doesn't match.
2215
+ */
2216
+ enrichSubmarineSwapInvoice(swap, invoice) {
2217
+ let paymentHash;
2218
+ try {
2219
+ const decoded = decodeInvoice(invoice);
2220
+ if (!decoded.paymentHash) {
2221
+ throw new Error("Invoice missing payment hash");
2222
+ }
2223
+ paymentHash = decoded.paymentHash;
2224
+ } catch (error) {
2225
+ if (error instanceof Error) {
2226
+ throw new Error(`Invalid Lightning invoice: ${error.message}`);
2227
+ }
2228
+ throw new Error(`Invalid Lightning invoice format`);
2229
+ }
2230
+ if (swap.preimageHash && paymentHash !== swap.preimageHash) {
2231
+ throw new Error(
2232
+ `Invoice payment hash does not match swap: expected ${swap.preimageHash}, got ${paymentHash}`
2233
+ );
2234
+ }
2235
+ swap.request.invoice = invoice;
2236
+ return swap;
2237
+ }
1972
2238
  async claimVHTLCwithOffchainTx(vhtlcIdentity, vhtlcScript, serverXOnlyPublicKey, input, output, arkInfos) {
1973
- const rawCheckpointTapscript = hex2.decode(arkInfos.checkpointTapscript);
2239
+ const rawCheckpointTapscript = hex3.decode(arkInfos.checkpointTapscript);
1974
2240
  const serverUnrollScript = CSVMultisigTapscript2.decode(
1975
2241
  rawCheckpointTapscript
1976
2242
  );
@@ -2003,7 +2269,7 @@ var ArkadeLightning = class {
2003
2269
  await this.arkProvider.finalizeTx(arkTxid, finalCheckpoints);
2004
2270
  }
2005
2271
  async refundVHTLCwithOffchainTx(pendingSwap, boltzXOnlyPublicKey, ourXOnlyPublicKey, serverXOnlyPublicKey, input, output, arkInfos) {
2006
- const rawCheckpointTapscript = hex2.decode(arkInfos.checkpointTapscript);
2272
+ const rawCheckpointTapscript = hex3.decode(arkInfos.checkpointTapscript);
2007
2273
  const serverUnrollScript = CSVMultisigTapscript2.decode(
2008
2274
  rawCheckpointTapscript
2009
2275
  );
@@ -2021,7 +2287,7 @@ var ArkadeLightning = class {
2021
2287
  unsignedRefundTx,
2022
2288
  unsignedCheckpointTx
2023
2289
  );
2024
- const boltzXOnlyPublicKeyHex = hex2.encode(boltzXOnlyPublicKey);
2290
+ const boltzXOnlyPublicKeyHex = hex3.encode(boltzXOnlyPublicKey);
2025
2291
  if (!verifySignatures(boltzSignedRefundTx, 0, [boltzXOnlyPublicKeyHex])) {
2026
2292
  throw new Error("Invalid Boltz signature in refund transaction");
2027
2293
  }
@@ -2049,9 +2315,9 @@ var ArkadeLightning = class {
2049
2315
  const tx = Transaction4.fromPSBT(base643.decode(finalArkTx));
2050
2316
  const inputIndex = 0;
2051
2317
  const requiredSigners = [
2052
- hex2.encode(ourXOnlyPublicKey),
2053
- hex2.encode(boltzXOnlyPublicKey),
2054
- hex2.encode(serverXOnlyPublicKey)
2318
+ hex3.encode(ourXOnlyPublicKey),
2319
+ hex3.encode(boltzXOnlyPublicKey),
2320
+ hex3.encode(serverXOnlyPublicKey)
2055
2321
  ];
2056
2322
  if (!verifySignatures(tx, inputIndex, requiredSigners)) {
2057
2323
  throw new Error("Invalid refund transaction");
@@ -2112,15 +2378,15 @@ var ArkadeLightning = class {
2112
2378
  timeoutBlockHeights
2113
2379
  }) {
2114
2380
  const receiverXOnlyPublicKey = this.normalizeToXOnlyPublicKey(
2115
- hex2.decode(receiverPubkey),
2381
+ hex3.decode(receiverPubkey),
2116
2382
  "receiver"
2117
2383
  );
2118
2384
  const senderXOnlyPublicKey = this.normalizeToXOnlyPublicKey(
2119
- hex2.decode(senderPubkey),
2385
+ hex3.decode(senderPubkey),
2120
2386
  "sender"
2121
2387
  );
2122
2388
  const serverXOnlyPublicKey = this.normalizeToXOnlyPublicKey(
2123
- hex2.decode(serverPubkey),
2389
+ hex3.decode(serverPubkey),
2124
2390
  "server"
2125
2391
  );
2126
2392
  const delayType = (num) => num < 512 ? "blocks" : "seconds";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arkade-os/boltz-swap",
3
- "version": "0.2.13",
3
+ "version": "0.2.14",
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",
@@ -34,6 +34,7 @@
34
34
  "@noble/hashes": "2.0.1",
35
35
  "@scure/base": "2.0.0",
36
36
  "@scure/btc-signer": "2.0.1",
37
+ "bip68": "^1.0.4",
37
38
  "light-bolt11-decoder": "3.2.0"
38
39
  },
39
40
  "devDependencies": {