@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.
@@ -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 = Number(decoded.sections.find((s) => s.name === "amount")?.value ?? "0");
995
+ const millisats = BigInt(decoded.sections.find((s) => s.name === "amount")?.value ?? "0");
980
996
  return {
981
997
  expiry: decoded.expiry ?? 3600,
982
- amountSats: Math.floor(millisats / 1e3),
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 = normalizeToXOnlyKey(
3427
- hex8.decode(arkInfo.signerPubkey),
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
- serverPubkey: hex8.encode(serverXOnly),
3437
- timeoutBlockHeights: vhtlcTimeouts
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 { vhtlcScript, vhtlcAddress } = this.createVHTLCScript({
3702
- network: resolvedArkInfo.network,
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
- serverPubkey: hex8.encode(serverXOnlyPublicKey),
3707
- timeoutBlockHeights: vhtlcTimeouts
3727
+ timeoutBlockHeights: vhtlcTimeouts,
3728
+ lockupAddress,
3729
+ swapId: swap.id
3708
3730
  });
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
- );
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 isResolved = false;
4182
+ let isFinal = false;
4183
+ let isSettled = false;
4133
4184
  const onStatusUpdate = async (status) => {
4134
- if (isResolved) return;
4135
- const saveStatus = (additionalFields) => updateSubmarineSwapStatus(
4136
- pendingSwap,
4137
- status,
4138
- this.savePendingSubmarineSwap.bind(this),
4139
- additionalFields
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
- isResolved = true;
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
- isResolved = true;
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
- isResolved = true;
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
- isResolved = true;
4174
- const { preimage } = await this.swapProvider.getSwapPreimage(
4175
- pendingSwap.id
4176
- );
4177
- await saveStatus({ preimage });
4178
- resolve({ preimage });
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, onStatusUpdate).catch((error) => {
4187
- if (!isResolved) {
4188
- isResolved = true;
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
- 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) {
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 claim: invalid VHTLC address"
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
- 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) {
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
- ({ status }) => updateSubmarineSwapStatus(
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
- ).catch((error) => {
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,