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