@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.
@@ -239,6 +239,22 @@ var isSubmarineFinalStatus = (status) => {
239
239
  var isSubmarineRefundableStatus = (status) => {
240
240
  return ["invoice.failedToPay", "transaction.lockupFailed", "swap.expired"].includes(status);
241
241
  };
242
+ var SUBMARINE_STATUS_PROGRESSION = [
243
+ "swap.created",
244
+ "invoice.set",
245
+ "transaction.mempool",
246
+ "transaction.confirmed",
247
+ "invoice.pending",
248
+ "invoice.paid",
249
+ "transaction.claim.pending",
250
+ "transaction.claimed"
251
+ ];
252
+ var hasSubmarineStatusReached = (status, target) => {
253
+ const progression = SUBMARINE_STATUS_PROGRESSION;
254
+ const statusIndex = progression.indexOf(status);
255
+ const targetIndex = progression.indexOf(target);
256
+ return statusIndex >= 0 && targetIndex >= 0 && statusIndex >= targetIndex;
257
+ };
242
258
  var isSubmarineSuccessStatus = (status) => {
243
259
  return status === "transaction.claimed";
244
260
  };
@@ -1281,10 +1297,10 @@ var import_light_bolt11_decoder = __toESM(require("light-bolt11-decoder"), 1);
1281
1297
  var import_sdk2 = require("@arkade-os/sdk");
1282
1298
  var decodeInvoice = (invoice) => {
1283
1299
  const decoded = import_light_bolt11_decoder.default.decode(invoice);
1284
- const millisats = Number(decoded.sections.find((s) => s.name === "amount")?.value ?? "0");
1300
+ const millisats = BigInt(decoded.sections.find((s) => s.name === "amount")?.value ?? "0");
1285
1301
  return {
1286
1302
  expiry: decoded.expiry ?? 3600,
1287
- amountSats: Math.floor(millisats / 1e3),
1303
+ amountSats: Number(millisats / 1000n),
1288
1304
  description: decoded.sections.find((s) => s.name === "description")?.value ?? "",
1289
1305
  paymentHash: decoded.sections.find((s) => s.name === "payment_hash")?.value ?? ""
1290
1306
  };
@@ -2899,6 +2915,19 @@ var createVHTLCScript = (args) => {
2899
2915
  const vhtlcAddress = vhtlcScript.address(hrp, serverXOnlyPublicKey).encode();
2900
2916
  return { vhtlcScript, vhtlcAddress };
2901
2917
  };
2918
+ var candidateServerPubkeys = (arkInfo) => {
2919
+ const current = import_base8.hex.encode(normalizeToXOnlyKey(import_base8.hex.decode(arkInfo.signerPubkey), "server"));
2920
+ const seen = /* @__PURE__ */ new Set([current]);
2921
+ const candidates = [current];
2922
+ for (const deprecated of arkInfo.deprecatedSigners ?? []) {
2923
+ if (!deprecated.pubkey) continue;
2924
+ const key = import_base8.hex.encode(normalizeToXOnlyKey(import_base8.hex.decode(deprecated.pubkey), "server"));
2925
+ if (seen.has(key)) continue;
2926
+ seen.add(key);
2927
+ candidates.push(key);
2928
+ }
2929
+ return candidates;
2930
+ };
2902
2931
  var joinBatch = async (arkProvider, identity, input, output, {
2903
2932
  forfeitPubkey,
2904
2933
  forfeitAddress,
@@ -3313,6 +3342,10 @@ var ArkadeSwaps = class _ArkadeSwaps {
3313
3342
  ...args.description?.trim() ? { description: args.description.trim() } : {}
3314
3343
  };
3315
3344
  const swapResponse = await this.swapProvider.createReverseSwap(swapRequest);
3345
+ const decodedInvoice = decodeInvoice(swapResponse.invoice);
3346
+ if (decodedInvoice.paymentHash !== preimageHash) {
3347
+ throw new SwapError({ message: "Preimage hash does not match invoice payment hash" });
3348
+ }
3316
3349
  const pendingSwap = {
3317
3350
  id: swapResponse.id,
3318
3351
  type: "reverse",
@@ -3356,27 +3389,15 @@ var ArkadeSwaps = class _ArkadeSwaps {
3356
3389
  "boltz",
3357
3390
  pendingSwap.id
3358
3391
  );
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,
3392
+ const { vhtlcScript, serverXOnlyPublicKey: serverXOnly } = this.resolveVHTLCForLockup({
3393
+ arkInfo,
3366
3394
  preimageHash: (0, import_sha23.sha256)(preimage),
3367
3395
  receiverPubkey: import_base9.hex.encode(receiverXOnly),
3368
3396
  senderPubkey: import_base9.hex.encode(senderXOnly),
3369
- serverPubkey: import_base9.hex.encode(serverXOnly),
3370
- timeoutBlockHeights: vhtlcTimeouts
3397
+ timeoutBlockHeights: vhtlcTimeouts,
3398
+ lockupAddress,
3399
+ swapId: pendingSwap.id
3371
3400
  });
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
3401
  let unspentVtxos = [];
3381
3402
  let rawVtxos = [];
3382
3403
  for (let attempt = 1; attempt <= CLAIM_VTXO_RETRY_ATTEMPTS; attempt++) {
@@ -3527,16 +3548,6 @@ var ArkadeSwaps = class _ArkadeSwaps {
3527
3548
  this.swapProvider.monitorSwap(pendingSwap.id, onStatusUpdate).catch(reject);
3528
3549
  });
3529
3550
  }
3530
- // =========================================================================
3531
- // Lightning: Submarine swaps (send Arkade -> Lightning)
3532
- // =========================================================================
3533
- /**
3534
- * Sends a Lightning payment via a submarine swap (Arkade → Lightning).
3535
- * Creates the swap, sends funds, and waits for settlement. Auto-refunds on failure.
3536
- * @param args.invoice - BOLT11 Lightning invoice to pay.
3537
- * @returns The amount paid, preimage (proof of payment), and transaction ID.
3538
- * @throws {TransactionFailedError} If the payment fails (auto-refunds if possible).
3539
- */
3540
3551
  async sendLightningPayment(args) {
3541
3552
  const pendingSwap = await this.createSubmarineSwap(args);
3542
3553
  if (!pendingSwap.response.address)
@@ -3547,6 +3558,18 @@ var ArkadeSwaps = class _ArkadeSwaps {
3547
3558
  amount: pendingSwap.response.expectedAmount
3548
3559
  });
3549
3560
  try {
3561
+ if (args.waitFor === "funded") {
3562
+ if (!this.swapManager) {
3563
+ logger.warn(
3564
+ `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`
3565
+ );
3566
+ }
3567
+ await this.waitForSwapFunded(pendingSwap);
3568
+ return {
3569
+ amount: pendingSwap.response.expectedAmount,
3570
+ txid
3571
+ };
3572
+ }
3550
3573
  const { preimage } = await this.waitForSwapSettlement(pendingSwap);
3551
3574
  return {
3552
3575
  amount: pendingSwap.response.expectedAmount,
@@ -3618,11 +3641,6 @@ var ArkadeSwaps = class _ArkadeSwaps {
3618
3641
  "our",
3619
3642
  swap.id
3620
3643
  );
3621
- const serverXOnlyPublicKey = normalizeToXOnlyKey(
3622
- import_base9.hex.decode(resolvedArkInfo.signerPubkey),
3623
- "server",
3624
- swap.id
3625
- );
3626
3644
  const { claimPublicKey, timeoutBlockHeights: vhtlcTimeouts } = swap.response;
3627
3645
  if (!claimPublicKey || !vhtlcTimeouts)
3628
3646
  throw new Error(`Swap ${swap.id}: incomplete submarine swap response`);
@@ -3631,20 +3649,19 @@ var ArkadeSwaps = class _ArkadeSwaps {
3631
3649
  "boltz",
3632
3650
  swap.id
3633
3651
  );
3634
- const { vhtlcScript, vhtlcAddress } = this.createVHTLCScript({
3635
- network: resolvedArkInfo.network,
3652
+ const lockupAddress = swap.response.address;
3653
+ if (!lockupAddress)
3654
+ throw new Error(`Swap ${swap.id}: missing lockup address in submarine swap response`);
3655
+ const { vhtlcScript, serverXOnlyPublicKey } = this.resolveVHTLCForLockup({
3656
+ arkInfo: resolvedArkInfo,
3636
3657
  preimageHash: import_base9.hex.decode(preimageHash),
3637
3658
  receiverPubkey: import_base9.hex.encode(boltzXOnlyPublicKey),
3638
3659
  senderPubkey: import_base9.hex.encode(ourXOnlyPublicKey),
3639
- serverPubkey: import_base9.hex.encode(serverXOnlyPublicKey),
3640
- timeoutBlockHeights: vhtlcTimeouts
3660
+ timeoutBlockHeights: vhtlcTimeouts,
3661
+ lockupAddress,
3662
+ swapId: swap.id
3641
3663
  });
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
- );
3664
+ const vhtlcAddress = lockupAddress;
3648
3665
  const vhtlcPkScriptHex = import_base9.hex.encode(vhtlcScript.pkScript);
3649
3666
  return {
3650
3667
  arkInfo: resolvedArkInfo,
@@ -4054,6 +4071,9 @@ var ArkadeSwaps = class _ArkadeSwaps {
4054
4071
  }
4055
4072
  /**
4056
4073
  * Waits for a submarine swap's Lightning payment to settle.
4074
+ * Resolves only at the terminal "transaction.claimed" status, once Boltz
4075
+ * has swept the HTLC. To resolve as soon as the payment is in flight, use
4076
+ * {@link waitForSwapFunded} instead.
4057
4077
  * @param pendingSwap - The submarine swap to monitor.
4058
4078
  * @returns The preimage from the settled Lightning payment (proof of payment).
4059
4079
  * @throws {SwapExpiredError} If the swap expires.
@@ -4061,19 +4081,59 @@ var ArkadeSwaps = class _ArkadeSwaps {
4061
4081
  * @throws {TransactionLockupFailedError} If the lockup transaction fails.
4062
4082
  */
4063
4083
  async waitForSwapSettlement(pendingSwap) {
4084
+ return this.waitForSubmarineSwap(pendingSwap);
4085
+ }
4086
+ /**
4087
+ * Waits until a submarine swap is funded: resolves as soon as the lockup
4088
+ * transaction is observed ("transaction.mempool" or any later status in
4089
+ * the lifecycle — statuses can be skipped since subscriptions report only
4090
+ * the current one). The sender's funds are committed and the swap is
4091
+ * refundable from this point, which is when most Lightning wallets show
4092
+ * a payment as "sent".
4093
+ *
4094
+ * Monitoring continues in the background until the swap reaches a
4095
+ * terminal status, persisting updates to the repository (the preimage on
4096
+ * claim, the refundable flag on failure), but this promise no longer
4097
+ * rejects once resolved — acting on a late failure is the caller's
4098
+ * responsibility (the SwapManager handles it automatically when enabled).
4099
+ * @param pendingSwap - The submarine swap to monitor.
4100
+ * @throws {SwapExpiredError} If the swap expires before funding.
4101
+ * @throws {InvoiceFailedToPayError} If Boltz fails to route the payment.
4102
+ * @throws {TransactionLockupFailedError} If the lockup transaction fails.
4103
+ */
4104
+ async waitForSwapFunded(pendingSwap) {
4105
+ await this.waitForSubmarineSwap(pendingSwap, "transaction.mempool");
4106
+ }
4107
+ /**
4108
+ * Shared wait machinery: monitors the swap and resolves at the terminal
4109
+ * "transaction.claimed" status (with the preimage) or, when `resolveAt`
4110
+ * is given, as soon as that status — or any later one in the successful
4111
+ * progression — is observed (without a preimage).
4112
+ */
4113
+ async waitForSubmarineSwap(pendingSwap, resolveAt) {
4064
4114
  return new Promise((resolve, reject) => {
4065
- let isResolved = false;
4115
+ let isFinal = false;
4116
+ let isSettled = false;
4066
4117
  const onStatusUpdate = async (status) => {
4067
- if (isResolved) return;
4068
- const saveStatus = (additionalFields) => updateSubmarineSwapStatus(
4069
- pendingSwap,
4070
- status,
4071
- this.savePendingSubmarineSwap.bind(this),
4072
- additionalFields
4073
- );
4118
+ if (isFinal) return;
4119
+ const saveStatus = async (additionalFields) => {
4120
+ try {
4121
+ await updateSubmarineSwapStatus(
4122
+ pendingSwap,
4123
+ status,
4124
+ this.savePendingSubmarineSwap.bind(this),
4125
+ additionalFields
4126
+ );
4127
+ } catch (error) {
4128
+ logger.error(
4129
+ `Swap ${pendingSwap.id}: failed to persist status "${status}": ${error}`
4130
+ );
4131
+ }
4132
+ };
4074
4133
  switch (status) {
4075
4134
  case "swap.expired":
4076
- isResolved = true;
4135
+ isFinal = true;
4136
+ isSettled = true;
4077
4137
  await saveStatus({ refundable: true });
4078
4138
  reject(
4079
4139
  new SwapExpiredError({
@@ -4083,7 +4143,8 @@ var ArkadeSwaps = class _ArkadeSwaps {
4083
4143
  );
4084
4144
  break;
4085
4145
  case "invoice.failedToPay":
4086
- isResolved = true;
4146
+ isFinal = true;
4147
+ isSettled = true;
4087
4148
  await saveStatus({ refundable: true });
4088
4149
  reject(
4089
4150
  new InvoiceFailedToPayError({
@@ -4093,7 +4154,8 @@ var ArkadeSwaps = class _ArkadeSwaps {
4093
4154
  );
4094
4155
  break;
4095
4156
  case "transaction.lockupFailed":
4096
- isResolved = true;
4157
+ isFinal = true;
4158
+ isSettled = true;
4097
4159
  await saveStatus({ refundable: true });
4098
4160
  reject(
4099
4161
  new TransactionLockupFailedError({
@@ -4103,23 +4165,47 @@ var ArkadeSwaps = class _ArkadeSwaps {
4103
4165
  );
4104
4166
  break;
4105
4167
  case "transaction.claimed": {
4106
- isResolved = true;
4107
- const { preimage } = await this.swapProvider.getSwapPreimage(
4108
- pendingSwap.id
4109
- );
4110
- await saveStatus({ preimage });
4111
- resolve({ preimage });
4168
+ isFinal = true;
4169
+ isSettled = true;
4170
+ try {
4171
+ const { preimage } = await this.swapProvider.getSwapPreimage(
4172
+ pendingSwap.id
4173
+ );
4174
+ await saveStatus({ preimage });
4175
+ resolve({ preimage });
4176
+ } catch (error) {
4177
+ logger.error(
4178
+ `Swap ${pendingSwap.id}: failed to fetch preimage on claim: ${error}`
4179
+ );
4180
+ reject(error);
4181
+ }
4112
4182
  break;
4113
4183
  }
4114
4184
  default:
4115
4185
  await saveStatus();
4186
+ if (resolveAt && hasSubmarineStatusReached(status, resolveAt)) {
4187
+ isSettled = true;
4188
+ resolve({ preimage: void 0 });
4189
+ }
4116
4190
  break;
4117
4191
  }
4118
4192
  };
4119
- this.swapProvider.monitorSwap(pendingSwap.id, onStatusUpdate).catch((error) => {
4120
- if (!isResolved) {
4121
- isResolved = true;
4193
+ this.swapProvider.monitorSwap(pendingSwap.id, (status) => {
4194
+ onStatusUpdate(status).catch(
4195
+ (error) => logger.error(
4196
+ `Swap ${pendingSwap.id}: error handling status "${status}": ${error}`
4197
+ )
4198
+ );
4199
+ }).catch((error) => {
4200
+ if (!isSettled) {
4201
+ isFinal = true;
4202
+ isSettled = true;
4122
4203
  reject(error);
4204
+ } else {
4205
+ isFinal = true;
4206
+ logger.warn(
4207
+ `Swap ${pendingSwap.id}: monitor failed after settlement: ${error}`
4208
+ );
4123
4209
  }
4124
4210
  });
4125
4211
  });
@@ -4357,29 +4443,26 @@ var ArkadeSwaps = class _ArkadeSwaps {
4357
4443
  "user",
4358
4444
  pendingSwap.id
4359
4445
  );
4360
- const serverXOnlyPublicKey = normalizeToXOnlyKey(
4361
- import_base9.hex.decode(arkInfo.signerPubkey),
4362
- "server",
4363
- pendingSwap.id
4364
- );
4365
4446
  const boltzXOnlyPublicKey = normalizeToXOnlyKey(
4366
4447
  import_base9.hex.decode(pendingSwap.response.lockupDetails.serverPublicKey),
4367
4448
  "boltz",
4368
4449
  pendingSwap.id
4369
4450
  );
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) {
4451
+ let vhtlcScript;
4452
+ let serverXOnlyPublicKey;
4453
+ try {
4454
+ ({ vhtlcScript, serverXOnlyPublicKey } = this.resolveVHTLCForLockup({
4455
+ arkInfo,
4456
+ preimageHash: import_base9.hex.decode(pendingSwap.request.preimageHash),
4457
+ senderPubkey: import_base9.hex.encode(ourXOnlyPublicKey),
4458
+ receiverPubkey: import_base9.hex.encode(boltzXOnlyPublicKey),
4459
+ timeoutBlockHeights: pendingSwap.response.lockupDetails.timeouts,
4460
+ lockupAddress: pendingSwap.response.lockupDetails.lockupAddress,
4461
+ swapId: pendingSwap.id
4462
+ }));
4463
+ } catch (error) {
4381
4464
  throw new SwapError({
4382
- message: "Unable to claim: invalid VHTLC address"
4465
+ message: error instanceof Error ? error.message : "Unable to refund: invalid VHTLC address"
4383
4466
  });
4384
4467
  }
4385
4468
  const { vtxos } = await this.indexerProvider.getVtxos({
@@ -4638,20 +4721,21 @@ var ArkadeSwaps = class _ArkadeSwaps {
4638
4721
  pendingSwap.response.claimDetails.serverPublicKey,
4639
4722
  "sender"
4640
4723
  );
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) {
4724
+ let vhtlcScript;
4725
+ let serverXOnlyPublicKey;
4726
+ try {
4727
+ ({ vhtlcScript, serverXOnlyPublicKey } = this.resolveVHTLCForLockup({
4728
+ arkInfo,
4729
+ preimageHash: import_base9.hex.decode(pendingSwap.request.preimageHash),
4730
+ senderPubkey: import_base9.hex.encode(senderXOnlyPublicKey),
4731
+ receiverPubkey: import_base9.hex.encode(receiverXOnlyPublicKey),
4732
+ timeoutBlockHeights: pendingSwap.response.claimDetails.timeouts,
4733
+ lockupAddress: pendingSwap.response.claimDetails.lockupAddress,
4734
+ swapId: pendingSwap.id
4735
+ }));
4736
+ } catch (error) {
4653
4737
  throw new SwapError({
4654
- message: "Unable to claim: invalid VHTLC address"
4738
+ message: error instanceof Error ? error.message : "Unable to claim: invalid VHTLC address"
4655
4739
  });
4656
4740
  }
4657
4741
  let vtxo;
@@ -5013,6 +5097,55 @@ var ArkadeSwaps = class _ArkadeSwaps {
5013
5097
  createVHTLCScript(args) {
5014
5098
  return createVHTLCScript(args);
5015
5099
  }
5100
+ /**
5101
+ * Reconstruct a swap's VHTLC by matching the persisted `lockupAddress`
5102
+ * against the current and deprecated server signers, returning the matched
5103
+ * script together with the server key it was minted under.
5104
+ *
5105
+ * Recovery paths (claim/refund/lookup) must use this instead of building the
5106
+ * VHTLC from the current signer alone: a swap created before a planned arkd
5107
+ * signer rotation is locked to a now-deprecated signer, so the current key
5108
+ * would yield the wrong address and strand the funds. The returned
5109
+ * `serverXOnlyPublicKey` is the original (possibly deprecated) key and MUST
5110
+ * be threaded into downstream signing/verification.
5111
+ *
5112
+ * Throws a descriptive mismatch error when no candidate reproduces the
5113
+ * lockup address (e.g. the swap predates an already-pruned deprecated
5114
+ * signer) — replacing the previous current-signer-only equality check.
5115
+ */
5116
+ resolveVHTLCForLockup(args) {
5117
+ const candidates = candidateServerPubkeys(args.arkInfo);
5118
+ for (const serverPubkey of candidates) {
5119
+ const { vhtlcScript, vhtlcAddress } = this.createVHTLCScript({
5120
+ network: args.arkInfo.network,
5121
+ preimageHash: args.preimageHash,
5122
+ receiverPubkey: args.receiverPubkey,
5123
+ senderPubkey: args.senderPubkey,
5124
+ serverPubkey,
5125
+ timeoutBlockHeights: args.timeoutBlockHeights
5126
+ });
5127
+ if (vhtlcAddress !== args.lockupAddress) continue;
5128
+ if (!vhtlcScript.claimScript && !vhtlcScript.refundScript) {
5129
+ throw new Error(
5130
+ `Swap ${args.swapId}: VHTLC address matched but claim/refund script leaves are empty`
5131
+ );
5132
+ }
5133
+ return {
5134
+ vhtlcScript,
5135
+ // The matched (possibly deprecated) key must flow into downstream
5136
+ // signing/verification: arkd signs a deprecated-signer input with
5137
+ // the deprecated key, so the claim/refund leaf checks use it.
5138
+ serverXOnlyPublicKey: normalizeToXOnlyKey(
5139
+ import_base9.hex.decode(serverPubkey),
5140
+ "server",
5141
+ args.swapId
5142
+ )
5143
+ };
5144
+ }
5145
+ throw new Error(
5146
+ `Swap ${args.swapId}: VHTLC address mismatch. Expected ${args.lockupAddress}; no current or deprecated server signer (${candidates.length} candidate(s) tried) reproduced it`
5147
+ );
5148
+ }
5016
5149
  async getFees(from, to) {
5017
5150
  if (from && to) {
5018
5151
  return this.swapProvider.getChainFees(from, to);
@@ -5096,13 +5229,28 @@ var ArkadeSwaps = class _ArkadeSwaps {
5096
5229
  for (const swap of await this.getPendingSubmarineSwapsFromStorage()) {
5097
5230
  if (isSubmarineFinalStatus(swap.status)) continue;
5098
5231
  promises.push(
5099
- this.getSwapStatus(swap.id).then(
5100
- ({ status }) => updateSubmarineSwapStatus(
5232
+ this.getSwapStatus(swap.id).then(async ({ status }) => {
5233
+ let additionalFields;
5234
+ if (isSubmarineSuccessStatus(status) && !swap.preimage) {
5235
+ try {
5236
+ const { preimage } = await this.swapProvider.getSwapPreimage(
5237
+ swap.id
5238
+ );
5239
+ additionalFields = { preimage };
5240
+ } catch (error) {
5241
+ logger.warn(
5242
+ `Failed to fetch preimage for settled swap ${swap.id}:`,
5243
+ error
5244
+ );
5245
+ }
5246
+ }
5247
+ await updateSubmarineSwapStatus(
5101
5248
  swap,
5102
5249
  status,
5103
- this.savePendingSubmarineSwap.bind(this)
5104
- )
5105
- ).catch((error) => {
5250
+ this.savePendingSubmarineSwap.bind(this),
5251
+ additionalFields
5252
+ );
5253
+ }).catch((error) => {
5106
5254
  logger.error(`Failed to refresh swap status for ${swap.id}:`, error);
5107
5255
  })
5108
5256
  );
@@ -5412,6 +5560,9 @@ var ExpoArkadeSwaps = class _ExpoArkadeSwaps {
5412
5560
  waitForSwapSettlement(pendingSwap) {
5413
5561
  return this.inner.waitForSwapSettlement(pendingSwap);
5414
5562
  }
5563
+ waitForSwapFunded(pendingSwap) {
5564
+ return this.inner.waitForSwapFunded(pendingSwap);
5565
+ }
5415
5566
  restoreSwaps(boltzFees) {
5416
5567
  return this.inner.restoreSwaps(boltzFees);
5417
5568
  }
@@ -1,7 +1,7 @@
1
- import { I as IArkadeSwaps, A as ArkadeSwaps, Q as QuoteSwapOptions, V as VhtlcTimeouts } from '../arkade-swaps-DG9UepoS.cjs';
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
- import { E as ExpoArkadeSwapsConfig } from '../swapsPollProcessor-BlyUrhtO.cjs';
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
+ import { I as IArkadeSwaps, A as ArkadeSwaps, Q as QuoteSwapOptions, V as VhtlcTimeouts } from '../arkade-swaps-C3sUFr5f.cjs';
2
+ import { n as SwapManagerClient, C as CreateLightningInvoiceRequest, e as CreateLightningInvoiceResponse, S as SendLightningPaymentRequest, o as SendLightningPaymentResponse, O as OptimisticSendLightningPaymentResponse, c as BoltzSubmarineSwap, b as BoltzReverseSwap, f as SubmarineRefundOutcome, g as SubmarineRecoveryInfo, h as SubmarineRecoveryResult, F as FeesResponse, a as BoltzChainSwap, j as ArkToBtcResponse, l as ChainArkRefundOutcome, k as BtcToArkResponse, d as Chain, i as ChainFeesResponse, L as LimitsResponse, G as GetSwapStatusResponse, B as BoltzSwap } from '../types-8NrCdOpS.cjs';
3
+ import { E as ExpoArkadeSwapsConfig } from '../swapsPollProcessor-CGMXUKPe.cjs';
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-CGMXUKPe.cjs';
5
5
  import { ArkInfo, Identity, ArkTxInput, VHTLC } from '@arkade-os/sdk';
6
6
  import { TransactionOutput } from '@scure/btc-signer/psbt.js';
7
7
  import '@arkade-os/sdk/worker/expo';
@@ -84,7 +84,10 @@ declare class ExpoArkadeSwaps implements IArkadeSwaps {
84
84
  stopSwapManager(): Promise<void>;
85
85
  getSwapManager(): SwapManagerClient | null;
86
86
  createLightningInvoice(args: CreateLightningInvoiceRequest): Promise<CreateLightningInvoiceResponse>;
87
- sendLightningPayment(args: SendLightningPaymentRequest): Promise<SendLightningPaymentResponse>;
87
+ sendLightningPayment(args: SendLightningPaymentRequest & {
88
+ waitFor?: "settled";
89
+ }): Promise<SendLightningPaymentResponse>;
90
+ sendLightningPayment(args: SendLightningPaymentRequest): Promise<OptimisticSendLightningPaymentResponse>;
88
91
  createSubmarineSwap(args: SendLightningPaymentRequest): Promise<BoltzSubmarineSwap>;
89
92
  createReverseSwap(args: CreateLightningInvoiceRequest): Promise<BoltzReverseSwap>;
90
93
  claimVHTLC(pendingSwap: BoltzReverseSwap): Promise<void>;
@@ -99,6 +102,7 @@ declare class ExpoArkadeSwaps implements IArkadeSwaps {
99
102
  waitForSwapSettlement(pendingSwap: BoltzSubmarineSwap): Promise<{
100
103
  preimage: string;
101
104
  }>;
105
+ waitForSwapFunded(pendingSwap: BoltzSubmarineSwap): Promise<void>;
102
106
  restoreSwaps(boltzFees?: FeesResponse): Promise<{
103
107
  chainSwaps: BoltzChainSwap[];
104
108
  reverseSwaps: BoltzReverseSwap[];
@@ -1,7 +1,7 @@
1
- import { I as IArkadeSwaps, A as ArkadeSwaps, Q as QuoteSwapOptions, V as VhtlcTimeouts } from '../arkade-swaps-Uet3tgN6.js';
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
- import { E as ExpoArkadeSwapsConfig } from '../swapsPollProcessor-Bv4Z2R7g.js';
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
+ import { I as IArkadeSwaps, A as ArkadeSwaps, Q as QuoteSwapOptions, V as VhtlcTimeouts } from '../arkade-swaps-LvsGHtre.js';
2
+ import { n as SwapManagerClient, C as CreateLightningInvoiceRequest, e as CreateLightningInvoiceResponse, S as SendLightningPaymentRequest, o as SendLightningPaymentResponse, O as OptimisticSendLightningPaymentResponse, c as BoltzSubmarineSwap, b as BoltzReverseSwap, f as SubmarineRefundOutcome, g as SubmarineRecoveryInfo, h as SubmarineRecoveryResult, F as FeesResponse, a as BoltzChainSwap, j as ArkToBtcResponse, l as ChainArkRefundOutcome, k as BtcToArkResponse, d as Chain, i as ChainFeesResponse, L as LimitsResponse, G as GetSwapStatusResponse, B as BoltzSwap } from '../types-8NrCdOpS.js';
3
+ import { E as ExpoArkadeSwapsConfig } from '../swapsPollProcessor-CuDM6sxV.js';
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-CuDM6sxV.js';
5
5
  import { ArkInfo, Identity, ArkTxInput, VHTLC } from '@arkade-os/sdk';
6
6
  import { TransactionOutput } from '@scure/btc-signer/psbt.js';
7
7
  import '@arkade-os/sdk/worker/expo';
@@ -84,7 +84,10 @@ declare class ExpoArkadeSwaps implements IArkadeSwaps {
84
84
  stopSwapManager(): Promise<void>;
85
85
  getSwapManager(): SwapManagerClient | null;
86
86
  createLightningInvoice(args: CreateLightningInvoiceRequest): Promise<CreateLightningInvoiceResponse>;
87
- sendLightningPayment(args: SendLightningPaymentRequest): Promise<SendLightningPaymentResponse>;
87
+ sendLightningPayment(args: SendLightningPaymentRequest & {
88
+ waitFor?: "settled";
89
+ }): Promise<SendLightningPaymentResponse>;
90
+ sendLightningPayment(args: SendLightningPaymentRequest): Promise<OptimisticSendLightningPaymentResponse>;
88
91
  createSubmarineSwap(args: SendLightningPaymentRequest): Promise<BoltzSubmarineSwap>;
89
92
  createReverseSwap(args: CreateLightningInvoiceRequest): Promise<BoltzReverseSwap>;
90
93
  claimVHTLC(pendingSwap: BoltzReverseSwap): Promise<void>;
@@ -99,6 +102,7 @@ declare class ExpoArkadeSwaps implements IArkadeSwaps {
99
102
  waitForSwapSettlement(pendingSwap: BoltzSubmarineSwap): Promise<{
100
103
  preimage: string;
101
104
  }>;
105
+ waitForSwapFunded(pendingSwap: BoltzSubmarineSwap): Promise<void>;
102
106
  restoreSwaps(boltzFees?: FeesResponse): Promise<{
103
107
  chainSwaps: BoltzChainSwap[];
104
108
  reverseSwaps: BoltzReverseSwap[];
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  SWAP_POLL_TASK_TYPE
3
- } from "../chunk-DNCIVDU5.js";
3
+ } from "../chunk-CWY37W4B.js";
4
4
  import {
5
5
  ArkadeSwaps
6
- } from "../chunk-CFB2NNGT.js";
6
+ } from "../chunk-UXYHW7KV.js";
7
7
  import "../chunk-SJQJQO7P.js";
8
8
 
9
9
  // src/expo/arkade-lightning.ts
@@ -162,6 +162,9 @@ var ExpoArkadeSwaps = class _ExpoArkadeSwaps {
162
162
  waitForSwapSettlement(pendingSwap) {
163
163
  return this.inner.waitForSwapSettlement(pendingSwap);
164
164
  }
165
+ waitForSwapFunded(pendingSwap) {
166
+ return this.inner.waitForSwapFunded(pendingSwap);
167
+ }
165
168
  restoreSwaps(boltzFees) {
166
169
  return this.inner.restoreSwaps(boltzFees);
167
170
  }