bright_lightning/
ln_address.rs

1use base64::prelude::*;
2use lightning_invoice::Bolt11Invoice;
3use serde::{Deserialize, Serialize};
4
5#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
6pub struct LnAddressPaymentRequest {
7    pub pr: String,
8}
9impl LnAddressPaymentRequest {
10    #[cfg(not(target_arch = "wasm32"))]
11    pub async fn new(
12        address: &LightningAddress,
13        millisatoshis: u64,
14        client: &reqwest::Client,
15    ) -> anyhow::Result<Self> {
16        let confirmation = LnAddressConfirmation::new(address, client).await?;
17        tracing::info!("Confirmation: {:?}", confirmation);
18        if millisatoshis < confirmation.min_sendable {
19            return Err(anyhow::anyhow!("Amount too low"));
20        }
21        let pr_url = format!("{}?amount={}", confirmation.callback, millisatoshis);
22        let pay_request_fetch = client.get(&pr_url).send().await?.text().await?;
23        tracing::debug!("Pay request: {}", pay_request_fetch);
24        Self::try_from(pay_request_fetch)
25    }
26    pub fn r_hash(&self) -> anyhow::Result<String> {
27        let r_hash_b = self
28            .pr
29            .parse::<Bolt11Invoice>()
30            .map_err(|e| anyhow::anyhow!(e.to_string()))?;
31        let r_hash = BASE64_STANDARD.encode(r_hash_b.payment_hash());
32        Ok(r_hash)
33    }
34    pub fn r_hash_url_safe(&self) -> anyhow::Result<String> {
35        let r_hash = self
36            .pr
37            .parse::<Bolt11Invoice>()
38            .map_err(|e| anyhow::anyhow!(e.to_string()))?;
39        let url_safe = BASE64_URL_SAFE.encode(r_hash.payment_hash());
40        Ok(url_safe)
41    }
42}
43impl std::fmt::Display for LnAddressPaymentRequest {
44    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
45        write!(f, "{}", serde_json::to_string(self).unwrap_or_default())
46    }
47}
48impl TryFrom<String> for LnAddressPaymentRequest {
49    type Error = anyhow::Error;
50    fn try_from(value: String) -> Result<Self, Self::Error> {
51        Ok(serde_json::from_str(&value)?)
52    }
53}
54
55#[derive(Debug, Clone, Serialize, Deserialize)]
56pub struct LnAddressConfirmation {
57    pub callback: String,
58    #[serde(rename = "minSendable")]
59    pub min_sendable: u64,
60    #[serde(rename = "maxSendable")]
61    pub max_sendable: u64,
62}
63impl LnAddressConfirmation {
64    #[cfg(not(target_arch = "wasm32"))]
65    pub async fn new(address: &LightningAddress, client: &reqwest::Client) -> anyhow::Result<Self> {
66        let (user, domain) = address
67            .0
68            .split_once('@')
69            .ok_or_else(|| anyhow::anyhow!("Invalid address"))?;
70        let url = format!("https://{domain}/.well-known/lnurlp/{user}");
71        let response = client.get(&url).send().await?.text().await?;
72        Self::try_from(response)
73    }
74}
75impl std::fmt::Display for LnAddressConfirmation {
76    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
77        write!(f, "{}", serde_json::to_string(self).unwrap_or_default())
78    }
79}
80impl TryFrom<String> for LnAddressConfirmation {
81    type Error = anyhow::Error;
82    fn try_from(value: String) -> Result<Self, Self::Error> {
83        Ok(serde_json::from_str(&value)?)
84    }
85}
86
87pub struct LightningAddress(pub &'static str);
88impl LightningAddress {
89    #[cfg(not(target_arch = "wasm32"))]
90    pub async fn get_invoice(
91        &self,
92        client: &reqwest::Client,
93        millisatoshis: u64,
94    ) -> anyhow::Result<LnAddressPaymentRequest> {
95        LnAddressPaymentRequest::new(self, millisatoshis, client).await
96    }
97}
98
99#[cfg(test)]
100mod tests {
101
102    use super::*;
103
104    #[tokio::test]
105    #[tracing_test::traced_test]
106    pub async fn get_ln_url_invoice() -> Result<(), anyhow::Error> {
107        let client = reqwest::Client::new();
108        let address = LightningAddress("42pupusas@blink.sv");
109        let invoice = address.get_invoice(&client, 1000).await?;
110        tracing::info!("Invoice: {:?}", invoice);
111        Ok(())
112    }
113}