@arkade-os/boltz-swap 0.2.13 → 0.2.15

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.cjs CHANGED
@@ -138,7 +138,7 @@ var TransactionRefundedError = class extends SwapError {
138
138
  // src/arkade-lightning.ts
139
139
  var import_sdk5 = require("@arkade-os/sdk");
140
140
  var import_sha22 = require("@noble/hashes/sha2.js");
141
- var import_base3 = require("@scure/base");
141
+ var import_base4 = require("@scure/base");
142
142
  var import_utils = require("@noble/hashes/utils.js");
143
143
 
144
144
  // src/boltz-swap-provider.ts
@@ -180,7 +180,7 @@ var isSubmarineSwapRefundable = (swap) => {
180
180
  return isSubmarineRefundableStatus(swap.status) && isPendingSubmarineSwap(swap) && swap.refundable !== false && swap.refunded !== true;
181
181
  };
182
182
  var isGetReverseSwapTxIdResponse = (data) => {
183
- return data && typeof data === "object" && typeof data.id === "string" && typeof data.timeoutBlockHeight === "number";
183
+ return data && typeof data === "object" && typeof data.id === "string" && typeof data.hex === "string" && typeof data.timeoutBlockHeight === "number";
184
184
  };
185
185
  var isGetSwapStatusResponse = (data) => {
186
186
  return data && typeof data === "object" && typeof data.status === "string" && (data.zeroConfRejected === void 0 || typeof data.zeroConfRejected === "boolean") && (data.transaction === void 0 || data.transaction && typeof data.transaction === "object" && typeof data.transaction.id === "string" && (data.transaction.eta === void 0 || typeof data.transaction.eta === "number") && (data.transaction.hex === void 0 || typeof data.transaction.hex === "string") && (data.transaction.preimage === void 0 || typeof data.transaction.preimage === "string"));
@@ -203,6 +203,26 @@ var isCreateReverseSwapResponse = (data) => {
203
203
  var isRefundSubmarineSwapResponse = (data) => {
204
204
  return data && typeof data === "object" && typeof data.transaction === "string" && typeof data.checkpoint === "string";
205
205
  };
206
+ var isLeaf = (data) => {
207
+ return data && typeof data === "object" && typeof data.version === "number" && typeof data.output === "string";
208
+ };
209
+ var isTree = (data) => {
210
+ return data && typeof data === "object" && isLeaf(data.claimLeaf) && isLeaf(data.refundLeaf) && isLeaf(data.refundWithoutBoltzLeaf) && isLeaf(data.unilateralClaimLeaf) && isLeaf(data.unilateralRefundLeaf) && isLeaf(data.unilateralRefundWithoutBoltzLeaf);
211
+ };
212
+ var isDetails = (data) => {
213
+ 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");
214
+ };
215
+ var isRestoredSubmarineSwap = (data) => {
216
+ 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);
217
+ };
218
+ var isRestoredReverseSwap = (data) => {
219
+ 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);
220
+ };
221
+ var isCreateSwapsRestoreResponse = (data) => {
222
+ return Array.isArray(data) && data.every(
223
+ (item) => isRestoredReverseSwap(item) || isRestoredSubmarineSwap(item)
224
+ );
225
+ };
206
226
  var BASE_URLS = {
207
227
  mutinynet: "https://api.boltz.mutinynet.arkade.sh",
208
228
  regtest: "http://localhost:9069"
@@ -433,6 +453,21 @@ var BoltzSwapProvider = class {
433
453
  };
434
454
  });
435
455
  }
