1use std::fmt;
2
3use anyhow::Context;
4use bitcoin::Amount;
5use lightning::util::ser::Writeable;
6use lnurllib::lightning_address::LightningAddress;
7use log::{info, warn};
8use server_rpc::protos;
9
10use ark::lightning::{Bolt12Invoice, Bolt12InvoiceExt, Invoice, Offer, PaymentHash, Preimage};
11
12use crate::Wallet;
13use crate::WalletVtxo;
14use crate::actions::DriveMode;
15use crate::actions::lightning::pay::ln_pay_action_id;
16use crate::actions::lightning::pay::{
17 Htlcs, LightningSend, LightningSendState, Progress, settle_lightning_send_payment,
18 start_lightning_send,
19};
20use crate::lightning::lnaddr_invoice;
21use crate::movement::PaymentMethod;
22
23impl Wallet {
24 pub async fn pending_lightning_sends(&self) -> anyhow::Result<Vec<LightningSend>> {
26 let mut result = Vec::new();
27 for cp in self.inner.db.get_all_wallet_action_checkpoints().await? {
28 if let Some(ls) = cp.into_lightning_send() {
29 result.push(ls);
30 }
31 }
32 Ok(result)
33 }
34
35 pub async fn stuck_failed_lightning_sends(&self) -> anyhow::Result<Vec<LightningSend>> {
39 let mut result = Vec::new();
40 for send in self.pending_lightning_sends().await? {
41 if send.has_failed_revocation() {
42 result.push(send);
43 }
44 }
45 Ok(result)
46 }
47
48 pub async fn allow_lightning_send_to_exit(&self, hash: PaymentHash) -> anyhow::Result<()> {
56 let key = ln_pay_action_id(hash);
57 let _guard = self.inner.lock_manager.try_lock(&key).await
58 .context("Payment operation already in progress for this invoice")?;
59
60 let mut send = self.lightning_send_checkpoint(hash).await?
61 .with_context(|| format!("no in-progress lightning send for payment hash {hash}"))?;
62 send.allow_exit_of_htlcs = true;
63 self.inner.db.upsert_wallet_action_checkpoint(&send.id(), &send.into()).await?;
64 Ok(())
65 }
66
67 pub async fn pending_lightning_send_vtxos(&self) -> anyhow::Result<Vec<WalletVtxo>> {
69 let mut vtxos = Vec::new();
70 for send in self.pending_lightning_sends().await? {
71 let ids: Vec<_> = match &send.progress {
72 Progress::Start => send.input_vtxo_ids.clone(),
73 Progress::HtlcReceived(h) => h.vtxo_ids.clone(),
74 Progress::PaymentInitiated(h) => h.vtxo_ids.clone(),
75 Progress::RevocableHtlcs { htlcs, .. } => htlcs.vtxo_ids.clone(),
76 Progress::RevocationStuck { htlcs, .. } => htlcs.vtxo_ids.clone(),
77 };
78 for id in ids {
79 vtxos.push(self.get_vtxo_by_id(id).await?);
80 }
81 }
82 Ok(vtxos)
83 }
84
85 pub async fn sync_pending_lightning_send_vtxos(&self) -> anyhow::Result<()> {
89 let pending = self.pending_lightning_sends().await?;
90 if pending.is_empty() {
91 return Ok(());
92 }
93 info!("Syncing {} pending lightning sends", pending.len());
94 for send in pending {
95 let id = send.id();
96 if let Err(e) = self.drive_action(send, DriveMode::UntilParkOrDone).await {
97 warn!("Failed to sync lightning send {}: {:#}", id, e);
98 }
99 }
100 Ok(())
101 }
102
103 pub async fn lightning_send_checkpoint(&self, hash: PaymentHash)
105 -> anyhow::Result<Option<LightningSend>>
106 {
107 Ok(self.inner.db.get_wallet_action_checkpoint(&ln_pay_action_id(hash)).await?
108 .and_then(|cp| cp.into_lightning_send()))
109 }
110
111 pub async fn lightning_send_state(&self, hash: PaymentHash)
113 -> anyhow::Result<LightningSendState>
114 {
115 if let Some(paid) = self.inner.db.get_paid_invoice(hash).await? {
116 return Ok(LightningSendState::Paid(paid));
117 }
118 if let Some(cp) = self.lightning_send_checkpoint(hash).await? {
119 return Ok(LightningSendState::InProgress(cp));
120 }
121 Ok(LightningSendState::Unknown)
122 }
123
124 pub async fn is_invoice_paid(&self, hash: PaymentHash) -> anyhow::Result<bool> {
126 Ok(self.inner.db.get_paid_invoice(hash).await?.is_some())
127 }
128
129 pub async fn check_lightning_payment(&self, hash: PaymentHash, wait: bool)
133 -> anyhow::Result<LightningSendState>
134 {
135 let send = match self.lightning_send_state(hash).await? {
136 LightningSendState::InProgress(s) => s,
137 s => return Ok(s),
138 };
139
140 let mode = if wait { DriveMode::UntilDone } else { DriveMode::UntilParkOrDone };
141 self.drive_action(send, mode).await?;
142 self.lightning_send_state(hash).await
143 }
144
145 pub(crate) async fn settle_lightning_send_with_preimage(
148 &self,
149 send: LightningSend,
150 htlcs: Htlcs,
151 preimage: Preimage,
152 ) -> anyhow::Result<()> {
153 let payment_hash = send.invoice.payment_hash();
154 if preimage.compute_payment_hash() != payment_hash {
155 bail!("preimage mismatch for payment hash {}", payment_hash);
156 }
157 settle_lightning_send_payment(self, &send, &htlcs, preimage).await?;
158 self.inner.db.remove_wallet_action_checkpoint(&ln_pay_action_id(payment_hash)).await?;
161 Ok(())
162 }
163
164 pub async fn pay_lightning_invoice<T>(
172 &self,
173 invoice: T,
174 user_amount: Option<Amount>,
175 wait: bool,
176 ) -> anyhow::Result<Invoice>
177 where
178 T: TryInto<Invoice>,
179 T::Error: std::error::Error + fmt::Display + Send + Sync + 'static,
180 {
181 let invoice = invoice.try_into().context("failed to parse invoice")?;
182 let amount = invoice.get_payment_amount(user_amount)?;
183 info!("Sending bolt11 payment of {} to invoice {}", amount, invoice);
184 self.make_lightning_payment(&invoice, invoice.clone().into(), user_amount, wait).await?;
185 Ok(invoice)
186 }
187
188 pub async fn pay_lightning_address(
191 &self,
192 addr: &LightningAddress,
193 amount: Amount,
194 comment: Option<impl AsRef<str>>,
195 wait: bool,
196 ) -> anyhow::Result<Invoice> {
197 let comment = comment.as_ref();
198 let invoice: Invoice = lnaddr_invoice(addr, amount, comment).await
199 .context("lightning address error")?.into();
200 info!("Sending {} to lightning address {}", amount, addr);
201 self.make_lightning_payment(&invoice, addr.clone().into(), None, wait).await?;
202 info!("Paid invoice {}", invoice);
203 Ok(invoice)
204 }
205
206 pub async fn pay_lightning_offer(
208 &self,
209 offer: Offer,
210 user_amount: Option<Amount>,
211 wait: bool,
212 ) -> anyhow::Result<Invoice> {
213 let (mut srv, _) = self.require_server().await?;
214
215 let offer_bytes = {
216 let mut bytes = Vec::new();
217 offer.write(&mut bytes).context("failed to serialize BOLT12 offer")?;
218 bytes
219 };
220
221 let req = protos::FetchBolt12InvoiceRequest {
222 offer: offer_bytes,
223 amount_sat: user_amount.map(|a| a.to_sat()),
224 };
225
226 if let Some(amt) = user_amount {
227 info!("Sending bolt12 payment of {} (user amount) to offer {}", amt, offer);
228 } else if let Some(amt) = offer.amount() {
229 info!("Sending bolt12 payment of {:?} (invoice amount) to offer {}", amt, offer);
230 } else {
231 warn!("Paying offer without amount nor user amount provided: {}", offer);
232 }
233
234 let resp = srv.client.fetch_bolt12_invoice(req).await?.into_inner();
235 let invoice = Bolt12Invoice::try_from(resp.invoice)
236 .map_err(|e| anyhow!("invalid invoice: {:?}", e))?;
237
238 invoice.validate_issuance(&offer)
239 .context("invalid BOLT12 invoice received from offer")?;
240
241 let invoice: Invoice = invoice.into();
242 self.make_lightning_payment(&invoice, offer.into(), None, wait).await?;
243 info!("Paid invoice: {}", invoice);
244 Ok(invoice)
245 }
246
247 pub async fn make_lightning_payment(
250 &self,
251 invoice: &Invoice,
252 original_payment_method: PaymentMethod,
253 user_amount: Option<Amount>,
254 wait: bool,
255 ) -> anyhow::Result<()> {
256 if !original_payment_method.is_lightning() && !original_payment_method.is_custom() {
257 bail!("Invalid original payment method for lightning payment");
258 }
259
260 let payment_hash = invoice.payment_hash();
261 let mode = if wait { DriveMode::UntilDone } else { DriveMode::UntilParkOrDone };
262
263 if self.is_invoice_paid(payment_hash).await? {
264 bail!("Invoice has already been paid");
265 }
266
267 let key = ln_pay_action_id(payment_hash);
268 let guard = self.inner.lock_manager.try_lock(&key).await
269 .context("Payment operation already in progress for this invoice")?;
270
271 let action = match self.lightning_send_checkpoint(payment_hash).await? {
273 Some(existing) => existing,
274 None => {
275 let start = start_lightning_send(
276 self, invoice.clone(), user_amount, original_payment_method,
277 ).await?;
278
279 self.inner.db.upsert_wallet_action_checkpoint(
280 &start.id(), &start.clone().into()
281 ).await?;
282
283 start
284 },
285 };
286
287 self.drive_action_with_guard(action, mode, guard).await
288 }
289}