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