@arkade-os/boltz-swap 0.2.6 → 0.2.7

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
@@ -49,7 +49,8 @@ __export(index_exports, {
49
49
  isReverseFinalStatus: () => isReverseFinalStatus,
50
50
  isSubmarineFinalStatus: () => isSubmarineFinalStatus,
51
51
  isSubmarineRefundableStatus: () => isSubmarineRefundableStatus,
52
- isSubmarineSwapRefundable: () => isSubmarineSwapRefundable
52
+ isSubmarineSwapRefundable: () => isSubmarineSwapRefundable,
53
+ verifySignatures: () => verifySignatures
53
54
  });
54
55
  module.exports = __toCommonJS(index_exports);
55
56
 
@@ -129,12 +130,14 @@ var TransactionRefundedError = class extends SwapError {
129
130
  };
130
131
 
131
132
  // src/arkade-lightning.ts
132
- var import_sdk = require("@arkade-os/sdk");
133
+ var import_sdk3 = require("@arkade-os/sdk");
133
134
  var import_sha2 = require("@noble/hashes/sha2.js");
134
- var import_base = require("@scure/base");
135
+ var import_base2 = require("@scure/base");
135
136
  var import_utils = require("@noble/hashes/utils.js");
136
137
 
137
138
  // src/boltz-swap-provider.ts
