@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 +200 -116
- package/dist/index.d.cts +18 -2
- package/dist/index.d.ts +18 -2
- package/dist/index.js +184 -100
- package/package.json +2 -2
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
|
|
133
|
+
var import_sdk3 = require("@arkade-os/sdk");
|
|
133
134
|
var import_sha2 = require("@noble/hashes/sha2.js");
|
|
134
|
-
var
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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:
|
|
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 =
|
|
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
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
)
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
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:
|
|
672
|
-
senderPubkey:
|
|
673
|
-
serverPubkey:
|
|
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: [
|
|
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,
|
|
737
|
+
(0, import_sdk3.setArkPsbtField)(signedTx, 0, import_sdk3.ConditionWitness, [preimage]);
|
|
699
738
|
return signedTx;
|
|
700
739
|
},
|
|
701
|
-
xOnlyPublicKey:
|
|
740
|
+
xOnlyPublicKey: ourXOnlyPublicKey,
|
|
702
741
|
signerSession: getSignerSession(this.wallet)
|
|
703
742
|
};
|
|
704
|
-
const rawCheckpointTapscript =
|
|
705
|
-
const serverUnrollScript =
|
|
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,
|
|
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:
|
|
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
|
-
|
|
727
|
-
checkpoints.map((c) =>
|
|
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(
|
|
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
|
|
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
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
)
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
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:
|
|
816
|
+
preimageHash: import_base2.hex.decode(
|
|
779
817
|
getInvoicePaymentHash(pendingSwap.request.invoice)
|
|
780
818
|
),
|
|
781
|
-
receiverPubkey:
|
|
782
|
-
senderPubkey:
|
|
783
|
-
|
|
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: [
|
|
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 (
|
|
801
|
-
const cpy =
|
|
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:
|
|
847
|
+
xOnlyPublicKey: ourXOnlyPublicKey,
|
|
812
848
|
signerSession: getSignerSession(this.wallet)
|
|
813
849
|
};
|
|
814
|
-
const rawCheckpointTapscript =
|
|
815
|
-
const serverUnrollScript =
|
|
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,
|
|
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:
|
|
865
|
+
script: import_sdk3.ArkAddress.decode(address).pkScript
|
|
830
866
|
}
|
|
831
867
|
],
|
|
832
868
|
serverUnrollScript
|
|
833
869
|
);
|
|
834
|
-
|
|
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
|
-
|
|
837
|
-
|
|
905
|
+
import_base2.base64.encode(combinedSignedRefundTx.toPSBT()),
|
|
906
|
+
[import_base2.base64.encode(unsignedCheckpointTx.toPSBT())]
|
|
838
907
|
);
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
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
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
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
|
-
|
|
856
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
)
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
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(
|
|
624
|
-
senderPubkey:
|
|
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 =
|
|
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:
|
|
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
|
-
|
|
679
|
-
checkpoints.map((c) =>
|
|
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 =
|
|
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
|
|
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
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
)
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
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:
|
|
734
|
-
senderPubkey: hex.encode(
|
|
735
|
-
|
|
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 (
|
|
753
|
-
const cpy =
|
|
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
|
|
795
|
+
return Transaction2.fromPSBT(signedTx.toPSBT(), {
|
|
760
796
|
allowUnknown: true
|
|
761
797
|
});
|
|
762
798
|
},
|
|
763
|
-
xOnlyPublicKey:
|
|
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
|
-
|
|
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
|
-
|
|
789
|
-
|
|
857
|
+
base642.encode(combinedSignedRefundTx.toPSBT()),
|
|
858
|
+
[base642.encode(unsignedCheckpointTx.toPSBT())]
|
|
790
859
|
);
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
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
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
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
|
-
|
|
808
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
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.
|
|
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.
|
|
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",
|