use crate::lndrpc::lnrpc;
use bitcoin::hashes::Hash;
use lightning_invoice::{Bolt11Invoice, SignedRawBolt11Invoice};
use reqwest::{Client, Error};
use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;
use tokio::sync::Mutex;
use crate::lnclient;
use crate::utils;
#[derive(Debug, Clone)]
pub struct LNURLOptions {
pub address: String,
}
#[derive(Debug, serde::Deserialize)]
pub struct LnAddressUrlResJson {
callback: String,
#[serde(rename = "maxSendable")]
max_sendable: u64,
#[serde(rename = "minSendable")]
min_sendable: u64,
metadata: String,
#[serde(rename = "commentAllowed", default)]
comment_allowed: u32,
tag: String,
}
#[derive(Debug, serde::Deserialize)]
pub struct CallbackUrlResJson {
pr: String,
}
#[derive(Debug, serde::Deserialize)]
pub struct DecodedPR {
currency: String,
created_at: i32,
expiry: i32,
payee: String,
msatoshi: i64,
description: Option<String>,
description_hash: Option<String>,
payment_hash: String,
min_final_cltv_expiry: i32,
}
impl LnAddressUrlResJson {
pub async fn new_client(ln_client_config: &lnclient::LNClientConfig) -> Result<Arc<Mutex<dyn lnclient::LNClient>>, Box<dyn std::error::Error + Send + Sync>> {
let lnurl_options = ln_client_config
.lnurl_config
.clone()
.ok_or("LN_CLIENT_TYPE is LNURL but lnurl_config is missing")?;
let (username, domain) = utils::parse_ln_address(lnurl_options.address)?;
let scheme = if domain.ends_with(".onion") { "http" } else { "https" };
let ln_address_url = format!("{}://{}/.well-known/lnurlp/{}", scheme, domain, username);
let ln_address_url_res_body = do_get_request(&ln_address_url).await?;
let ln_address_url_res: LnAddressUrlResJson = serde_json::from_str(&ln_address_url_res_body)?;
Ok(Arc::new(Mutex::new(ln_address_url_res)))
}
}
impl lnclient::LNClient for LnAddressUrlResJson {
fn add_invoice(
&self,
ln_invoice: lnrpc::Invoice,
) -> Pin<
Box<
dyn Future<
Output = Result<
lnrpc::AddInvoiceResponse,
Box<dyn std::error::Error + Send + Sync>,
>,
> + Send,
>,
> {
let callback_url = format!("{}?amount={}", self.callback, ln_invoice.value_msat);
Box::pin(async move {
let callback_url_res_body = do_get_request(&callback_url).await?;
let callback_url_res_json: CallbackUrlResJson =
serde_json::from_str(&callback_url_res_body)?;
let invoice = callback_url_res_json.pr;
let signed = invoice
.parse::<SignedRawBolt11Invoice>()
.map_err(|e| format!("LNURL callback returned an unparsable invoice: {:?}", e))?;
let decoded_invoice = Bolt11Invoice::from_signed(signed)
.map_err(|e| format!("LNURL callback invoice failed validation: {:?}", e))?;
let payment_hash = decoded_invoice.payment_hash();
let payment_addr = decoded_invoice.payment_secret();
Ok(lnrpc::AddInvoiceResponse {
r_hash: payment_hash.to_byte_array().to_vec(),
payment_request: invoice,
add_index: 0,
payment_addr: payment_addr.0.to_vec(),
})
})
}
}
async fn do_get_request(url: &str) -> Result<String, Error> {
let client = Client::builder()
.timeout(std::time::Duration::from_secs(10))
.build()?;
let raw_resp = client.get(url).send().await?;
let resp = raw_resp.error_for_status()?;
let text = resp.text().await?;
Ok(text)
}