@arkade-os/boltz-swap 0.3.6 → 0.3.8

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.
@@ -2,8 +2,8 @@ import {
2
2
  defineExpoSwapBackgroundTask,
3
3
  registerExpoSwapBackgroundTask,
4
4
  unregisterExpoSwapBackgroundTask
5
- } from "./chunk-LMHVRFGY.js";
6
- import "./chunk-SUFFGYDJ.js";
5
+ } from "./chunk-XUBVFCQZ.js";
6
+ import "./chunk-ZJ2Q5QSA.js";
7
7
  import "./chunk-3RG5ZIWI.js";
8
8
  export {
9
9
  defineExpoSwapBackgroundTask,
@@ -8,7 +8,7 @@ import {
8
8
  isSubmarineFinalStatus,
9
9
  isSubmarineSwapRefundable,
10
10
  logger
11
- } from "./chunk-SUFFGYDJ.js";
11
+ } from "./chunk-ZJ2Q5QSA.js";
12
12
  import {
13
13
  __require
14
14
  } from "./chunk-3RG5ZIWI.js";
@@ -814,10 +814,16 @@ var isValidArkAddress = (address) => {
814
814
  // src/utils/signatures.ts
815
815
  import { verifyTapscriptSignatures } from "@arkade-os/sdk";
816
816
  import { hex } from "@scure/base";
817
- var verifySignatures = (tx, inputIndex, requiredSigners) => {
817
+ var verifySignatures = (tx, inputIndex, requiredSigners, expectedLeafHash) => {
818
818
  try {
819
819
  verifyTapscriptSignatures(tx, inputIndex, requiredSigners);
820
- return true;
820
+ const input = tx.getInput(inputIndex);
821
+ const expectedHex = hex.encode(expectedLeafHash);
822
+ return requiredSigners.every(
823
+ (signer) => input.tapScriptSig?.some(
824
+ ([{ pubKey, leafHash }]) => hex.encode(pubKey) === signer && hex.encode(leafHash) === expectedHex
825
+ )
826
+ );
821
827
  } catch (_) {
822
828
  return false;
823
829
  }
@@ -2429,7 +2435,8 @@ import {
2429
2435
  VtxoScript as VtxoScript2,
2430
2436
  VtxoTaprootTree,
2431
2437
  CSVMultisigTapscript as CSVMultisigTapscript2,
2432
- combineTapscriptSigs
2438
+ combineTapscriptSigs,
2439
+ Transaction as Transaction5
2433
2440
  } from "@arkade-os/sdk";
2434
2441
  import { hex as hex7, base64 as base643 } from "@scure/base";
2435
2442
 
@@ -2593,7 +2600,8 @@ function createForfeitTx(input, forfeitOutputScript, connector) {
2593
2600
 
2594
2601
  // src/utils/vhtlc.ts
2595
2602
  import { ripemd160 } from "@noble/hashes/legacy.js";
2596
- import { Address, OutScript, Transaction as Transaction5 } from "@scure/btc-signer";
2603
+ import { Address, OutScript } from "@scure/btc-signer";
2604
+ import { tapLeafHash as tapLeafHash3 } from "@scure/btc-signer/payment.js";
2597
2605
  var createVHTLCScript = (args) => {
2598
2606
  const {
2599
2607
  network,
@@ -2741,14 +2749,28 @@ var claimVHTLCwithOffchainTx = async (identity, vhtlcScript, serverXOnlyPublicKe
2741
2749
  base643.encode(signedArkTx.toPSBT()),
2742
2750
  checkpoints.map((c) => base643.encode(c.toPSBT()))
2743
2751
  );
2744
- if (!validFinalArkTx(finalArkTx, serverXOnlyPublicKey, vhtlcScript.leaves)) {
2745
- throw new Error("Invalid final Ark transaction");
2752
+ const finalTx = Transaction5.fromPSBT(base643.decode(finalArkTx));
2753
+ const serverPubkeyHex = hex7.encode(serverXOnlyPublicKey);
2754
+ const claimLeafHash = tapLeafHash3(
2755
+ scriptFromTapLeafScript(vhtlcScript.claim())
2756
+ );
2757
+ for (let i = 0; i < finalTx.inputsLength; i++) {
2758
+ if (!verifySignatures(finalTx, i, [serverPubkeyHex], claimLeafHash)) {
2759
+ throw new Error("Invalid final Ark transaction");
2760
+ }
2746
2761
  }
2747
2762
  const finalCheckpoints = await Promise.all(
2748
- signedCheckpointTxs.map(async (c) => {
2749
- const tx = Transaction5.fromPSBT(base643.decode(c), {
2750
- allowUnknown: true
2751
- });
2763
+ signedCheckpointTxs.map(async (c, idx) => {
2764
+ const tx = Transaction5.fromPSBT(base643.decode(c));
2765
+ const checkpointLeaf = checkpoints[idx].getInput(0).tapLeafScript[0];
2766
+ const cpLeafHash = tapLeafHash3(
2767
+ scriptFromTapLeafScript(checkpointLeaf)
2768
+ );
2769
+ if (!verifySignatures(tx, 0, [serverPubkeyHex], cpLeafHash)) {
2770
+ throw new Error(
2771
+ "Invalid server signature in checkpoint transaction"
2772
+ );
2773
+ }
2752
2774
  const signedCheckpoint = await identity.sign(tx, [0]);
2753
2775
  return base643.encode(signedCheckpoint.toPSBT());
2754
2776
  })
@@ -2771,10 +2793,27 @@ var refundVHTLCwithOffchainTx = async (swapId, identity, arkProvider, boltzXOnly
2771
2793
  checkpoint: boltzSignedCheckpointTx
2772
2794
  } = await refundFunc(swapId, unsignedRefundTx, unsignedCheckpointTx);
2773
2795
  const boltzXOnlyPublicKeyHex = hex7.encode(boltzXOnlyPublicKey);
2774
- if (!verifySignatures(boltzSignedRefundTx, 0, [boltzXOnlyPublicKeyHex])) {
2796
+ const refundLeafHash = tapLeafHash3(
2797
+ scriptFromTapLeafScript(input.tapLeafScript)
2798
+ );
2799
+ if (!verifySignatures(
2800
+ boltzSignedRefundTx,
2801
+ 0,
2802
+ [boltzXOnlyPublicKeyHex],
2803
+ refundLeafHash
2804
+ )) {
2775
2805
  throw new Error("Invalid Boltz signature in refund transaction");
2776
2806
  }
2777
- if (!verifySignatures(boltzSignedCheckpointTx, 0, [boltzXOnlyPublicKeyHex])) {
2807
+ const checkpointLeaf = unsignedCheckpointTx.getInput(0).tapLeafScript[0];
2808
+ const checkpointLeafHash = tapLeafHash3(
2809
+ scriptFromTapLeafScript(checkpointLeaf)
2810
+ );
2811
+ if (!verifySignatures(
2812
+ boltzSignedCheckpointTx,
2813
+ 0,
2814
+ [boltzXOnlyPublicKeyHex],
2815
+ checkpointLeafHash
2816
+ )) {
2778
2817
  throw new Error("Invalid Boltz signature in checkpoint transaction");
2779
2818
  }
2780
2819
  const signedRefundTx = await identity.sign(unsignedRefundTx);
@@ -2798,7 +2837,7 @@ var refundVHTLCwithOffchainTx = async (swapId, identity, arkProvider, boltzXOnly
2798
2837
  hex7.encode(boltzXOnlyPublicKey),
2799
2838
  hex7.encode(serverXOnlyPublicKey)
2800
2839
  ];
2801
- if (!verifySignatures(tx, inputIndex, requiredSigners)) {
2840
+ if (!verifySignatures(tx, inputIndex, requiredSigners, refundLeafHash)) {
2802
2841
  throw new Error("Invalid refund transaction");
2803
2842
  }
2804
2843
  if (signedCheckpointTxs.length !== 1) {
@@ -2809,6 +2848,15 @@ var refundVHTLCwithOffchainTx = async (swapId, identity, arkProvider, boltzXOnly
2809
2848
  const serverSignedCheckpointTx = Transaction5.fromPSBT(
2810
2849
  base643.decode(signedCheckpointTxs[0])
2811
2850
  );
2851
+ const serverPubkeyHex = hex7.encode(serverXOnlyPublicKey);
2852
+ if (!verifySignatures(
2853
+ serverSignedCheckpointTx,
2854
+ 0,
2855
+ [serverPubkeyHex],
2856
+ checkpointLeafHash
2857
+ )) {
2858
+ throw new Error("Invalid server signature in checkpoint transaction");
2859
+ }
2812
2860
  const finalCheckpointTx = combineTapscriptSigs(
2813
2861
  combinedSignedCheckpointTx,
2814
2862
  serverSignedCheckpointTx
@@ -2817,17 +2865,9 @@ var refundVHTLCwithOffchainTx = async (swapId, identity, arkProvider, boltzXOnly
2817
2865
  base643.encode(finalCheckpointTx.toPSBT())
2818
2866
  ]);
2819
2867
  };
2820
- var validFinalArkTx = (finalArkTx, _pubkey, _tapLeaves) => {
2821
- const tx = Transaction5.fromPSBT(base643.decode(finalArkTx), {
2822
- allowUnknown: true
2823
- });
2824
- if (!tx) return false;
2825
- const inputs = [];
2826
- for (let i = 0; i < tx.inputsLength; i++) {
2827
- inputs.push(tx.getInput(i));
2828
- }
2829
- return inputs.every((input) => input.witnessUtxo);
2830
- };
2868
+ function scriptFromTapLeafScript(leaf) {
2869
+ return leaf[1].subarray(0, leaf[1].length - 1);
2870
+ }
2831
2871
 
2832
2872
  // src/arkade-swaps.ts
2833
2873
  var ArkadeSwaps = class _ArkadeSwaps {
@@ -3253,7 +3293,7 @@ var ArkadeSwaps = class _ArkadeSwaps {
3253
3293
  async sendLightningPayment(args) {
3254
3294
  const pendingSwap = await this.createSubmarineSwap(args);
3255
3295
  await this.savePendingSubmarineSwap(pendingSwap);
3256
- const txid = await this.wallet.sendBitcoin({
3296
+ const txid = await this.wallet.send({
3257
3297
  address: pendingSwap.response.address,
3258
3298
  amount: pendingSwap.response.expectedAmount
3259
3299
  });
@@ -1164,10 +1164,16 @@ var init_signatures = __esm({
1164
1164
  "use strict";
1165
1165
  import_sdk3 = require("@arkade-os/sdk");
1166
1166
  import_base4 = require("@scure/base");
1167
- verifySignatures = (tx, inputIndex, requiredSigners) => {
1167
+ verifySignatures = (tx, inputIndex, requiredSigners, expectedLeafHash) => {
1168
1168
  try {
1169
1169
  (0, import_sdk3.verifyTapscriptSignatures)(tx, inputIndex, requiredSigners);
1170
- return true;
1170
+ const input = tx.getInput(inputIndex);
1171
+ const expectedHex = import_base4.hex.encode(expectedLeafHash);
1172
+ return requiredSigners.every(
1173
+ (signer) => input.tapScriptSig?.some(
1174
+ ([{ pubKey, leafHash }]) => import_base4.hex.encode(pubKey) === signer && import_base4.hex.encode(leafHash) === expectedHex
1175
+ )
1176
+ );
1171
1177
  } catch (_) {
1172
1178
  return false;
1173
1179
  }
@@ -2563,7 +2569,10 @@ var init_batch = __esm({
2563
2569
  });
2564
2570
 
2565
2571
  // src/utils/vhtlc.ts
2566
- var import_sdk7, import_base8, import_legacy, import_btc_signer4, createVHTLCScript, joinBatch, claimVHTLCwithOffchainTx, refundVHTLCwithOffchainTx, validFinalArkTx;
2572
+ function scriptFromTapLeafScript(leaf) {
2573
+ return leaf[1].subarray(0, leaf[1].length - 1);
2574
+ }
2575
+ var import_sdk7, import_base8, import_legacy, import_btc_signer4, import_payment3, createVHTLCScript, joinBatch, claimVHTLCwithOffchainTx, refundVHTLCwithOffchainTx;
2567
2576
  var init_vhtlc = __esm({
2568
2577
  "src/utils/vhtlc.ts"() {
2569
2578
  "use strict";
@@ -2573,6 +2582,7 @@ var init_vhtlc = __esm({
2573
2582
  init_batch();
2574
2583
  import_legacy = require("@noble/hashes/legacy.js");
2575
2584
  import_btc_signer4 = require("@scure/btc-signer");
2585
+ import_payment3 = require("@scure/btc-signer/payment.js");
2576
2586
  init_signatures();
2577
2587
  createVHTLCScript = (args) => {
2578
2588
  const {
@@ -2721,14 +2731,28 @@ var init_vhtlc = __esm({
2721
2731
  import_base8.base64.encode(signedArkTx.toPSBT()),
2722
2732
  checkpoints.map((c) => import_base8.base64.encode(c.toPSBT()))
2723
2733
  );
2724
- if (!validFinalArkTx(finalArkTx, serverXOnlyPublicKey, vhtlcScript.leaves)) {
2725
- throw new Error("Invalid final Ark transaction");
2734
+ const finalTx = import_sdk7.Transaction.fromPSBT(import_base8.base64.decode(finalArkTx));
2735
+ const serverPubkeyHex = import_base8.hex.encode(serverXOnlyPublicKey);
2736
+ const claimLeafHash = (0, import_payment3.tapLeafHash)(
2737
+ scriptFromTapLeafScript(vhtlcScript.claim())
2738
+ );
2739
+ for (let i = 0; i < finalTx.inputsLength; i++) {
2740
+ if (!verifySignatures(finalTx, i, [serverPubkeyHex], claimLeafHash)) {
2741
+ throw new Error("Invalid final Ark transaction");
2742
+ }
2726
2743
  }
2727
2744
  const finalCheckpoints = await Promise.all(
2728
- signedCheckpointTxs.map(async (c) => {
2729
- const tx = import_btc_signer4.Transaction.fromPSBT(import_base8.base64.decode(c), {
2730
- allowUnknown: true
2731
- });
2745
+ signedCheckpointTxs.map(async (c, idx) => {
2746
+ const tx = import_sdk7.Transaction.fromPSBT(import_base8.base64.decode(c));
2747
+ const checkpointLeaf = checkpoints[idx].getInput(0).tapLeafScript[0];
2748
+ const cpLeafHash = (0, import_payment3.tapLeafHash)(
2749
+ scriptFromTapLeafScript(checkpointLeaf)
2750
+ );
2751
+ if (!verifySignatures(tx, 0, [serverPubkeyHex], cpLeafHash)) {
2752
+ throw new Error(
2753
+ "Invalid server signature in checkpoint transaction"
2754
+ );
2755
+ }
2732
2756
  const signedCheckpoint = await identity.sign(tx, [0]);
2733
2757
  return import_base8.base64.encode(signedCheckpoint.toPSBT());
2734
2758
  })
@@ -2751,10 +2775,27 @@ var init_vhtlc = __esm({
2751
2775
  checkpoint: boltzSignedCheckpointTx
2752
2776
  } = await refundFunc(swapId, unsignedRefundTx, unsignedCheckpointTx);
2753
2777
  const boltzXOnlyPublicKeyHex = import_base8.hex.encode(boltzXOnlyPublicKey);
2754
- if (!verifySignatures(boltzSignedRefundTx, 0, [boltzXOnlyPublicKeyHex])) {
2778
+ const refundLeafHash = (0, import_payment3.tapLeafHash)(
2779
+ scriptFromTapLeafScript(input.tapLeafScript)
2780
+ );
2781
+ if (!verifySignatures(
2782
+ boltzSignedRefundTx,
2783
+ 0,
2784
+ [boltzXOnlyPublicKeyHex],
2785
+ refundLeafHash
2786
+ )) {
2755
2787
  throw new Error("Invalid Boltz signature in refund transaction");
2756
2788
  }
2757
- if (!verifySignatures(boltzSignedCheckpointTx, 0, [boltzXOnlyPublicKeyHex])) {
2789
+ const checkpointLeaf = unsignedCheckpointTx.getInput(0).tapLeafScript[0];
2790
+ const checkpointLeafHash = (0, import_payment3.tapLeafHash)(
2791
+ scriptFromTapLeafScript(checkpointLeaf)
2792
+ );
2793
+ if (!verifySignatures(
2794
+ boltzSignedCheckpointTx,
2795
+ 0,
2796
+ [boltzXOnlyPublicKeyHex],
2797
+ checkpointLeafHash
2798
+ )) {
2758
2799
  throw new Error("Invalid Boltz signature in checkpoint transaction");
2759
2800
  }
2760
2801
  const signedRefundTx = await identity.sign(unsignedRefundTx);
@@ -2771,14 +2812,14 @@ var init_vhtlc = __esm({
2771
2812
  import_base8.base64.encode(combinedSignedRefundTx.toPSBT()),
2772
2813
  [import_base8.base64.encode(unsignedCheckpointTx.toPSBT())]
2773
2814
  );
2774
- const tx = import_btc_signer4.Transaction.fromPSBT(import_base8.base64.decode(finalArkTx));
2815
+ const tx = import_sdk7.Transaction.fromPSBT(import_base8.base64.decode(finalArkTx));
2775
2816
  const inputIndex = 0;
2776
2817
  const requiredSigners = [
2777
2818
  import_base8.hex.encode(ourXOnlyPublicKey),
2778
2819
  import_base8.hex.encode(boltzXOnlyPublicKey),
2779
2820
  import_base8.hex.encode(serverXOnlyPublicKey)
2780
2821
  ];
2781
- if (!verifySignatures(tx, inputIndex, requiredSigners)) {
2822
+ if (!verifySignatures(tx, inputIndex, requiredSigners, refundLeafHash)) {
2782
2823
  throw new Error("Invalid refund transaction");
2783
2824
  }
2784
2825
  if (signedCheckpointTxs.length !== 1) {
@@ -2786,9 +2827,18 @@ var init_vhtlc = __esm({
2786
2827
  `Expected one signed checkpoint transaction, got ${signedCheckpointTxs.length}`
2787
2828
  );
2788
2829
  }
2789
- const serverSignedCheckpointTx = import_btc_signer4.Transaction.fromPSBT(
2830
+ const serverSignedCheckpointTx = import_sdk7.Transaction.fromPSBT(
2790
2831
  import_base8.base64.decode(signedCheckpointTxs[0])
2791
2832
  );
2833
+ const serverPubkeyHex = import_base8.hex.encode(serverXOnlyPublicKey);
2834
+ if (!verifySignatures(
2835
+ serverSignedCheckpointTx,
2836
+ 0,
2837
+ [serverPubkeyHex],
2838
+ checkpointLeafHash
2839
+ )) {
2840
+ throw new Error("Invalid server signature in checkpoint transaction");
2841
+ }
2792
2842
  const finalCheckpointTx = (0, import_sdk7.combineTapscriptSigs)(
2793
2843
  combinedSignedCheckpointTx,
2794
2844
  serverSignedCheckpointTx
@@ -2797,17 +2847,6 @@ var init_vhtlc = __esm({
2797
2847
  import_base8.base64.encode(finalCheckpointTx.toPSBT())
2798
2848
  ]);
2799
2849
  };
2800
- validFinalArkTx = (finalArkTx, _pubkey, _tapLeaves) => {
2801
- const tx = import_btc_signer4.Transaction.fromPSBT(import_base8.base64.decode(finalArkTx), {
2802
- allowUnknown: true
2803
- });
2804
- if (!tx) return false;
2805
- const inputs = [];
2806
- for (let i = 0; i < tx.inputsLength; i++) {
2807
- inputs.push(tx.getInput(i));
2808
- }
2809
- return inputs.every((input) => input.witnessUtxo);
2810
- };
2811
2850
  }
2812
2851
  });
2813
2852
 
@@ -3259,7 +3298,7 @@ var init_arkade_swaps = __esm({
3259
3298
  async sendLightningPayment(args) {
3260
3299
  const pendingSwap = await this.createSubmarineSwap(args);
3261
3300
  await this.savePendingSubmarineSwap(pendingSwap);
3262
- const txid = await this.wallet.sendBitcoin({
3301
+ const txid = await this.wallet.send({
3263
3302
  address: pendingSwap.response.address,
3264
3303
  amount: pendingSwap.response.expectedAmount
3265
3304
  });
@@ -4,10 +4,10 @@ import {
4
4
  registerExpoSwapBackgroundTask,
5
5
  swapsPollProcessor,
6
6
  unregisterExpoSwapBackgroundTask
7
- } from "../chunk-LMHVRFGY.js";
7
+ } from "../chunk-XUBVFCQZ.js";
8
8
  import {
9
9
  ArkadeSwaps
10
- } from "../chunk-SUFFGYDJ.js";
10
+ } from "../chunk-ZJ2Q5QSA.js";
11
11
  import "../chunk-3RG5ZIWI.js";
12
12
 
13
13
  // src/expo/arkade-lightning.ts
@@ -56,7 +56,7 @@ var ExpoArkadeSwaps = class _ExpoArkadeSwaps {
56
56
  await instance.seedSwapPollTask();
57
57
  if (config.background.minimumBackgroundInterval) {
58
58
  try {
59
- const { registerExpoSwapBackgroundTask: registerExpoSwapBackgroundTask2 } = await import("../background-5IKGSVTP.js");
59
+ const { registerExpoSwapBackgroundTask: registerExpoSwapBackgroundTask2 } = await import("../background-RLZLTWCI.js");
60
60
  await registerExpoSwapBackgroundTask2(
61
61
  config.background.taskName,
62
62
  {
@@ -119,7 +119,7 @@ var ExpoArkadeSwaps = class _ExpoArkadeSwaps {
119
119
  }
120
120
  await this.inner.dispose();
121
121
  try {
122
- const { unregisterExpoSwapBackgroundTask: unregisterExpoSwapBackgroundTask2 } = await import("../background-5IKGSVTP.js");
122
+ const { unregisterExpoSwapBackgroundTask: unregisterExpoSwapBackgroundTask2 } = await import("../background-RLZLTWCI.js");
123
123
  await unregisterExpoSwapBackgroundTask2(this.taskName);
124
124
  } catch (err) {
125
125
  const message = err instanceof Error ? err.message : String(err);
package/dist/index.cjs CHANGED
@@ -1288,10 +1288,16 @@ var isValidArkAddress = (address) => {
1288
1288
  // src/utils/signatures.ts
1289
1289
  var import_sdk3 = require("@arkade-os/sdk");
1290
1290
  var import_base4 = require("@scure/base");
1291
- var verifySignatures = (tx, inputIndex, requiredSigners) => {
1291
+ var verifySignatures = (tx, inputIndex, requiredSigners, expectedLeafHash) => {
1292
1292
  try {
1293
1293
  (0, import_sdk3.verifyTapscriptSignatures)(tx, inputIndex, requiredSigners);
1294
- return true;
1294
+ const input = tx.getInput(inputIndex);
1295
+ const expectedHex = import_base4.hex.encode(expectedLeafHash);
1296
+ return requiredSigners.every(
1297
+ (signer) => input.tapScriptSig?.some(
1298
+ ([{ pubKey, leafHash }]) => import_base4.hex.encode(pubKey) === signer && import_base4.hex.encode(leafHash) === expectedHex
1299
+ )
1300
+ );
1295
1301
  } catch (_) {
1296
1302
  return false;
1297
1303
  }
@@ -2654,6 +2660,7 @@ function createForfeitTx(input, forfeitOutputScript, connector) {
2654
2660
  // src/utils/vhtlc.ts
2655
2661
  var import_legacy = require("@noble/hashes/legacy.js");
2656
2662
  var import_btc_signer4 = require("@scure/btc-signer");
2663
+ var import_payment3 = require("@scure/btc-signer/payment.js");
2657
2664
  var createVHTLCScript = (args) => {
2658
2665
  const {
2659
2666
  network,
@@ -2801,14 +2808,28 @@ var claimVHTLCwithOffchainTx = async (identity, vhtlcScript, serverXOnlyPublicKe
2801
2808
  import_base8.base64.encode(signedArkTx.toPSBT()),
2802
2809
  checkpoints.map((c) => import_base8.base64.encode(c.toPSBT()))
2803
2810
  );
2804
- if (!validFinalArkTx(finalArkTx, serverXOnlyPublicKey, vhtlcScript.leaves)) {
2805
- throw new Error("Invalid final Ark transaction");
2811
+ const finalTx = import_sdk7.Transaction.fromPSBT(import_base8.base64.decode(finalArkTx));
2812
+ const serverPubkeyHex = import_base8.hex.encode(serverXOnlyPublicKey);
2813
+ const claimLeafHash = (0, import_payment3.tapLeafHash)(
2814
+ scriptFromTapLeafScript(vhtlcScript.claim())
2815
+ );
2816
+ for (let i = 0; i < finalTx.inputsLength; i++) {
2817
+ if (!verifySignatures(finalTx, i, [serverPubkeyHex], claimLeafHash)) {
2818
+ throw new Error("Invalid final Ark transaction");
2819
+ }
2806
2820
  }
2807
2821
  const finalCheckpoints = await Promise.all(
2808
- signedCheckpointTxs.map(async (c) => {
2809
- const tx = import_btc_signer4.Transaction.fromPSBT(import_base8.base64.decode(c), {
2810
- allowUnknown: true
2811
- });
2822
+ signedCheckpointTxs.map(async (c, idx) => {
2823
+ const tx = import_sdk7.Transaction.fromPSBT(import_base8.base64.decode(c));
2824
+ const checkpointLeaf = checkpoints[idx].getInput(0).tapLeafScript[0];
2825
+ const cpLeafHash = (0, import_payment3.tapLeafHash)(
2826
+ scriptFromTapLeafScript(checkpointLeaf)
2827
+ );
2828
+ if (!verifySignatures(tx, 0, [serverPubkeyHex], cpLeafHash)) {
2829
+ throw new Error(
2830
+ "Invalid server signature in checkpoint transaction"
2831
+ );
2832
+ }
2812
2833
  const signedCheckpoint = await identity.sign(tx, [0]);
2813
2834
  return import_base8.base64.encode(signedCheckpoint.toPSBT());
2814
2835
  })
@@ -2831,10 +2852,27 @@ var refundVHTLCwithOffchainTx = async (swapId, identity, arkProvider, boltzXOnly
2831
2852
  checkpoint: boltzSignedCheckpointTx
2832
2853
  } = await refundFunc(swapId, unsignedRefundTx, unsignedCheckpointTx);
2833
2854
  const boltzXOnlyPublicKeyHex = import_base8.hex.encode(boltzXOnlyPublicKey);
2834
- if (!verifySignatures(boltzSignedRefundTx, 0, [boltzXOnlyPublicKeyHex])) {
2855
+ const refundLeafHash = (0, import_payment3.tapLeafHash)(
2856
+ scriptFromTapLeafScript(input.tapLeafScript)
2857
+ );
2858
+ if (!verifySignatures(
2859
+ boltzSignedRefundTx,
2860
+ 0,
2861
+ [boltzXOnlyPublicKeyHex],
2862
+ refundLeafHash
2863
+ )) {
2835
2864
  throw new Error("Invalid Boltz signature in refund transaction");
2836
2865
  }
2837
- if (!verifySignatures(boltzSignedCheckpointTx, 0, [boltzXOnlyPublicKeyHex])) {
2866
+ const checkpointLeaf = unsignedCheckpointTx.getInput(0).tapLeafScript[0];
2867
+ const checkpointLeafHash = (0, import_payment3.tapLeafHash)(
2868
+ scriptFromTapLeafScript(checkpointLeaf)
2869
+ );
2870
+ if (!verifySignatures(
2871
+ boltzSignedCheckpointTx,
2872
+ 0,
2873
+ [boltzXOnlyPublicKeyHex],
2874
+ checkpointLeafHash
2875
+ )) {
2838
2876
  throw new Error("Invalid Boltz signature in checkpoint transaction");
2839
2877
  }
2840
2878
  const signedRefundTx = await identity.sign(unsignedRefundTx);
@@ -2851,14 +2889,14 @@ var refundVHTLCwithOffchainTx = async (swapId, identity, arkProvider, boltzXOnly
2851
2889
  import_base8.base64.encode(combinedSignedRefundTx.toPSBT()),
2852
2890
  [import_base8.base64.encode(unsignedCheckpointTx.toPSBT())]
2853
2891
  );
2854
- const tx = import_btc_signer4.Transaction.fromPSBT(import_base8.base64.decode(finalArkTx));
2892
+ const tx = import_sdk7.Transaction.fromPSBT(import_base8.base64.decode(finalArkTx));
2855
2893
  const inputIndex = 0;
2856
2894
  const requiredSigners = [
2857
2895
  import_base8.hex.encode(ourXOnlyPublicKey),
2858
2896
  import_base8.hex.encode(boltzXOnlyPublicKey),
2859
2897
  import_base8.hex.encode(serverXOnlyPublicKey)
2860
2898
  ];
2861
- if (!verifySignatures(tx, inputIndex, requiredSigners)) {
2899
+ if (!verifySignatures(tx, inputIndex, requiredSigners, refundLeafHash)) {
2862
2900
  throw new Error("Invalid refund transaction");
2863
2901
  }
2864
2902
  if (signedCheckpointTxs.length !== 1) {
@@ -2866,9 +2904,18 @@ var refundVHTLCwithOffchainTx = async (swapId, identity, arkProvider, boltzXOnly
2866
2904
  `Expected one signed checkpoint transaction, got ${signedCheckpointTxs.length}`
2867
2905
  );
2868
2906
  }
2869
- const serverSignedCheckpointTx = import_btc_signer4.Transaction.fromPSBT(
2907
+ const serverSignedCheckpointTx = import_sdk7.Transaction.fromPSBT(
2870
2908
  import_base8.base64.decode(signedCheckpointTxs[0])
2871
2909
  );
2910
+ const serverPubkeyHex = import_base8.hex.encode(serverXOnlyPublicKey);
2911
+ if (!verifySignatures(
2912
+ serverSignedCheckpointTx,
2913
+ 0,
2914
+ [serverPubkeyHex],
2915
+ checkpointLeafHash
2916
+ )) {
2917
+ throw new Error("Invalid server signature in checkpoint transaction");
2918
+ }
2872
2919
  const finalCheckpointTx = (0, import_sdk7.combineTapscriptSigs)(
2873
2920
  combinedSignedCheckpointTx,
2874
2921
  serverSignedCheckpointTx
@@ -2877,17 +2924,9 @@ var refundVHTLCwithOffchainTx = async (swapId, identity, arkProvider, boltzXOnly
2877
2924
  import_base8.base64.encode(finalCheckpointTx.toPSBT())
2878
2925
  ]);
2879
2926
  };
2880
- var validFinalArkTx = (finalArkTx, _pubkey, _tapLeaves) => {
2881
- const tx = import_btc_signer4.Transaction.fromPSBT(import_base8.base64.decode(finalArkTx), {
2882
- allowUnknown: true
2883
- });
2884
- if (!tx) return false;
2885
- const inputs = [];
2886
- for (let i = 0; i < tx.inputsLength; i++) {
2887
- inputs.push(tx.getInput(i));
2888
- }
2889
- return inputs.every((input) => input.witnessUtxo);
2890
- };
2927
+ function scriptFromTapLeafScript(leaf) {
2928
+ return leaf[1].subarray(0, leaf[1].length - 1);
2929
+ }
2891
2930
 
2892
2931
  // src/arkade-swaps.ts
2893
2932
  var ArkadeSwaps = class _ArkadeSwaps {
@@ -3313,7 +3352,7 @@ var ArkadeSwaps = class _ArkadeSwaps {
3313
3352
  async sendLightningPayment(args) {
3314
3353
  const pendingSwap = await this.createSubmarineSwap(args);
3315
3354
  await this.savePendingSubmarineSwap(pendingSwap);
3316
- const txid = await this.wallet.sendBitcoin({
3355
+ const txid = await this.wallet.send({
3317
3356
  address: pendingSwap.response.address,
3318
3357
  amount: pendingSwap.response.expectedAmount
3319
3358
  });
@@ -4997,6 +5036,28 @@ var ArkadeSwapsMessageHandler = class _ArkadeSwapsMessageHandler {
4997
5036
  };
4998
5037
 
4999
5038
  // src/serviceWorker/arkade-swaps-runtime.ts
5039
+ var import_sdk10 = require("@arkade-os/sdk");
5040
+ function isMessageBusNotInitializedError(error) {
5041
+ return error instanceof Error && error.message.includes(import_sdk10.MESSAGE_BUS_NOT_INITIALIZED);
5042
+ }
5043
+ var DEDUPABLE_REQUEST_TYPES = /* @__PURE__ */ new Set([
5044
+ "GET_FEES",
5045
+ "GET_LIMITS",
5046
+ "GET_SWAP_STATUS",
5047
+ "GET_PENDING_SUBMARINE_SWAPS",
5048
+ "GET_PENDING_REVERSE_SWAPS",
5049
+ "GET_PENDING_CHAIN_SWAPS",
5050
+ "GET_SWAP_HISTORY",
5051
+ "QUOTE_SWAP",
5052
+ "SM-GET_PENDING_SWAPS",
5053
+ "SM-HAS_SWAP",
5054
+ "SM-IS_PROCESSING",
5055
+ "SM-GET_STATS"
5056
+ ]);
5057
+ function getRequestDedupKey(request) {
5058
+ const { id, tag, ...rest } = request;
5059
+ return JSON.stringify(rest);
5060
+ }
5000
5061
  var ServiceWorkerArkadeSwaps = class _ServiceWorkerArkadeSwaps {
5001
5062
  constructor(messageTag, serviceWorker, swapRepository, withSwapManager) {
5002
5063
  this.messageTag = messageTag;
@@ -5011,6 +5072,10 @@ var ServiceWorkerArkadeSwaps = class _ServiceWorkerArkadeSwaps {
5011
5072
  actionExecutedListeners = /* @__PURE__ */ new Set();
5012
5073
  wsConnectedListeners = /* @__PURE__ */ new Set();
5013
5074
  wsDisconnectedListeners = /* @__PURE__ */ new Set();
5075
+ initPayload = null;
5076
+ reinitPromise = null;
5077
+ pingPromise = null;
5078
+ inflightRequests = /* @__PURE__ */ new Map();
5014
5079
  static async create(config) {
5015
5080
  const messageTag = config.messageTag ?? DEFAULT_MESSAGE_TAG;
5016
5081
  const swapRepository = config.swapRepository ?? new IndexedDbSwapRepository();
@@ -5020,18 +5085,20 @@ var ServiceWorkerArkadeSwaps = class _ServiceWorkerArkadeSwaps {
5020
5085
  swapRepository,
5021
5086
  Boolean(config.swapManager)
5022
5087
  );
5088
+ const initPayload = {
5089
+ network: config.network,
5090
+ arkServerUrl: config.arkServerUrl,
5091
+ swapProvider: { baseUrl: config.swapProvider.getApiUrl() },
5092
+ swapManager: config.swapManager
5093
+ };
5023
5094
  const initMessage = {
5024
5095
  tag: messageTag,
5025
5096
  id: getRandomId(),
5026
5097
  type: "INIT_ARKADE_SWAPS",
5027
- payload: {
5028
- network: config.network,
5029
- arkServerUrl: config.arkServerUrl,
5030
- swapProvider: { baseUrl: config.swapProvider.getApiUrl() },
5031
- swapManager: config.swapManager
5032
- }
5098
+ payload: initPayload
5033
5099
  };
5034
5100
  await svcArkadeSwaps.sendMessage(initMessage);
5101
+ svcArkadeSwaps.initPayload = initPayload;
5035
5102
  return svcArkadeSwaps;
5036
5103
  }
5037
5104
  async startSwapManager() {
@@ -5572,17 +5639,23 @@ var ServiceWorkerArkadeSwaps = class _ServiceWorkerArkadeSwaps {
5572
5639
  async [Symbol.asyncDispose]() {
5573
5640
  return this.dispose();
5574
5641
  }
5575
- async sendMessage(request) {
5642
+ sendMessageDirect(request) {
5576
5643
  return new Promise((resolve, reject) => {
5577
- const timeoutMs = 3e4;
5578
- let timeout;
5579
5644
  const cleanup = () => {
5580
- if (timeout) clearTimeout(timeout);
5645
+ clearTimeout(timeoutId);
5581
5646
  navigator.serviceWorker.removeEventListener(
5582
5647
  "message",
5583
5648
  messageHandler
5584
5649
  );
5585
5650
  };
5651
+ const timeoutId = setTimeout(() => {
5652
+ cleanup();
5653
+ reject(
5654
+ new import_sdk10.ServiceWorkerTimeoutError(
5655
+ `Service worker message timed out (${request.type})`
5656
+ )
5657
+ );
5658
+ }, 3e4);
5586
5659
  const messageHandler = (event) => {
5587
5660
  const response = event.data;
5588
5661
  if (!response || response.tag !== this.messageTag || response.id !== request.id) {
@@ -5595,17 +5668,97 @@ var ServiceWorkerArkadeSwaps = class _ServiceWorkerArkadeSwaps {
5595
5668
  resolve(response);
5596
5669
  }
5597
5670
  };
5598
- timeout = setTimeout(() => {
5671
+ navigator.serviceWorker.addEventListener("message", messageHandler);
5672
+ this.serviceWorker.postMessage(request);
5673
+ });
5674
+ }
5675
+ async sendMessage(request) {
5676
+ if (!DEDUPABLE_REQUEST_TYPES.has(request.type)) {
5677
+ return this.sendMessageWithRetry(request);
5678
+ }
5679
+ const key = getRequestDedupKey(request);
5680
+ const existing = this.inflightRequests.get(key);
5681
+ if (existing) return existing;
5682
+ const promise = this.sendMessageWithRetry(request).finally(() => {
5683
+ this.inflightRequests.delete(key);
5684
+ });
5685
+ this.inflightRequests.set(key, promise);
5686
+ return promise;
5687
+ }
5688
+ pingServiceWorker() {
5689
+ if (this.pingPromise) return this.pingPromise;
5690
+ this.pingPromise = new Promise((resolve, reject) => {
5691
+ const pingId = getRandomId();
5692
+ const cleanup = () => {
5693
+ clearTimeout(timeoutId);
5694
+ navigator.serviceWorker.removeEventListener(
5695
+ "message",
5696
+ onMessage
5697
+ );
5698
+ };
5699
+ const timeoutId = setTimeout(() => {
5599
5700
  cleanup();
5600
5701
  reject(
5601
- new Error(
5602
- `Timed out waiting for service worker response: ${request.type}`
5702
+ new import_sdk10.ServiceWorkerTimeoutError(
5703
+ "Service worker ping timed out"
5603
5704
  )
5604
5705
  );
5605
- }, timeoutMs);
5606
- navigator.serviceWorker.addEventListener("message", messageHandler);
5607
- this.serviceWorker.postMessage(request);
5706
+ }, 2e3);
5707
+ const onMessage = (event) => {
5708
+ if (event.data?.id === pingId && event.data?.tag === "PONG") {
5709
+ cleanup();
5710
+ resolve();
5711
+ }
5712
+ };
5713
+ navigator.serviceWorker.addEventListener("message", onMessage);
5714
+ this.serviceWorker.postMessage({
5715
+ id: pingId,
5716
+ tag: "PING"
5717
+ });
5718
+ }).finally(() => {
5719
+ this.pingPromise = null;
5720
+ });
5721
+ return this.pingPromise;
5722
+ }
5723
+ // Send a message, retrying up to 2 times if the service worker was
5724
+ // killed and restarted by the OS (mobile browsers do this aggressively).
5725
+ async sendMessageWithRetry(request) {
5726
+ if (this.initPayload) {
5727
+ try {
5728
+ await this.pingServiceWorker();
5729
+ } catch {
5730
+ await this.reinitialize();
5731
+ }
5732
+ }
5733
+ const maxRetries = 2;
5734
+ for (let attempt = 0; ; attempt++) {
5735
+ try {
5736
+ return await this.sendMessageDirect(request);
5737
+ } catch (error) {
5738
+ if (!isMessageBusNotInitializedError(error) || attempt >= maxRetries) {
5739
+ throw error;
5740
+ }
5741
+ await this.reinitialize();
5742
+ }
5743
+ }
5744
+ }
5745
+ async reinitialize() {
5746
+ if (this.reinitPromise) return this.reinitPromise;
5747
+ this.reinitPromise = (async () => {
5748
+ if (!this.initPayload) {
5749
+ throw new Error("Cannot re-initialize: missing configuration");
5750
+ }
5751
+ const initMessage = {
5752
+ tag: this.messageTag,
5753
+ type: "INIT_ARKADE_SWAPS",
5754
+ id: getRandomId(),
5755
+ payload: this.initPayload
5756
+ };
5757
+ await this.sendMessageDirect(initMessage);
5758
+ })().finally(() => {
5759
+ this.reinitPromise = null;
5608
5760
  });
5761
+ return this.reinitPromise;
5609
5762
  }
5610
5763
  initEventStream() {
5611
5764
  if (this.eventListenerInitialized) return;
package/dist/index.d.cts CHANGED
@@ -83,7 +83,17 @@ declare const getInvoiceSatoshis: (invoice: string) => number;
83
83
  declare const getInvoicePaymentHash: (invoice: string) => string;
84
84
  declare const isValidArkAddress: (address: string) => boolean;
85
85
 
86
- declare const verifySignatures: (tx: Transaction, inputIndex: number, requiredSigners: string[]) => boolean;
86
+ /**
87
+ * Verifies that a transaction input has valid tapscript signatures
88
+ * from all required signers for a specific tapscript leaf.
89
+ *
90
+ * @param tx - The transaction to verify.
91
+ * @param inputIndex - The index of the input to check.
92
+ * @param requiredSigners - Hex-encoded x-only public keys of all required signers.
93
+ * @param expectedLeafHash - The tapscript leaf hash that signatures must commit to.
94
+ * @returns `true` if all required signers have valid signatures on the expected leaf, `false` otherwise.
95
+ */
96
+ declare const verifySignatures: (tx: Transaction, inputIndex: number, requiredSigners: string[], expectedLeafHash: Uint8Array) => boolean;
87
97
 
88
98
  /**
89
99
  * Generic type for swap save functions
@@ -552,6 +562,10 @@ declare class ServiceWorkerArkadeSwaps implements IArkadeSwaps {
552
562
  private actionExecutedListeners;
553
563
  private wsConnectedListeners;
554
564
  private wsDisconnectedListeners;
565
+ private initPayload;
566
+ private reinitPromise;
567
+ private pingPromise;
568
+ private inflightRequests;
555
569
  private constructor();
556
570
  static create(config: SvcWrkArkadeSwapsConfig): Promise<ServiceWorkerArkadeSwaps>;
557
571
  startSwapManager(): Promise<void>;
@@ -644,7 +658,11 @@ declare class ServiceWorkerArkadeSwaps implements IArkadeSwaps {
644
658
  refreshSwapsStatus(): Promise<void>;
645
659
  dispose(): Promise<void>;
646
660
  [Symbol.asyncDispose](): Promise<void>;
661
+ private sendMessageDirect;
647
662
  private sendMessage;
663
+ private pingServiceWorker;
664
+ private sendMessageWithRetry;
665
+ private reinitialize;
648
666
  private initEventStream;
649
667
  private handleEventMessage;
650
668
  }
package/dist/index.d.ts CHANGED
@@ -83,7 +83,17 @@ declare const getInvoiceSatoshis: (invoice: string) => number;
83
83
  declare const getInvoicePaymentHash: (invoice: string) => string;
84
84
  declare const isValidArkAddress: (address: string) => boolean;
85
85
 
86
- declare const verifySignatures: (tx: Transaction, inputIndex: number, requiredSigners: string[]) => boolean;
86
+ /**
87
+ * Verifies that a transaction input has valid tapscript signatures
88
+ * from all required signers for a specific tapscript leaf.
89
+ *
90
+ * @param tx - The transaction to verify.
91
+ * @param inputIndex - The index of the input to check.
92
+ * @param requiredSigners - Hex-encoded x-only public keys of all required signers.
93
+ * @param expectedLeafHash - The tapscript leaf hash that signatures must commit to.
94
+ * @returns `true` if all required signers have valid signatures on the expected leaf, `false` otherwise.
95
+ */
96
+ declare const verifySignatures: (tx: Transaction, inputIndex: number, requiredSigners: string[], expectedLeafHash: Uint8Array) => boolean;
87
97
 
88
98
  /**
89
99
  * Generic type for swap save functions
@@ -552,6 +562,10 @@ declare class ServiceWorkerArkadeSwaps implements IArkadeSwaps {
552
562
  private actionExecutedListeners;
553
563
  private wsConnectedListeners;
554
564
  private wsDisconnectedListeners;
565
+ private initPayload;
566
+ private reinitPromise;
567
+ private pingPromise;
568
+ private inflightRequests;
555
569
  private constructor();
556
570
  static create(config: SvcWrkArkadeSwapsConfig): Promise<ServiceWorkerArkadeSwaps>;
557
571
  startSwapManager(): Promise<void>;
@@ -644,7 +658,11 @@ declare class ServiceWorkerArkadeSwaps implements IArkadeSwaps {
644
658
  refreshSwapsStatus(): Promise<void>;
645
659
  dispose(): Promise<void>;
646
660
  [Symbol.asyncDispose](): Promise<void>;
661
+ private sendMessageDirect;
647
662
  private sendMessage;
663
+ private pingServiceWorker;
664
+ private sendMessageWithRetry;
665
+ private reinitialize;
648
666
  private initEventStream;
649
667
  private handleEventMessage;
650
668
  }
package/dist/index.js CHANGED
@@ -49,7 +49,7 @@ import {
49
49
  updateReverseSwapStatus,
50
50
  updateSubmarineSwapStatus,
51
51
  verifySignatures
52
- } from "./chunk-SUFFGYDJ.js";
52
+ } from "./chunk-ZJ2Q5QSA.js";
53
53
  import "./chunk-3RG5ZIWI.js";
54
54
 
55
55
  // src/serviceWorker/arkade-swaps-message-handler.ts
@@ -526,6 +526,31 @@ var ArkadeSwapsMessageHandler = class _ArkadeSwapsMessageHandler {
526
526
  };
527
527
 
528
528
  // src/serviceWorker/arkade-swaps-runtime.ts
529
+ import {
530
+ MESSAGE_BUS_NOT_INITIALIZED,
531
+ ServiceWorkerTimeoutError
532
+ } from "@arkade-os/sdk";
533
+ function isMessageBusNotInitializedError(error) {
534
+ return error instanceof Error && error.message.includes(MESSAGE_BUS_NOT_INITIALIZED);
535
+ }
536
+ var DEDUPABLE_REQUEST_TYPES = /* @__PURE__ */ new Set([
537
+ "GET_FEES",
538
+ "GET_LIMITS",
539
+ "GET_SWAP_STATUS",
540
+ "GET_PENDING_SUBMARINE_SWAPS",
541
+ "GET_PENDING_REVERSE_SWAPS",
542
+ "GET_PENDING_CHAIN_SWAPS",
543
+ "GET_SWAP_HISTORY",
544
+ "QUOTE_SWAP",
545
+ "SM-GET_PENDING_SWAPS",
546
+ "SM-HAS_SWAP",
547
+ "SM-IS_PROCESSING",
548
+ "SM-GET_STATS"
549
+ ]);
550
+ function getRequestDedupKey(request) {
551
+ const { id, tag, ...rest } = request;
552
+ return JSON.stringify(rest);
553
+ }
529
554
  var ServiceWorkerArkadeSwaps = class _ServiceWorkerArkadeSwaps {
530
555
  constructor(messageTag, serviceWorker, swapRepository, withSwapManager) {
531
556
  this.messageTag = messageTag;
@@ -540,6 +565,10 @@ var ServiceWorkerArkadeSwaps = class _ServiceWorkerArkadeSwaps {
540
565
  actionExecutedListeners = /* @__PURE__ */ new Set();
541
566
  wsConnectedListeners = /* @__PURE__ */ new Set();
542
567
  wsDisconnectedListeners = /* @__PURE__ */ new Set();
568
+ initPayload = null;
569
+ reinitPromise = null;
570
+ pingPromise = null;
571
+ inflightRequests = /* @__PURE__ */ new Map();
543
572
  static async create(config) {
544
573
  const messageTag = config.messageTag ?? DEFAULT_MESSAGE_TAG;
545
574
  const swapRepository = config.swapRepository ?? new IndexedDbSwapRepository();
@@ -549,18 +578,20 @@ var ServiceWorkerArkadeSwaps = class _ServiceWorkerArkadeSwaps {
549
578
  swapRepository,
550
579
  Boolean(config.swapManager)
551
580
  );
581
+ const initPayload = {
582
+ network: config.network,
583
+ arkServerUrl: config.arkServerUrl,
584
+ swapProvider: { baseUrl: config.swapProvider.getApiUrl() },
585
+ swapManager: config.swapManager
586
+ };
552
587
  const initMessage = {
553
588
  tag: messageTag,
554
589
  id: getRandomId(),
555
590
  type: "INIT_ARKADE_SWAPS",
556
- payload: {
557
- network: config.network,
558
- arkServerUrl: config.arkServerUrl,
559
- swapProvider: { baseUrl: config.swapProvider.getApiUrl() },
560
- swapManager: config.swapManager
561
- }
591
+ payload: initPayload
562
592
  };
563
593
  await svcArkadeSwaps.sendMessage(initMessage);
594
+ svcArkadeSwaps.initPayload = initPayload;
564
595
  return svcArkadeSwaps;
565
596
  }
566
597
  async startSwapManager() {
@@ -1101,17 +1132,23 @@ var ServiceWorkerArkadeSwaps = class _ServiceWorkerArkadeSwaps {
1101
1132
  async [Symbol.asyncDispose]() {
1102
1133
  return this.dispose();
1103
1134
  }
1104
- async sendMessage(request) {
1135
+ sendMessageDirect(request) {
1105
1136
  return new Promise((resolve, reject) => {
1106
- const timeoutMs = 3e4;
1107
- let timeout;
1108
1137
  const cleanup = () => {
1109
- if (timeout) clearTimeout(timeout);
1138
+ clearTimeout(timeoutId);
1110
1139
  navigator.serviceWorker.removeEventListener(
1111
1140
  "message",
1112
1141
  messageHandler
1113
1142
  );
1114
1143
  };
1144
+ const timeoutId = setTimeout(() => {
1145
+ cleanup();
1146
+ reject(
1147
+ new ServiceWorkerTimeoutError(
1148
+ `Service worker message timed out (${request.type})`
1149
+ )
1150
+ );
1151
+ }, 3e4);
1115
1152
  const messageHandler = (event) => {
1116
1153
  const response = event.data;
1117
1154
  if (!response || response.tag !== this.messageTag || response.id !== request.id) {
@@ -1124,17 +1161,97 @@ var ServiceWorkerArkadeSwaps = class _ServiceWorkerArkadeSwaps {
1124
1161
  resolve(response);
1125
1162
  }
1126
1163
  };
1127
- timeout = setTimeout(() => {
1164
+ navigator.serviceWorker.addEventListener("message", messageHandler);
1165
+ this.serviceWorker.postMessage(request);
1166
+ });
1167
+ }
1168
+ async sendMessage(request) {
1169
+ if (!DEDUPABLE_REQUEST_TYPES.has(request.type)) {
1170
+ return this.sendMessageWithRetry(request);
1171
+ }
1172
+ const key = getRequestDedupKey(request);
1173
+ const existing = this.inflightRequests.get(key);
1174
+ if (existing) return existing;
1175
+ const promise = this.sendMessageWithRetry(request).finally(() => {
1176
+ this.inflightRequests.delete(key);
1177
+ });
1178
+ this.inflightRequests.set(key, promise);
1179
+ return promise;
1180
+ }
1181
+ pingServiceWorker() {
1182
+ if (this.pingPromise) return this.pingPromise;
1183
+ this.pingPromise = new Promise((resolve, reject) => {
1184
+ const pingId = getRandomId();
1185
+ const cleanup = () => {
1186
+ clearTimeout(timeoutId);
1187
+ navigator.serviceWorker.removeEventListener(
1188
+ "message",
1189
+ onMessage
1190
+ );
1191
+ };
1192
+ const timeoutId = setTimeout(() => {
1128
1193
  cleanup();
1129
1194
  reject(
1130
- new Error(
1131
- `Timed out waiting for service worker response: ${request.type}`
1195
+ new ServiceWorkerTimeoutError(
1196
+ "Service worker ping timed out"
1132
1197
  )
1133
1198
  );
1134
- }, timeoutMs);
1135
- navigator.serviceWorker.addEventListener("message", messageHandler);
1136
- this.serviceWorker.postMessage(request);
1199
+ }, 2e3);
1200
+ const onMessage = (event) => {
1201
+ if (event.data?.id === pingId && event.data?.tag === "PONG") {
1202
+ cleanup();
1203
+ resolve();
1204
+ }
1205
+ };
1206
+ navigator.serviceWorker.addEventListener("message", onMessage);
1207
+ this.serviceWorker.postMessage({
1208
+ id: pingId,
1209
+ tag: "PING"
1210
+ });
1211
+ }).finally(() => {
1212
+ this.pingPromise = null;
1213
+ });
1214
+ return this.pingPromise;
1215
+ }
1216
+ // Send a message, retrying up to 2 times if the service worker was
1217
+ // killed and restarted by the OS (mobile browsers do this aggressively).
1218
+ async sendMessageWithRetry(request) {
1219
+ if (this.initPayload) {
1220
+ try {
1221
+ await this.pingServiceWorker();
1222
+ } catch {
1223
+ await this.reinitialize();
1224
+ }
1225
+ }
1226
+ const maxRetries = 2;
1227
+ for (let attempt = 0; ; attempt++) {
1228
+ try {
1229
+ return await this.sendMessageDirect(request);
1230
+ } catch (error) {
1231
+ if (!isMessageBusNotInitializedError(error) || attempt >= maxRetries) {
1232
+ throw error;
1233
+ }
1234
+ await this.reinitialize();
1235
+ }
1236
+ }
1237
+ }
1238
+ async reinitialize() {
1239
+ if (this.reinitPromise) return this.reinitPromise;
1240
+ this.reinitPromise = (async () => {
1241
+ if (!this.initPayload) {
1242
+ throw new Error("Cannot re-initialize: missing configuration");
1243
+ }
1244
+ const initMessage = {
1245
+ tag: this.messageTag,
1246
+ type: "INIT_ARKADE_SWAPS",
1247
+ id: getRandomId(),
1248
+ payload: this.initPayload
1249
+ };
1250
+ await this.sendMessageDirect(initMessage);
1251
+ })().finally(() => {
1252
+ this.reinitPromise = null;
1137
1253
  });
1254
+ return this.reinitPromise;
1138
1255
  }
1139
1256
  initEventStream() {
1140
1257
  if (this.eventListenerInitialized) return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arkade-os/boltz-swap",
3
- "version": "0.3.6",
3
+ "version": "0.3.8",
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",
@@ -60,7 +60,7 @@
60
60
  "author": "Arkade-OS",
61
61
  "license": "MIT",
62
62
  "dependencies": {
63
- "@arkade-os/sdk": "0.4.8",
63
+ "@arkade-os/sdk": "0.4.10",
64
64
  "@noble/hashes": "2.0.1",
65
65
  "@scure/base": "2.0.0",
66
66
  "@scure/btc-signer": "2.0.1",