Skip to main content

bark/lightning/
receive.rs

1use std::str::FromStr;
2
3use anyhow::Context;
4use ark::arkoor::package::ArkoorPackageBuilder;
5use bitcoin::{Amount, SignedAmount};
6use bitcoin::hex::DisplayHex;
7use futures::StreamExt;
8use lightning_invoice::Bolt11Invoice;
9use log::{trace, debug, info, warn};
10
11use ark::{ProtocolEncoding, Vtxo, VtxoPolicy};
12use ark::attestations::{LightningReceiveAttestation};
13use ark::fees::validate_and_subtract_fee;
14use ark::lightning::{Bolt11InvoiceExt, PaymentHash, Preimage};
15use bitcoin_ext::{BlockDelta, BlockHeight};
16use server_rpc::protos;
17use server_rpc::protos::prepare_lightning_receive_claim_request::LightningReceiveAntiDos;
18
19use crate::subsystem::{LightningMovement, LightningReceiveMovement, Subsystem};
20use crate::{Wallet, error};
21use crate::movement::{MovementDestination, MovementStatus};
22use crate::movement::update::MovementUpdate;
23use crate::persist::models::LightningReceive;
24
25/// Leniency delta to allow claim when blocks were mined between htlc
26/// receive and claim preparation
27const LIGHTNING_PREPARE_CLAIM_DELTA: BlockDelta = 2;
28
29impl Wallet {
30	/// Fetches all pending lightning receives ordered from newest to oldest.
31	pub async fn pending_lightning_receives(&self) -> anyhow::Result<Vec<LightningReceive>> {
32		Ok(self.db.get_all_pending_lightning_receives().await?)
33	}
34
35	/// Calculates how much balance can currently be claimed via inbound lightning payments.
36	/// Invoices which have yet to be paid are not including in this.
37	pub async fn claimable_lightning_receive_balance(&self) -> anyhow::Result<Amount> {
38		let receives = self.pending_lightning_receives().await?;
39
40		let mut total = Amount::ZERO;
41		for receive in receives {
42			total += receive.htlc_vtxos.iter().map(|v| v.amount()).sum::<Amount>();
43		}
44
45		Ok(total)
46	}
47
48	/// Create, store and return a [Bolt11Invoice] for offchain boarding
49	pub async fn bolt11_invoice(&self, amount: Amount) -> anyhow::Result<Bolt11Invoice> {
50		if amount == Amount::ZERO {
51			bail!("Cannot create invoice for 0 sats (this would create an explicit 0 sat invoice, not an any-amount invoice)");
52		}
53
54		let (mut srv, ark_info) = self.require_server().await?;
55		let config = self.config();
56
57		// Calculate and validate lightning receive fees
58		let fee = ark_info.fees.lightning_receive.calculate(amount).context("fee overflowed")?;
59		validate_and_subtract_fee(amount, fee)?;
60
61		// User needs to enfore the following delta:
62		// - vtxo exit delta + htlc expiry delta (to give him time to exit the vtxo before htlc expires)
63		// - vtxo exit margin (to give him time to exit the vtxo before htlc expires)
64		// - htlc recv claim delta (to give him time to claim the htlc before it expires)
65		let requested_min_cltv_delta = ark_info.vtxo_exit_delta +
66			ark_info.htlc_expiry_delta +
67			config.vtxo_exit_margin +
68			config.htlc_recv_claim_delta +
69			LIGHTNING_PREPARE_CLAIM_DELTA;
70
71		if requested_min_cltv_delta > ark_info.max_user_invoice_cltv_delta {
72			bail!("HTLC CLTV delta ({}) is greater than Server's max HTLC recv CLTV delta: {}",
73				requested_min_cltv_delta,
74				ark_info.max_user_invoice_cltv_delta,
75			);
76		}
77
78		let preimage = Preimage::random();
79		let payment_hash = preimage.compute_payment_hash();
80		info!("Start bolt11 board with preimage / payment hash: {} / {}",
81			preimage.as_hex(), payment_hash.as_hex());
82
83		let mailbox_kp = self.seed.to_mailbox_keypair();
84		let mailbox_id = ark::mailbox::MailboxIdentifier::from_pubkey(mailbox_kp.public_key());
85
86		let req = protos::StartLightningReceiveRequest {
87			payment_hash: payment_hash.to_vec(),
88			amount_sat: amount.to_sat(),
89			min_cltv_delta: requested_min_cltv_delta as u32,
90			mailbox_id: Some(mailbox_id.to_vec()),
91		};
92
93		let resp = srv.client.start_lightning_receive(req).await?.into_inner();
94		info!("Ark Server is ready to receive LN payment to invoice: {}.", resp.bolt11);
95
96		let invoice = Bolt11Invoice::from_str(&resp.bolt11)
97			.context("invalid bolt11 invoice returned by Ark server")?;
98
99		self.db.store_lightning_receive(
100			payment_hash,
101			preimage,
102			&invoice,
103			requested_min_cltv_delta,
104		).await?;
105
106		Ok(invoice)
107	}
108
109	/// Fetches the status of a lightning receive for the given [PaymentHash].
110	pub async fn lightning_receive_status(
111		&self,
112		payment: impl Into<PaymentHash>,
113	) -> anyhow::Result<Option<LightningReceive>> {
114		Ok(self.db.fetch_lightning_receive_by_payment_hash(payment.into()).await?)
115	}
116
117	/// Claim given incoming lightning payment.
118	///
119	/// This function reveals the preimage of the lightning payment in
120	/// exchange of getting pubkey VTXOs from HTLC ones
121	///
122	/// # Returns
123	///
124	/// Returns an `anyhow::Result<()>`, which is:
125	/// * `Ok(())` if the process completes successfully.
126	///   the receive object is also updated correctly
127	/// * `Err` if an error occurs at any stage of the operation.
128	///
129	/// # Remarks
130	///
131	/// * The list of HTLC VTXOs must have the hash lock clause matching the given
132	///   [PaymentHash].
133	/// * The preimage is revealed to the server before the cosign response is
134	///   received. If the call fails after that point, the server's
135	///   `claim_lightning_receive` is idempotent so this method can be retried
136	///   to obtain fresh cosign signatures.
137	async fn claim_lightning_receive(
138		&self,
139		receive: &mut LightningReceive,
140	) -> anyhow::Result<()> {
141		let movement_id = receive.movement_id
142			.context("No movement created for lightning receive")?;
143		let (mut srv, _) = self.require_server().await?;
144
145		// order inputs by vtxoid before we generate nonces
146		let inputs = {
147			ensure!(!receive.htlc_vtxos.is_empty(), "no HTLC VTXOs set on record yet");
148			let mut ret = receive.htlc_vtxos.iter().map(|v| &v.vtxo).collect::<Vec<_>>();
149			ret.sort_by_key(|v| v.id());
150			ret
151		};
152
153		let mut keypairs = Vec::with_capacity(inputs.len());
154		for v in &inputs {
155			keypairs.push(self.get_vtxo_key(*v).await?);
156		}
157
158		// Claiming arkoor against preimage
159		let (claim_keypair, _) = self.derive_store_next_keypair().await?;
160		let receive_policy = VtxoPolicy::new_pubkey(claim_keypair.public_key());
161
162		trace!("ln arkoor builder params: inputs: {:?}; policy: {:?}",
163			inputs.iter().map(|v| v.id()).collect::<Vec<_>>(), receive_policy,
164		);
165		let builder = ArkoorPackageBuilder::new_claim_all_without_checkpoints(
166			inputs.iter().copied().cloned(),
167			receive_policy.clone(),
168		).context("creating claim arkoor builder failed")?;
169		let builder = builder.generate_user_nonces(&keypairs)
170			.context("arkoor nonce generation for claim failed")?;
171
172		info!("Claiming arkoor against payment preimage");
173		self.db.set_preimage_revealed(receive.payment_hash).await?;
174		let package_cosign_request = protos::ArkoorPackageCosignRequest::from(
175			builder.cosign_request(),
176		);
177		let resp = srv.client.claim_lightning_receive(protos::ClaimLightningReceiveRequest {
178			payment_hash: receive.payment_hash.to_byte_array().to_vec(),
179			payment_preimage: receive.payment_preimage.to_vec(),
180			cosign_request: Some(package_cosign_request),
181		}).await?.into_inner();
182		let cosign_resp = resp.try_into().context("invalid cosign response")?;
183
184		let outputs = builder.user_cosign(&keypairs, cosign_resp)
185			.context("claim arkoor cosign failed with user response")?
186			.build_signed_vtxos();
187
188		let mut effective_balance = Amount::ZERO;
189		for vtxo in &outputs {
190			// NB: bailing here results in vtxos not being registered despite the
191			// preimage being revealed.  The server's claim_lightning_receive is
192			// idempotent, so bark can retry and obtain fresh cosign signatures,
193			// but if all retries fail the user will be forced to exit on-chain.
194			trace!("Validating Lightning receive claim VTXO {}: {}",
195				vtxo.id(), vtxo.serialize_hex(),
196			);
197			self.validate_vtxo(vtxo).await
198				.context("invalid arkoor from lightning receive")?;
199			effective_balance += vtxo.amount();
200		}
201
202		self.store_spendable_vtxos(&outputs).await?;
203		self.mark_vtxos_as_spent(inputs).await?;
204
205		info!("Got arkoors from lightning: {}",
206			outputs.iter().map(|v| v.id().to_string()).collect::<Vec<_>>().join(", ")
207		);
208
209		self.movements.finish_movement_with_update(
210			movement_id,
211			MovementStatus::Successful,
212			MovementUpdate::new()
213				.effective_balance(effective_balance.to_signed()?)
214				.produced_vtxos(&outputs)
215		).await?;
216
217		self.db.finish_pending_lightning_receive(receive.payment_hash).await?;
218		*receive = self.db.fetch_lightning_receive_by_payment_hash(receive.payment_hash).await
219			.context("Database error")?
220			.context("Receive not found")?;
221
222		Ok(())
223	}
224
225	async fn compute_lightning_receive_anti_dos(
226		&self,
227		payment_hash: PaymentHash,
228		token: Option<&str>,
229	) -> anyhow::Result<LightningReceiveAntiDos> {
230		Ok(if let Some(token) = token {
231			LightningReceiveAntiDos::Token(token.to_string())
232		} else {
233			// We get an existing VTXO as an anti-dos measure.
234			let vtxo = self.select_vtxos_to_cover(Amount::ONE_SAT).await
235				.and_then(|vtxos| vtxos.into_iter().next()
236					.context("have no spendable vtxo to prove ownership of")
237				)?;
238			let vtxo_keypair = self.get_vtxo_key(&vtxo).await.expect("owned vtxo should be in database");
239			let attestation = LightningReceiveAttestation::new(payment_hash, vtxo.id(), &vtxo_keypair);
240			LightningReceiveAntiDos::InputVtxo(protos::InputVtxo {
241				vtxo_id: vtxo.id().to_bytes().to_vec(),
242				attestation: attestation.serialize(),
243			})
244		})
245	}
246
247	/// Check for incoming lightning payment with the given [PaymentHash].
248	///
249	/// This function checks for an incoming lightning payment with the
250	/// given [PaymentHash] and returns the HTLC VTXOs that are associated
251	/// with it.
252	///
253	/// # Arguments
254	///
255	/// * `payment_hash` - The [PaymentHash] of the lightning payment
256	/// to check for.
257	/// * `wait` - Whether to wait for the payment to be initiated by the sender.
258	/// * `token` - An optional lightning receive token used to authenticate a lightning
259	/// receive when no spendable VTXOs are owned by this wallet.
260	///
261	/// # Returns
262	///
263	/// Returns an `anyhow::Result<Option<LightningReceive>>`, which is:
264	/// * `Ok(Some(lightning_receive))` if the payment was initiated by
265	///   the sender and the HTLC VTXOs were successfully prepared.
266	/// * `Ok(None)` if the payment was not initiated by the sender or
267	///   the payment was canceled by server.
268	/// * `Err` if an error occurs at any stage of the operation.
269	///
270	/// # Remarks
271	///
272	/// * The invoice must contain an explicit amount specified in milli-satoshis.
273	/// * The HTLC expiry height is calculated by adding the servers' HTLC expiry delta to the
274	///   current chain tip.
275	/// * The payment hash must be from an invoice previously generated using
276	///   [Wallet::bolt11_invoice].
277	async fn check_lightning_receive(
278		&self,
279		payment_hash: PaymentHash,
280		wait: bool,
281		token: Option<&str>,
282	) -> anyhow::Result<Option<LightningReceive>> {
283		let (mut srv, ark_info) = self.require_server().await?;
284		let current_height = self.chain.tip().await?;
285
286		let mut receive = self.db.fetch_lightning_receive_by_payment_hash(payment_hash).await?
287			.context("no pending lightning receive found for payment hash, might already be claimed")?;
288
289		// If we have already HTLC VTXOs stored, we can return them without asking the server
290		if !receive.htlc_vtxos.is_empty() {
291			return Ok(Some(receive))
292		}
293
294		trace!("Requesting updates for ln-receive to server with for wait={} and hash={}", wait, payment_hash);
295		let sub = srv.client.check_lightning_receive(protos::CheckLightningReceiveRequest {
296			hash: payment_hash.to_byte_array().to_vec(), wait,
297		}).await?.into_inner();
298
299
300		let status = protos::LightningReceiveStatus::try_from(sub.status)
301			.with_context(|| format!("unknown payment status: {}", sub.status))?;
302
303		debug!("Received status {:?} for {}", status, payment_hash);
304		match status {
305			// this is the good case
306			protos::LightningReceiveStatus::Accepted |
307			protos::LightningReceiveStatus::HtlcsReady => {},
308			protos::LightningReceiveStatus::Created => {
309				return Ok(None);
310			},
311			protos::LightningReceiveStatus::Settled => bail!("payment already settled"),
312			protos::LightningReceiveStatus::Canceled => {
313				warn!("payment was canceled. removing pending lightning receive");
314				self.exit_or_cancel_lightning_receive(&receive).await?;
315				return Ok(None);
316			},
317		}
318
319		let lightning_receive_anti_dos = match self.compute_lightning_receive_anti_dos(
320			payment_hash, token,
321		).await {
322			Ok(anti_dos) => Some(anti_dos),
323			Err(e) => {
324				warn!("Could not compute anti-dos: {e:#}. Trying without");
325				None
326			},
327		};
328
329		let htlc_recv_expiry = current_height + receive.htlc_recv_cltv_delta as BlockHeight;
330
331		let (next_keypair, _) = self.derive_store_next_keypair().await?;
332		let req = protos::PrepareLightningReceiveClaimRequest {
333			payment_hash: receive.payment_hash.to_vec(),
334			user_pubkey: next_keypair.public_key().serialize().to_vec(),
335			htlc_recv_expiry,
336			lightning_receive_anti_dos,
337		};
338		let res = srv.client.prepare_lightning_receive_claim(req).await
339			.context("error preparing lightning receive claim")?.into_inner();
340		let vtxos = res.htlc_vtxos.into_iter()
341			.map(|b| Vtxo::deserialize(&b))
342			.collect::<Result<Vec<_>, _>>()
343			.context("invalid htlc vtxos from server")?;
344
345		// sanity check the vtxos
346		let mut htlc_amount = Amount::ZERO;
347		for vtxo in &vtxos {
348			trace!("Received HTLC VTXO {} from server: {}", vtxo.id(), vtxo.serialize_hex());
349			self.validate_vtxo(vtxo).await
350				.context("received invalid HTLC VTXO from server")?;
351			htlc_amount += vtxo.amount();
352
353			if let VtxoPolicy::ServerHtlcRecv(p) = vtxo.policy() {
354				if p.payment_hash != receive.payment_hash {
355					bail!("invalid payment hash on HTLC VTXOs received from server: {}",
356						p.payment_hash,
357					);
358				}
359				if p.user_pubkey != next_keypair.public_key() {
360					bail!("invalid pubkey on HTLC VTXOs received from server: {}", p.user_pubkey);
361				}
362				if p.htlc_expiry < htlc_recv_expiry {
363					bail!("HTLC VTXO expiry height is less than requested: Requested {}, received {}", htlc_recv_expiry, p.htlc_expiry);
364				}
365			} else {
366				bail!("invalid HTLC VTXO policy: {:?}", vtxo.policy());
367			}
368		}
369
370		// Check that the sum exceeds the invoice amount; we can't entirely trust the
371		// server-reported payment amount, so if there is a discrepancy, we should fall back to
372		// checking the invoice amount.
373		let invoice_amount = receive.invoice.get_final_amount(None)
374			.context("ln receive invoice should have amount")?;
375		let server_received_amount = res.receive.map(|r| Amount::from_sat(r.amount_sat));
376		let fee = {
377			let fee = server_received_amount
378				.and_then(|a| ark_info.fees.lightning_receive.calculate(a));
379			match (server_received_amount, fee) {
380				(Some(amount), Some(fee)) if htlc_amount + fee == amount => {
381					// If this is true then the server is telling the truth.
382					fee
383				},
384				_ => {
385					// We should verify against the invoice amount instead. Unfortunately, that
386					// means the fee value in the movement won't be entirely accurate, however, it's
387					// better to avoid rejecting payments when we have received enough to cover an
388					// invoice.
389					ark_info.fees.lightning_receive.calculate(invoice_amount)
390						.expect("we previously validated this")
391				}
392			}
393		};
394		let received = htlc_amount + fee;
395		ensure!(received >= invoice_amount,
396			"Server didn't return enough VTXOs to cover invoice amount"
397		);
398
399		let movement_id = if let Some(movement_id) = receive.movement_id {
400			movement_id
401		} else {
402			self.movements.new_movement_with_update(
403				Subsystem::LIGHTNING_RECEIVE,
404				LightningReceiveMovement::Receive.to_string(),
405				MovementUpdate::new()
406					.intended_balance(invoice_amount.to_signed()?)
407					.effective_balance(htlc_amount.to_signed()?)
408					.fee(fee)
409					.metadata(LightningMovement::metadata(
410						receive.payment_hash, &vtxos, Some(receive.payment_preimage),
411					))
412					.received_on(
413						[MovementDestination::new(receive.invoice.clone().into(), received)],
414					),
415			).await?
416		};
417		self.store_locked_vtxos(&vtxos, Some(movement_id)).await?;
418
419		let vtxo_ids = vtxos.iter().map(|v| v.id()).collect::<Vec<_>>();
420		self.db.update_lightning_receive(payment_hash, &vtxo_ids, movement_id).await?;
421
422		let mut wallet_vtxos = vec![];
423		for vtxo in vtxos {
424			let v =  self.db.get_wallet_vtxo(vtxo.id()).await?
425				.context("Failed to get wallet VTXO for lightning receive")?;
426			wallet_vtxos.push(v);
427		}
428
429		receive.htlc_vtxos = wallet_vtxos;
430		receive.movement_id = Some(movement_id);
431
432		Ok(Some(receive))
433	}
434
435	/// Exit HTLC-recv VTXOs when preimage has been disclosed but the claim failed.
436	///
437	/// NOTE: Calling this function will always result in the HTLC VTXO being exited
438	/// regardless of the presence of the `preimage_revealed_at` field of
439	/// the `lightning_receive` struct.
440	async fn exit_lightning_receive(
441		&self,
442		lightning_receive: &LightningReceive,
443	) -> anyhow::Result<()> {
444		ensure!(!lightning_receive.htlc_vtxos.is_empty(), "no HTLC VTXOs to exit");
445		let vtxos = lightning_receive.htlc_vtxos.iter().map(|v| &v.vtxo).collect::<Vec<_>>();
446
447		info!("Exiting HTLC VTXOs for lightning_receive with payment hash {}", lightning_receive.payment_hash);
448		self.exit.write().await.start_exit_for_vtxos(&vtxos).await?;
449
450		if let Some(movement_id) = lightning_receive.movement_id {
451			self.movements.finish_movement_with_update(
452				movement_id,
453				MovementStatus::Failed,
454				MovementUpdate::new().exited_vtxos(vtxos),
455			).await?;
456		} else {
457			error!("movement id is missing but we disclosed preimage: {}", lightning_receive.payment_hash);
458		}
459
460		self.db.finish_pending_lightning_receive(lightning_receive.payment_hash).await?;
461		Ok(())
462	}
463
464	pub(crate) async fn exit_or_cancel_lightning_receive(
465		&self,
466		lightning_receive: &LightningReceive,
467	) -> anyhow::Result<()> {
468		let vtxos = &lightning_receive.htlc_vtxos;
469
470		let update_opt = match (vtxos.is_empty(), lightning_receive.preimage_revealed_at) {
471			(false, Some(_)) => {
472				return self.exit_lightning_receive(lightning_receive).await;
473			}
474			(false, None) => {
475				warn!("HTLC-recv VTXOs are about to expire, but preimage has not been disclosed yet. Canceling");
476				self.mark_vtxos_as_spent(vtxos).await?;
477				if let Some(movement_id) = lightning_receive.movement_id {
478					Some((
479						movement_id,
480						MovementUpdate::new()
481							.effective_balance(SignedAmount::ZERO),
482						MovementStatus::Canceled,
483					))
484				} else {
485					error!("movement id is missing but we got HTLC vtxos: {}", lightning_receive.payment_hash);
486					None
487				}
488			}
489			(true, Some(_)) => {
490				error!("No HTLC vtxos set on ln receive but preimage has been disclosed. Canceling");
491				lightning_receive.movement_id.map(|id| (id,
492					MovementUpdate::new()
493						.effective_balance(SignedAmount::ZERO),
494					MovementStatus::Canceled,
495				))
496			}
497			(true, None) => None,
498		};
499
500		if let Some((movement_id, update, status)) = update_opt {
501			self.movements.finish_movement_with_update(movement_id, status, update).await?;
502		}
503
504		self.db.finish_pending_lightning_receive(lightning_receive.payment_hash).await?;
505
506		Ok(())
507	}
508
509	/// Check and claim a Lightning receive
510	///
511	/// This function checks for an incoming lightning payment with the given [PaymentHash]
512	/// and then claims the payment using returned HTLC VTXOs.
513	///
514	/// # Arguments
515	///
516	/// * `payment_hash` - The [PaymentHash] of the lightning payment
517	/// to check for.
518	/// * `wait` - Whether to wait for the payment to be received.
519	/// * `token` - An optional lightning receive token used to authenticate a lightning
520	/// receive when no spendable VTXOs are owned by this wallet.
521	///
522	/// # Returns
523	///
524	/// Returns an `anyhow::Result<LightningReceive>`, which is:
525	/// * `Ok(LightningReceive)` if the claim was completed or is awaiting HTLC VTXOs
526	/// * `Err` if an error occurs at any stage of the operation.
527	///
528	/// # Remarks
529	///
530	/// * The payment hash must be from an invoice previously generated using
531	///   [Wallet::bolt11_invoice].
532	pub async fn try_claim_lightning_receive(
533		&self,
534		payment_hash: PaymentHash,
535		wait: bool,
536		token: Option<&str>,
537	) -> anyhow::Result<LightningReceive> {
538		trace!("Claiming lightning receive for payment hash: {}", payment_hash);
539
540		// Try to mark this payment as in-flight to prevent concurrent claim attempts.
541		// This prevents race conditions where multiple concurrent calls could both
542		// attempt to check/claim the same receive, leading to duplicate operations.
543		{
544			let mut inflight = self.inflight_lightning_payments.lock().await;
545			if !inflight.insert(payment_hash) {
546				bail!("Receive operation already in progress for this payment");
547			}
548		}
549
550		let result = self.try_claim_lightning_receive_inner(payment_hash, wait, token).await;
551
552		// Always remove from inflight set when done
553		{
554			let mut inflight = self.inflight_lightning_payments.lock().await;
555			inflight.remove(&payment_hash);
556		}
557
558		result
559	}
560
561	/// Internal implementation of lightning receive claim after concurrency check.
562	async fn try_claim_lightning_receive_inner(
563		&self,
564		payment_hash: PaymentHash,
565		wait: bool,
566		token: Option<&str>,
567	) -> anyhow::Result<LightningReceive> {
568		// check_lightning_receive returns None if there is no incoming payment (yet)
569		// In that case we just return and don't try to claim
570		let mut receive = match self.check_lightning_receive(payment_hash, wait, token).await? {
571			Some(receive) => receive,
572			None => {
573				return self.db.fetch_lightning_receive_by_payment_hash(payment_hash).await?
574					.context("No receive for payment_hash")
575			}
576		};
577
578		if receive.finished_at.is_some() {
579			return Ok(receive);
580		}
581
582		// No need to claim anything if there
583		// are no htlcs yet
584		if receive.htlc_vtxos.is_empty() {
585			return Ok(receive);
586		}
587
588		match self.claim_lightning_receive(&mut receive).await {
589			Ok(()) => Ok(receive),
590			Err(e) => {
591				error!("Failed to claim htlcs for payment_hash: {}", receive.payment_hash);
592				// We're now in a our havoc era. Just exit. The server prepared our HTLCs,
593				// but then later refused to / couldn't allow our claim. This shoul be quite
594				// rare.
595				warn!("Exiting lightning receive VTXOs");
596				self.exit_lightning_receive(&receive).await?;
597				return Err(e)
598			}
599		}
600	}
601
602	/// Check and claim all opened Lightning receive
603	///
604	/// This function fetches all opened lightning receives and then
605	/// concurrently tries to check and claim them.
606	///
607	/// # Arguments
608	///
609	/// * `wait` - Whether to wait for each payment to be received.
610	///
611	/// # Returns
612	///
613	/// Returns an `anyhow::Result<()>`, which is:
614	/// * `Ok(Vec<LightningReceive>)` contains the successfully claimed receives, if at least one
615	/// claim succeeded, or an empty vector if no claim succeeded.
616	/// * `Err` if all claim attempts failed.
617	pub async fn try_claim_all_lightning_receives(&self, wait: bool) -> anyhow::Result<Vec<LightningReceive>> {
618		let pending = self.pending_lightning_receives().await?;
619		let total = pending.len();
620
621		if total == 0 {
622			return Ok(vec![]);
623		}
624
625		let results: Vec<_> = tokio_stream::iter(pending)
626			.map(|rcv| async move {
627				self.try_claim_lightning_receive(rcv.invoice.into(), wait, None).await
628			})
629			.buffer_unordered(3)
630			.collect()
631			.await;
632
633		let mut claimed = vec![];
634		let mut failed = 0;
635
636		for result in results {
637			match result {
638				Ok(receive) => claimed.push(receive),
639				Err(e) => {
640					error!("Error claiming lightning receive: {:#}", e);
641					failed += 1;
642				}
643			}
644		}
645
646		if failed > 0 {
647			info!(
648				"Lightning receive claims: {} succeeded, {} failed out of {} pending",
649				claimed.len(), failed, total
650			);
651		}
652
653		if claimed.is_empty() {
654			anyhow::bail!("All {} lightning receive claim(s) failed", failed);
655		}
656
657		Ok(claimed)
658	}
659}