@arkade-os/boltz-swap 0.3.39 → 0.3.41
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 +29 -0
- package/dist/{arkade-swaps-Uet3tgN6.d.ts → arkade-swaps-C3sUFr5f.d.cts} +76 -5
- package/dist/{arkade-swaps-DG9UepoS.d.cts → arkade-swaps-LvsGHtre.d.ts} +76 -5
- package/dist/{chunk-DNCIVDU5.js → chunk-CWY37W4B.js} +1 -1
- package/dist/{chunk-CFB2NNGT.js → chunk-UXYHW7KV.js} +248 -99
- package/dist/expo/background.cjs +247 -99
- package/dist/expo/background.d.cts +3 -3
- package/dist/expo/background.d.ts +3 -3
- package/dist/expo/background.js +2 -2
- package/dist/expo/index.cjs +250 -99
- package/dist/expo/index.d.cts +9 -5
- package/dist/expo/index.d.ts +9 -5
- package/dist/expo/index.js +5 -2
- package/dist/index.cjs +266 -99
- package/dist/index.d.cts +21 -9
- package/dist/index.d.ts +21 -9
- package/dist/index.js +20 -1
- package/dist/repositories/realm/index.d.cts +1 -1
- package/dist/repositories/realm/index.d.ts +1 -1
- package/dist/repositories/sqlite/index.d.cts +1 -1
- package/dist/repositories/sqlite/index.d.ts +1 -1
- package/dist/{swapsPollProcessor-BlyUrhtO.d.cts → swapsPollProcessor-CGMXUKPe.d.cts} +1 -1
- package/dist/{swapsPollProcessor-Bv4Z2R7g.d.ts → swapsPollProcessor-CuDM6sxV.d.ts} +1 -1
- package/dist/{types-D97i1LFu.d.cts → types-8NrCdOpS.d.cts} +51 -1
- package/dist/{types-D97i1LFu.d.ts → types-8NrCdOpS.d.ts} +51 -1
- package/package.json +2 -2
|
@@ -233,6 +233,22 @@ var isSubmarinePendingStatus = (status) => {
|
|
|
233
233
|
var isSubmarineRefundableStatus = (status) => {
|
|
234
234
|
return ["invoice.failedToPay", "transaction.lockupFailed", "swap.expired"].includes(status);
|
|
235
235
|
};
|
|
236
|
+
var SUBMARINE_STATUS_PROGRESSION = [
|
|
237
|
+
"swap.created",
|
|
238
|
+
"invoice.set",
|
|
239
|
+
"transaction.mempool",
|
|
240
|
+
"transaction.confirmed",
|
|
241
|
+
"invoice.pending",
|
|
242
|
+
"invoice.paid",
|
|
243
|
+
"transaction.claim.pending",
|
|
244
|
+
"transaction.claimed"
|
|
245
|
+
];
|
|
246
|
+
var hasSubmarineStatusReached = (status, target) => {
|
|
247
|
+
const progression = SUBMARINE_STATUS_PROGRESSION;
|
|
248
|
+
const statusIndex = progression.indexOf(status);
|
|
249
|
+
const targetIndex = progression.indexOf(target);
|
|
250
|
+
return statusIndex >= 0 && targetIndex >= 0 && statusIndex >= targetIndex;
|
|
251
|
+
};
|
|
236
252
|
var isSubmarineSuccessStatus = (status) => {
|
|
237
253
|
return status === "transaction.claimed";
|
|
238
254
|
};
|
|
@@ -976,10 +992,10 @@ import bolt11 from "light-bolt11-decoder";
|
|
|
976
992
|
import { ArkAddress } from "@arkade-os/sdk";
|
|
977
993
|
var decodeInvoice = (invoice) => {
|
|
978
994
|
const decoded = bolt11.decode(invoice);
|
|
979
|
-
const millisats =
|
|
995
|
+
const millisats = BigInt(decoded.sections.find((s) => s.name === "amount")?.value ?? "0");
|
|
980
996
|
return {
|
|
981
997
|
expiry: decoded.expiry ?? 3600,
|
|
982
|
-
amountSats:
|
|
998
|
+
amountSats: Number(millisats / 1000n),
|
|
983
999
|
description: decoded.sections.find((s) => s.name === "description")?.value ?? "",
|
|
984
1000
|
paymentHash: decoded.sections.find((s) => s.name === "payment_hash")?.value ?? ""
|
|
985
1001
|
};
|
|
@@ -2966,6 +2982,19 @@ var createVHTLCScript = (args) => {
|
|
|
2966
2982
|
const vhtlcAddress = vhtlcScript.address(hrp, serverXOnlyPublicKey).encode();
|
|
2967
2983
|
return { vhtlcScript, vhtlcAddress };
|
|
2968
2984
|
};
|
|
2985
|
+
var candidateServerPubkeys = (arkInfo) => {
|
|
2986
|
+
const current = hex7.encode(normalizeToXOnlyKey(hex7.decode(arkInfo.signerPubkey), "server"));
|
|
2987
|
+
const seen = /* @__PURE__ */ new Set([current]);
|
|
2988
|
+
const candidates = [current];
|
|
2989
|
+
for (const deprecated of arkInfo.deprecatedSigners ?? []) {
|
|
2990
|
+
if (!deprecated.pubkey) continue;
|
|
2991
|
+
const key = hex7.encode(normalizeToXOnlyKey(hex7.decode(deprecated.pubkey), "server"));
|
|
2992
|
+
if (seen.has(key)) continue;
|
|
2993
|
+
seen.add(key);
|
|
2994
|
+
candidates.push(key);
|
|
2995
|
+
}
|
|
2996
|
+
return candidates;
|
|
2997
|
+
};
|
|
2969
2998
|
var joinBatch = async (arkProvider, identity, input, output, {
|
|
2970
2999
|
forfeitPubkey,
|
|
2971
3000
|
forfeitAddress,
|
|
@@ -3380,6 +3409,10 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
3380
3409
|
...args.description?.trim() ? { description: args.description.trim() } : {}
|
|
3381
3410
|
};
|
|
3382
3411
|
const swapResponse = await this.swapProvider.createReverseSwap(swapRequest);
|
|
3412
|
+
const decodedInvoice = decodeInvoice(swapResponse.invoice);
|
|
3413
|
+
if (decodedInvoice.paymentHash !== preimageHash) {
|
|
3414
|
+
throw new SwapError({ message: "Preimage hash does not match invoice payment hash" });
|
|
3415
|
+
}
|
|
3383
3416
|
const pendingSwap = {
|
|
3384
3417
|
id: swapResponse.id,
|
|
3385
3418
|
type: "reverse",
|
|
@@ -3423,27 +3456,15 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
3423
3456
|
"boltz",
|
|
3424
3457
|
pendingSwap.id
|
|
3425
3458
|
);
|
|
3426
|
-
const serverXOnly =
|
|
3427
|
-
|
|
3428
|
-
"server",
|
|
3429
|
-
pendingSwap.id
|
|
3430
|
-
);
|
|
3431
|
-
const { vhtlcScript, vhtlcAddress } = this.createVHTLCScript({
|
|
3432
|
-
network: arkInfo.network,
|
|
3459
|
+
const { vhtlcScript, serverXOnlyPublicKey: serverXOnly } = this.resolveVHTLCForLockup({
|
|
3460
|
+
arkInfo,
|
|
3433
3461
|
preimageHash: sha2563(preimage),
|
|
3434
3462
|
receiverPubkey: hex8.encode(receiverXOnly),
|
|
3435
3463
|
senderPubkey: hex8.encode(senderXOnly),
|
|
3436
|
-
|
|
3437
|
-
|
|
3464
|
+
timeoutBlockHeights: vhtlcTimeouts,
|
|
3465
|
+
lockupAddress,
|
|
3466
|
+
swapId: pendingSwap.id
|
|
3438
3467
|
});
|
|
3439
|
-
if (!vhtlcScript.claimScript)
|
|
3440
|
-
throw new Error(
|
|
3441
|
-
`Swap ${pendingSwap.id}: failed to create VHTLC script for reverse swap`
|
|
3442
|
-
);
|
|
3443
|
-
if (vhtlcAddress !== lockupAddress)
|
|
3444
|
-
throw new Error(
|
|
3445
|
-
`Swap ${pendingSwap.id}: VHTLC address mismatch. Expected ${lockupAddress}, got ${vhtlcAddress}`
|
|
3446
|
-
);
|
|
3447
3468
|
let unspentVtxos = [];
|
|
3448
3469
|
let rawVtxos = [];
|
|
3449
3470
|
for (let attempt = 1; attempt <= CLAIM_VTXO_RETRY_ATTEMPTS; attempt++) {
|
|
@@ -3594,16 +3615,6 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
3594
3615
|
this.swapProvider.monitorSwap(pendingSwap.id, onStatusUpdate).catch(reject);
|
|
3595
3616
|
});
|
|
3596
3617
|
}
|
|
3597
|
-
// =========================================================================
|
|
3598
|
-
// Lightning: Submarine swaps (send Arkade -> Lightning)
|
|
3599
|
-
// =========================================================================
|
|
3600
|
-
/**
|
|
3601
|
-
* Sends a Lightning payment via a submarine swap (Arkade → Lightning).
|
|
3602
|
-
* Creates the swap, sends funds, and waits for settlement. Auto-refunds on failure.
|
|
3603
|
-
* @param args.invoice - BOLT11 Lightning invoice to pay.
|
|
3604
|
-
* @returns The amount paid, preimage (proof of payment), and transaction ID.
|
|
3605
|
-
* @throws {TransactionFailedError} If the payment fails (auto-refunds if possible).
|
|
3606
|
-
*/
|
|
3607
3618
|
async sendLightningPayment(args) {
|
|
3608
3619
|
const pendingSwap = await this.createSubmarineSwap(args);
|
|
3609
3620
|
if (!pendingSwap.response.address)
|
|
@@ -3614,6 +3625,18 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
3614
3625
|
amount: pendingSwap.response.expectedAmount
|
|
3615
3626
|
});
|
|
3616
3627
|
try {
|
|
3628
|
+
if (args.waitFor === "funded") {
|
|
3629
|
+
if (!this.swapManager) {
|
|
3630
|
+
logger.warn(
|
|
3631
|
+
`Swap ${pendingSwap.id}: sendLightningPayment with waitFor "funded" but SwapManager is disabled \u2014 a failure after this promise resolves is only persisted as refundable, not auto-refunded; recover via restoreSwaps/recoverSubmarineFunds`
|
|
3632
|
+
);
|
|
3633
|
+
}
|
|
3634
|
+
await this.waitForSwapFunded(pendingSwap);
|
|
3635
|
+
return {
|
|
3636
|
+
amount: pendingSwap.response.expectedAmount,
|
|
3637
|
+
txid
|
|
3638
|
+
};
|
|
3639
|
+
}
|
|
3617
3640
|
const { preimage } = await this.waitForSwapSettlement(pendingSwap);
|
|
3618
3641
|
return {
|
|
3619
3642
|
amount: pendingSwap.response.expectedAmount,
|
|
@@ -3685,11 +3708,6 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
3685
3708
|
"our",
|
|
3686
3709
|
swap.id
|
|
3687
3710
|
);
|
|
3688
|
-
const serverXOnlyPublicKey = normalizeToXOnlyKey(
|
|
3689
|
-
hex8.decode(resolvedArkInfo.signerPubkey),
|
|
3690
|
-
"server",
|
|
3691
|
-
swap.id
|
|
3692
|
-
);
|
|
3693
3711
|
const { claimPublicKey, timeoutBlockHeights: vhtlcTimeouts } = swap.response;
|
|
3694
3712
|
if (!claimPublicKey || !vhtlcTimeouts)
|
|
3695
3713
|
throw new Error(`Swap ${swap.id}: incomplete submarine swap response`);
|
|
@@ -3698,20 +3716,19 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
3698
3716
|
"boltz",
|
|
3699
3717
|
swap.id
|
|
3700
3718
|
);
|
|
3701
|
-
const
|
|
3702
|
-
|
|
3719
|
+
const lockupAddress = swap.response.address;
|
|
3720
|
+
if (!lockupAddress)
|
|
3721
|
+
throw new Error(`Swap ${swap.id}: missing lockup address in submarine swap response`);
|
|
3722
|
+
const { vhtlcScript, serverXOnlyPublicKey } = this.resolveVHTLCForLockup({
|
|
3723
|
+
arkInfo: resolvedArkInfo,
|
|
3703
3724
|
preimageHash: hex8.decode(preimageHash),
|
|
3704
3725
|
receiverPubkey: hex8.encode(boltzXOnlyPublicKey),
|
|
3705
3726
|
senderPubkey: hex8.encode(ourXOnlyPublicKey),
|
|
3706
|
-
|
|
3707
|
-
|
|
3727
|
+
timeoutBlockHeights: vhtlcTimeouts,
|
|
3728
|
+
lockupAddress,
|
|
3729
|
+
swapId: swap.id
|
|
3708
3730
|
});
|
|
3709
|
-
|
|
3710
|
-
throw new Error(`Swap ${swap.id}: failed to create VHTLC script for submarine swap`);
|
|
3711
|
-
if (vhtlcAddress !== swap.response.address)
|
|
3712
|
-
throw new Error(
|
|
3713
|
-
`VHTLC address mismatch for swap ${swap.id}: expected ${swap.response.address}, got ${vhtlcAddress}`
|
|
3714
|
-
);
|
|
3731
|
+
const vhtlcAddress = lockupAddress;
|
|
3715
3732
|
const vhtlcPkScriptHex = hex8.encode(vhtlcScript.pkScript);
|
|
3716
3733
|
return {
|
|
3717
3734
|
arkInfo: resolvedArkInfo,
|
|
@@ -4121,6 +4138,9 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
4121
4138
|
}
|
|
4122
4139
|
/**
|
|
4123
4140
|
* Waits for a submarine swap's Lightning payment to settle.
|
|
4141
|
+
* Resolves only at the terminal "transaction.claimed" status, once Boltz
|
|
4142
|
+
* has swept the HTLC. To resolve as soon as the payment is in flight, use
|
|
4143
|
+
* {@link waitForSwapFunded} instead.
|
|
4124
4144
|
* @param pendingSwap - The submarine swap to monitor.
|
|
4125
4145
|
* @returns The preimage from the settled Lightning payment (proof of payment).
|
|
4126
4146
|
* @throws {SwapExpiredError} If the swap expires.
|
|
@@ -4128,19 +4148,59 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
4128
4148
|
* @throws {TransactionLockupFailedError} If the lockup transaction fails.
|
|
4129
4149
|
*/
|
|
4130
4150
|
async waitForSwapSettlement(pendingSwap) {
|
|
4151
|
+
return this.waitForSubmarineSwap(pendingSwap);
|
|
4152
|
+
}
|
|
4153
|
+
/**
|
|
4154
|
+
* Waits until a submarine swap is funded: resolves as soon as the lockup
|
|
4155
|
+
* transaction is observed ("transaction.mempool" or any later status in
|
|
4156
|
+
* the lifecycle — statuses can be skipped since subscriptions report only
|
|
4157
|
+
* the current one). The sender's funds are committed and the swap is
|
|
4158
|
+
* refundable from this point, which is when most Lightning wallets show
|
|
4159
|
+
* a payment as "sent".
|
|
4160
|
+
*
|
|
4161
|
+
* Monitoring continues in the background until the swap reaches a
|
|
4162
|
+
* terminal status, persisting updates to the repository (the preimage on
|
|
4163
|
+
* claim, the refundable flag on failure), but this promise no longer
|
|
4164
|
+
* rejects once resolved — acting on a late failure is the caller's
|
|
4165
|
+
* responsibility (the SwapManager handles it automatically when enabled).
|
|
4166
|
+
* @param pendingSwap - The submarine swap to monitor.
|
|
4167
|
+
* @throws {SwapExpiredError} If the swap expires before funding.
|
|
4168
|
+
* @throws {InvoiceFailedToPayError} If Boltz fails to route the payment.
|
|
4169
|
+
* @throws {TransactionLockupFailedError} If the lockup transaction fails.
|
|
4170
|
+
*/
|
|
4171
|
+
async waitForSwapFunded(pendingSwap) {
|
|
4172
|
+
await this.waitForSubmarineSwap(pendingSwap, "transaction.mempool");
|
|
4173
|
+
}
|
|
4174
|
+
/**
|
|
4175
|
+
* Shared wait machinery: monitors the swap and resolves at the terminal
|
|
4176
|
+
* "transaction.claimed" status (with the preimage) or, when `resolveAt`
|
|
4177
|
+
* is given, as soon as that status — or any later one in the successful
|
|
4178
|
+
* progression — is observed (without a preimage).
|
|
4179
|
+
*/
|
|
4180
|
+
async waitForSubmarineSwap(pendingSwap, resolveAt) {
|
|
4131
4181
|
return new Promise((resolve, reject) => {
|
|
4132
|
-
let
|
|
4182
|
+
let isFinal = false;
|
|
4183
|
+
let isSettled = false;
|
|
4133
4184
|
const onStatusUpdate = async (status) => {
|
|
4134
|
-
if (
|
|
4135
|
-
const saveStatus = (additionalFields) =>
|
|
4136
|
-
|
|
4137
|
-
|
|
4138
|
-
|
|
4139
|
-
|
|
4140
|
-
|
|
4185
|
+
if (isFinal) return;
|
|
4186
|
+
const saveStatus = async (additionalFields) => {
|
|
4187
|
+
try {
|
|
4188
|
+
await updateSubmarineSwapStatus(
|
|
4189
|
+
pendingSwap,
|
|
4190
|
+
status,
|
|
4191
|
+
this.savePendingSubmarineSwap.bind(this),
|
|
4192
|
+
additionalFields
|
|
4193
|
+
);
|
|
4194
|
+
} catch (error) {
|
|
4195
|
+
logger.error(
|
|
4196
|
+
`Swap ${pendingSwap.id}: failed to persist status "${status}": ${error}`
|
|
4197
|
+
);
|
|
4198
|
+
}
|
|
4199
|
+
};
|
|
4141
4200
|
switch (status) {
|
|
4142
4201
|
case "swap.expired":
|
|
4143
|
-
|
|
4202
|
+
isFinal = true;
|
|
4203
|
+
isSettled = true;
|
|
4144
4204
|
await saveStatus({ refundable: true });
|
|
4145
4205
|
reject(
|
|
4146
4206
|
new SwapExpiredError({
|
|
@@ -4150,7 +4210,8 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
4150
4210
|
);
|
|
4151
4211
|
break;
|
|
4152
4212
|
case "invoice.failedToPay":
|
|
4153
|
-
|
|
4213
|
+
isFinal = true;
|
|
4214
|
+
isSettled = true;
|
|
4154
4215
|
await saveStatus({ refundable: true });
|
|
4155
4216
|
reject(
|
|
4156
4217
|
new InvoiceFailedToPayError({
|
|
@@ -4160,7 +4221,8 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
4160
4221
|
);
|
|
4161
4222
|
break;
|
|
4162
4223
|
case "transaction.lockupFailed":
|
|
4163
|
-
|
|
4224
|
+
isFinal = true;
|
|
4225
|
+
isSettled = true;
|
|
4164
4226
|
await saveStatus({ refundable: true });
|
|
4165
4227
|
reject(
|
|
4166
4228
|
new TransactionLockupFailedError({
|
|
@@ -4170,23 +4232,47 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
4170
4232
|
);
|
|
4171
4233
|
break;
|
|
4172
4234
|
case "transaction.claimed": {
|
|
4173
|
-
|
|
4174
|
-
|
|
4175
|
-
|
|
4176
|
-
|
|
4177
|
-
|
|
4178
|
-
|
|
4235
|
+
isFinal = true;
|
|
4236
|
+
isSettled = true;
|
|
4237
|
+
try {
|
|
4238
|
+
const { preimage } = await this.swapProvider.getSwapPreimage(
|
|
4239
|
+
pendingSwap.id
|
|
4240
|
+
);
|
|
4241
|
+
await saveStatus({ preimage });
|
|
4242
|
+
resolve({ preimage });
|
|
4243
|
+
} catch (error) {
|
|
4244
|
+
logger.error(
|
|
4245
|
+
`Swap ${pendingSwap.id}: failed to fetch preimage on claim: ${error}`
|
|
4246
|
+
);
|
|
4247
|
+
reject(error);
|
|
4248
|
+
}
|
|
4179
4249
|
break;
|
|
4180
4250
|
}
|
|
4181
4251
|
default:
|
|
4182
4252
|
await saveStatus();
|
|
4253
|
+
if (resolveAt && hasSubmarineStatusReached(status, resolveAt)) {
|
|
4254
|
+
isSettled = true;
|
|
4255
|
+
resolve({ preimage: void 0 });
|
|
4256
|
+
}
|
|
4183
4257
|
break;
|
|
4184
4258
|
}
|
|
4185
4259
|
};
|
|
4186
|
-
this.swapProvider.monitorSwap(pendingSwap.id,
|
|
4187
|
-
|
|
4188
|
-
|
|
4260
|
+
this.swapProvider.monitorSwap(pendingSwap.id, (status) => {
|
|
4261
|
+
onStatusUpdate(status).catch(
|
|
4262
|
+
(error) => logger.error(
|
|
4263
|
+
`Swap ${pendingSwap.id}: error handling status "${status}": ${error}`
|
|
4264
|
+
)
|
|
4265
|
+
);
|
|
4266
|
+
}).catch((error) => {
|
|
4267
|
+
if (!isSettled) {
|
|
4268
|
+
isFinal = true;
|
|
4269
|
+
isSettled = true;
|
|
4189
4270
|
reject(error);
|
|
4271
|
+
} else {
|
|
4272
|
+
isFinal = true;
|
|
4273
|
+
logger.warn(
|
|
4274
|
+
`Swap ${pendingSwap.id}: monitor failed after settlement: ${error}`
|
|
4275
|
+
);
|
|
4190
4276
|
}
|
|
4191
4277
|
});
|
|
4192
4278
|
});
|
|
@@ -4424,29 +4510,26 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
4424
4510
|
"user",
|
|
4425
4511
|
pendingSwap.id
|
|
4426
4512
|
);
|
|
4427
|
-
const serverXOnlyPublicKey = normalizeToXOnlyKey(
|
|
4428
|
-
hex8.decode(arkInfo.signerPubkey),
|
|
4429
|
-
"server",
|
|
4430
|
-
pendingSwap.id
|
|
4431
|
-
);
|
|
4432
4513
|
const boltzXOnlyPublicKey = normalizeToXOnlyKey(
|
|
4433
4514
|
hex8.decode(pendingSwap.response.lockupDetails.serverPublicKey),
|
|
4434
4515
|
"boltz",
|
|
4435
4516
|
pendingSwap.id
|
|
4436
4517
|
);
|
|
4437
|
-
|
|
4438
|
-
|
|
4439
|
-
|
|
4440
|
-
|
|
4441
|
-
|
|
4442
|
-
|
|
4443
|
-
|
|
4444
|
-
|
|
4445
|
-
|
|
4446
|
-
|
|
4447
|
-
|
|
4518
|
+
let vhtlcScript;
|
|
4519
|
+
let serverXOnlyPublicKey;
|
|
4520
|
+
try {
|
|
4521
|
+
({ vhtlcScript, serverXOnlyPublicKey } = this.resolveVHTLCForLockup({
|
|
4522
|
+
arkInfo,
|
|
4523
|
+
preimageHash: hex8.decode(pendingSwap.request.preimageHash),
|
|
4524
|
+
senderPubkey: hex8.encode(ourXOnlyPublicKey),
|
|
4525
|
+
receiverPubkey: hex8.encode(boltzXOnlyPublicKey),
|
|
4526
|
+
timeoutBlockHeights: pendingSwap.response.lockupDetails.timeouts,
|
|
4527
|
+
lockupAddress: pendingSwap.response.lockupDetails.lockupAddress,
|
|
4528
|
+
swapId: pendingSwap.id
|
|
4529
|
+
}));
|
|
4530
|
+
} catch (error) {
|
|
4448
4531
|
throw new SwapError({
|
|
4449
|
-
message: "Unable to
|
|
4532
|
+
message: error instanceof Error ? error.message : "Unable to refund: invalid VHTLC address"
|
|
4450
4533
|
});
|
|
4451
4534
|
}
|
|
4452
4535
|
const { vtxos } = await this.indexerProvider.getVtxos({
|
|
@@ -4705,20 +4788,21 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
4705
4788
|
pendingSwap.response.claimDetails.serverPublicKey,
|
|
4706
4789
|
"sender"
|
|
4707
4790
|
);
|
|
4708
|
-
|
|
4709
|
-
|
|
4710
|
-
|
|
4711
|
-
|
|
4712
|
-
|
|
4713
|
-
|
|
4714
|
-
|
|
4715
|
-
|
|
4716
|
-
|
|
4717
|
-
|
|
4718
|
-
|
|
4719
|
-
|
|
4791
|
+
let vhtlcScript;
|
|
4792
|
+
let serverXOnlyPublicKey;
|
|
4793
|
+
try {
|
|
4794
|
+
({ vhtlcScript, serverXOnlyPublicKey } = this.resolveVHTLCForLockup({
|
|
4795
|
+
arkInfo,
|
|
4796
|
+
preimageHash: hex8.decode(pendingSwap.request.preimageHash),
|
|
4797
|
+
senderPubkey: hex8.encode(senderXOnlyPublicKey),
|
|
4798
|
+
receiverPubkey: hex8.encode(receiverXOnlyPublicKey),
|
|
4799
|
+
timeoutBlockHeights: pendingSwap.response.claimDetails.timeouts,
|
|
4800
|
+
lockupAddress: pendingSwap.response.claimDetails.lockupAddress,
|
|
4801
|
+
swapId: pendingSwap.id
|
|
4802
|
+
}));
|
|
4803
|
+
} catch (error) {
|
|
4720
4804
|
throw new SwapError({
|
|
4721
|
-
message: "Unable to claim: invalid VHTLC address"
|
|
4805
|
+
message: error instanceof Error ? error.message : "Unable to claim: invalid VHTLC address"
|
|
4722
4806
|
});
|
|
4723
4807
|
}
|
|
4724
4808
|
let vtxo;
|
|
@@ -5080,6 +5164,55 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
5080
5164
|
createVHTLCScript(args) {
|
|
5081
5165
|
return createVHTLCScript(args);
|
|
5082
5166
|
}
|
|
5167
|
+
/**
|
|
5168
|
+
* Reconstruct a swap's VHTLC by matching the persisted `lockupAddress`
|
|
5169
|
+
* against the current and deprecated server signers, returning the matched
|
|
5170
|
+
* script together with the server key it was minted under.
|
|
5171
|
+
*
|
|
5172
|
+
* Recovery paths (claim/refund/lookup) must use this instead of building the
|
|
5173
|
+
* VHTLC from the current signer alone: a swap created before a planned arkd
|
|
5174
|
+
* signer rotation is locked to a now-deprecated signer, so the current key
|
|
5175
|
+
* would yield the wrong address and strand the funds. The returned
|
|
5176
|
+
* `serverXOnlyPublicKey` is the original (possibly deprecated) key and MUST
|
|
5177
|
+
* be threaded into downstream signing/verification.
|
|
5178
|
+
*
|
|
5179
|
+
* Throws a descriptive mismatch error when no candidate reproduces the
|
|
5180
|
+
* lockup address (e.g. the swap predates an already-pruned deprecated
|
|
5181
|
+
* signer) — replacing the previous current-signer-only equality check.
|
|
5182
|
+
*/
|
|
5183
|
+
resolveVHTLCForLockup(args) {
|
|
5184
|
+
const candidates = candidateServerPubkeys(args.arkInfo);
|
|
5185
|
+
for (const serverPubkey of candidates) {
|
|
5186
|
+
const { vhtlcScript, vhtlcAddress } = this.createVHTLCScript({
|
|
5187
|
+
network: args.arkInfo.network,
|
|
5188
|
+
preimageHash: args.preimageHash,
|
|
5189
|
+
receiverPubkey: args.receiverPubkey,
|
|
5190
|
+
senderPubkey: args.senderPubkey,
|
|
5191
|
+
serverPubkey,
|
|
5192
|
+
timeoutBlockHeights: args.timeoutBlockHeights
|
|
5193
|
+
});
|
|
5194
|
+
if (vhtlcAddress !== args.lockupAddress) continue;
|
|
5195
|
+
if (!vhtlcScript.claimScript && !vhtlcScript.refundScript) {
|
|
5196
|
+
throw new Error(
|
|
5197
|
+
`Swap ${args.swapId}: VHTLC address matched but claim/refund script leaves are empty`
|
|
5198
|
+
);
|
|
5199
|
+
}
|
|
5200
|
+
return {
|
|
5201
|
+
vhtlcScript,
|
|
5202
|
+
// The matched (possibly deprecated) key must flow into downstream
|
|
5203
|
+
// signing/verification: arkd signs a deprecated-signer input with
|
|
5204
|
+
// the deprecated key, so the claim/refund leaf checks use it.
|
|
5205
|
+
serverXOnlyPublicKey: normalizeToXOnlyKey(
|
|
5206
|
+
hex8.decode(serverPubkey),
|
|
5207
|
+
"server",
|
|
5208
|
+
args.swapId
|
|
5209
|
+
)
|
|
5210
|
+
};
|
|
5211
|
+
}
|
|
5212
|
+
throw new Error(
|
|
5213
|
+
`Swap ${args.swapId}: VHTLC address mismatch. Expected ${args.lockupAddress}; no current or deprecated server signer (${candidates.length} candidate(s) tried) reproduced it`
|
|
5214
|
+
);
|
|
5215
|
+
}
|
|
5083
5216
|
async getFees(from, to) {
|
|
5084
5217
|
if (from && to) {
|
|
5085
5218
|
return this.swapProvider.getChainFees(from, to);
|
|
@@ -5163,13 +5296,28 @@ var ArkadeSwaps = class _ArkadeSwaps {
|
|
|
5163
5296
|
for (const swap of await this.getPendingSubmarineSwapsFromStorage()) {
|
|
5164
5297
|
if (isSubmarineFinalStatus(swap.status)) continue;
|
|
5165
5298
|
promises.push(
|
|
5166
|
-
this.getSwapStatus(swap.id).then(
|
|
5167
|
-
|
|
5299
|
+
this.getSwapStatus(swap.id).then(async ({ status }) => {
|
|
5300
|
+
let additionalFields;
|
|
5301
|
+
if (isSubmarineSuccessStatus(status) && !swap.preimage) {
|
|
5302
|
+
try {
|
|
5303
|
+
const { preimage } = await this.swapProvider.getSwapPreimage(
|
|
5304
|
+
swap.id
|
|
5305
|
+
);
|
|
5306
|
+
additionalFields = { preimage };
|
|
5307
|
+
} catch (error) {
|
|
5308
|
+
logger.warn(
|
|
5309
|
+
`Failed to fetch preimage for settled swap ${swap.id}:`,
|
|
5310
|
+
error
|
|
5311
|
+
);
|
|
5312
|
+
}
|
|
5313
|
+
}
|
|
5314
|
+
await updateSubmarineSwapStatus(
|
|
5168
5315
|
swap,
|
|
5169
5316
|
status,
|
|
5170
|
-
this.savePendingSubmarineSwap.bind(this)
|
|
5171
|
-
|
|
5172
|
-
|
|
5317
|
+
this.savePendingSubmarineSwap.bind(this),
|
|
5318
|
+
additionalFields
|
|
5319
|
+
);
|
|
5320
|
+
}).catch((error) => {
|
|
5173
5321
|
logger.error(`Failed to refresh swap status for ${swap.id}:`, error);
|
|
5174
5322
|
})
|
|
5175
5323
|
);
|
|
@@ -5339,6 +5487,7 @@ export {
|
|
|
5339
5487
|
isSubmarineFinalStatus,
|
|
5340
5488
|
isSubmarinePendingStatus,
|
|
5341
5489
|
isSubmarineRefundableStatus,
|
|
5490
|
+
hasSubmarineStatusReached,
|
|
5342
5491
|
isSubmarineSuccessStatus,
|
|
5343
5492
|
isReverseFailedStatus,
|
|
5344
5493
|
isReverseFinalStatus,
|