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