139
+ var import_sdk = require("@arkade-os/sdk");
140
+ var import_base = require("@scure/base");
138
141
  var isSubmarineFinalStatus = (status) => {
139
142
  return [
140
143
  "invoice.failedToPay",
@@ -168,7 +171,7 @@ var isSubmarineRefundableStatus = (status) => {
168
171
  ].includes(status);
169
172
  };
170
173
  var isSubmarineSwapRefundable = (swap) => {
171
- return isSubmarineRefundableStatus(swap.status) && isPendingSubmarineSwap(swap) && swap.refundable !== false;
174
+ return isSubmarineRefundableStatus(swap.status) && isPendingSubmarineSwap(swap) && swap.refundable !== false && swap.refunded !== true;
172
175
  };
173
176
  var isGetReverseSwapTxIdResponse = (data) => {
174
177
  return data && typeof data === "object" && typeof data.id === "string" && typeof data.timeoutBlockHeight === "number";
@@ -191,6 +194,9 @@ var isGetSwapPreimageResponse = (data) => {
191
194
  var isCreateReverseSwapResponse = (data) => {
192
195
  return data && typeof data === "object" && typeof data.id === "string" && typeof data.invoice === "string" && typeof data.onchainAmount === "number" && typeof data.lockupAddress === "string" && typeof data.refundPublicKey === "string" && data.timeoutBlockHeights && typeof data.timeoutBlockHeights === "object" && typeof data.timeoutBlockHeights.refund === "number" && typeof data.timeoutBlockHeights.unilateralClaim === "number" && typeof data.timeoutBlockHeights.unilateralRefund === "number" && typeof data.timeoutBlockHeights.unilateralRefundWithoutReceiver === "number";
193
196
  };
197
+ var isRefundSubmarineSwapResponse = (data) => {
198
+ return data && typeof data === "object" && typeof data.transaction === "string" && typeof data.checkpoint === "string";
199
+ };
194
200
  var BASE_URLS = {
195
201
  mutinynet: "https://api.boltz.mutinynet.arkade.sh",
196
202
  regtest: "http://localhost:9069"
@@ -337,6 +343,29 @@ var BoltzSwapProvider = class {
337
343
  throw new SchemaError({ message: "Error creating reverse swap" });
338
344
  return response;
339
345
  }
346
+ async refundSubmarineSwap(swapId, transaction, checkpoint) {
347
+ const requestBody = {
348
+ checkpoint: import_base.base64.encode(checkpoint.toPSBT()),
349
+ transaction: import_base.base64.encode(transaction.toPSBT())
350
+ };
351
+ const response = await this.request(
352
+ `/v2/swap/submarine/${swapId}/refund/ark`,
353
+ "POST",
354
+ requestBody
355
+ );
356
+ if (!isRefundSubmarineSwapResponse(response))
357
+ throw new SchemaError({
358
+ message: "Error refunding submarine swap"
359
+ });
360
+ return {
361
+ transaction: import_sdk.Transaction.fromPSBT(
362
+ import_base.base64.decode(response.transaction)
363
+ ),
364
+ checkpoint: import_sdk.Transaction.fromPSBT(
365
+ import_base.base64.decode(response.checkpoint)
366
+ )
367
+ };
368
+ }
340
369
  async monitorSwap(swapId, update) {
341
370
  return new Promise((resolve, reject) => {
342
371
  const webSocket = new globalThis.WebSocket(this.wsUrl);
@@ -450,6 +479,17 @@ var getInvoicePaymentHash = (invoice) => {
450
479
  return decodeInvoice(invoice).paymentHash;
451
480
  };
452
481
 
482
+ // src/utils/signatures.ts
483
+ var import_sdk2 = require("@arkade-os/sdk");
484
+ var verifySignatures = (tx, inputIndex, requiredSigners) => {
485
+ try {
486
+ (0, import_sdk2.verifyTapscriptSignatures)(tx, inputIndex, requiredSigners);
487
+ return true;
488
+ } catch (_) {
489
+ return false;
490
+ }
491
+ };
492
+
453
493
  // src/arkade-lightning.ts
454
494
  function getSignerSession(wallet) {
455
495
  const signerSession = wallet.identity.signerSession;
@@ -578,7 +618,7 @@ var ArkadeLightning = class {
578
618
  * @returns The created pending submarine swap.
579
619
  */
580
620
  async createSubmarineSwap(args) {
581
- const refundPublicKey = import_base.hex.encode(
621
+ const refundPublicKey = import_base2.hex.encode(
582
622
  await this.wallet.identity.compressedPublicKey()
583
623
  );
584
624
  if (!refundPublicKey)
@@ -611,7 +651,7 @@ var ArkadeLightning = class {
611
651
  async createReverseSwap(args) {
612
652
  if (args.amount <= 0)
613
653
  throw new SwapError({ message: "Amount must be greater than 0" });
614
- const claimPublicKey = import_base.hex.encode(
654
+ const claimPublicKey = import_base2.hex.encode(
615
655
  await this.wallet.identity.compressedPublicKey()
616
656
  );
617
657
  if (!claimPublicKey)
@@ -619,7 +659,7 @@ var ArkadeLightning = class {
619
659
  message: "Failed to get claim public key from wallet"
620
660
  });
621
661
  const preimage = (0, import_utils.randomBytes)(32);
622
- const preimageHash = import_base.hex.encode((0, import_sha2.sha256)(preimage));
662
+ const preimageHash = import_base2.hex.encode((0, import_sha2.sha256)(preimage));
623
663
  if (!preimageHash)
624
664
  throw new SwapError({ message: "Failed to get preimage hash" });
625
665
  const swapRequest = {
@@ -633,7 +673,7 @@ var ArkadeLightning = class {
633
673
  id: swapResponse.id,
634
674
  type: "reverse",
635
675
  createdAt: Math.floor(Date.now() / 1e3),
636
- preimage: import_base.hex.encode(preimage),
676
+ preimage: import_base2.hex.encode(preimage),
637
677
  request: swapRequest,
638
678
  response: swapResponse,
639
679
  status: "swap.created"
@@ -646,31 +686,30 @@ var ArkadeLightning = class {
646
686
  * @param pendingSwap - The pending reverse swap to claim the VHTLC.
647
687
  */
648
688
  async claimVHTLC(pendingSwap) {
649
- const preimage = import_base.hex.decode(pendingSwap.preimage);
689
+ const preimage = import_base2.hex.decode(pendingSwap.preimage);
650
690
  const aspInfo = await this.arkProvider.getInfo();
651
691
  const address = await this.wallet.getAddress();
652
- let receiverXOnlyPublicKey = await this.wallet.identity.xOnlyPublicKey();
653
- if (receiverXOnlyPublicKey.length == 33) {
654
- receiverXOnlyPublicKey = receiverXOnlyPublicKey.slice(1);
655
- } else if (receiverXOnlyPublicKey.length !== 32) {
656
- throw new Error(
657
- `Invalid receiver public key length: ${receiverXOnlyPublicKey.length}`
658
- );
659
- }
660
- let serverXOnlyPublicKey = import_base.hex.decode(aspInfo.signerPubkey);
661
- if (serverXOnlyPublicKey.length == 33) {
662
- serverXOnlyPublicKey = serverXOnlyPublicKey.slice(1);
663
- } else if (serverXOnlyPublicKey.length !== 32) {
664
- throw new Error(
665
- `Invalid server public key length: ${serverXOnlyPublicKey.length}`
666
- );
667
- }
692
+ const ourXOnlyPublicKey = this.normalizeToXOnlyPublicKey(
693
+ await this.wallet.identity.xOnlyPublicKey(),
694
+ "our",
695
+ pendingSwap.id
696
+ );
697
+ const boltzXOnlyPublicKey = this.normalizeToXOnlyPublicKey(
698
+ import_base2.hex.decode(pendingSwap.response.refundPublicKey),
699
+ "boltz",
700
+ pendingSwap.id
701
+ );
702
+ const serverXOnlyPublicKey = this.normalizeToXOnlyPublicKey(
703
+ import_base2.hex.decode(aspInfo.signerPubkey),
704
+ "server",
705
+ pendingSwap.id
706
+ );
668
707
  const { vhtlcScript, vhtlcAddress } = this.createVHTLCScript({
669
708
  network: aspInfo.network,
670
709
  preimageHash: (0, import_sha2.sha256)(preimage),
671
- receiverPubkey: import_base.hex.encode(receiverXOnlyPublicKey),
672
- senderPubkey: pendingSwap.response.refundPublicKey,
673
- serverPubkey: import_base.hex.encode(serverXOnlyPublicKey),
710
+ receiverPubkey: import_base2.hex.encode(ourXOnlyPublicKey),
711
+ senderPubkey: import_base2.hex.encode(boltzXOnlyPublicKey),
712
+ serverPubkey: import_base2.hex.encode(serverXOnlyPublicKey),
674
713
  timeoutBlockHeights: pendingSwap.response.timeoutBlockHeights
675
714
  });
676
715
  if (!vhtlcScript)
@@ -678,7 +717,7 @@ var ArkadeLightning = class {
678
717
  if (vhtlcAddress !== pendingSwap.response.lockupAddress)
679
718
  throw new Error("Boltz is trying to scam us");
680
719
  const spendableVtxos = await this.indexerProvider.getVtxos({
681
- scripts: [import_base.hex.encode(vhtlcScript.pkScript)],
720
+ scripts: [import_base2.hex.encode(vhtlcScript.pkScript)],
682
721
  spendableOnly: true
683
722
  });
684
723
  if (spendableVtxos.vtxos.length === 0)
@@ -695,17 +734,17 @@ var ArkadeLightning = class {
695
734
  signedTx = import_btc_signer.Transaction.fromPSBT(signedTx.toPSBT(), {
696
735
  allowUnknown: true
697
736
  });
698
- (0, import_sdk.setArkPsbtField)(signedTx, 0, import_sdk.ConditionWitness, [preimage]);
737
+ (0, import_sdk3.setArkPsbtField)(signedTx, 0, import_sdk3.ConditionWitness, [preimage]);
699
738
  return signedTx;
700
739
  },
701
- xOnlyPublicKey: receiverXOnlyPublicKey,
740
+ xOnlyPublicKey: ourXOnlyPublicKey,
702
741
  signerSession: getSignerSession(this.wallet)
703
742
  };
704
- const rawCheckpointTapscript = import_base.hex.decode(aspInfo.checkpointTapscript);
705
- const serverUnrollScript = import_sdk.CSVMultisigTapscript.decode(
743
+ const rawCheckpointTapscript = import_base2.hex.decode(aspInfo.checkpointTapscript);
744
+ const serverUnrollScript = import_sdk3.CSVMultisigTapscript.decode(
706
745
  rawCheckpointTapscript
707
746
  );
708
- const { arkTx, checkpoints } = (0, import_sdk.buildOffchainTx)(
747
+ const { arkTx, checkpoints } = (0, import_sdk3.buildOffchainTx)(
709
748
  [
710
749
  {
711
750
  ...spendableVtxos.vtxos[0],
@@ -716,15 +755,15 @@ var ArkadeLightning = class {
716
755
  [
717
756
  {
718
757
  amount: BigInt(vtxo.value),
719
- script: import_sdk.ArkAddress.decode(address).pkScript
758
+ script: import_sdk3.ArkAddress.decode(address).pkScript
720
759
  }
721
760
  ],
722
761
  serverUnrollScript
723
762
  );
724
763
  const signedArkTx = await vhtlcIdentity.sign(arkTx);
725
764
  const { arkTxid, finalArkTx, signedCheckpointTxs } = await this.arkProvider.submitTx(
726
- import_base.base64.encode(signedArkTx.toPSBT()),
727
- checkpoints.map((c) => import_base.base64.encode(c.toPSBT()))
765
+ import_base2.base64.encode(signedArkTx.toPSBT()),
766
+ checkpoints.map((c) => import_base2.base64.encode(c.toPSBT()))
728
767
  );
729
768
  if (!this.validFinalArkTx(
730
769
  finalArkTx,
@@ -735,11 +774,11 @@ var ArkadeLightning = class {
735
774
  }
736
775
  const finalCheckpoints = await Promise.all(
737
776
  signedCheckpointTxs.map(async (c) => {
738
- const tx = import_btc_signer.Transaction.fromPSBT(import_base.base64.decode(c), {
777
+ const tx = import_btc_signer.Transaction.fromPSBT(import_base2.base64.decode(c), {
739
778
  allowUnknown: true
740
779
  });
741
780
  const signedCheckpoint = await vhtlcIdentity.sign(tx, [0]);
742
- return import_base.base64.encode(signedCheckpoint.toPSBT());
781
+ return import_base2.base64.encode(signedCheckpoint.toPSBT());
743
782
  })
744
783
  );
745
784
  await this.arkProvider.finalizeTx(arkTxid, finalCheckpoints);
@@ -757,32 +796,29 @@ var ArkadeLightning = class {
757
796
  const aspInfo = await this.arkProvider.getInfo();
758
797
  const address = await this.wallet.getAddress();
759
798
  if (!address) throw new Error("Failed to get ark address from wallet");
760
- let receiverXOnlyPublicKey = await this.wallet.identity.xOnlyPublicKey();
761
- if (receiverXOnlyPublicKey.length == 33) {
762
- receiverXOnlyPublicKey = receiverXOnlyPublicKey.slice(1);
763
- } else if (receiverXOnlyPublicKey.length !== 32) {
764
- throw new Error(
765
- `Invalid receiver public key length: ${receiverXOnlyPublicKey.length}`
766
- );
767
- }
768
- let serverXOnlyPublicKey = import_base.hex.decode(aspInfo.signerPubkey);
769
- if (serverXOnlyPublicKey.length == 33) {
770
- serverXOnlyPublicKey = serverXOnlyPublicKey.slice(1);
771
- } else if (serverXOnlyPublicKey.length !== 32) {
772
- throw new Error(
773
- `Invalid server public key length: ${serverXOnlyPublicKey.length}`
774
- );
775
- }
799
+ const ourXOnlyPublicKey = this.normalizeToXOnlyPublicKey(
800
+ await this.wallet.identity.xOnlyPublicKey(),
801
+ "our",
802
+ pendingSwap.id
803
+ );
804
+ const serverXOnlyPublicKey = this.normalizeToXOnlyPublicKey(
805
+ import_base2.hex.decode(aspInfo.signerPubkey),
806
+ "server",
807
+ pendingSwap.id
808
+ );
809
+ const boltzXOnlyPublicKey = this.normalizeToXOnlyPublicKey(
810
+ import_base2.hex.decode(pendingSwap.response.claimPublicKey),
811
+ "boltz",
812
+ pendingSwap.id
813
+ );
776
814
  const { vhtlcScript, vhtlcAddress } = this.createVHTLCScript({
777
815
  network: aspInfo.network,
778
- preimageHash: import_base.hex.decode(
816
+ preimageHash: import_base2.hex.decode(
779
817
  getInvoicePaymentHash(pendingSwap.request.invoice)
780
818
  ),
781
- receiverPubkey: pendingSwap.response.claimPublicKey,
782
- senderPubkey: import_base.hex.encode(
783
- await this.wallet.identity.xOnlyPublicKey()
784
- ),
785
- serverPubkey: aspInfo.signerPubkey,
819
+ receiverPubkey: import_base2.hex.encode(boltzXOnlyPublicKey),
820
+ senderPubkey: import_base2.hex.encode(ourXOnlyPublicKey),
821
+ serverPubkey: import_base2.hex.encode(serverXOnlyPublicKey),
786
822
  timeoutBlockHeights: pendingSwap.response.timeoutBlockHeights
787
823
  });
788
824
  if (!vhtlcScript)
@@ -790,15 +826,15 @@ var ArkadeLightning = class {
790
826
  if (vhtlcAddress !== pendingSwap.response.address)
791
827
  throw new Error("Boltz is trying to scam us");
792
828
  const spendableVtxos = await this.indexerProvider.getVtxos({
793
- scripts: [import_base.hex.encode(vhtlcScript.pkScript)],
829
+ scripts: [import_base2.hex.encode(vhtlcScript.pkScript)],
794
830
  spendableOnly: true
795
831
  });
796
832
  if (spendableVtxos.vtxos.length === 0) {
797
833
  throw new Error("No spendable virtual coins found");
798
834
  }
799
835
  const vhtlcIdentity = {
800
- sign: async (tx, inputIndexes) => {
801
- const cpy = tx.clone();
836
+ sign: async (tx2, inputIndexes) => {
837
+ const cpy = tx2.clone();
802
838
  let signedTx = await signTransaction(
803
839
  this.wallet,
804
840
  cpy,
@@ -808,14 +844,14 @@ var ArkadeLightning = class {
808
844
  allowUnknown: true
809
845
  });
810
846
  },
811
- xOnlyPublicKey: receiverXOnlyPublicKey,
847
+ xOnlyPublicKey: ourXOnlyPublicKey,
812
848
  signerSession: getSignerSession(this.wallet)
813
849
  };
814
- const rawCheckpointTapscript = import_base.hex.decode(aspInfo.checkpointTapscript);
815
- const serverUnrollScript = import_sdk.CSVMultisigTapscript.decode(
850
+ const rawCheckpointTapscript = import_base2.hex.decode(aspInfo.checkpointTapscript);
851
+ const serverUnrollScript = import_sdk3.CSVMultisigTapscript.decode(
816
852
  rawCheckpointTapscript
817
853
  );
818
- const { arkTx, checkpoints } = (0, import_sdk.buildOffchainTx)(
854
+ const { arkTx: unsignedRefundTx, checkpoints: checkpointPtxs } = (0, import_sdk3.buildOffchainTx)(
819
855
  [
820
856
  {
821
857
  ...spendableVtxos.vtxos[0],
@@ -826,37 +862,78 @@ var ArkadeLightning = class {
826
862
  [
827
863
  {
828
864
  amount: BigInt(spendableVtxos.vtxos[0].value),
829
- script: import_sdk.ArkAddress.decode(address).pkScript
865
+ script: import_sdk3.ArkAddress.decode(address).pkScript
830
866
  }
831
867
  ],
832
868
  serverUnrollScript
833
869
  );
834
- const signedArkTx = await vhtlcIdentity.sign(arkTx);
870
+ if (checkpointPtxs.length !== 1)
871
+ throw new Error(
872
+ `Expected one checkpoint transaction, got ${checkpointPtxs.length}`
873
+ );
874
+ const unsignedCheckpointTx = checkpointPtxs[0];
875
+ const {
876
+ transaction: boltzSignedRefundTx,
877
+ checkpoint: boltzSignedCheckpointTx
878
+ } = await this.swapProvider.refundSubmarineSwap(
879
+ pendingSwap.id,
880
+ unsignedRefundTx,
881
+ unsignedCheckpointTx
882
+ );
883
+ const boltzXOnlyPublicKeyHex = import_base2.hex.encode(boltzXOnlyPublicKey);
884
+ if (!verifySignatures(boltzSignedRefundTx, 0, [boltzXOnlyPublicKeyHex])) {
885
+ throw new Error("Invalid Boltz signature in refund transaction");
886
+ }
887
+ if (!verifySignatures(boltzSignedCheckpointTx, 0, [
888
+ boltzXOnlyPublicKeyHex
889
+ ])) {
890
+ throw new Error(
891
+ "Invalid Boltz signature in checkpoint transaction"
892
+ );
893
+ }
894
+ const signedRefundTx = await vhtlcIdentity.sign(unsignedRefundTx);
895
+ const signedCheckpointTx = await vhtlcIdentity.sign(unsignedCheckpointTx);
896
+ const combinedSignedRefundTx = (0, import_sdk3.combineTapscriptSigs)(
897
+ boltzSignedRefundTx,
898
+ signedRefundTx
899
+ );
900
+ const combinedSignedCheckpointTx = (0, import_sdk3.combineTapscriptSigs)(
901
+ boltzSignedCheckpointTx,
902
+ signedCheckpointTx
903
+ );
835
904
  const { arkTxid, finalArkTx, signedCheckpointTxs } = await this.arkProvider.submitTx(
836
- import_base.base64.encode(signedArkTx.toPSBT()),
837
- checkpoints.map((c) => import_base.base64.encode(c.toPSBT()))
905
+ import_base2.base64.encode(combinedSignedRefundTx.toPSBT()),
906
+ [import_base2.base64.encode(unsignedCheckpointTx.toPSBT())]
838
907
  );
839
- if (!this.validFinalArkTx(
840
- finalArkTx,
841
- serverXOnlyPublicKey,
842
- vhtlcScript.leaves
843
- )) {
844
- throw new Error("Invalid final Ark transaction");
908
+ const tx = import_btc_signer.Transaction.fromPSBT(import_base2.base64.decode(finalArkTx));
909
+ const inputIndex = 0;
910
+ const requiredSigners = [
911
+ import_base2.hex.encode(ourXOnlyPublicKey),
912
+ import_base2.hex.encode(boltzXOnlyPublicKey),
913
+ import_base2.hex.encode(serverXOnlyPublicKey)
914
+ ];
915
+ if (!verifySignatures(tx, inputIndex, requiredSigners)) {
916
+ throw new Error("Invalid refund transaction");
845
917
  }
846
- const finalCheckpoints = await Promise.all(
847
- signedCheckpointTxs.map(async (c) => {
848
- const tx = import_btc_signer.Transaction.fromPSBT(import_base.base64.decode(c), {
849
- allowUnknown: true
850
- });
851
- const signedCheckpoint = await vhtlcIdentity.sign(tx, [0]);
852
- return import_base.base64.encode(signedCheckpoint.toPSBT());
853
- })
918
+ if (signedCheckpointTxs.length !== 1) {
919
+ throw new Error(
920
+ `Expected one signed checkpoint transaction, got ${signedCheckpointTxs.length}`
921
+ );
922
+ }
923
+ const serverSignedCheckpointTx = import_btc_signer.Transaction.fromPSBT(
924
+ import_base2.base64.decode(signedCheckpointTxs[0])
854
925
  );
855
- await this.arkProvider.finalizeTx(arkTxid, finalCheckpoints);
856
- const finalStatus = await this.getSwapStatus(pendingSwap.id);
926
+ const finalCheckpointTx = (0, import_sdk3.combineTapscriptSigs)(
927
+ combinedSignedCheckpointTx,
928
+ serverSignedCheckpointTx
929
+ );
930
+ await this.arkProvider.finalizeTx(arkTxid, [
931
+ import_base2.base64.encode(finalCheckpointTx.toPSBT())
932
+ ]);
857
933
  await this.savePendingSubmarineSwap({
858
934
  ...pendingSwap,
859
- status: finalStatus.status
935
+ refundable: true,
936
+ refunded: true
860
937
  });
861
938
  }
862
939
  /**
@@ -1039,7 +1116,7 @@ var ArkadeLightning = class {
1039
1116
  * @returns True if the final Ark transaction is valid, false otherwise.
1040
1117
  */
1041
1118
  validFinalArkTx = (finalArkTx, _pubkey, _tapLeaves) => {
1042
- const tx = import_btc_signer.Transaction.fromPSBT(import_base.base64.decode(finalArkTx), {
1119
+ const tx = import_btc_signer.Transaction.fromPSBT(import_base2.base64.decode(finalArkTx), {
1043
1120
  allowUnknown: true
1044
1121
  });
1045
1122
  if (!tx) return false;
@@ -1066,32 +1143,20 @@ var ArkadeLightning = class {
1066
1143
  serverPubkey,
1067
1144
  timeoutBlockHeights
1068
1145
  }) {
1069
- let receiverXOnlyPublicKey = import_base.hex.decode(receiverPubkey);
1070
- if (receiverXOnlyPublicKey.length == 33) {
1071
- receiverXOnlyPublicKey = receiverXOnlyPublicKey.slice(1);
1072
- } else if (receiverXOnlyPublicKey.length !== 32) {
1073
- throw new Error(
1074
- `Invalid receiver public key length: ${receiverXOnlyPublicKey.length}`
1075
- );
1076
- }
1077
- let senderXOnlyPublicKey = import_base.hex.decode(senderPubkey);
1078
- if (senderXOnlyPublicKey.length == 33) {
1079
- senderXOnlyPublicKey = senderXOnlyPublicKey.slice(1);
1080
- } else if (senderXOnlyPublicKey.length !== 32) {
1081
- throw new Error(
1082
- `Invalid sender public key length: ${senderXOnlyPublicKey.length}`
1083
- );
1084
- }
1085
- let serverXOnlyPublicKey = import_base.hex.decode(serverPubkey);
1086
- if (serverXOnlyPublicKey.length == 33) {
1087
- serverXOnlyPublicKey = serverXOnlyPublicKey.slice(1);
1088
- } else if (serverXOnlyPublicKey.length !== 32) {
1089
- throw new Error(
1090
- `Invalid server public key length: ${serverXOnlyPublicKey.length}`
1091
- );
1092
- }
1146
+ const receiverXOnlyPublicKey = this.normalizeToXOnlyPublicKey(
1147
+ import_base2.hex.decode(receiverPubkey),
1148
+ "receiver"
1149
+ );
1150
+ const senderXOnlyPublicKey = this.normalizeToXOnlyPublicKey(
1151
+ import_base2.hex.decode(senderPubkey),
1152
+ "sender"
1153
+ );
1154
+ const serverXOnlyPublicKey = this.normalizeToXOnlyPublicKey(
1155
+ import_base2.hex.decode(serverPubkey),
1156
+ "server"
1157
+ );
1093
1158
  const delayType = (num) => num < 512 ? "blocks" : "seconds";
1094
- const vhtlcScript = new import_sdk.VHTLC.Script({
1159
+ const vhtlcScript = new import_sdk3.VHTLC.Script({
1095
1160
  preimageHash: (0, import_legacy.ripemd160)(preimageHash),
1096
1161
  sender: senderXOnlyPublicKey,
1097
1162
  receiver: receiverXOnlyPublicKey,
@@ -1214,6 +1279,24 @@ var ArkadeLightning = class {
1214
1279
  });
1215
1280
  }
1216
1281
  }
1282
+ /**
1283
+ * Validate we are using a x-only public key
1284
+ * @param publicKey
1285
+ * @param keyName
1286
+ * @param swapId
1287
+ * @returns Uint8Array
1288
+ */
1289
+ normalizeToXOnlyPublicKey(publicKey, keyName, swapId) {
1290
+ if (publicKey.length === 33) {
1291
+ return publicKey.slice(1);
1292
+ }
1293
+ if (publicKey.length !== 32) {
1294
+ throw new Error(
1295
+ `Invalid ${keyName} public key length: ${publicKey.length} ${swapId ? "for swap " + swapId : ""}`
1296
+ );
1297
+ }
1298
+ return publicKey;
1299
+ }
1217
1300
  };
1218
1301
  // Annotate the CommonJS export names for ESM import in node:
1219
1302
  0 && (module.exports = {
@@ -1236,5 +1319,6 @@ var ArkadeLightning = class {
1236
1319
  isReverseFinalStatus,
1237
1320
  isSubmarineFinalStatus,
1238
1321
  isSubmarineRefundableStatus,
1239
- isSubmarineSwapRefundable
1322
+ isSubmarineSwapRefundable,
1323
+ verifySignatures
1240
1324
  });
package/dist/index.d.cts CHANGED
@@ -1,4 +1,5 @@
1
- import { Wallet, ServiceWorkerWallet, ArkProvider, IndexerProvider, VHTLC } from '@arkade-os/sdk';
1
+ import { Transaction, Wallet, ServiceWorkerWallet, ArkProvider, IndexerProvider, VHTLC } from '@arkade-os/sdk';
2
+ import { Transaction as Transaction$1 } from '@scure/btc-signer';
2
3
 
3
4
  interface SwapProviderConfig {
4
5
  apiUrl?: string;
@@ -81,6 +82,10 @@ declare class BoltzSwapProvider {
81
82
  getSwapPreimage(id: string): Promise<GetSwapPreimageResponse>;
82
83
  createSubmarineSwap({ invoice, refundPublicKey, }: CreateSubmarineSwapRequest): Promise<CreateSubmarineSwapResponse>;
83
84
  createReverseSwap({ invoiceAmount, claimPublicKey, preimageHash, description, }: CreateReverseSwapRequest): Promise<CreateReverseSwapResponse>;
85
+ refundSubmarineSwap(swapId: string, transaction: Transaction, checkpoint: Transaction): Promise<{
86
+ transaction: Transaction;
87
+ checkpoint: Transaction;
88
+ }>;
84
89
  monitorSwap(swapId: string, update: (type: BoltzSwapStatus, data?: any) => void): Promise<void>;
85
90
  private request;
86
91
  }
@@ -131,6 +136,7 @@ interface PendingSubmarineSwap {
131
136
  type: "submarine";
132
137
  createdAt: number;
133
138
  preimage?: string;
139
+ refunded?: boolean;
134
140
  refundable?: boolean;
135
141
  status: BoltzSwapStatus;
136
142
  request: CreateSubmarineSwapRequest;
@@ -345,6 +351,14 @@ declare class ArkadeLightning {
345
351
  * User should manually retry or delete it if refund fails.
346
352
  */
347
353
  refreshSwapsStatus(): Promise<void>;
354
+ /**
355
+ * Validate we are using a x-only public key
356
+ * @param publicKey
357
+ * @param keyName
358
+ * @param swapId
359
+ * @returns Uint8Array
360
+ */
361
+ private normalizeToXOnlyPublicKey;
348
362
  }
349
363
 
350
364
  interface ErrorOptions {
@@ -392,4 +406,6 @@ declare const decodeInvoice: (invoice: string) => DecodedInvoice;
392
406
  declare const getInvoiceSatoshis: (invoice: string) => number;
393
407
  declare const getInvoicePaymentHash: (invoice: string) => string;
394
408
 
395
- export { ArkadeLightning, type ArkadeLightningConfig, BoltzSwapProvider, type BoltzSwapStatus, type CreateLightningInvoiceResponse, type DecodedInvoice, type FeeConfig, type FeesResponse, type IncomingPaymentSubscription, InsufficientFundsError, InvoiceExpiredError, InvoiceFailedToPayError, type LimitsResponse, type Network, NetworkError, type PendingReverseSwap, type PendingSubmarineSwap, type RefundHandler, type RetryConfig, SchemaError, type SendLightningPaymentRequest, type SendLightningPaymentResponse, SwapError, SwapExpiredError, type TimeoutConfig, TransactionFailedError, type Vtxo, decodeInvoice, getInvoicePaymentHash, getInvoiceSatoshis, isPendingReverseSwap, isPendingSubmarineSwap, isReverseClaimableStatus, isReverseFinalStatus, isSubmarineFinalStatus, isSubmarineRefundableStatus, isSubmarineSwapRefundable };
409
+ declare const verifySignatures: (tx: Transaction$1, inputIndex: number, requiredSigners: string[]) => boolean;
410
+
411
+ export { ArkadeLightning, type ArkadeLightningConfig, BoltzSwapProvider, type BoltzSwapStatus, type CreateLightningInvoiceResponse, type DecodedInvoice, type FeeConfig, type FeesResponse, type IncomingPaymentSubscription, InsufficientFundsError, InvoiceExpiredError, InvoiceFailedToPayError, type LimitsResponse, type Network, NetworkError, type PendingReverseSwap, type PendingSubmarineSwap, type RefundHandler, type RetryConfig, SchemaError, type SendLightningPaymentRequest, type SendLightningPaymentResponse, SwapError, SwapExpiredError, type TimeoutConfig, TransactionFailedError, type Vtxo, decodeInvoice, getInvoicePaymentHash, getInvoiceSatoshis, isPendingReverseSwap, isPendingSubmarineSwap, isReverseClaimableStatus, isReverseFinalStatus, isSubmarineFinalStatus, isSubmarineRefundableStatus, isSubmarineSwapRefundable, verifySignatures };
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
- import { Wallet, ServiceWorkerWallet, ArkProvider, IndexerProvider, VHTLC } from '@arkade-os/sdk';
1
+ import { Transaction, Wallet, ServiceWorkerWallet, ArkProvider, IndexerProvider, VHTLC } from '@arkade-os/sdk';
2
+ import { Transaction as Transaction$1 } from '@scure/btc-signer';
2
3
 
3
4
  interface SwapProviderConfig {
4
5
  apiUrl?: string;
@@ -81,6 +82,10 @@ declare class BoltzSwapProvider {
81
82
  getSwapPreimage(id: string): Promise<GetSwapPreimageResponse>;
82
83
  createSubmarineSwap({ invoice, refundPublicKey, }: CreateSubmarineSwapRequest): Promise<CreateSubmarineSwapResponse>;
83
84
  createReverseSwap({ invoiceAmount, claimPublicKey, preimageHash, description, }: CreateReverseSwapRequest): Promise<CreateReverseSwapResponse>;
85
+ refundSubmarineSwap(swapId: string, transaction: Transaction, checkpoint: Transaction): Promise<{
86
+ transaction: Transaction;
87
+ checkpoint: Transaction;
88
+ }>;
84
89
  monitorSwap(swapId: string, update: (type: BoltzSwapStatus, data?: any) => void): Promise<void>;
85
90
  private request;
86
91
  }
@@ -131,6 +136,7 @@ interface PendingSubmarineSwap {
131
136
  type: "submarine";
132
137
  createdAt: number;
133
138
  preimage?: string;
139
+ refunded?: boolean;
134
140
  refundable?: boolean;
135
141
  status: BoltzSwapStatus;
136
142
  request: CreateSubmarineSwapRequest;
@@ -345,6 +351,14 @@ declare class ArkadeLightning {
345
351
  * User should manually retry or delete it if refund fails.
346
352
  */
347
353
  refreshSwapsStatus(): Promise<void>;
354
+ /**
355
+ * Validate we are using a x-only public key
356
+ * @param publicKey
357
+ * @param keyName
358
+ * @param swapId
359
+ * @returns Uint8Array
360
+ */
361
+ private normalizeToXOnlyPublicKey;
348
362
  }
349
363
 
350
364
  interface ErrorOptions {
@@ -392,4 +406,6 @@ declare const decodeInvoice: (invoice: string) => DecodedInvoice;
392
406
  declare const getInvoiceSatoshis: (invoice: string) => number;
393
407
  declare const getInvoicePaymentHash: (invoice: string) => string;
394
408
 
395
- export { ArkadeLightning, type ArkadeLightningConfig, BoltzSwapProvider, type BoltzSwapStatus, type CreateLightningInvoiceResponse, type DecodedInvoice, type FeeConfig, type FeesResponse, type IncomingPaymentSubscription, InsufficientFundsError, InvoiceExpiredError, InvoiceFailedToPayError, type LimitsResponse, type Network, NetworkError, type PendingReverseSwap, type PendingSubmarineSwap, type RefundHandler, type RetryConfig, SchemaError, type SendLightningPaymentRequest, type SendLightningPaymentResponse, SwapError, SwapExpiredError, type TimeoutConfig, TransactionFailedError, type Vtxo, decodeInvoice, getInvoicePaymentHash, getInvoiceSatoshis, isPendingReverseSwap, isPendingSubmarineSwap, isReverseClaimableStatus, isReverseFinalStatus, isSubmarineFinalStatus, isSubmarineRefundableStatus, isSubmarineSwapRefundable };
409
+ declare const verifySignatures: (tx: Transaction$1, inputIndex: number, requiredSigners: string[]) => boolean;
410
+
411
+ export { ArkadeLightning, type ArkadeLightningConfig, BoltzSwapProvider, type BoltzSwapStatus, type CreateLightningInvoiceResponse, type DecodedInvoice, type FeeConfig, type FeesResponse, type IncomingPaymentSubscription, InsufficientFundsError, InvoiceExpiredError, InvoiceFailedToPayError, type LimitsResponse, type Network, NetworkError, type PendingReverseSwap, type PendingSubmarineSwap, type RefundHandler, type RetryConfig, SchemaError, type SendLightningPaymentRequest, type SendLightningPaymentResponse, SwapError, SwapExpiredError, type TimeoutConfig, TransactionFailedError, type Vtxo, decodeInvoice, getInvoicePaymentHash, getInvoiceSatoshis, isPendingReverseSwap, isPendingSubmarineSwap, isReverseClaimableStatus, isReverseFinalStatus, isSubmarineFinalStatus, isSubmarineRefundableStatus, isSubmarineSwapRefundable, verifySignatures };
package/dist/index.js CHANGED
@@ -80,13 +80,16 @@ import {
80
80
  ConditionWitness,
81
81
  CSVMultisigTapscript,
82
82
  setArkPsbtField,
83
- VHTLC
83
+ VHTLC,
84
+ combineTapscriptSigs
84
85
  } from "@arkade-os/sdk";
85
86
  import { sha256 } from "@noble/hashes/sha2.js";
86
- import { base64, hex } from "@scure/base";
87
+ import { base64 as base642, hex } from "@scure/base";
87
88
  import { randomBytes } from "@noble/hashes/utils.js";
88
89
 
89
90
  // src/boltz-swap-provider.ts
91
+ import { Transaction } from "@arkade-os/sdk";
92
+ import { base64 } from "@scure/base";
90
93
  var isSubmarineFinalStatus = (status) => {
91
94
  return [
92
95
  "invoice.failedToPay",
@@ -120,7 +123,7 @@ var isSubmarineRefundableStatus = (status) => {
120
123
  ].includes(status);
121
124
  };
122
125
  var isSubmarineSwapRefundable = (swap) => {
123
- return isSubmarineRefundableStatus(swap.status) && isPendingSubmarineSwap(swap) && swap.refundable !== false;
126
+ return isSubmarineRefundableStatus(swap.status) && isPendingSubmarineSwap(swap) && swap.refundable !== false && swap.refunded !== true;
124
127
  };
125
128
  var isGetReverseSwapTxIdResponse = (data) => {
126
129
  return data && typeof data === "object" && typeof data.id === "string" && typeof data.timeoutBlockHeight === "number";
@@ -143,6 +146,9 @@ var isGetSwapPreimageResponse = (data) => {
143
146
  var isCreateReverseSwapResponse = (data) => {
144
147
  return data && typeof data === "object" && typeof data.id === "string" && typeof data.invoice === "string" && typeof data.onchainAmount === "number" && typeof data.lockupAddress === "string" && typeof data.refundPublicKey === "string" && data.timeoutBlockHeights && typeof data.timeoutBlockHeights === "object" && typeof data.timeoutBlockHeights.refund === "number" && typeof data.timeoutBlockHeights.unilateralClaim === "number" && typeof data.timeoutBlockHeights.unilateralRefund === "number" && typeof data.timeoutBlockHeights.unilateralRefundWithoutReceiver === "number";
145
148
  };
149
+ var isRefundSubmarineSwapResponse = (data) => {
150
+ return data && typeof data === "object" && typeof data.transaction === "string" && typeof data.checkpoint === "string";
151
+ };
146
152
  var BASE_URLS = {
147
153
  mutinynet: "https://api.boltz.mutinynet.arkade.sh",
148
154
  regtest: "http://localhost:9069"
@@ -289,6 +295,29 @@ var BoltzSwapProvider = class {
289
295
  throw new SchemaError({ message: "Error creating reverse swap" });
290
296
  return response;
291
297
  }
298
+ async refundSubmarineSwap(swapId, transaction, checkpoint) {
299
+ const requestBody = {
300
+ checkpoint: base64.encode(checkpoint.toPSBT()),
301
+ transaction: base64.encode(transaction.toPSBT())
302
+ };
303
+ const response = await this.request(
304
+ `/v2/swap/submarine/${swapId}/refund/ark`,
305
+ "POST",
306
+ requestBody
307
+ );
308
+ if (!isRefundSubmarineSwapResponse(response))
309
+ throw new SchemaError({
310
+ message: "Error refunding submarine swap"
311
+ });
312
+ return {
313
+ transaction: Transaction.fromPSBT(
314
+ base64.decode(response.transaction)
315
+ ),
316
+ checkpoint: Transaction.fromPSBT(
317
+ base64.decode(response.checkpoint)
318
+ )
319
+ };
320
+ }
292
321
  async monitorSwap(swapId, update) {
293
322
  return new Promise((resolve, reject) => {
294
323
  const webSocket = new globalThis.WebSocket(this.wsUrl);
@@ -378,7 +407,7 @@ var BoltzSwapProvider = class {
378
407
  };
379
408
 
380
409
  // src/arkade-lightning.ts
381
- import { Transaction } from "@scure/btc-signer";
410
+ import { Transaction as Transaction2 } from "@scure/btc-signer";
382
411
  import { ripemd160 } from "@noble/hashes/legacy.js";
383
412
 
384
413
  // src/utils/decoding.ts
@@ -402,6 +431,17 @@ var getInvoicePaymentHash = (invoice) => {
402
431
  return decodeInvoice(invoice).paymentHash;
403
432
  };
404
433
 
434
+ // src/utils/signatures.ts
435
+ import { verifyTapscriptSignatures } from "@arkade-os/sdk";
436
+ var verifySignatures = (tx, inputIndex, requiredSigners) => {
437
+ try {
438
+ verifyTapscriptSignatures(tx, inputIndex, requiredSigners);
439
+ return true;
440
+ } catch (_) {
441
+ return false;
442
+ }
443
+ };
444
+
405
445
  // src/arkade-lightning.ts
406
446
  function getSignerSession(wallet) {
407
447
  const signerSession = wallet.identity.signerSession;
@@ -601,27 +641,26 @@ var ArkadeLightning = class {
601
641
  const preimage = hex.decode(pendingSwap.preimage);
602
642
  const aspInfo = await this.arkProvider.getInfo();
603
643
  const address = await this.wallet.getAddress();
604
- let receiverXOnlyPublicKey = await this.wallet.identity.xOnlyPublicKey();
605
- if (receiverXOnlyPublicKey.length == 33) {
606
- receiverXOnlyPublicKey = receiverXOnlyPublicKey.slice(1);
607
- } else if (receiverXOnlyPublicKey.length !== 32) {
608
- throw new Error(
609
- `Invalid receiver public key length: ${receiverXOnlyPublicKey.length}`
610
- );
611
- }
612
- let serverXOnlyPublicKey = hex.decode(aspInfo.signerPubkey);
613
- if (serverXOnlyPublicKey.length == 33) {
614
- serverXOnlyPublicKey = serverXOnlyPublicKey.slice(1);
615
- } else if (serverXOnlyPublicKey.length !== 32) {
616
- throw new Error(
617
- `Invalid server public key length: ${serverXOnlyPublicKey.length}`
618
- );
619
- }
644
+ const ourXOnlyPublicKey = this.normalizeToXOnlyPublicKey(
645
+ await this.wallet.identity.xOnlyPublicKey(),
646
+ "our",
647
+ pendingSwap.id
648
+ );
649
+ const boltzXOnlyPublicKey = this.normalizeToXOnlyPublicKey(
650
+ hex.decode(pendingSwap.response.refundPublicKey),
651
+ "boltz",
652
+ pendingSwap.id
653
+ );
654
+ const serverXOnlyPublicKey = this.normalizeToXOnlyPublicKey(
655
+ hex.decode(aspInfo.signerPubkey),
656
+ "server",
657
+ pendingSwap.id
658
+ );
620
659
  const { vhtlcScript, vhtlcAddress } = this.createVHTLCScript({
621
660
  network: aspInfo.network,
622
661
  preimageHash: sha256(preimage),
623
- receiverPubkey: hex.encode(receiverXOnlyPublicKey),
624
- senderPubkey: pendingSwap.response.refundPublicKey,
662
+ receiverPubkey: hex.encode(ourXOnlyPublicKey),
663
+ senderPubkey: hex.encode(boltzXOnlyPublicKey),
625
664
  serverPubkey: hex.encode(serverXOnlyPublicKey),
626
665
  timeoutBlockHeights: pendingSwap.response.timeoutBlockHeights
627
666
  });
@@ -644,13 +683,13 @@ var ArkadeLightning = class {
644
683
  cpy,
645
684
  inputIndexes
646
685
  );
647
- signedTx = Transaction.fromPSBT(signedTx.toPSBT(), {
686
+ signedTx = Transaction2.fromPSBT(signedTx.toPSBT(), {
648
687
  allowUnknown: true
649
688
  });
650
689
  setArkPsbtField(signedTx, 0, ConditionWitness, [preimage]);
651
690
  return signedTx;
652
691
  },
653
- xOnlyPublicKey: receiverXOnlyPublicKey,
692
+ xOnlyPublicKey: ourXOnlyPublicKey,
654
693
  signerSession: getSignerSession(this.wallet)
655
694
  };
656
695
  const rawCheckpointTapscript = hex.decode(aspInfo.checkpointTapscript);
@@ -675,8 +714,8 @@ var ArkadeLightning = class {
675
714
  );
676
715
  const signedArkTx = await vhtlcIdentity.sign(arkTx);
677
716
  const { arkTxid, finalArkTx, signedCheckpointTxs } = await this.arkProvider.submitTx(
678
- base64.encode(signedArkTx.toPSBT()),
679
- checkpoints.map((c) => base64.encode(c.toPSBT()))
717
+ base642.encode(signedArkTx.toPSBT()),
718
+ checkpoints.map((c) => base642.encode(c.toPSBT()))
680
719
  );
681
720
  if (!this.validFinalArkTx(
682
721
  finalArkTx,
@@ -687,11 +726,11 @@ var ArkadeLightning = class {
687
726
  }
688
727
  const finalCheckpoints = await Promise.all(
689
728
  signedCheckpointTxs.map(async (c) => {
690
- const tx = Transaction.fromPSBT(base64.decode(c), {
729
+ const tx = Transaction2.fromPSBT(base642.decode(c), {
691
730
  allowUnknown: true
692
731
  });
693
732
  const signedCheckpoint = await vhtlcIdentity.sign(tx, [0]);
694
- return base64.encode(signedCheckpoint.toPSBT());
733
+ return base642.encode(signedCheckpoint.toPSBT());
695
734
  })
696
735
  );
697
736
  await this.arkProvider.finalizeTx(arkTxid, finalCheckpoints);
@@ -709,32 +748,29 @@ var ArkadeLightning = class {
709
748
  const aspInfo = await this.arkProvider.getInfo();
710
749
  const address = await this.wallet.getAddress();
711
750
  if (!address) throw new Error("Failed to get ark address from wallet");
712
- let receiverXOnlyPublicKey = await this.wallet.identity.xOnlyPublicKey();
713
- if (receiverXOnlyPublicKey.length == 33) {
714
- receiverXOnlyPublicKey = receiverXOnlyPublicKey.slice(1);
715
- } else if (receiverXOnlyPublicKey.length !== 32) {
716
- throw new Error(
717
- `Invalid receiver public key length: ${receiverXOnlyPublicKey.length}`
718
- );
719
- }
720
- let serverXOnlyPublicKey = hex.decode(aspInfo.signerPubkey);
721
- if (serverXOnlyPublicKey.length == 33) {
722
- serverXOnlyPublicKey = serverXOnlyPublicKey.slice(1);
723
- } else if (serverXOnlyPublicKey.length !== 32) {
724
- throw new Error(
725
- `Invalid server public key length: ${serverXOnlyPublicKey.length}`
726
- );
727
- }
751
+ const ourXOnlyPublicKey = this.normalizeToXOnlyPublicKey(
752
+ await this.wallet.identity.xOnlyPublicKey(),
753
+ "our",
754
+ pendingSwap.id
755
+ );
756
+ const serverXOnlyPublicKey = this.normalizeToXOnlyPublicKey(
757
+ hex.decode(aspInfo.signerPubkey),
758
+ "server",
759
+ pendingSwap.id
760
+ );
761
+ const boltzXOnlyPublicKey = this.normalizeToXOnlyPublicKey(
762
+ hex.decode(pendingSwap.response.claimPublicKey),
763
+ "boltz",
764
+ pendingSwap.id
765
+ );
728
766
  const { vhtlcScript, vhtlcAddress } = this.createVHTLCScript({
729
767
  network: aspInfo.network,
730
768
  preimageHash: hex.decode(
731
769
  getInvoicePaymentHash(pendingSwap.request.invoice)
732
770
  ),
733
- receiverPubkey: pendingSwap.response.claimPublicKey,
734
- senderPubkey: hex.encode(
735
- await this.wallet.identity.xOnlyPublicKey()
736
- ),
737
- serverPubkey: aspInfo.signerPubkey,
771
+ receiverPubkey: hex.encode(boltzXOnlyPublicKey),
772
+ senderPubkey: hex.encode(ourXOnlyPublicKey),
773
+ serverPubkey: hex.encode(serverXOnlyPublicKey),
738
774
  timeoutBlockHeights: pendingSwap.response.timeoutBlockHeights
739
775
  });
740
776
  if (!vhtlcScript)
@@ -749,25 +785,25 @@ var ArkadeLightning = class {
749
785
  throw new Error("No spendable virtual coins found");
750
786
  }
751
787
  const vhtlcIdentity = {
752
- sign: async (tx, inputIndexes) => {
753
- const cpy = tx.clone();
788
+ sign: async (tx2, inputIndexes) => {
789
+ const cpy = tx2.clone();
754
790
  let signedTx = await signTransaction(
755
791
  this.wallet,
756
792
  cpy,
757
793
  inputIndexes
758
794
  );
759
- return Transaction.fromPSBT(signedTx.toPSBT(), {
795
+ return Transaction2.fromPSBT(signedTx.toPSBT(), {
760
796
  allowUnknown: true
761
797
  });
762
798
  },
763
- xOnlyPublicKey: receiverXOnlyPublicKey,
799
+ xOnlyPublicKey: ourXOnlyPublicKey,
764
800
  signerSession: getSignerSession(this.wallet)
765
801
  };
766
802
  const rawCheckpointTapscript = hex.decode(aspInfo.checkpointTapscript);
767
803
  const serverUnrollScript = CSVMultisigTapscript.decode(
768
804
  rawCheckpointTapscript
769
805
  );
770
- const { arkTx, checkpoints } = buildOffchainTx(
806
+ const { arkTx: unsignedRefundTx, checkpoints: checkpointPtxs } = buildOffchainTx(
771
807
  [
772
808
  {
773
809
  ...spendableVtxos.vtxos[0],
@@ -783,32 +819,73 @@ var ArkadeLightning = class {
783
819
  ],
784
820
  serverUnrollScript
785
821
  );
786
- const signedArkTx = await vhtlcIdentity.sign(arkTx);
822
+ if (checkpointPtxs.length !== 1)
823
+ throw new Error(
824
+ `Expected one checkpoint transaction, got ${checkpointPtxs.length}`
825
+ );
826
+ const unsignedCheckpointTx = checkpointPtxs[0];
827
+ const {
828
+ transaction: boltzSignedRefundTx,
829
+ checkpoint: boltzSignedCheckpointTx
830
+ } = await this.swapProvider.refundSubmarineSwap(
831
+ pendingSwap.id,
832
+ unsignedRefundTx,
833
+ unsignedCheckpointTx
834
+ );
835
+ const boltzXOnlyPublicKeyHex = hex.encode(boltzXOnlyPublicKey);
836
+ if (!verifySignatures(boltzSignedRefundTx, 0, [boltzXOnlyPublicKeyHex])) {
837
+ throw new Error("Invalid Boltz signature in refund transaction");
838
+ }
839
+ if (!verifySignatures(boltzSignedCheckpointTx, 0, [
840
+ boltzXOnlyPublicKeyHex
841
+ ])) {
842
+ throw new Error(
843
+ "Invalid Boltz signature in checkpoint transaction"
844
+ );
845
+ }
846
+ const signedRefundTx = await vhtlcIdentity.sign(unsignedRefundTx);
847
+ const signedCheckpointTx = await vhtlcIdentity.sign(unsignedCheckpointTx);
848
+ const combinedSignedRefundTx = combineTapscriptSigs(
849
+ boltzSignedRefundTx,
850
+ signedRefundTx
851
+ );
852
+ const combinedSignedCheckpointTx = combineTapscriptSigs(
853
+ boltzSignedCheckpointTx,
854
+ signedCheckpointTx
855
+ );
787
856
  const { arkTxid, finalArkTx, signedCheckpointTxs } = await this.arkProvider.submitTx(
788
- base64.encode(signedArkTx.toPSBT()),
789
- checkpoints.map((c) => base64.encode(c.toPSBT()))
857
+ base642.encode(combinedSignedRefundTx.toPSBT()),
858
+ [base642.encode(unsignedCheckpointTx.toPSBT())]
790
859
  );
791
- if (!this.validFinalArkTx(
792
- finalArkTx,
793
- serverXOnlyPublicKey,
794
- vhtlcScript.leaves
795
- )) {
796
- throw new Error("Invalid final Ark transaction");
860
+ const tx = Transaction2.fromPSBT(base642.decode(finalArkTx));
861
+ const inputIndex = 0;
862
+ const requiredSigners = [
863
+ hex.encode(ourXOnlyPublicKey),
864
+ hex.encode(boltzXOnlyPublicKey),
865
+ hex.encode(serverXOnlyPublicKey)
866
+ ];
867
+ if (!verifySignatures(tx, inputIndex, requiredSigners)) {
868
+ throw new Error("Invalid refund transaction");
797
869
  }
798
- const finalCheckpoints = await Promise.all(
799
- signedCheckpointTxs.map(async (c) => {
800
- const tx = Transaction.fromPSBT(base64.decode(c), {
801
- allowUnknown: true
802
- });
803
- const signedCheckpoint = await vhtlcIdentity.sign(tx, [0]);
804
- return base64.encode(signedCheckpoint.toPSBT());
805
- })
870
+ if (signedCheckpointTxs.length !== 1) {
871
+ throw new Error(
872
+ `Expected one signed checkpoint transaction, got ${signedCheckpointTxs.length}`
873
+ );
874
+ }
875
+ const serverSignedCheckpointTx = Transaction2.fromPSBT(
876
+ base642.decode(signedCheckpointTxs[0])
806
877
  );
807
- await this.arkProvider.finalizeTx(arkTxid, finalCheckpoints);
808
- const finalStatus = await this.getSwapStatus(pendingSwap.id);
878
+ const finalCheckpointTx = combineTapscriptSigs(
879
+ combinedSignedCheckpointTx,
880
+ serverSignedCheckpointTx
881
+ );
882
+ await this.arkProvider.finalizeTx(arkTxid, [
883
+ base642.encode(finalCheckpointTx.toPSBT())
884
+ ]);
809
885
  await this.savePendingSubmarineSwap({
810
886
  ...pendingSwap,
811
- status: finalStatus.status
887
+ refundable: true,
888
+ refunded: true
812
889
  });
813
890
  }
814
891
  /**
@@ -991,7 +1068,7 @@ var ArkadeLightning = class {
991
1068
  * @returns True if the final Ark transaction is valid, false otherwise.
992
1069
  */
993
1070
  validFinalArkTx = (finalArkTx, _pubkey, _tapLeaves) => {
994
- const tx = Transaction.fromPSBT(base64.decode(finalArkTx), {
1071
+ const tx = Transaction2.fromPSBT(base642.decode(finalArkTx), {
995
1072
  allowUnknown: true
996
1073
  });
997
1074
  if (!tx) return false;
@@ -1018,30 +1095,18 @@ var ArkadeLightning = class {
1018
1095
  serverPubkey,
1019
1096
  timeoutBlockHeights
1020
1097
  }) {
1021
- let receiverXOnlyPublicKey = hex.decode(receiverPubkey);
1022
- if (receiverXOnlyPublicKey.length == 33) {
1023
- receiverXOnlyPublicKey = receiverXOnlyPublicKey.slice(1);
1024
- } else if (receiverXOnlyPublicKey.length !== 32) {
1025
- throw new Error(
1026
- `Invalid receiver public key length: ${receiverXOnlyPublicKey.length}`
1027
- );
1028
- }
1029
- let senderXOnlyPublicKey = hex.decode(senderPubkey);
1030
- if (senderXOnlyPublicKey.length == 33) {
1031
- senderXOnlyPublicKey = senderXOnlyPublicKey.slice(1);
1032
- } else if (senderXOnlyPublicKey.length !== 32) {
1033
- throw new Error(
1034
- `Invalid sender public key length: ${senderXOnlyPublicKey.length}`
1035
- );
1036
- }
1037
- let serverXOnlyPublicKey = hex.decode(serverPubkey);
1038
- if (serverXOnlyPublicKey.length == 33) {
1039
- serverXOnlyPublicKey = serverXOnlyPublicKey.slice(1);
1040
- } else if (serverXOnlyPublicKey.length !== 32) {
1041
- throw new Error(
1042
- `Invalid server public key length: ${serverXOnlyPublicKey.length}`
1043
- );
1044
- }
1098
+ const receiverXOnlyPublicKey = this.normalizeToXOnlyPublicKey(
1099
+ hex.decode(receiverPubkey),
1100
+ "receiver"
1101
+ );
1102
+ const senderXOnlyPublicKey = this.normalizeToXOnlyPublicKey(
1103
+ hex.decode(senderPubkey),
1104
+ "sender"
1105
+ );
1106
+ const serverXOnlyPublicKey = this.normalizeToXOnlyPublicKey(
1107
+ hex.decode(serverPubkey),
1108
+ "server"
1109
+ );
1045
1110
  const delayType = (num) => num < 512 ? "blocks" : "seconds";
1046
1111
  const vhtlcScript = new VHTLC.Script({
1047
1112
  preimageHash: ripemd160(preimageHash),
@@ -1166,6 +1231,24 @@ var ArkadeLightning = class {
1166
1231
  });
1167
1232
  }
1168
1233
  }
1234
+ /**
1235
+ * Validate we are using a x-only public key
1236
+ * @param publicKey
1237
+ * @param keyName
1238
+ * @param swapId
1239
+ * @returns Uint8Array
1240
+ */
1241
+ normalizeToXOnlyPublicKey(publicKey, keyName, swapId) {
1242
+ if (publicKey.length === 33) {
1243
+ return publicKey.slice(1);
1244
+ }
1245
+ if (publicKey.length !== 32) {
1246
+ throw new Error(
1247
+ `Invalid ${keyName} public key length: ${publicKey.length} ${swapId ? "for swap " + swapId : ""}`
1248
+ );
1249
+ }
1250
+ return publicKey;
1251
+ }
1169
1252
  };
1170
1253
  export {
1171
1254
  ArkadeLightning,
@@ -1187,5 +1270,6 @@ export {
1187
1270
  isReverseFinalStatus,
1188
1271
  isSubmarineFinalStatus,
1189
1272
  isSubmarineRefundableStatus,
1190
- isSubmarineSwapRefundable
1273
+ isSubmarineSwapRefundable,
1274
+ verifySignatures
1191
1275
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arkade-os/boltz-swap",
3
- "version": "0.2.6",
3
+ "version": "0.2.7",
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",
@@ -30,7 +30,7 @@
30
30
  "author": "Arkade-OS",
31
31
  "license": "MIT",
32
32
  "dependencies": {
33
- "@arkade-os/sdk": "0.3.3",
33
+ "@arkade-os/sdk": "0.3.4",
34
34
  "@noble/hashes": "2.0.0",
35
35
  "@scure/base": "2.0.0",
36
36
  "@scure/btc-signer": "2.0.1",