@arkade-os/boltz-swap 0.3.39 → 0.3.40

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.
@@ -428,6 +428,23 @@ declare class ArkadeSwaps {
428
428
  vhtlcScript: VHTLC.Script;
429
429
  vhtlcAddress: string;
430
430
  };
431
+ /**
432
+ * Reconstruct a swap's VHTLC by matching the persisted `lockupAddress`
433
+ * against the current and deprecated server signers, returning the matched
434
+ * script together with the server key it was minted under.
435
+ *
436
+ * Recovery paths (claim/refund/lookup) must use this instead of building the
437
+ * VHTLC from the current signer alone: a swap created before a planned arkd
438
+ * signer rotation is locked to a now-deprecated signer, so the current key
439
+ * would yield the wrong address and strand the funds. The returned
440
+ * `serverXOnlyPublicKey` is the original (possibly deprecated) key and MUST
441
+ * be threaded into downstream signing/verification.
442
+ *
443
+ * Throws a descriptive mismatch error when no candidate reproduces the
444
+ * lockup address (e.g. the swap predates an already-pruned deprecated
445
+ * signer) — replacing the previous current-signer-only equality check.
446
+ */
447
+ private resolveVHTLCForLockup;
431
448
  /**
432
449
  * Retrieves fees for swaps.
433
450
  * - No arguments: returns lightning (submarine/reverse) fees
@@ -428,6 +428,23 @@ declare class ArkadeSwaps {
428
428
  vhtlcScript: VHTLC.Script;
429
429
  vhtlcAddress: string;
430
430
  };
431
+ /**
432
+ * Reconstruct a swap's VHTLC by matching the persisted `lockupAddress`
433
+ * against the current and deprecated server signers, returning the matched
434
+ * script together with the server key it was minted under.
435
+ *
436
+ * Recovery paths (claim/refund/lookup) must use this instead of building the
437
+ * VHTLC from the current signer alone: a swap created before a planned arkd
438
+ * signer rotation is locked to a now-deprecated signer, so the current key
439
+ * would yield the wrong address and strand the funds. The returned
440
+ * `serverXOnlyPublicKey` is the original (possibly deprecated) key and MUST
441
+ * be threaded into downstream signing/verification.
442
+ *
443
+ * Throws a descriptive mismatch error when no candidate reproduces the
444
+ * lockup address (e.g. the swap predates an already-pruned deprecated
445
+ * signer) — replacing the previous current-signer-only equality check.
446
+ */
447
+ private resolveVHTLCForLockup;
431
448
  /**
432
449
  * Retrieves fees for swaps.
433
450
  * - No arguments: returns lightning (submarine/reverse) fees
@@ -976,10 +976,10 @@ import bolt11 from "light-bolt11-decoder";
976
976
  import { ArkAddress } from "@arkade-os/sdk";
977
977
  var decodeInvoice = (invoice) => {
978
978
  const decoded = bolt11.decode(invoice);
979
- const millisats = Number(decoded.sections.find((s) => s.name === "amount")?.value ?? "0");
979
+ const millisats = BigInt(decoded.sections.find((s) => s.name === "amount")?.value ?? "0");
980
980
  return {
981
981
  expiry: decoded.expiry ?? 3600,
982
- amountSats: Math.floor(millisats / 1e3),
982
+ amountSats: Number(millisats / 1000n),
983
983
  description: decoded.sections.find((s) => s.name === "description")?.value ?? "",
984
984
  paymentHash: decoded.sections.find((s) => s.name === "payment_hash")?.value ?? ""
985
985
  };
@@ -2966,6 +2966,19 @@ var createVHTLCScript = (args) => {
2966
2966
  const vhtlcAddress = vhtlcScript.address(hrp, serverXOnlyPublicKey).encode();
2967
2967
  return { vhtlcScript, vhtlcAddress };
2968
2968
  };
2969
+ var candidateServerPubkeys = (arkInfo) => {
2970
+ const current = hex7.encode(normalizeToXOnlyKey(hex7.decode(arkInfo.signerPubkey), "server"));
2971
+ const seen = /* @__PURE__ */ new Set([current]);
2972
+ const candidates = [current];
2973
+ for (const deprecated of arkInfo.deprecatedSigners ?? []) {
2974
+ if (!deprecated.pubkey) continue;
2975
+ const key = hex7.encode(normalizeToXOnlyKey(hex7.decode(deprecated.pubkey), "server"));
2976
+ if (seen.has(key)) continue;
2977
+ seen.add(key);
2978
+ candidates.push(key);
2979
+ }
2980
+ return candidates;
2981
+ };
2969
2982
  var joinBatch = async (arkProvider, identity, input, output, {
2970
2983
  forfeitPubkey,
2971
2984
  forfeitAddress,
@@ -3380,6 +3393,10 @@ var ArkadeSwaps = class _ArkadeSwaps {
3380
3393
  ...args.description?.trim() ? { description: args.description.trim() } : {}
3381
3394
  };
3382
3395
  const swapResponse = await this.swapProvider.createReverseSwap(swapRequest);
3396
+ const decodedInvoice = decodeInvoice(swapResponse.invoice);
3397
+ if (decodedInvoice.paymentHash !== preimageHash) {
3398
+ throw new SwapError({ message: "Preimage hash does not match invoice payment hash" });
3399
+ }
3383
3400
  const pendingSwap = {
3384
3401
  id: swapResponse.id,
3385
3402
  type: "reverse",
@@ -3423,27 +3440,15 @@ var ArkadeSwaps = class _ArkadeSwaps {
3423
3440
  "boltz",
3424
3441
  pendingSwap.id
3425
3442
  );
3426
- const serverXOnly = normalizeToXOnlyKey(
3427
- hex8.decode(arkInfo.signerPubkey),
3428
- "server",
3429
- pendingSwap.id
3430
- );
3431
- const { vhtlcScript, vhtlcAddress } = this.createVHTLCScript({
3432
- network: arkInfo.network,
3443
+ const { vhtlcScript, serverXOnlyPublicKey: serverXOnly } = this.resolveVHTLCForLockup({
3444
+ arkInfo,
3433
3445
  preimageHash: sha2563(preimage),
3434
3446
  receiverPubkey: hex8.encode(receiverXOnly),
3435
3447
  senderPubkey: hex8.encode(senderXOnly),
3436
- serverPubkey: hex8.encode(serverXOnly),
3437
- timeoutBlockHeights: vhtlcTimeouts
3448
+ timeoutBlockHeights: vhtlcTimeouts,
3449
+ lockupAddress,
3450
+ swapId: pendingSwap.id
3438
3451
  });
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
3452
  let unspentVtxos = [];
3448
3453
  let rawVtxos = [];
3449
3454
  for (let attempt = 1; attempt <= CLAIM_VTXO_RETRY_ATTEMPTS; attempt++) {
@@ -3685,11 +3690,6 @@ var ArkadeSwaps = class _ArkadeSwaps {
3685
3690
  "our",
3686
3691
  swap.id
3687
3692
  );
3688
- const serverXOnlyPublicKey = normalizeToXOnlyKey(
3689
- hex8.decode(resolvedArkInfo.signerPubkey),
3690
- "server",
3691
- swap.id
3692
- );
3693
3693
  const { claimPublicKey, timeoutBlockHeights: vhtlcTimeouts } = swap.response;
3694
3694
  if (!claimPublicKey || !vhtlcTimeouts)
3695
3695
  throw new Error(`Swap ${swap.id}: incomplete submarine swap response`);
@@ -3698,20 +3698,19 @@ var ArkadeSwaps = class _ArkadeSwaps {
3698
3698
  "boltz",
3699
3699
  swap.id
3700
3700
  );
3701
- const { vhtlcScript, vhtlcAddress } = this.createVHTLCScript({
3702
- network: resolvedArkInfo.network,
3701
+ const lockupAddress = swap.response.address;
3702
+ if (!lockupAddress)
3703
+ throw new Error(`Swap ${swap.id}: missing lockup address in submarine swap response`);
3704
+ const { vhtlcScript, serverXOnlyPublicKey } = this.resolveVHTLCForLockup({
3705
+ arkInfo: resolvedArkInfo,
3703
3706
  preimageHash: hex8.decode(preimageHash),
3704
3707
  receiverPubkey: hex8.encode(boltzXOnlyPublicKey),
3705
3708
  senderPubkey: hex8.encode(ourXOnlyPublicKey),
3706
- serverPubkey: hex8.encode(serverXOnlyPublicKey),
3707
- timeoutBlockHeights: vhtlcTimeouts
3709
+ timeoutBlockHeights: vhtlcTimeouts,
3710
+ lockupAddress,
3711
+ swapId: swap.id
3708
3712
  });
3709
- if (!vhtlcScript.claimScript)
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
- );
3713
+ const vhtlcAddress = lockupAddress;
3715
3714
  const vhtlcPkScriptHex = hex8.encode(vhtlcScript.pkScript);
3716
3715
  return {
3717
3716
  arkInfo: resolvedArkInfo,
@@ -4424,29 +4423,26 @@ var ArkadeSwaps = class _ArkadeSwaps {
4424
4423
  "user",
4425
4424
  pendingSwap.id
4426
4425
  );
4427
- const serverXOnlyPublicKey = normalizeToXOnlyKey(
4428
- hex8.decode(arkInfo.signerPubkey),
4429
- "server",
4430
- pendingSwap.id
4431
- );
4432
4426
  const boltzXOnlyPublicKey = normalizeToXOnlyKey(
4433
4427
  hex8.decode(pendingSwap.response.lockupDetails.serverPublicKey),
4434
4428
  "boltz",
4435
4429
  pendingSwap.id
4436
4430
  );
4437
- const { vhtlcAddress, vhtlcScript } = this.createVHTLCScript({
4438
- network: arkInfo.network,
4439
- preimageHash: hex8.decode(pendingSwap.request.preimageHash),
4440
- serverPubkey: hex8.encode(serverXOnlyPublicKey),
4441
- senderPubkey: hex8.encode(ourXOnlyPublicKey),
4442
- receiverPubkey: hex8.encode(boltzXOnlyPublicKey),
4443
- timeoutBlockHeights: pendingSwap.response.lockupDetails.timeouts
4444
- });
4445
- if (!vhtlcScript.refundScript)
4446
- throw new Error(`Swap ${pendingSwap.id}: failed to create VHTLC script for chain swap`);
4447
- if (pendingSwap.response.lockupDetails.lockupAddress !== vhtlcAddress) {
4431
+ let vhtlcScript;
4432
+ let serverXOnlyPublicKey;
4433
+ try {
4434
+ ({ vhtlcScript, serverXOnlyPublicKey } = this.resolveVHTLCForLockup({
4435
+ arkInfo,
4436
+ preimageHash: hex8.decode(pendingSwap.request.preimageHash),
4437
+ senderPubkey: hex8.encode(ourXOnlyPublicKey),
4438
+ receiverPubkey: hex8.encode(boltzXOnlyPublicKey),
4439
+ timeoutBlockHeights: pendingSwap.response.lockupDetails.timeouts,
4440
+ lockupAddress: pendingSwap.response.lockupDetails.lockupAddress,
4441
+ swapId: pendingSwap.id
4442
+ }));
4443
+ } catch (error) {
4448
4444
  throw new SwapError({
4449
- message: "Unable to claim: invalid VHTLC address"
4445
+ message: error instanceof Error ? error.message : "Unable to refund: invalid VHTLC address"
4450
4446
  });
4451
4447
  }
4452
4448
  const { vtxos } = await this.indexerProvider.getVtxos({
@@ -4705,20 +4701,21 @@ var ArkadeSwaps = class _ArkadeSwaps {
4705
4701
  pendingSwap.response.claimDetails.serverPublicKey,
4706
4702
  "sender"
4707
4703
  );
4708
- const serverXOnlyPublicKey = normalizeToXOnlyKey(arkInfo.signerPubkey, "server");
4709
- const { vhtlcAddress, vhtlcScript } = this.createVHTLCScript({
4710
- network: arkInfo.network,
4711
- preimageHash: hex8.decode(pendingSwap.request.preimageHash),
4712
- serverPubkey: hex8.encode(serverXOnlyPublicKey),
4713
- senderPubkey: hex8.encode(senderXOnlyPublicKey),
4714
- receiverPubkey: hex8.encode(receiverXOnlyPublicKey),
4715
- timeoutBlockHeights: pendingSwap.response.claimDetails.timeouts
4716
- });
4717
- if (!vhtlcScript.claimScript)
4718
- throw new Error(`Swap ${pendingSwap.id}: failed to create VHTLC script for chain swap`);
4719
- if (pendingSwap.response.claimDetails.lockupAddress !== vhtlcAddress) {
4704
+ let vhtlcScript;
4705
+ let serverXOnlyPublicKey;
4706
+ try {
4707
+ ({ vhtlcScript, serverXOnlyPublicKey } = this.resolveVHTLCForLockup({
4708
+ arkInfo,
4709
+ preimageHash: hex8.decode(pendingSwap.request.preimageHash),
4710
+ senderPubkey: hex8.encode(senderXOnlyPublicKey),
4711
+ receiverPubkey: hex8.encode(receiverXOnlyPublicKey),
4712
+ timeoutBlockHeights: pendingSwap.response.claimDetails.timeouts,
4713
+ lockupAddress: pendingSwap.response.claimDetails.lockupAddress,
4714
+ swapId: pendingSwap.id
4715
+ }));
4716
+ } catch (error) {
4720
4717
  throw new SwapError({
4721
- message: "Unable to claim: invalid VHTLC address"
4718
+ message: error instanceof Error ? error.message : "Unable to claim: invalid VHTLC address"
4722
4719
  });
4723
4720
  }
4724
4721
  let vtxo;
@@ -5080,6 +5077,55 @@ var ArkadeSwaps = class _ArkadeSwaps {
5080
5077
  createVHTLCScript(args) {
5081
5078
  return createVHTLCScript(args);
5082
5079
  }
5080
+ /**
5081
+ * Reconstruct a swap's VHTLC by matching the persisted `lockupAddress`
5082
+ * against the current and deprecated server signers, returning the matched
5083
+ * script together with the server key it was minted under.
5084
+ *
5085
+ * Recovery paths (claim/refund/lookup) must use this instead of building the
5086
+ * VHTLC from the current signer alone: a swap created before a planned arkd
5087
+ * signer rotation is locked to a now-deprecated signer, so the current key
5088
+ * would yield the wrong address and strand the funds. The returned
5089
+ * `serverXOnlyPublicKey` is the original (possibly deprecated) key and MUST
5090
+ * be threaded into downstream signing/verification.
5091
+ *
5092
+ * Throws a descriptive mismatch error when no candidate reproduces the
5093
+ * lockup address (e.g. the swap predates an already-pruned deprecated
5094
+ * signer) — replacing the previous current-signer-only equality check.
5095
+ */
5096
+ resolveVHTLCForLockup(args) {
5097
+ const candidates = candidateServerPubkeys(args.arkInfo);
5098
+ for (const serverPubkey of candidates) {
5099
+ const { vhtlcScript, vhtlcAddress } = this.createVHTLCScript({
5100
+ network: args.arkInfo.network,
5101
+ preimageHash: args.preimageHash,
5102
+ receiverPubkey: args.receiverPubkey,
5103
+ senderPubkey: args.senderPubkey,
5104
+ serverPubkey,
5105
+ timeoutBlockHeights: args.timeoutBlockHeights
5106
+ });
5107
+ if (vhtlcAddress !== args.lockupAddress) continue;
5108
+ if (!vhtlcScript.claimScript && !vhtlcScript.refundScript) {
5109
+ throw new Error(
5110
+ `Swap ${args.swapId}: VHTLC address matched but claim/refund script leaves are empty`
5111
+ );
5112
+ }
5113
+ return {
5114
+ vhtlcScript,
5115
+ // The matched (possibly deprecated) key must flow into downstream
5116
+ // signing/verification: arkd signs a deprecated-signer input with
5117
+ // the deprecated key, so the claim/refund leaf checks use it.
5118
+ serverXOnlyPublicKey: normalizeToXOnlyKey(
5119
+ hex8.decode(serverPubkey),
5120
+ "server",
5121
+ args.swapId
5122
+ )
5123
+ };
5124
+ }
5125
+ throw new Error(
5126
+ `Swap ${args.swapId}: VHTLC address mismatch. Expected ${args.lockupAddress}; no current or deprecated server signer (${candidates.length} candidate(s) tried) reproduced it`
5127
+ );
5128
+ }
5083
5129
  async getFees(from, to) {
5084
5130
  if (from && to) {
5085
5131
  return this.swapProvider.getChainFees(from, to);
@@ -7,7 +7,7 @@ import {
7
7
  isSubmarineFinalStatus,
8
8
  isSubmarineSwapRefundable,
9
9
  logger
10
- } from "./chunk-CFB2NNGT.js";
10
+ } from "./chunk-GYWMQOCP.js";
11
11
 
12
12
  // src/expo/swapsPollProcessor.ts
13
13
  var SWAP_POLL_TASK_TYPE = "swap-poll";
@@ -1291,10 +1291,10 @@ var import_light_bolt11_decoder = __toESM(require("light-bolt11-decoder"), 1);
1291
1291
  var import_sdk2 = require("@arkade-os/sdk");
1292
1292
  var decodeInvoice = (invoice) => {
1293
1293
  const decoded = import_light_bolt11_decoder.default.decode(invoice);
1294
- const millisats = Number(decoded.sections.find((s) => s.name === "amount")?.value ?? "0");
1294
+ const millisats = BigInt(decoded.sections.find((s) => s.name === "amount")?.value ?? "0");
1295
1295
  return {
1296
1296
  expiry: decoded.expiry ?? 3600,
1297
- amountSats: Math.floor(millisats / 1e3),
1297
+ amountSats: Number(millisats / 1000n),
1298
1298
  description: decoded.sections.find((s) => s.name === "description")?.value ?? "",
1299
1299
  paymentHash: decoded.sections.find((s) => s.name === "payment_hash")?.value ?? ""
1300
1300
  };
@@ -2909,6 +2909,19 @@ var createVHTLCScript = (args) => {
2909
2909
  const vhtlcAddress = vhtlcScript.address(hrp, serverXOnlyPublicKey).encode();
2910
2910
  return { vhtlcScript, vhtlcAddress };
2911
2911
  };
2912
+ var candidateServerPubkeys = (arkInfo) => {
2913
+ const current = import_base8.hex.encode(normalizeToXOnlyKey(import_base8.hex.decode(arkInfo.signerPubkey), "server"));
2914
+ const seen = /* @__PURE__ */ new Set([current]);
2915
+ const candidates = [current];
2916
+ for (const deprecated of arkInfo.deprecatedSigners ?? []) {
2917
+ if (!deprecated.pubkey) continue;
2918
+ const key = import_base8.hex.encode(normalizeToXOnlyKey(import_base8.hex.decode(deprecated.pubkey), "server"));
2919
+ if (seen.has(key)) continue;
2920
+ seen.add(key);
2921
+ candidates.push(key);
2922
+ }
2923
+ return candidates;
2924
+ };
2912
2925
  var joinBatch = async (arkProvider, identity, input, output, {
2913
2926
  forfeitPubkey,
2914
2927
  forfeitAddress,
@@ -3323,6 +3336,10 @@ var ArkadeSwaps = class _ArkadeSwaps {
3323
3336
  ...args.description?.trim() ? { description: args.description.trim() } : {}
3324
3337
  };
3325
3338
  const swapResponse = await this.swapProvider.createReverseSwap(swapRequest);
3339
+ const decodedInvoice = decodeInvoice(swapResponse.invoice);
3340
+ if (decodedInvoice.paymentHash !== preimageHash) {
3341
+ throw new SwapError({ message: "Preimage hash does not match invoice payment hash" });
3342
+ }
3326
3343
  const pendingSwap = {
3327
3344
  id: swapResponse.id,
3328
3345
  type: "reverse",
@@ -3366,27 +3383,15 @@ var ArkadeSwaps = class _ArkadeSwaps {
3366
3383
  "boltz",
3367
3384
  pendingSwap.id
3368
3385
  );
3369
- const serverXOnly = normalizeToXOnlyKey(
3370
- import_base9.hex.decode(arkInfo.signerPubkey),
3371
- "server",
3372
- pendingSwap.id
3373
- );
3374
- const { vhtlcScript, vhtlcAddress } = this.createVHTLCScript({
3375
- network: arkInfo.network,
3386
+ const { vhtlcScript, serverXOnlyPublicKey: serverXOnly } = this.resolveVHTLCForLockup({
3387
+ arkInfo,
3376
3388
  preimageHash: (0, import_sha23.sha256)(preimage),
3377
3389
  receiverPubkey: import_base9.hex.encode(receiverXOnly),
3378
3390
  senderPubkey: import_base9.hex.encode(senderXOnly),
3379
- serverPubkey: import_base9.hex.encode(serverXOnly),
3380
- timeoutBlockHeights: vhtlcTimeouts
3391
+ timeoutBlockHeights: vhtlcTimeouts,
3392
+ lockupAddress,
3393
+ swapId: pendingSwap.id
3381
3394
  });
3382
- if (!vhtlcScript.claimScript)
3383
- throw new Error(
3384
- `Swap ${pendingSwap.id}: failed to create VHTLC script for reverse swap`
3385
- );
3386
- if (vhtlcAddress !== lockupAddress)
3387
- throw new Error(
3388
- `Swap ${pendingSwap.id}: VHTLC address mismatch. Expected ${lockupAddress}, got ${vhtlcAddress}`
3389
- );
3390
3395
  let unspentVtxos = [];
3391
3396
  let rawVtxos = [];
3392
3397
  for (let attempt = 1; attempt <= CLAIM_VTXO_RETRY_ATTEMPTS; attempt++) {
@@ -3628,11 +3633,6 @@ var ArkadeSwaps = class _ArkadeSwaps {
3628
3633
  "our",
3629
3634
  swap.id
3630
3635
  );
3631
- const serverXOnlyPublicKey = normalizeToXOnlyKey(
3632
- import_base9.hex.decode(resolvedArkInfo.signerPubkey),
3633
- "server",
3634
- swap.id
3635
- );
3636
3636
  const { claimPublicKey, timeoutBlockHeights: vhtlcTimeouts } = swap.response;
3637
3637
  if (!claimPublicKey || !vhtlcTimeouts)
3638
3638
  throw new Error(`Swap ${swap.id}: incomplete submarine swap response`);
@@ -3641,20 +3641,19 @@ var ArkadeSwaps = class _ArkadeSwaps {
3641
3641
  "boltz",
3642
3642
  swap.id
3643
3643
  );
3644
- const { vhtlcScript, vhtlcAddress } = this.createVHTLCScript({
3645
- network: resolvedArkInfo.network,
3644
+ const lockupAddress = swap.response.address;
3645
+ if (!lockupAddress)
3646
+ throw new Error(`Swap ${swap.id}: missing lockup address in submarine swap response`);
3647
+ const { vhtlcScript, serverXOnlyPublicKey } = this.resolveVHTLCForLockup({
3648
+ arkInfo: resolvedArkInfo,
3646
3649
  preimageHash: import_base9.hex.decode(preimageHash),
3647
3650
  receiverPubkey: import_base9.hex.encode(boltzXOnlyPublicKey),
3648
3651
  senderPubkey: import_base9.hex.encode(ourXOnlyPublicKey),
3649
- serverPubkey: import_base9.hex.encode(serverXOnlyPublicKey),
3650
- timeoutBlockHeights: vhtlcTimeouts
3652
+ timeoutBlockHeights: vhtlcTimeouts,
3653
+ lockupAddress,
3654
+ swapId: swap.id
3651
3655
  });
3652
- if (!vhtlcScript.claimScript)
3653
- throw new Error(`Swap ${swap.id}: failed to create VHTLC script for submarine swap`);
3654
- if (vhtlcAddress !== swap.response.address)
3655
- throw new Error(
3656
- `VHTLC address mismatch for swap ${swap.id}: expected ${swap.response.address}, got ${vhtlcAddress}`
3657
- );
3656
+ const vhtlcAddress = lockupAddress;
3658
3657
  const vhtlcPkScriptHex = import_base9.hex.encode(vhtlcScript.pkScript);
3659
3658
  return {
3660
3659
  arkInfo: resolvedArkInfo,
@@ -4367,29 +4366,26 @@ var ArkadeSwaps = class _ArkadeSwaps {
4367
4366
  "user",
4368
4367
  pendingSwap.id
4369
4368
  );
4370
- const serverXOnlyPublicKey = normalizeToXOnlyKey(
4371
- import_base9.hex.decode(arkInfo.signerPubkey),
4372
- "server",
4373
- pendingSwap.id
4374
- );
4375
4369
  const boltzXOnlyPublicKey = normalizeToXOnlyKey(
4376
4370
  import_base9.hex.decode(pendingSwap.response.lockupDetails.serverPublicKey),
4377
4371
  "boltz",
4378
4372
  pendingSwap.id
4379
4373
  );
4380
- const { vhtlcAddress, vhtlcScript } = this.createVHTLCScript({
4381
- network: arkInfo.network,
4382
- preimageHash: import_base9.hex.decode(pendingSwap.request.preimageHash),
4383
- serverPubkey: import_base9.hex.encode(serverXOnlyPublicKey),
4384
- senderPubkey: import_base9.hex.encode(ourXOnlyPublicKey),
4385
- receiverPubkey: import_base9.hex.encode(boltzXOnlyPublicKey),
4386
- timeoutBlockHeights: pendingSwap.response.lockupDetails.timeouts
4387
- });
4388
- if (!vhtlcScript.refundScript)
4389
- throw new Error(`Swap ${pendingSwap.id}: failed to create VHTLC script for chain swap`);
4390
- if (pendingSwap.response.lockupDetails.lockupAddress !== vhtlcAddress) {
4374
+ let vhtlcScript;
4375
+ let serverXOnlyPublicKey;
4376
+ try {
4377
+ ({ vhtlcScript, serverXOnlyPublicKey } = this.resolveVHTLCForLockup({
4378
+ arkInfo,
4379
+ preimageHash: import_base9.hex.decode(pendingSwap.request.preimageHash),
4380
+ senderPubkey: import_base9.hex.encode(ourXOnlyPublicKey),
4381
+ receiverPubkey: import_base9.hex.encode(boltzXOnlyPublicKey),
4382
+ timeoutBlockHeights: pendingSwap.response.lockupDetails.timeouts,
4383
+ lockupAddress: pendingSwap.response.lockupDetails.lockupAddress,
4384
+ swapId: pendingSwap.id
4385
+ }));
4386
+ } catch (error) {
4391
4387
  throw new SwapError({
4392
- message: "Unable to claim: invalid VHTLC address"
4388
+ message: error instanceof Error ? error.message : "Unable to refund: invalid VHTLC address"
4393
4389
  });
4394
4390
  }
4395
4391
  const { vtxos } = await this.indexerProvider.getVtxos({
@@ -4648,20 +4644,21 @@ var ArkadeSwaps = class _ArkadeSwaps {
4648
4644
  pendingSwap.response.claimDetails.serverPublicKey,
4649
4645
  "sender"
4650
4646
  );
4651
- const serverXOnlyPublicKey = normalizeToXOnlyKey(arkInfo.signerPubkey, "server");
4652
- const { vhtlcAddress, vhtlcScript } = this.createVHTLCScript({
4653
- network: arkInfo.network,
4654
- preimageHash: import_base9.hex.decode(pendingSwap.request.preimageHash),
4655
- serverPubkey: import_base9.hex.encode(serverXOnlyPublicKey),
4656
- senderPubkey: import_base9.hex.encode(senderXOnlyPublicKey),
4657
- receiverPubkey: import_base9.hex.encode(receiverXOnlyPublicKey),
4658
- timeoutBlockHeights: pendingSwap.response.claimDetails.timeouts
4659
- });
4660
- if (!vhtlcScript.claimScript)
4661
- throw new Error(`Swap ${pendingSwap.id}: failed to create VHTLC script for chain swap`);
4662
- if (pendingSwap.response.claimDetails.lockupAddress !== vhtlcAddress) {
4647
+ let vhtlcScript;
4648
+ let serverXOnlyPublicKey;
4649
+ try {
4650
+ ({ vhtlcScript, serverXOnlyPublicKey } = this.resolveVHTLCForLockup({
4651
+ arkInfo,
4652
+ preimageHash: import_base9.hex.decode(pendingSwap.request.preimageHash),
4653
+ senderPubkey: import_base9.hex.encode(senderXOnlyPublicKey),
4654
+ receiverPubkey: import_base9.hex.encode(receiverXOnlyPublicKey),
4655
+ timeoutBlockHeights: pendingSwap.response.claimDetails.timeouts,
4656
+ lockupAddress: pendingSwap.response.claimDetails.lockupAddress,
4657
+ swapId: pendingSwap.id
4658
+ }));
4659
+ } catch (error) {
4663
4660
  throw new SwapError({
4664
- message: "Unable to claim: invalid VHTLC address"
4661
+ message: error instanceof Error ? error.message : "Unable to claim: invalid VHTLC address"
4665
4662
  });
4666
4663
  }
4667
4664
  let vtxo;
@@ -5023,6 +5020,55 @@ var ArkadeSwaps = class _ArkadeSwaps {
5023
5020
  createVHTLCScript(args) {
5024
5021
  return createVHTLCScript(args);
5025
5022
  }
5023
+ /**
5024
+ * Reconstruct a swap's VHTLC by matching the persisted `lockupAddress`
5025
+ * against the current and deprecated server signers, returning the matched
5026
+ * script together with the server key it was minted under.
5027
+ *
5028
+ * Recovery paths (claim/refund/lookup) must use this instead of building the
5029
+ * VHTLC from the current signer alone: a swap created before a planned arkd
5030
+ * signer rotation is locked to a now-deprecated signer, so the current key
5031
+ * would yield the wrong address and strand the funds. The returned
5032
+ * `serverXOnlyPublicKey` is the original (possibly deprecated) key and MUST
5033
+ * be threaded into downstream signing/verification.
5034
+ *
5035
+ * Throws a descriptive mismatch error when no candidate reproduces the
5036
+ * lockup address (e.g. the swap predates an already-pruned deprecated
5037
+ * signer) — replacing the previous current-signer-only equality check.
5038
+ */
5039
+ resolveVHTLCForLockup(args) {
5040
+ const candidates = candidateServerPubkeys(args.arkInfo);
5041
+ for (const serverPubkey of candidates) {
5042
+ const { vhtlcScript, vhtlcAddress } = this.createVHTLCScript({
5043
+ network: args.arkInfo.network,
5044
+ preimageHash: args.preimageHash,
5045
+ receiverPubkey: args.receiverPubkey,
5046
+ senderPubkey: args.senderPubkey,
5047
+ serverPubkey,
5048
+ timeoutBlockHeights: args.timeoutBlockHeights
5049
+ });
5050
+ if (vhtlcAddress !== args.lockupAddress) continue;
5051
+ if (!vhtlcScript.claimScript && !vhtlcScript.refundScript) {
5052
+ throw new Error(
5053
+ `Swap ${args.swapId}: VHTLC address matched but claim/refund script leaves are empty`
5054
+ );
5055
+ }
5056
+ return {
5057
+ vhtlcScript,
5058
+ // The matched (possibly deprecated) key must flow into downstream
5059
+ // signing/verification: arkd signs a deprecated-signer input with
5060
+ // the deprecated key, so the claim/refund leaf checks use it.
5061
+ serverXOnlyPublicKey: normalizeToXOnlyKey(
5062
+ import_base9.hex.decode(serverPubkey),
5063
+ "server",
5064
+ args.swapId
5065
+ )
5066
+ };
5067
+ }
5068
+ throw new Error(
5069
+ `Swap ${args.swapId}: VHTLC address mismatch. Expected ${args.lockupAddress}; no current or deprecated server signer (${candidates.length} candidate(s) tried) reproduced it`
5070
+ );
5071
+ }
5026
5072
  async getFees(from, to) {
5027
5073
  if (from && to) {
5028
5074
  return this.swapProvider.getChainFees(from, to);
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  SWAP_POLL_TASK_TYPE,
3
3
  swapsPollProcessor
4
- } from "../chunk-DNCIVDU5.js";
4
+ } from "../chunk-WGLBFONB.js";
5
5
  import {
6
6
  BoltzSwapProvider
7
- } from "../chunk-CFB2NNGT.js";
7
+ } from "../chunk-GYWMQOCP.js";
8
8
  import "../chunk-SJQJQO7P.js";
9
9
 
10
10
  // src/expo/background.ts
@@ -1281,10 +1281,10 @@ var import_light_bolt11_decoder = __toESM(require("light-bolt11-decoder"), 1);
1281
1281
  var import_sdk2 = require("@arkade-os/sdk");
1282
1282
  var decodeInvoice = (invoice) => {
1283
1283
  const decoded = import_light_bolt11_decoder.default.decode(invoice);
1284
- const millisats = Number(decoded.sections.find((s) => s.name === "amount")?.value ?? "0");
1284
+ const millisats = BigInt(decoded.sections.find((s) => s.name === "amount")?.value ?? "0");
1285
1285
  return {
1286
1286
  expiry: decoded.expiry ?? 3600,
1287
- amountSats: Math.floor(millisats / 1e3),
1287
+ amountSats: Number(millisats / 1000n),
1288
1288
  description: decoded.sections.find((s) => s.name === "description")?.value ?? "",
1289
1289
  paymentHash: decoded.sections.find((s) => s.name === "payment_hash")?.value ?? ""
1290
1290
  };
@@ -2899,6 +2899,19 @@ var createVHTLCScript = (args) => {
2899
2899
  const vhtlcAddress = vhtlcScript.address(hrp, serverXOnlyPublicKey).encode();
2900
2900
  return { vhtlcScript, vhtlcAddress };
2901
2901
  };
2902
+ var candidateServerPubkeys = (arkInfo) => {
2903
+ const current = import_base8.hex.encode(normalizeToXOnlyKey(import_base8.hex.decode(arkInfo.signerPubkey), "server"));
2904
+ const seen = /* @__PURE__ */ new Set([current]);
2905
+ const candidates = [current];
2906
+ for (const deprecated of arkInfo.deprecatedSigners ?? []) {
2907
+ if (!deprecated.pubkey) continue;
2908
+ const key = import_base8.hex.encode(normalizeToXOnlyKey(import_base8.hex.decode(deprecated.pubkey), "server"));
2909
+ if (seen.has(key)) continue;
2910
+ seen.add(key);
2911
+ candidates.push(key);
2912
+ }
2913
+ return candidates;
2914
+ };
2902
2915
  var joinBatch = async (arkProvider, identity, input, output, {
2903
2916
  forfeitPubkey,
2904
2917
  forfeitAddress,
@@ -3313,6 +3326,10 @@ var ArkadeSwaps = class _ArkadeSwaps {
3313
3326
  ...args.description?.trim() ? { description: args.description.trim() } : {}
3314
3327
  };
3315
3328
  const swapResponse = await this.swapProvider.createReverseSwap(swapRequest);
3329
+ const decodedInvoice = decodeInvoice(swapResponse.invoice);
3330
+ if (decodedInvoice.paymentHash !== preimageHash) {
3331
+ throw new SwapError({ message: "Preimage hash does not match invoice payment hash" });
3332
+ }
3316
3333
  const pendingSwap = {
3317
3334
  id: swapResponse.id,
3318
3335
  type: "reverse",
@@ -3356,27 +3373,15 @@ var ArkadeSwaps = class _ArkadeSwaps {
3356
3373
  "boltz",
3357
3374
  pendingSwap.id
3358
3375
  );
3359
- const serverXOnly = normalizeToXOnlyKey(
3360
- import_base9.hex.decode(arkInfo.signerPubkey),
3361
- "server",
3362
- pendingSwap.id
3363
- );
3364
- const { vhtlcScript, vhtlcAddress } = this.createVHTLCScript({
3365
- network: arkInfo.network,
3376
+ const { vhtlcScript, serverXOnlyPublicKey: serverXOnly } = this.resolveVHTLCForLockup({
3377
+ arkInfo,
3366
3378
  preimageHash: (0, import_sha23.sha256)(preimage),
3367
3379
  receiverPubkey: import_base9.hex.encode(receiverXOnly),
3368
3380
  senderPubkey: import_base9.hex.encode(senderXOnly),
3369
- serverPubkey: import_base9.hex.encode(serverXOnly),
3370
- timeoutBlockHeights: vhtlcTimeouts
3381
+ timeoutBlockHeights: vhtlcTimeouts,
3382
+ lockupAddress,
3383
+ swapId: pendingSwap.id
3371
3384
  });
3372
- if (!vhtlcScript.claimScript)
3373
- throw new Error(
3374
- `Swap ${pendingSwap.id}: failed to create VHTLC script for reverse swap`
3375
- );
3376
- if (vhtlcAddress !== lockupAddress)
3377
- throw new Error(
3378
- `Swap ${pendingSwap.id}: VHTLC address mismatch. Expected ${lockupAddress}, got ${vhtlcAddress}`
3379
- );
3380
3385
  let unspentVtxos = [];
3381
3386
  let rawVtxos = [];
3382
3387
  for (let attempt = 1; attempt <= CLAIM_VTXO_RETRY_ATTEMPTS; attempt++) {
@@ -3618,11 +3623,6 @@ var ArkadeSwaps = class _ArkadeSwaps {
3618
3623
  "our",
3619
3624
  swap.id
3620
3625
  );
3621
- const serverXOnlyPublicKey = normalizeToXOnlyKey(
3622
- import_base9.hex.decode(resolvedArkInfo.signerPubkey),
3623
- "server",
3624
- swap.id
3625
- );
3626
3626
  const { claimPublicKey, timeoutBlockHeights: vhtlcTimeouts } = swap.response;
3627
3627
  if (!claimPublicKey || !vhtlcTimeouts)
3628
3628
  throw new Error(`Swap ${swap.id}: incomplete submarine swap response`);
@@ -3631,20 +3631,19 @@ var ArkadeSwaps = class _ArkadeSwaps {
3631
3631
  "boltz",
3632
3632
  swap.id
3633
3633
  );
3634
- const { vhtlcScript, vhtlcAddress } = this.createVHTLCScript({
3635
- network: resolvedArkInfo.network,
3634
+ const lockupAddress = swap.response.address;
3635
+ if (!lockupAddress)
3636
+ throw new Error(`Swap ${swap.id}: missing lockup address in submarine swap response`);
3637
+ const { vhtlcScript, serverXOnlyPublicKey } = this.resolveVHTLCForLockup({
3638
+ arkInfo: resolvedArkInfo,
3636
3639
  preimageHash: import_base9.hex.decode(preimageHash),
3637
3640
  receiverPubkey: import_base9.hex.encode(boltzXOnlyPublicKey),
3638
3641
  senderPubkey: import_base9.hex.encode(ourXOnlyPublicKey),
3639
- serverPubkey: import_base9.hex.encode(serverXOnlyPublicKey),
3640
- timeoutBlockHeights: vhtlcTimeouts
3642
+ timeoutBlockHeights: vhtlcTimeouts,
3643
+ lockupAddress,
3644
+ swapId: swap.id
3641
3645
  });
3642
- if (!vhtlcScript.claimScript)
3643
- throw new Error(`Swap ${swap.id}: failed to create VHTLC script for submarine swap`);
3644
- if (vhtlcAddress !== swap.response.address)
3645
- throw new Error(
3646
- `VHTLC address mismatch for swap ${swap.id}: expected ${swap.response.address}, got ${vhtlcAddress}`
3647
- );
3646
+ const vhtlcAddress = lockupAddress;
3648
3647
  const vhtlcPkScriptHex = import_base9.hex.encode(vhtlcScript.pkScript);
3649
3648
  return {
3650
3649
  arkInfo: resolvedArkInfo,
@@ -4357,29 +4356,26 @@ var ArkadeSwaps = class _ArkadeSwaps {
4357
4356
  "user",
4358
4357
  pendingSwap.id
4359
4358
  );
4360
- const serverXOnlyPublicKey = normalizeToXOnlyKey(
4361
- import_base9.hex.decode(arkInfo.signerPubkey),
4362
- "server",
4363
- pendingSwap.id
4364
- );
4365
4359
  const boltzXOnlyPublicKey = normalizeToXOnlyKey(
4366
4360
  import_base9.hex.decode(pendingSwap.response.lockupDetails.serverPublicKey),
4367
4361
  "boltz",
4368
4362
  pendingSwap.id
4369
4363
  );
4370
- const { vhtlcAddress, vhtlcScript } = this.createVHTLCScript({
4371
- network: arkInfo.network,
4372
- preimageHash: import_base9.hex.decode(pendingSwap.request.preimageHash),
4373
- serverPubkey: import_base9.hex.encode(serverXOnlyPublicKey),
4374
- senderPubkey: import_base9.hex.encode(ourXOnlyPublicKey),
4375
- receiverPubkey: import_base9.hex.encode(boltzXOnlyPublicKey),
4376
- timeoutBlockHeights: pendingSwap.response.lockupDetails.timeouts
4377
- });
4378
- if (!vhtlcScript.refundScript)
4379
- throw new Error(`Swap ${pendingSwap.id}: failed to create VHTLC script for chain swap`);
4380
- if (pendingSwap.response.lockupDetails.lockupAddress !== vhtlcAddress) {
4364
+ let vhtlcScript;
4365
+ let serverXOnlyPublicKey;
4366
+ try {
4367
+ ({ vhtlcScript, serverXOnlyPublicKey } = this.resolveVHTLCForLockup({
4368
+ arkInfo,
4369
+ preimageHash: import_base9.hex.decode(pendingSwap.request.preimageHash),
4370
+ senderPubkey: import_base9.hex.encode(ourXOnlyPublicKey),
4371
+ receiverPubkey: import_base9.hex.encode(boltzXOnlyPublicKey),
4372
+ timeoutBlockHeights: pendingSwap.response.lockupDetails.timeouts,
4373
+ lockupAddress: pendingSwap.response.lockupDetails.lockupAddress,
4374
+ swapId: pendingSwap.id
4375
+ }));
4376
+ } catch (error) {
4381
4377
  throw new SwapError({
4382
- message: "Unable to claim: invalid VHTLC address"
4378
+ message: error instanceof Error ? error.message : "Unable to refund: invalid VHTLC address"
4383
4379
  });
4384
4380
  }
4385
4381
  const { vtxos } = await this.indexerProvider.getVtxos({
@@ -4638,20 +4634,21 @@ var ArkadeSwaps = class _ArkadeSwaps {
4638
4634
  pendingSwap.response.claimDetails.serverPublicKey,
4639
4635
  "sender"
4640
4636
  );
4641
- const serverXOnlyPublicKey = normalizeToXOnlyKey(arkInfo.signerPubkey, "server");
4642
- const { vhtlcAddress, vhtlcScript } = this.createVHTLCScript({
4643
- network: arkInfo.network,
4644
- preimageHash: import_base9.hex.decode(pendingSwap.request.preimageHash),
4645
- serverPubkey: import_base9.hex.encode(serverXOnlyPublicKey),
4646
- senderPubkey: import_base9.hex.encode(senderXOnlyPublicKey),
4647
- receiverPubkey: import_base9.hex.encode(receiverXOnlyPublicKey),
4648
- timeoutBlockHeights: pendingSwap.response.claimDetails.timeouts
4649
- });
4650
- if (!vhtlcScript.claimScript)
4651
- throw new Error(`Swap ${pendingSwap.id}: failed to create VHTLC script for chain swap`);
4652
- if (pendingSwap.response.claimDetails.lockupAddress !== vhtlcAddress) {
4637
+ let vhtlcScript;
4638
+ let serverXOnlyPublicKey;
4639
+ try {
4640
+ ({ vhtlcScript, serverXOnlyPublicKey } = this.resolveVHTLCForLockup({
4641
+ arkInfo,
4642
+ preimageHash: import_base9.hex.decode(pendingSwap.request.preimageHash),
4643
+ senderPubkey: import_base9.hex.encode(senderXOnlyPublicKey),
4644
+ receiverPubkey: import_base9.hex.encode(receiverXOnlyPublicKey),
4645
+ timeoutBlockHeights: pendingSwap.response.claimDetails.timeouts,
4646
+ lockupAddress: pendingSwap.response.claimDetails.lockupAddress,
4647
+ swapId: pendingSwap.id
4648
+ }));
4649
+ } catch (error) {
4653
4650
  throw new SwapError({
4654
- message: "Unable to claim: invalid VHTLC address"
4651
+ message: error instanceof Error ? error.message : "Unable to claim: invalid VHTLC address"
4655
4652
  });
4656
4653
  }
4657
4654
  let vtxo;
@@ -5013,6 +5010,55 @@ var ArkadeSwaps = class _ArkadeSwaps {
5013
5010
  createVHTLCScript(args) {
5014
5011
  return createVHTLCScript(args);
5015
5012
  }
5013
+ /**
5014
+ * Reconstruct a swap's VHTLC by matching the persisted `lockupAddress`
5015
+ * against the current and deprecated server signers, returning the matched
5016
+ * script together with the server key it was minted under.
5017
+ *
5018
+ * Recovery paths (claim/refund/lookup) must use this instead of building the
5019
+ * VHTLC from the current signer alone: a swap created before a planned arkd
5020
+ * signer rotation is locked to a now-deprecated signer, so the current key
5021
+ * would yield the wrong address and strand the funds. The returned
5022
+ * `serverXOnlyPublicKey` is the original (possibly deprecated) key and MUST
5023
+ * be threaded into downstream signing/verification.
5024
+ *
5025
+ * Throws a descriptive mismatch error when no candidate reproduces the
5026
+ * lockup address (e.g. the swap predates an already-pruned deprecated
5027
+ * signer) — replacing the previous current-signer-only equality check.
5028
+ */
5029
+ resolveVHTLCForLockup(args) {
5030
+ const candidates = candidateServerPubkeys(args.arkInfo);
5031
+ for (const serverPubkey of candidates) {
5032
+ const { vhtlcScript, vhtlcAddress } = this.createVHTLCScript({
5033
+ network: args.arkInfo.network,
5034
+ preimageHash: args.preimageHash,
5035
+ receiverPubkey: args.receiverPubkey,
5036
+ senderPubkey: args.senderPubkey,
5037
+ serverPubkey,
5038
+ timeoutBlockHeights: args.timeoutBlockHeights
5039
+ });
5040
+ if (vhtlcAddress !== args.lockupAddress) continue;
5041
+ if (!vhtlcScript.claimScript && !vhtlcScript.refundScript) {
5042
+ throw new Error(
5043
+ `Swap ${args.swapId}: VHTLC address matched but claim/refund script leaves are empty`
5044
+ );
5045
+ }
5046
+ return {
5047
+ vhtlcScript,
5048
+ // The matched (possibly deprecated) key must flow into downstream
5049
+ // signing/verification: arkd signs a deprecated-signer input with
5050
+ // the deprecated key, so the claim/refund leaf checks use it.
5051
+ serverXOnlyPublicKey: normalizeToXOnlyKey(
5052
+ import_base9.hex.decode(serverPubkey),
5053
+ "server",
5054
+ args.swapId
5055
+ )
5056
+ };
5057
+ }
5058
+ throw new Error(
5059
+ `Swap ${args.swapId}: VHTLC address mismatch. Expected ${args.lockupAddress}; no current or deprecated server signer (${candidates.length} candidate(s) tried) reproduced it`
5060
+ );
5061
+ }
5016
5062
  async getFees(from, to) {
5017
5063
  if (from && to) {
5018
5064
  return this.swapProvider.getChainFees(from, to);
@@ -1,4 +1,4 @@
1
- import { I as IArkadeSwaps, A as ArkadeSwaps, Q as QuoteSwapOptions, V as VhtlcTimeouts } from '../arkade-swaps-DG9UepoS.cjs';
1
+ import { I as IArkadeSwaps, A as ArkadeSwaps, Q as QuoteSwapOptions, V as VhtlcTimeouts } from '../arkade-swaps-CObmwpZQ.cjs';
2
2
  import { o as SwapManagerClient, C as CreateLightningInvoiceRequest, e as CreateLightningInvoiceResponse, S as SendLightningPaymentRequest, f as SendLightningPaymentResponse, c as BoltzSubmarineSwap, b as BoltzReverseSwap, g as SubmarineRefundOutcome, h as SubmarineRecoveryInfo, i as SubmarineRecoveryResult, F as FeesResponse, a as BoltzChainSwap, k as ArkToBtcResponse, m as ChainArkRefundOutcome, l as BtcToArkResponse, d as Chain, j as ChainFeesResponse, L as LimitsResponse, G as GetSwapStatusResponse, B as BoltzSwap } from '../types-D97i1LFu.cjs';
3
3
  import { E as ExpoArkadeSwapsConfig } from '../swapsPollProcessor-BlyUrhtO.cjs';
4
4
  export { D as DefineSwapBackgroundTaskOptions, b as ExpoArkadeLightningConfig, c as ExpoSwapBackgroundConfig, P as PersistedSwapBackgroundConfig, S as SWAP_POLL_TASK_TYPE, a as SwapTaskDependencies } from '../swapsPollProcessor-BlyUrhtO.cjs';
@@ -1,4 +1,4 @@
1
- import { I as IArkadeSwaps, A as ArkadeSwaps, Q as QuoteSwapOptions, V as VhtlcTimeouts } from '../arkade-swaps-Uet3tgN6.js';
1
+ import { I as IArkadeSwaps, A as ArkadeSwaps, Q as QuoteSwapOptions, V as VhtlcTimeouts } from '../arkade-swaps-DnRiRcdW.js';
2
2
  import { o as SwapManagerClient, C as CreateLightningInvoiceRequest, e as CreateLightningInvoiceResponse, S as SendLightningPaymentRequest, f as SendLightningPaymentResponse, c as BoltzSubmarineSwap, b as BoltzReverseSwap, g as SubmarineRefundOutcome, h as SubmarineRecoveryInfo, i as SubmarineRecoveryResult, F as FeesResponse, a as BoltzChainSwap, k as ArkToBtcResponse, m as ChainArkRefundOutcome, l as BtcToArkResponse, d as Chain, j as ChainFeesResponse, L as LimitsResponse, G as GetSwapStatusResponse, B as BoltzSwap } from '../types-D97i1LFu.js';
3
3
  import { E as ExpoArkadeSwapsConfig } from '../swapsPollProcessor-Bv4Z2R7g.js';
4
4
  export { D as DefineSwapBackgroundTaskOptions, b as ExpoArkadeLightningConfig, c as ExpoSwapBackgroundConfig, P as PersistedSwapBackgroundConfig, S as SWAP_POLL_TASK_TYPE, a as SwapTaskDependencies } from '../swapsPollProcessor-Bv4Z2R7g.js';
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  SWAP_POLL_TASK_TYPE
3
- } from "../chunk-DNCIVDU5.js";
3
+ } from "../chunk-WGLBFONB.js";
4
4
  import {
5
5
  ArkadeSwaps
6
- } from "../chunk-CFB2NNGT.js";
6
+ } from "../chunk-GYWMQOCP.js";
7
7
  import "../chunk-SJQJQO7P.js";
8
8
 
9
9
  // src/expo/arkade-lightning.ts
package/dist/index.cjs CHANGED
@@ -1408,10 +1408,10 @@ var import_light_bolt11_decoder = __toESM(require("light-bolt11-decoder"), 1);
1408
1408
  var import_sdk2 = require("@arkade-os/sdk");
1409
1409
  var decodeInvoice = (invoice) => {
1410
1410
  const decoded = import_light_bolt11_decoder.default.decode(invoice);
1411
- const millisats = Number(decoded.sections.find((s) => s.name === "amount")?.value ?? "0");
1411
+ const millisats = BigInt(decoded.sections.find((s) => s.name === "amount")?.value ?? "0");
1412
1412
  return {
1413
1413
  expiry: decoded.expiry ?? 3600,
1414
- amountSats: Math.floor(millisats / 1e3),
1414
+ amountSats: Number(millisats / 1000n),
1415
1415
  description: decoded.sections.find((s) => s.name === "description")?.value ?? "",
1416
1416
  paymentHash: decoded.sections.find((s) => s.name === "payment_hash")?.value ?? ""
1417
1417
  };
@@ -3047,6 +3047,19 @@ var createVHTLCScript = (args) => {
3047
3047
  const vhtlcAddress = vhtlcScript.address(hrp, serverXOnlyPublicKey).encode();
3048
3048
  return { vhtlcScript, vhtlcAddress };
3049
3049
  };
3050
+ var candidateServerPubkeys = (arkInfo) => {
3051
+ const current = import_base8.hex.encode(normalizeToXOnlyKey(import_base8.hex.decode(arkInfo.signerPubkey), "server"));
3052
+ const seen = /* @__PURE__ */ new Set([current]);
3053
+ const candidates = [current];
3054
+ for (const deprecated of arkInfo.deprecatedSigners ?? []) {
3055
+ if (!deprecated.pubkey) continue;
3056
+ const key = import_base8.hex.encode(normalizeToXOnlyKey(import_base8.hex.decode(deprecated.pubkey), "server"));
3057
+ if (seen.has(key)) continue;
3058
+ seen.add(key);
3059
+ candidates.push(key);
3060
+ }
3061
+ return candidates;
3062
+ };
3050
3063
  var joinBatch = async (arkProvider, identity, input, output, {
3051
3064
  forfeitPubkey,
3052
3065
  forfeitAddress,
@@ -3461,6 +3474,10 @@ var ArkadeSwaps = class _ArkadeSwaps {
3461
3474
  ...args.description?.trim() ? { description: args.description.trim() } : {}
3462
3475
  };
3463
3476
  const swapResponse = await this.swapProvider.createReverseSwap(swapRequest);
3477
+ const decodedInvoice = decodeInvoice(swapResponse.invoice);
3478
+ if (decodedInvoice.paymentHash !== preimageHash) {
3479
+ throw new SwapError({ message: "Preimage hash does not match invoice payment hash" });
3480
+ }
3464
3481
  const pendingSwap = {
3465
3482
  id: swapResponse.id,
3466
3483
  type: "reverse",
@@ -3504,27 +3521,15 @@ var ArkadeSwaps = class _ArkadeSwaps {
3504
3521
  "boltz",
3505
3522
  pendingSwap.id
3506
3523
  );
3507
- const serverXOnly = normalizeToXOnlyKey(
3508
- import_base9.hex.decode(arkInfo.signerPubkey),
3509
- "server",
3510
- pendingSwap.id
3511
- );
3512
- const { vhtlcScript, vhtlcAddress } = this.createVHTLCScript({
3513
- network: arkInfo.network,
3524
+ const { vhtlcScript, serverXOnlyPublicKey: serverXOnly } = this.resolveVHTLCForLockup({
3525
+ arkInfo,
3514
3526
  preimageHash: (0, import_sha23.sha256)(preimage),
3515
3527
  receiverPubkey: import_base9.hex.encode(receiverXOnly),
3516
3528
  senderPubkey: import_base9.hex.encode(senderXOnly),
3517
- serverPubkey: import_base9.hex.encode(serverXOnly),
3518
- timeoutBlockHeights: vhtlcTimeouts
3529
+ timeoutBlockHeights: vhtlcTimeouts,
3530
+ lockupAddress,
3531
+ swapId: pendingSwap.id
3519
3532
  });
3520
- if (!vhtlcScript.claimScript)
3521
- throw new Error(
3522
- `Swap ${pendingSwap.id}: failed to create VHTLC script for reverse swap`
3523
- );
3524
- if (vhtlcAddress !== lockupAddress)
3525
- throw new Error(
3526
- `Swap ${pendingSwap.id}: VHTLC address mismatch. Expected ${lockupAddress}, got ${vhtlcAddress}`
3527
- );
3528
3533
  let unspentVtxos = [];
3529
3534
  let rawVtxos = [];
3530
3535
  for (let attempt = 1; attempt <= CLAIM_VTXO_RETRY_ATTEMPTS; attempt++) {
@@ -3766,11 +3771,6 @@ var ArkadeSwaps = class _ArkadeSwaps {
3766
3771
  "our",
3767
3772
  swap.id
3768
3773
  );
3769
- const serverXOnlyPublicKey = normalizeToXOnlyKey(
3770
- import_base9.hex.decode(resolvedArkInfo.signerPubkey),
3771
- "server",
3772
- swap.id
3773
- );
3774
3774
  const { claimPublicKey, timeoutBlockHeights: vhtlcTimeouts } = swap.response;
3775
3775
  if (!claimPublicKey || !vhtlcTimeouts)
3776
3776
  throw new Error(`Swap ${swap.id}: incomplete submarine swap response`);
@@ -3779,20 +3779,19 @@ var ArkadeSwaps = class _ArkadeSwaps {
3779
3779
  "boltz",
3780
3780
  swap.id
3781
3781
  );
3782
- const { vhtlcScript, vhtlcAddress } = this.createVHTLCScript({
3783
- network: resolvedArkInfo.network,
3782
+ const lockupAddress = swap.response.address;
3783
+ if (!lockupAddress)
3784
+ throw new Error(`Swap ${swap.id}: missing lockup address in submarine swap response`);
3785
+ const { vhtlcScript, serverXOnlyPublicKey } = this.resolveVHTLCForLockup({
3786
+ arkInfo: resolvedArkInfo,
3784
3787
  preimageHash: import_base9.hex.decode(preimageHash),
3785
3788
  receiverPubkey: import_base9.hex.encode(boltzXOnlyPublicKey),
3786
3789
  senderPubkey: import_base9.hex.encode(ourXOnlyPublicKey),
3787
- serverPubkey: import_base9.hex.encode(serverXOnlyPublicKey),
3788
- timeoutBlockHeights: vhtlcTimeouts
3790
+ timeoutBlockHeights: vhtlcTimeouts,
3791
+ lockupAddress,
3792
+ swapId: swap.id
3789
3793
  });
3790
- if (!vhtlcScript.claimScript)
3791
- throw new Error(`Swap ${swap.id}: failed to create VHTLC script for submarine swap`);
3792
- if (vhtlcAddress !== swap.response.address)
3793
- throw new Error(
3794
- `VHTLC address mismatch for swap ${swap.id}: expected ${swap.response.address}, got ${vhtlcAddress}`
3795
- );
3794
+ const vhtlcAddress = lockupAddress;
3796
3795
  const vhtlcPkScriptHex = import_base9.hex.encode(vhtlcScript.pkScript);
3797
3796
  return {
3798
3797
  arkInfo: resolvedArkInfo,
@@ -4505,29 +4504,26 @@ var ArkadeSwaps = class _ArkadeSwaps {
4505
4504
  "user",
4506
4505
  pendingSwap.id
4507
4506
  );
4508
- const serverXOnlyPublicKey = normalizeToXOnlyKey(
4509
- import_base9.hex.decode(arkInfo.signerPubkey),
4510
- "server",
4511
- pendingSwap.id
4512
- );
4513
4507
  const boltzXOnlyPublicKey = normalizeToXOnlyKey(
4514
4508
  import_base9.hex.decode(pendingSwap.response.lockupDetails.serverPublicKey),
4515
4509
  "boltz",
4516
4510
  pendingSwap.id
4517
4511
  );
4518
- const { vhtlcAddress, vhtlcScript } = this.createVHTLCScript({
4519
- network: arkInfo.network,
4520
- preimageHash: import_base9.hex.decode(pendingSwap.request.preimageHash),
4521
- serverPubkey: import_base9.hex.encode(serverXOnlyPublicKey),
4522
- senderPubkey: import_base9.hex.encode(ourXOnlyPublicKey),
4523
- receiverPubkey: import_base9.hex.encode(boltzXOnlyPublicKey),
4524
- timeoutBlockHeights: pendingSwap.response.lockupDetails.timeouts
4525
- });
4526
- if (!vhtlcScript.refundScript)
4527
- throw new Error(`Swap ${pendingSwap.id}: failed to create VHTLC script for chain swap`);
4528
- if (pendingSwap.response.lockupDetails.lockupAddress !== vhtlcAddress) {
4512
+ let vhtlcScript;
4513
+ let serverXOnlyPublicKey;
4514
+ try {
4515
+ ({ vhtlcScript, serverXOnlyPublicKey } = this.resolveVHTLCForLockup({
4516
+ arkInfo,
4517
+ preimageHash: import_base9.hex.decode(pendingSwap.request.preimageHash),
4518
+ senderPubkey: import_base9.hex.encode(ourXOnlyPublicKey),
4519
+ receiverPubkey: import_base9.hex.encode(boltzXOnlyPublicKey),
4520
+ timeoutBlockHeights: pendingSwap.response.lockupDetails.timeouts,
4521
+ lockupAddress: pendingSwap.response.lockupDetails.lockupAddress,
4522
+ swapId: pendingSwap.id
4523
+ }));
4524
+ } catch (error) {
4529
4525
  throw new SwapError({
4530
- message: "Unable to claim: invalid VHTLC address"
4526
+ message: error instanceof Error ? error.message : "Unable to refund: invalid VHTLC address"
4531
4527
  });
4532
4528
  }
4533
4529
  const { vtxos } = await this.indexerProvider.getVtxos({
@@ -4786,20 +4782,21 @@ var ArkadeSwaps = class _ArkadeSwaps {
4786
4782
  pendingSwap.response.claimDetails.serverPublicKey,
4787
4783
  "sender"
4788
4784
  );
4789
- const serverXOnlyPublicKey = normalizeToXOnlyKey(arkInfo.signerPubkey, "server");
4790
- const { vhtlcAddress, vhtlcScript } = this.createVHTLCScript({
4791
- network: arkInfo.network,
4792
- preimageHash: import_base9.hex.decode(pendingSwap.request.preimageHash),
4793
- serverPubkey: import_base9.hex.encode(serverXOnlyPublicKey),
4794
- senderPubkey: import_base9.hex.encode(senderXOnlyPublicKey),
4795
- receiverPubkey: import_base9.hex.encode(receiverXOnlyPublicKey),
4796
- timeoutBlockHeights: pendingSwap.response.claimDetails.timeouts
4797
- });
4798
- if (!vhtlcScript.claimScript)
4799
- throw new Error(`Swap ${pendingSwap.id}: failed to create VHTLC script for chain swap`);
4800
- if (pendingSwap.response.claimDetails.lockupAddress !== vhtlcAddress) {
4785
+ let vhtlcScript;
4786
+ let serverXOnlyPublicKey;
4787
+ try {
4788
+ ({ vhtlcScript, serverXOnlyPublicKey } = this.resolveVHTLCForLockup({
4789
+ arkInfo,
4790
+ preimageHash: import_base9.hex.decode(pendingSwap.request.preimageHash),
4791
+ senderPubkey: import_base9.hex.encode(senderXOnlyPublicKey),
4792
+ receiverPubkey: import_base9.hex.encode(receiverXOnlyPublicKey),
4793
+ timeoutBlockHeights: pendingSwap.response.claimDetails.timeouts,
4794
+ lockupAddress: pendingSwap.response.claimDetails.lockupAddress,
4795
+ swapId: pendingSwap.id
4796
+ }));
4797
+ } catch (error) {
4801
4798
  throw new SwapError({
4802
- message: "Unable to claim: invalid VHTLC address"
4799
+ message: error instanceof Error ? error.message : "Unable to claim: invalid VHTLC address"
4803
4800
  });
4804
4801
  }
4805
4802
  let vtxo;
@@ -5161,6 +5158,55 @@ var ArkadeSwaps = class _ArkadeSwaps {
5161
5158
  createVHTLCScript(args) {
5162
5159
  return createVHTLCScript(args);
5163
5160
  }
5161
+ /**
5162
+ * Reconstruct a swap's VHTLC by matching the persisted `lockupAddress`
5163
+ * against the current and deprecated server signers, returning the matched
5164
+ * script together with the server key it was minted under.
5165
+ *
5166
+ * Recovery paths (claim/refund/lookup) must use this instead of building the
5167
+ * VHTLC from the current signer alone: a swap created before a planned arkd
5168
+ * signer rotation is locked to a now-deprecated signer, so the current key
5169
+ * would yield the wrong address and strand the funds. The returned
5170
+ * `serverXOnlyPublicKey` is the original (possibly deprecated) key and MUST
5171
+ * be threaded into downstream signing/verification.
5172
+ *
5173
+ * Throws a descriptive mismatch error when no candidate reproduces the
5174
+ * lockup address (e.g. the swap predates an already-pruned deprecated
5175
+ * signer) — replacing the previous current-signer-only equality check.
5176
+ */
5177
+ resolveVHTLCForLockup(args) {
5178
+ const candidates = candidateServerPubkeys(args.arkInfo);
5179
+ for (const serverPubkey of candidates) {
5180
+ const { vhtlcScript, vhtlcAddress } = this.createVHTLCScript({
5181
+ network: args.arkInfo.network,
5182
+ preimageHash: args.preimageHash,
5183
+ receiverPubkey: args.receiverPubkey,
5184
+ senderPubkey: args.senderPubkey,
5185
+ serverPubkey,
5186
+ timeoutBlockHeights: args.timeoutBlockHeights
5187
+ });
5188
+ if (vhtlcAddress !== args.lockupAddress) continue;
5189
+ if (!vhtlcScript.claimScript && !vhtlcScript.refundScript) {
5190
+ throw new Error(
5191
+ `Swap ${args.swapId}: VHTLC address matched but claim/refund script leaves are empty`
5192
+ );
5193
+ }
5194
+ return {
5195
+ vhtlcScript,
5196
+ // The matched (possibly deprecated) key must flow into downstream
5197
+ // signing/verification: arkd signs a deprecated-signer input with
5198
+ // the deprecated key, so the claim/refund leaf checks use it.
5199
+ serverXOnlyPublicKey: normalizeToXOnlyKey(
5200
+ import_base9.hex.decode(serverPubkey),
5201
+ "server",
5202
+ args.swapId
5203
+ )
5204
+ };
5205
+ }
5206
+ throw new Error(
5207
+ `Swap ${args.swapId}: VHTLC address mismatch. Expected ${args.lockupAddress}; no current or deprecated server signer (${candidates.length} candidate(s) tried) reproduced it`
5208
+ );
5209
+ }
5164
5210
  async getFees(from, to) {
5165
5211
  if (from && to) {
5166
5212
  return this.swapProvider.getChainFees(from, to);
package/dist/index.d.cts CHANGED
@@ -1,5 +1,5 @@
1
- import { Q as QuoteSwapOptions, I as IArkadeSwaps, V as VhtlcTimeouts } from './arkade-swaps-DG9UepoS.cjs';
2
- export { A as ArkadeSwaps } from './arkade-swaps-DG9UepoS.cjs';
1
+ import { Q as QuoteSwapOptions, I as IArkadeSwaps, V as VhtlcTimeouts } from './arkade-swaps-CObmwpZQ.cjs';
2
+ export { A as ArkadeSwaps } from './arkade-swaps-CObmwpZQ.cjs';
3
3
  import { B as BoltzSwap, D as DecodedInvoice, a as BoltzChainSwap, b as BoltzReverseSwap, c as BoltzSubmarineSwap, A as ArkadeSwapsConfig, N as Network, C as CreateLightningInvoiceRequest, S as SendLightningPaymentRequest, F as FeesResponse, d as Chain, e as CreateLightningInvoiceResponse, f as SendLightningPaymentResponse, g as SubmarineRefundOutcome, h as SubmarineRecoveryInfo, i as SubmarineRecoveryResult, j as ChainFeesResponse, L as LimitsResponse, G as GetSwapStatusResponse, k as ArkToBtcResponse, l as BtcToArkResponse, m as ChainArkRefundOutcome, n as SwapRepository, o as SwapManagerClient, p as GetSwapsFilter } from './types-D97i1LFu.cjs';
4
4
  export { q as ArkadeSwapsCreateConfig, r as BoltzSwapProvider, s as BoltzSwapStatus, I as IncomingPaymentSubscription, P as PendingChainSwap, t as PendingReverseSwap, u as PendingSubmarineSwap, v as PendingSwap, w as SubmarineRecoveryStatus, x as SwapManager, y as SwapManagerCallbacks, z as SwapManagerConfig, E as SwapManagerEvents, V as Vtxo, H as isChainClaimableStatus, J as isChainFailedStatus, K as isChainFinalStatus, M as isChainPendingStatus, O as isChainRefundableStatus, Q as isChainSignableStatus, R as isChainSuccessStatus, T as isChainSwapClaimable, U as isChainSwapRefundable, W as isPendingChainSwap, X as isPendingReverseSwap, Y as isPendingSubmarineSwap, Z as isReverseClaimableStatus, _ as isReverseFailedStatus, $ as isReverseFinalStatus, a0 as isReversePendingStatus, a1 as isReverseSuccessStatus, a2 as isReverseSwapClaimable, a3 as isSubmarineFailedStatus, a4 as isSubmarineFinalStatus, a5 as isSubmarinePendingStatus, a6 as isSubmarineRefundableStatus, a7 as isSubmarineSuccessStatus, a8 as isSubmarineSwapRefundable } from './types-D97i1LFu.cjs';
5
5
  import { Transaction } from '@scure/btc-signer';
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { Q as QuoteSwapOptions, I as IArkadeSwaps, V as VhtlcTimeouts } from './arkade-swaps-Uet3tgN6.js';
2
- export { A as ArkadeSwaps } from './arkade-swaps-Uet3tgN6.js';
1
+ import { Q as QuoteSwapOptions, I as IArkadeSwaps, V as VhtlcTimeouts } from './arkade-swaps-DnRiRcdW.js';
2
+ export { A as ArkadeSwaps } from './arkade-swaps-DnRiRcdW.js';
3
3
  import { B as BoltzSwap, D as DecodedInvoice, a as BoltzChainSwap, b as BoltzReverseSwap, c as BoltzSubmarineSwap, A as ArkadeSwapsConfig, N as Network, C as CreateLightningInvoiceRequest, S as SendLightningPaymentRequest, F as FeesResponse, d as Chain, e as CreateLightningInvoiceResponse, f as SendLightningPaymentResponse, g as SubmarineRefundOutcome, h as SubmarineRecoveryInfo, i as SubmarineRecoveryResult, j as ChainFeesResponse, L as LimitsResponse, G as GetSwapStatusResponse, k as ArkToBtcResponse, l as BtcToArkResponse, m as ChainArkRefundOutcome, n as SwapRepository, o as SwapManagerClient, p as GetSwapsFilter } from './types-D97i1LFu.js';
4
4
  export { q as ArkadeSwapsCreateConfig, r as BoltzSwapProvider, s as BoltzSwapStatus, I as IncomingPaymentSubscription, P as PendingChainSwap, t as PendingReverseSwap, u as PendingSubmarineSwap, v as PendingSwap, w as SubmarineRecoveryStatus, x as SwapManager, y as SwapManagerCallbacks, z as SwapManagerConfig, E as SwapManagerEvents, V as Vtxo, H as isChainClaimableStatus, J as isChainFailedStatus, K as isChainFinalStatus, M as isChainPendingStatus, O as isChainRefundableStatus, Q as isChainSignableStatus, R as isChainSuccessStatus, T as isChainSwapClaimable, U as isChainSwapRefundable, W as isPendingChainSwap, X as isPendingReverseSwap, Y as isPendingSubmarineSwap, Z as isReverseClaimableStatus, _ as isReverseFailedStatus, $ as isReverseFinalStatus, a0 as isReversePendingStatus, a1 as isReverseSuccessStatus, a2 as isReverseSwapClaimable, a3 as isSubmarineFailedStatus, a4 as isSubmarineFinalStatus, a5 as isSubmarinePendingStatus, a6 as isSubmarineRefundableStatus, a7 as isSubmarineSuccessStatus, a8 as isSubmarineSwapRefundable } from './types-D97i1LFu.js';
5
5
  import { Transaction } from '@scure/btc-signer';
package/dist/index.js CHANGED
@@ -52,7 +52,7 @@ import {
52
52
  updateReverseSwapStatus,
53
53
  updateSubmarineSwapStatus,
54
54
  verifySignatures
55
- } from "./chunk-CFB2NNGT.js";
55
+ } from "./chunk-GYWMQOCP.js";
56
56
  import {
57
57
  applyCreatedAtOrder,
58
58
  applySwapsFilter
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arkade-os/boltz-swap",
3
- "version": "0.3.39",
3
+ "version": "0.3.40",
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",
@@ -76,7 +76,7 @@
76
76
  "@scure/btc-signer": "2.0.1",
77
77
  "bip68": "1.0.4",
78
78
  "light-bolt11-decoder": "3.2.0",
79
- "@arkade-os/sdk": "0.4.34"
79
+ "@arkade-os/sdk": "0.4.35"
80
80
  },
81
81
  "peerDependencies": {
82
82
  "expo-task-manager": ">=3.0.0",