1use std::str::FromStr;
2
3use anyhow::{Context, Result, bail};
4use clap::Parser;
5use fedimint_core::Amount;
6use lightning_invoice::Bolt11Invoice;
7use lnurl::lightning_address::LightningAddress;
8use lnurl::lnurl::LnUrl;
9
10#[derive(Parser, Debug)]
12#[command(author, version, about, long_about = None)]
13struct Args {
14 lnurl: String,
16
17 #[arg(long, short)]
19 amount: Amount,
20
21 #[arg(long, short)]
23 comment: Option<String>,
24}
25
26#[tokio::main]
27async fn main() -> Result<()> {
28 let args = Args::parse();
29 let invoice = get_invoice(&args.lnurl, args.amount, args.comment).await?;
30
31 println!("{}", invoice);
32 Ok(())
33}
34
35async fn get_invoice(
37 lnurl: &str,
38 amount: Amount,
39 lnurl_comment: Option<String>,
40) -> Result<Bolt11Invoice> {
41 let info = lnurl.trim();
42
43 let lnurl = if info.to_lowercase().starts_with("lnurl") {
45 LnUrl::from_str(info).context("Invalid LNURL format")?
46 } else if info.contains('@') {
47 LightningAddress::from_str(info)
48 .context("Invalid Lightning Address format")?
49 .lnurl()
50 } else {
51 bail!("Invalid invoice, LNURL, or Lightning Address");
52 };
53
54 let async_client = lnurl::AsyncClient::from_client(reqwest::Client::new());
55 let response = async_client.make_request(&lnurl.url).await?;
56
57 match response {
58 lnurl::LnUrlResponse::LnUrlPayResponse(response) => {
59 let invoice = async_client
60 .get_invoice(&response, amount.msats, None, lnurl_comment.as_deref())
61 .await?;
62
63 let invoice = Bolt11Invoice::from_str(invoice.invoice())
64 .context("LNURL server returned an invalid invoice")?;
65 let invoice_amount = invoice.amount_milli_satoshis();
66
67 if invoice_amount != Some(amount.msats) {
68 bail!(
69 "The amount generated by the LNURL ({invoice_amount:?}) is different from the requested amount ({amount}), try again using a different amount"
70 );
71 }
72
73 Ok(invoice)
74 }
75 other => {
76 bail!("Unexpected response from LNURL: {other:?}");
77 }
78 }
79}