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