456
+ async restoreSwaps(publicKey) {
457
+ const requestBody = {
458
+ publicKey
459
+ };
460
+ const response = await this.request(
461
+ "/v2/swap/restore",
462
+ "POST",
463
+ requestBody
464
+ );
465
+ if (!isCreateSwapsRestoreResponse(response))
466
+ throw new SchemaError({
467
+ message: "Invalid schema in response for swap restoration"
468
+ });
469
+ return response;
470
+ }
436
471
  async request(path, method, body) {
437
472
  const url = `${this.apiUrl}${path}`;
438
473
  try {
@@ -465,7 +500,7 @@ var BoltzSwapProvider = class {
465
500
  };
466
501
 
467
502
  // src/arkade-lightning.ts
468
- var import_btc_signer2 = require("@scure/btc-signer");
503
+ var import_btc_signer3 = require("@scure/btc-signer");
469
504
  var import_legacy = require("@noble/hashes/legacy.js");
470
505
 
471
506
  // src/utils/decoding.ts
@@ -500,6 +535,50 @@ var verifySignatures = (tx, inputIndex, requiredSigners) => {
500
535
  }
501
536
  };
502
537
 
538
+ // src/utils/restoration.ts
539
+ var import_base2 = require("@scure/base");
540
+ var import_btc_signer = require("@scure/btc-signer");
541
+ var import_bip68 = __toESM(require("bip68"), 1);
542
+ function extractTimeLockFromLeafOutput(scriptHex) {
543
+ if (!scriptHex) return 0;
544
+ try {
545
+ const opcodes = import_btc_signer.Script.decode(import_base2.hex.decode(scriptHex));
546
+ const hasCLTV = opcodes.findIndex((op) => op === "CHECKLOCKTIMEVERIFY");
547
+ if (hasCLTV > 0) {
548
+ const data = opcodes[hasCLTV - 1];
549
+ if (data instanceof Uint8Array) {
550
+ const dataBytes = new Uint8Array(data).reverse();
551
+ return parseInt(import_base2.hex.encode(dataBytes), 16);
552
+ }
553
+ }
554
+ const hasCSV = opcodes.findIndex((op) => op === "CHECKSEQUENCEVERIFY");
555
+ if (hasCSV > 0) {
556
+ const data = opcodes[hasCSV - 1];
557
+ if (data instanceof Uint8Array) {
558
+ const dataBytes = new Uint8Array(data).reverse();
559
+ const {
560
+ blocks,
561
+ seconds
562
+ } = import_bip68.default.decode(
563
+ parseInt(import_base2.hex.encode(dataBytes), 16)
564
+ );
565
+ return blocks ?? seconds ?? 0;
566
+ }
567
+ }
568
+ } catch (error) {
569
+ return 0;
570
+ }
571
+ return 0;
572
+ }
573
+ function extractInvoiceAmount(amountSats, fees) {
574
+ if (!amountSats) return 0;
575
+ const { percentage, minerFees } = fees.reverse;
576
+ const miner = minerFees.lockup + minerFees.claim;
577
+ if (percentage >= 100 || percentage < 0) return 0;
578
+ if (miner >= amountSats) return 0;
579
+ return Math.ceil((amountSats - miner) / (1 - percentage / 100));
580
+ }
581
+
503
582
  // src/logger.ts
504
583
  var logger = console;
505
584
  function setLogger(customLogger) {
@@ -997,6 +1076,12 @@ var SwapManager = class {
997
1076
  try {
998
1077
  this.swapsInProgress.add(swap.id);
999
1078
  if (isPendingReverseSwap(swap)) {
1079
+ if (!swap.preimage || swap.preimage.length === 0) {
1080
+ logger.log(
1081
+ `Skipping claim for swap ${swap.id}: missing preimage (restored swap)`
1082
+ );
1083
+ return;
1084
+ }
1000
1085
  if (isReverseClaimableStatus(swap.status)) {
1001
1086
  logger.log(`Auto-claiming reverse swap ${swap.id}`);
1002
1087
  await this.executeClaimAction(swap);
@@ -1005,6 +1090,12 @@ var SwapManager = class {
1005
1090
  );
1006
1091
  }
1007
1092
  } else if (isPendingSubmarineSwap(swap)) {
1093
+ if (!swap.request?.invoice || swap.request.invoice.length === 0) {
1094
+ logger.log(
1095
+ `Skipping refund for swap ${swap.id}: missing invoice (restored swap)`
1096
+ );
1097
+ return;
1098
+ }
1008
1099
  if (isSubmarineRefundableStatus(swap.status)) {
1009
1100
  logger.log(`Auto-refunding submarine swap ${swap.id}`);
1010
1101
  await this.executeRefundAction(swap);
@@ -1212,13 +1303,13 @@ function claimVHTLCIdentity(identity, preimage) {
1212
1303
  // src/batch.ts
1213
1304
  var import_sdk4 = require("@arkade-os/sdk");
1214
1305
  var import_sha2 = require("@noble/hashes/sha2.js");
1215
- var import_base2 = require("@scure/base");
1216
- var import_btc_signer = require("@scure/btc-signer");
1306
+ var import_base3 = require("@scure/base");
1307
+ var import_btc_signer2 = require("@scure/btc-signer");
1217
1308
  var import_payment = require("@scure/btc-signer/payment.js");
1218
1309
  function createVHTLCBatchHandler(intentId, vhtlc, arkProvider, identity, session, sweepPublicKey, forfeitOutputScript, connectorIndex = 0) {
1219
1310
  const utf8IntentId = new TextEncoder().encode(intentId);
1220
1311
  const intentIdHash = (0, import_sha2.sha256)(utf8IntentId);
1221
- const intentIdHashStr = import_base2.hex.encode(intentIdHash);
1312
+ const intentIdHashStr = import_base3.hex.encode(intentIdHash);
1222
1313
  let sweepTapTreeRoot;
1223
1314
  return {
1224
1315
  onBatchStarted: async (event) => {
@@ -1257,11 +1348,11 @@ function createVHTLCBatchHandler(intentId, vhtlc, arkProvider, identity, session
1257
1348
  );
1258
1349
  const signerPublicKey = await session.getPublicKey();
1259
1350
  const xonlySignerPublicKey = signerPublicKey.subarray(1);
1260
- if (!xOnlyPublicKeys.includes(import_base2.hex.encode(xonlySignerPublicKey))) {
1351
+ if (!xOnlyPublicKeys.includes(import_base3.hex.encode(xonlySignerPublicKey))) {
1261
1352
  return { skip: true };
1262
1353
  }
1263
1354
  const commitmentTx = import_sdk4.Transaction.fromPSBT(
1264
- import_base2.base64.decode(event.unsignedCommitmentTx)
1355
+ import_base3.base64.decode(event.unsignedCommitmentTx)
1265
1356
  );
1266
1357
  (0, import_sdk4.validateVtxoTxGraph)(vtxoTree, commitmentTx, sweepTapTreeRoot);
1267
1358
  const sharedOutput = commitmentTx.getOutput(0);
@@ -1269,7 +1360,7 @@ function createVHTLCBatchHandler(intentId, vhtlc, arkProvider, identity, session
1269
1360
  throw new Error("Shared output not found");
1270
1361
  }
1271
1362
  await session.init(vtxoTree, sweepTapTreeRoot, sharedOutput.amount);
1272
- const pubkey = import_base2.hex.encode(await session.getPublicKey());
1363
+ const pubkey = import_base3.hex.encode(await session.getPublicKey());
1273
1364
  const nonces = await session.getNonces();
1274
1365
  await arkProvider.submitTreeNonces(event.id, pubkey, nonces);
1275
1366
  return { skip: false };
@@ -1284,7 +1375,7 @@ function createVHTLCBatchHandler(intentId, vhtlc, arkProvider, identity, session
1284
1375
  );
1285
1376
  if (!hasAllNonces) return { fullySigned: false };
1286
1377
  const signatures = await session.sign();
1287
- const pubkey = import_base2.hex.encode(await session.getPublicKey());
1378
+ const pubkey = import_base3.hex.encode(await session.getPublicKey());
1288
1379
  await arkProvider.submitTreeSignatures(
1289
1380
  event.id,
1290
1381
  pubkey,
@@ -1315,7 +1406,7 @@ function createVHTLCBatchHandler(intentId, vhtlc, arkProvider, identity, session
1315
1406
  );
1316
1407
  const signedForfeitTx = await identity.sign(forfeitTx);
1317
1408
  await arkProvider.submitSignedForfeitTxs([
1318
- import_base2.base64.encode(signedForfeitTx.toPSBT())
1409
+ import_base3.base64.encode(signedForfeitTx.toPSBT())
1319
1410
  ]);
1320
1411
  }
1321
1412
  };
@@ -1341,7 +1432,7 @@ function createForfeitTx(input, forfeitOutputScript, connector) {
1341
1432
  amount: BigInt(input.value),
1342
1433
  script: import_sdk4.VtxoScript.decode(input.tapTree).pkScript
1343
1434
  },
1344
- sighashType: import_btc_signer.SigHash.DEFAULT,
1435
+ sighashType: import_btc_signer2.SigHash.DEFAULT,
1345
1436
  tapLeafScript: [input.tapLeafScript],
1346
1437
  sequence
1347
1438
  },
@@ -1537,7 +1628,7 @@ var ArkadeLightning = class {
1537
1628
  * @returns The created pending submarine swap.
1538
1629
  */
1539
1630
  async createSubmarineSwap(args) {
1540
- const refundPublicKey = import_base3.hex.encode(
1631
+ const refundPublicKey = import_base4.hex.encode(
1541
1632
  await this.wallet.identity.compressedPublicKey()
1542
1633
  );
1543
1634
  if (!refundPublicKey)
@@ -1573,7 +1664,7 @@ var ArkadeLightning = class {
1573
1664
  async createReverseSwap(args) {
1574
1665
  if (args.amount <= 0)
1575
1666
  throw new SwapError({ message: "Amount must be greater than 0" });
1576
- const claimPublicKey = import_base3.hex.encode(
1667
+ const claimPublicKey = import_base4.hex.encode(
1577
1668
  await this.wallet.identity.compressedPublicKey()
1578
1669
  );
1579
1670
  if (!claimPublicKey)
@@ -1581,7 +1672,7 @@ var ArkadeLightning = class {
1581
1672
  message: "Failed to get claim public key from wallet"
1582
1673
  });
1583
1674
  const preimage = (0, import_utils.randomBytes)(32);
1584
- const preimageHash = import_base3.hex.encode((0, import_sha22.sha256)(preimage));
1675
+ const preimageHash = import_base4.hex.encode((0, import_sha22.sha256)(preimage));
1585
1676
  if (!preimageHash)
1586
1677
  throw new SwapError({ message: "Failed to get preimage hash" });
1587
1678
  const swapRequest = {
@@ -1595,7 +1686,7 @@ var ArkadeLightning = class {
1595
1686
  id: swapResponse.id,
1596
1687
  type: "reverse",
1597
1688
  createdAt: Math.floor(Date.now() / 1e3),
1598
- preimage: import_base3.hex.encode(preimage),
1689
+ preimage: import_base4.hex.encode(preimage),
1599
1690
  request: swapRequest,
1600
1691
  response: swapResponse,
1601
1692
  status: "swap.created"
@@ -1612,7 +1703,9 @@ var ArkadeLightning = class {
1612
1703
  * @param pendingSwap - The pending reverse swap to claim the VHTLC.
1613
1704
  */
1614
1705
  async claimVHTLC(pendingSwap) {
1615
- const preimage = import_base3.hex.decode(pendingSwap.preimage);
1706
+ if (!pendingSwap.preimage)
1707
+ throw new Error("Preimage is required to claim VHTLC");
1708
+ const preimage = import_base4.hex.decode(pendingSwap.preimage);
1616
1709
  const aspInfo = await this.arkProvider.getInfo();
1617
1710
  const address = await this.wallet.getAddress();
1618
1711
  const ourXOnlyPublicKey = this.normalizeToXOnlyPublicKey(
@@ -1621,21 +1714,21 @@ var ArkadeLightning = class {
1621
1714
  pendingSwap.id
1622
1715
  );
1623
1716
  const boltzXOnlyPublicKey = this.normalizeToXOnlyPublicKey(
1624
- import_base3.hex.decode(pendingSwap.response.refundPublicKey),
1717
+ import_base4.hex.decode(pendingSwap.response.refundPublicKey),
1625
1718
  "boltz",
1626
1719
  pendingSwap.id
1627
1720
  );
1628
1721
  const serverXOnlyPublicKey = this.normalizeToXOnlyPublicKey(
1629
- import_base3.hex.decode(aspInfo.signerPubkey),
1722
+ import_base4.hex.decode(aspInfo.signerPubkey),
1630
1723
  "server",
1631
1724
  pendingSwap.id
1632
1725
  );
1633
1726
  const { vhtlcScript, vhtlcAddress } = this.createVHTLCScript({
1634
1727
  network: aspInfo.network,
1635
1728
  preimageHash: (0, import_sha22.sha256)(preimage),
1636
- receiverPubkey: import_base3.hex.encode(ourXOnlyPublicKey),
1637
- senderPubkey: import_base3.hex.encode(boltzXOnlyPublicKey),
1638
- serverPubkey: import_base3.hex.encode(serverXOnlyPublicKey),
1729
+ receiverPubkey: import_base4.hex.encode(ourXOnlyPublicKey),
1730
+ senderPubkey: import_base4.hex.encode(boltzXOnlyPublicKey),
1731
+ serverPubkey: import_base4.hex.encode(serverXOnlyPublicKey),
1639
1732
  timeoutBlockHeights: pendingSwap.response.timeoutBlockHeights
1640
1733
  });
1641
1734
  if (!vhtlcScript)
@@ -1643,7 +1736,7 @@ var ArkadeLightning = class {
1643
1736
  if (vhtlcAddress !== pendingSwap.response.lockupAddress)
1644
1737
  throw new Error("Boltz is trying to scam us");
1645
1738
  const { vtxos } = await this.indexerProvider.getVtxos({
1646
- scripts: [import_base3.hex.encode(vhtlcScript.pkScript)]
1739
+ scripts: [import_base4.hex.encode(vhtlcScript.pkScript)]
1647
1740
  });
1648
1741
  if (vtxos.length === 0)
1649
1742
  throw new Error("No spendable virtual coins found");
@@ -1691,11 +1784,14 @@ var ArkadeLightning = class {
1691
1784
  * @param pendingSwap - The pending submarine swap to refund the VHTLC.
1692
1785
  */
1693
1786
  async refundVHTLC(pendingSwap) {
1787
+ const preimageHash = pendingSwap.request.invoice ? getInvoicePaymentHash(pendingSwap.request.invoice) : pendingSwap.preimageHash;
1788
+ if (!preimageHash)
1789
+ throw new Error("Preimage hash is required to refund VHTLC");
1694
1790
  const vhtlcPkScript = import_sdk5.ArkAddress.decode(
1695
1791
  pendingSwap.response.address
1696
1792
  ).pkScript;
1697
1793
  const { vtxos } = await this.indexerProvider.getVtxos({
1698
- scripts: [import_base3.hex.encode(vhtlcPkScript)]
1794
+ scripts: [import_base4.hex.encode(vhtlcPkScript)]
1699
1795
  });
1700
1796
  if (vtxos.length === 0) {
1701
1797
  throw new Error(
@@ -1715,23 +1811,21 @@ var ArkadeLightning = class {
1715
1811
  pendingSwap.id
1716
1812
  );
1717
1813
  const serverXOnlyPublicKey = this.normalizeToXOnlyPublicKey(
1718
- import_base3.hex.decode(aspInfo.signerPubkey),
1814
+ import_base4.hex.decode(aspInfo.signerPubkey),
1719
1815
  "server",
1720
1816
  pendingSwap.id
1721
1817
  );
1722
1818
  const boltzXOnlyPublicKey = this.normalizeToXOnlyPublicKey(
1723
- import_base3.hex.decode(pendingSwap.response.claimPublicKey),
1819
+ import_base4.hex.decode(pendingSwap.response.claimPublicKey),
1724
1820
  "boltz",
1725
1821
  pendingSwap.id
1726
1822
  );
1727
1823
  const { vhtlcScript } = this.createVHTLCScript({
1728
1824
  network: aspInfo.network,
1729
- preimageHash: import_base3.hex.decode(
1730
- getInvoicePaymentHash(pendingSwap.request.invoice)
1731
- ),
1732
- receiverPubkey: import_base3.hex.encode(boltzXOnlyPublicKey),
1733
- senderPubkey: import_base3.hex.encode(ourXOnlyPublicKey),
1734
- serverPubkey: import_base3.hex.encode(serverXOnlyPublicKey),
1825
+ preimageHash: import_base4.hex.decode(preimageHash),
1826
+ receiverPubkey: import_base4.hex.encode(boltzXOnlyPublicKey),
1827
+ senderPubkey: import_base4.hex.encode(ourXOnlyPublicKey),
1828
+ serverPubkey: import_base4.hex.encode(serverXOnlyPublicKey),
1735
1829
  timeoutBlockHeights: pendingSwap.response.timeoutBlockHeights
1736
1830
  });
1737
1831
  if (!vhtlcScript)
@@ -1787,14 +1881,14 @@ var ArkadeLightning = class {
1787
1881
  onchain_output_indexes: [],
1788
1882
  valid_at: 0,
1789
1883
  expire_at: 0,
1790
- cosigners_public_keys: [import_base3.hex.encode(signerPublicKey)]
1884
+ cosigners_public_keys: [import_base4.hex.encode(signerPublicKey)]
1791
1885
  };
1792
1886
  const deleteMessage = {
1793
1887
  type: "delete",
1794
1888
  expire_at: 0
1795
1889
  };
1796
1890
  const intentInput = {
1797
- txid: import_base3.hex.decode(input.txid),
1891
+ txid: import_base4.hex.decode(input.txid),
1798
1892
  index: input.vout,
1799
1893
  witnessUtxo: {
1800
1894
  amount: BigInt(input.value),
@@ -1817,9 +1911,9 @@ var ArkadeLightning = class {
1817
1911
  const abortController = new AbortController();
1818
1912
  const intentId = await this.arkProvider.registerIntent({
1819
1913
  message: intentMessage,
1820
- proof: import_base3.base64.encode(signedRegisterIntent.toPSBT())
1914
+ proof: import_base4.base64.encode(signedRegisterIntent.toPSBT())
1821
1915
  });
1822
- const decodedAddress = (0, import_btc_signer2.Address)(
1916
+ const decodedAddress = (0, import_btc_signer3.Address)(
1823
1917
  network in import_sdk5.networks ? import_sdk5.networks[network] : import_sdk5.networks.bitcoin
1824
1918
  ).decode(forfeitAddress);
1825
1919
  try {
@@ -1829,11 +1923,11 @@ var ArkadeLightning = class {
1829
1923
  this.arkProvider,
1830
1924
  identity,
1831
1925
  signerSession,
1832
- import_base3.hex.decode(forfeitPubkey).slice(1),
1833
- isRecoverable2 ? void 0 : import_btc_signer2.OutScript.encode(decodedAddress)
1926
+ import_base4.hex.decode(forfeitPubkey).slice(1),
1927
+ isRecoverable2 ? void 0 : import_btc_signer3.OutScript.encode(decodedAddress)
1834
1928
  );
1835
1929
  const topics = [
1836
- import_base3.hex.encode(signerPublicKey),
1930
+ import_base4.hex.encode(signerPublicKey),
1837
1931
  `${input.txid}:${input.vout}`
1838
1932
  ];
1839
1933
  const eventStream = this.arkProvider.getEventStream(
@@ -1854,7 +1948,7 @@ var ArkadeLightning = class {
1854
1948
  try {
1855
1949
  await this.arkProvider.deleteIntent({
1856
1950
  message: deleteMessage,
1857
- proof: import_base3.base64.encode(signedDeleteIntent.toPSBT())
1951
+ proof: import_base4.base64.encode(signedDeleteIntent.toPSBT())
1858
1952
  });
1859
1953
  } catch (error2) {
1860
1954
  logger.error("Failed to delete intent:", error2);
@@ -2006,8 +2100,179 @@ var ArkadeLightning = class {
2006
2100
  });
2007
2101
  });
2008
2102
  }
2103
+ /**
2104
+ * Restore swaps from Boltz API.
2105
+ *
2106
+ * Note: restored swaps may lack local-only data such as the original
2107
+ * Lightning invoice or preimage. They are intended primarily for
2108
+ * display/monitoring and are not automatically wired into the SwapManager.
2109
+ * Do not call `claimVHTLC` / `refundVHTLC` on them unless you have
2110
+ * enriched the objects with the missing fields.
2111
+ *
2112
+ * @param boltzFees - Optional fees response to use for restoration.
2113
+ * @returns An object containing arrays of restored reverse and submarine swaps.
2114
+ */
2115
+ async restoreSwaps(boltzFees) {
2116
+ const publicKey = import_base4.hex.encode(
2117
+ await this.wallet.identity.compressedPublicKey()
2118
+ );
2119
+ if (!publicKey) throw new Error("Failed to get public key from wallet");
2120
+ const fees = boltzFees ?? await this.swapProvider.getFees();
2121
+ const reverseSwaps = [];
2122
+ const submarineSwaps = [];
2123
+ const restoredSwaps = await this.swapProvider.restoreSwaps(publicKey);
2124
+ for (const swap of restoredSwaps) {
2125
+ const { id, createdAt, status } = swap;
2126
+ if (isRestoredReverseSwap(swap)) {
2127
+ const {
2128
+ amount,
2129
+ lockupAddress,
2130
+ preimageHash,
2131
+ serverPublicKey,
2132
+ tree
2133
+ } = swap.claimDetails;
2134
+ reverseSwaps.push({
2135
+ id,
2136
+ createdAt,
2137
+ request: {
2138
+ invoiceAmount: extractInvoiceAmount(amount, fees),
2139
+ claimPublicKey: publicKey,
2140
+ preimageHash
2141
+ },
2142
+ response: {
2143
+ id,
2144
+ invoice: "",
2145
+ // TODO check if we can get the invoice from boltz
2146
+ onchainAmount: amount,
2147
+ lockupAddress,
2148
+ refundPublicKey: serverPublicKey,
2149
+ timeoutBlockHeights: {
2150
+ refund: extractTimeLockFromLeafOutput(
2151
+ tree.refundWithoutBoltzLeaf.output
2152
+ ),
2153
+ unilateralClaim: extractTimeLockFromLeafOutput(
2154
+ tree.unilateralClaimLeaf.output
2155
+ ),
2156
+ unilateralRefund: extractTimeLockFromLeafOutput(
2157
+ tree.unilateralRefundLeaf.output
2158
+ ),
2159
+ unilateralRefundWithoutReceiver: extractTimeLockFromLeafOutput(
2160
+ tree.unilateralRefundWithoutBoltzLeaf.output
2161
+ )
2162
+ }
2163
+ },
2164
+ status,
2165
+ type: "reverse",
2166
+ preimage: ""
2167
+ });
2168
+ } else if (isRestoredSubmarineSwap(swap)) {
2169
+ const { amount, lockupAddress, serverPublicKey, tree } = swap.refundDetails;
2170
+ let preimage = "";
2171
+ try {
2172
+ const data = await this.swapProvider.getSwapPreimage(
2173
+ swap.id
2174
+ );
2175
+ preimage = data.preimage;
2176
+ } catch (error) {
2177
+ logger.warn(
2178
+ `Failed to restore preimage for submarine swap ${id}`,
2179
+ error
2180
+ );
2181
+ }
2182
+ submarineSwaps.push({
2183
+ id,
2184
+ type: "submarine",
2185
+ createdAt,
2186
+ preimage,
2187
+ preimageHash: swap.preimageHash,
2188
+ status,
2189
+ request: {
2190
+ invoice: "",
2191
+ // TODO check if we can get the invoice from boltz
2192
+ refundPublicKey: publicKey
2193
+ },
2194
+ response: {
2195
+ id,
2196
+ address: lockupAddress,
2197
+ expectedAmount: amount,
2198
+ claimPublicKey: serverPublicKey,
2199
+ timeoutBlockHeights: {
2200
+ refund: extractTimeLockFromLeafOutput(
2201
+ tree.refundWithoutBoltzLeaf.output
2202
+ ),
2203
+ unilateralClaim: extractTimeLockFromLeafOutput(
2204
+ tree.unilateralClaimLeaf.output
2205
+ ),
2206
+ unilateralRefund: extractTimeLockFromLeafOutput(
2207
+ tree.unilateralRefundLeaf.output
2208
+ ),
2209
+ unilateralRefundWithoutReceiver: extractTimeLockFromLeafOutput(
2210
+ tree.unilateralRefundWithoutBoltzLeaf.output
2211
+ )
2212
+ }
2213
+ }
2214
+ });
2215
+ }
2216
+ }
2217
+ return { reverseSwaps, submarineSwaps };
2218
+ }
2219
+ // Swap enrichment and validation helpers
2220
+ /**
2221
+ * Enrich a restored reverse swap with its preimage.
2222
+ * This makes the swap claimable via `claimVHTLC`.
2223
+ * Validates that the preimage hash matches the swap's expected preimageHash.
2224
+ *
2225
+ * @param swap - The restored reverse swap to enrich.
2226
+ * @param preimage - The preimage (hex-encoded) for the swap.
2227
+ * @returns The enriched swap object (same reference, mutated).
2228
+ * @throws Error if the preimage does not match the swap's preimageHash.
2229
+ */
2230
+ enrichReverseSwapPreimage(swap, preimage) {
2231
+ const computedHash = import_base4.hex.encode((0, import_sha22.sha256)(import_base4.hex.decode(preimage)));
2232
+ if (computedHash !== swap.request.preimageHash) {
2233
+ throw new Error(
2234
+ `Preimage does not match swap: expected hash ${swap.request.preimageHash}, got ${computedHash}`
2235
+ );
2236
+ }
2237
+ swap.preimage = preimage;
2238
+ return swap;
2239
+ }
2240
+ /**
2241
+ * Enrich a restored submarine swap with its invoice.
2242
+ * This makes the swap refundable via `refundVHTLC`.
2243
+ * Validates that the invoice is well-formed and its payment hash can be extracted.
2244
+ * If the swap has a preimageHash (from restoration), validates that the invoice's
2245
+ * payment hash matches.
2246
+ *
2247
+ * @param swap - The restored submarine swap to enrich.
2248
+ * @param invoice - The Lightning invoice for the swap.
2249
+ * @returns The enriched swap object (same reference, mutated).
2250
+ * @throws Error if the invoice is invalid, cannot be decoded, or payment hash doesn't match.
2251
+ */
2252
+ enrichSubmarineSwapInvoice(swap, invoice) {
2253
+ let paymentHash;
2254
+ try {
2255
+ const decoded = decodeInvoice(invoice);
2256
+ if (!decoded.paymentHash) {
2257
+ throw new Error("Invoice missing payment hash");
2258
+ }
2259
+ paymentHash = decoded.paymentHash;
2260
+ } catch (error) {
2261
+ if (error instanceof Error) {
2262
+ throw new Error(`Invalid Lightning invoice: ${error.message}`);
2263
+ }
2264
+ throw new Error(`Invalid Lightning invoice format`);
2265
+ }
2266
+ if (swap.preimageHash && paymentHash !== swap.preimageHash) {
2267
+ throw new Error(
2268
+ `Invoice payment hash does not match swap: expected ${swap.preimageHash}, got ${paymentHash}`
2269
+ );
2270
+ }
2271
+ swap.request.invoice = invoice;
2272
+ return swap;
2273
+ }
2009
2274
  async claimVHTLCwithOffchainTx(vhtlcIdentity, vhtlcScript, serverXOnlyPublicKey, input, output, arkInfos) {
2010
- const rawCheckpointTapscript = import_base3.hex.decode(arkInfos.checkpointTapscript);
2275
+ const rawCheckpointTapscript = import_base4.hex.decode(arkInfos.checkpointTapscript);
2011
2276
  const serverUnrollScript = import_sdk5.CSVMultisigTapscript.decode(
2012
2277
  rawCheckpointTapscript
2013
2278
  );
@@ -2018,8 +2283,8 @@ var ArkadeLightning = class {
2018
2283
  );
2019
2284
  const signedArkTx = await vhtlcIdentity.sign(arkTx);
2020
2285
  const { arkTxid, finalArkTx, signedCheckpointTxs } = await this.arkProvider.submitTx(
2021
- import_base3.base64.encode(signedArkTx.toPSBT()),
2022
- checkpoints.map((c) => import_base3.base64.encode(c.toPSBT()))
2286
+ import_base4.base64.encode(signedArkTx.toPSBT()),
2287
+ checkpoints.map((c) => import_base4.base64.encode(c.toPSBT()))
2023
2288
  );
2024
2289
  if (!this.validFinalArkTx(
2025
2290
  finalArkTx,
@@ -2030,17 +2295,17 @@ var ArkadeLightning = class {
2030
2295
  }
2031
2296
  const finalCheckpoints = await Promise.all(
2032
2297
  signedCheckpointTxs.map(async (c) => {
2033
- const tx = import_btc_signer2.Transaction.fromPSBT(import_base3.base64.decode(c), {
2298
+ const tx = import_btc_signer3.Transaction.fromPSBT(import_base4.base64.decode(c), {
2034
2299
  allowUnknown: true
2035
2300
  });
2036
2301
  const signedCheckpoint = await vhtlcIdentity.sign(tx, [0]);
2037
- return import_base3.base64.encode(signedCheckpoint.toPSBT());
2302
+ return import_base4.base64.encode(signedCheckpoint.toPSBT());
2038
2303
  })
2039
2304
  );
2040
2305
  await this.arkProvider.finalizeTx(arkTxid, finalCheckpoints);
2041
2306
  }
2042
2307
  async refundVHTLCwithOffchainTx(pendingSwap, boltzXOnlyPublicKey, ourXOnlyPublicKey, serverXOnlyPublicKey, input, output, arkInfos) {
2043
- const rawCheckpointTapscript = import_base3.hex.decode(arkInfos.checkpointTapscript);
2308
+ const rawCheckpointTapscript = import_base4.hex.decode(arkInfos.checkpointTapscript);
2044
2309
  const serverUnrollScript = import_sdk5.CSVMultisigTapscript.decode(
2045
2310
  rawCheckpointTapscript
2046
2311
  );
@@ -2058,7 +2323,7 @@ var ArkadeLightning = class {
2058
2323
  unsignedRefundTx,
2059
2324
  unsignedCheckpointTx
2060
2325
  );
2061
- const boltzXOnlyPublicKeyHex = import_base3.hex.encode(boltzXOnlyPublicKey);
2326
+ const boltzXOnlyPublicKeyHex = import_base4.hex.encode(boltzXOnlyPublicKey);
2062
2327
  if (!verifySignatures(boltzSignedRefundTx, 0, [boltzXOnlyPublicKeyHex])) {
2063
2328
  throw new Error("Invalid Boltz signature in refund transaction");
2064
2329
  }
@@ -2080,15 +2345,15 @@ var ArkadeLightning = class {
2080
2345
  signedCheckpointTx
2081
2346
  );
2082
2347
  const { arkTxid, finalArkTx, signedCheckpointTxs } = await this.arkProvider.submitTx(
2083
- import_base3.base64.encode(combinedSignedRefundTx.toPSBT()),
2084
- [import_base3.base64.encode(unsignedCheckpointTx.toPSBT())]
2348
+ import_base4.base64.encode(combinedSignedRefundTx.toPSBT()),
2349
+ [import_base4.base64.encode(unsignedCheckpointTx.toPSBT())]
2085
2350
  );
2086
- const tx = import_btc_signer2.Transaction.fromPSBT(import_base3.base64.decode(finalArkTx));
2351
+ const tx = import_btc_signer3.Transaction.fromPSBT(import_base4.base64.decode(finalArkTx));
2087
2352
  const inputIndex = 0;
2088
2353
  const requiredSigners = [
2089
- import_base3.hex.encode(ourXOnlyPublicKey),
2090
- import_base3.hex.encode(boltzXOnlyPublicKey),
2091
- import_base3.hex.encode(serverXOnlyPublicKey)
2354
+ import_base4.hex.encode(ourXOnlyPublicKey),
2355
+ import_base4.hex.encode(boltzXOnlyPublicKey),
2356
+ import_base4.hex.encode(serverXOnlyPublicKey)
2092
2357
  ];
2093
2358
  if (!verifySignatures(tx, inputIndex, requiredSigners)) {
2094
2359
  throw new Error("Invalid refund transaction");
@@ -2098,15 +2363,15 @@ var ArkadeLightning = class {
2098
2363
  `Expected one signed checkpoint transaction, got ${signedCheckpointTxs.length}`
2099
2364
  );
2100
2365
  }
2101
- const serverSignedCheckpointTx = import_btc_signer2.Transaction.fromPSBT(
2102
- import_base3.base64.decode(signedCheckpointTxs[0])
2366
+ const serverSignedCheckpointTx = import_btc_signer3.Transaction.fromPSBT(
2367
+ import_base4.base64.decode(signedCheckpointTxs[0])
2103
2368
  );
2104
2369
  const finalCheckpointTx = (0, import_sdk5.combineTapscriptSigs)(
2105
2370
  combinedSignedCheckpointTx,
2106
2371
  serverSignedCheckpointTx
2107
2372
  );
2108
2373
  await this.arkProvider.finalizeTx(arkTxid, [
2109
- import_base3.base64.encode(finalCheckpointTx.toPSBT())
2374
+ import_base4.base64.encode(finalCheckpointTx.toPSBT())
2110
2375
  ]);
2111
2376
  }
2112
2377
  // validators
@@ -2121,7 +2386,7 @@ var ArkadeLightning = class {
2121
2386
  * @returns True if the final Ark transaction is valid, false otherwise.
2122
2387
  */
2123
2388
  validFinalArkTx = (finalArkTx, _pubkey, _tapLeaves) => {
2124
- const tx = import_btc_signer2.Transaction.fromPSBT(import_base3.base64.decode(finalArkTx), {
2389
+ const tx = import_btc_signer3.Transaction.fromPSBT(import_base4.base64.decode(finalArkTx), {
2125
2390
  allowUnknown: true
2126
2391
  });
2127
2392
  if (!tx) return false;
@@ -2149,15 +2414,15 @@ var ArkadeLightning = class {
2149
2414
  timeoutBlockHeights
2150
2415
  }) {
2151
2416
  const receiverXOnlyPublicKey = this.normalizeToXOnlyPublicKey(
2152
- import_base3.hex.decode(receiverPubkey),
2417
+ import_base4.hex.decode(receiverPubkey),
2153
2418
  "receiver"
2154
2419
  );
2155
2420
  const senderXOnlyPublicKey = this.normalizeToXOnlyPublicKey(
2156
- import_base3.hex.decode(senderPubkey),
2421
+ import_base4.hex.decode(senderPubkey),
2157
2422
  "sender"
2158
2423
  );
2159
2424
  const serverXOnlyPublicKey = this.normalizeToXOnlyPublicKey(
2160
- import_base3.hex.decode(serverPubkey),
2425
+ import_base4.hex.decode(serverPubkey),
2161
2426
  "server"
2162
2427
  );
2163
2428
  const delayType = (num) => num < 512 ? "blocks" : "seconds";