lnurlp/
main.rs

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/// A tiny command line tool to fetch BOLT11 invoices from LNURLs
11#[derive(Parser, Debug)]
12#[command(author, version, about, long_about = None)]
13struct Args {
14    /// The LNURL to fetch the invoice from (can be LNURL or Lightning Address)
15    lnurl: String,
16
17    /// The amount in millisatoshis to request
18    #[arg(long, short)]
19    amount: Amount,
20
21    /// Optional comment to include with the payment
22    #[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
35/// Get LN invoice with given settings
36async fn get_invoice(
37    lnurl: &str,
38    amount: Amount,
39    lnurl_comment: Option<String>,
40) -> Result<Bolt11Invoice> {
41    let info = lnurl.trim();
42
43    // If not a BOLT11 invoice, try to parse as LNURL or Lightning Address
44    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}