use lnurl::lnurl::LnUrl;
use mostro_core::prelude::*;
use once_cell::sync::Lazy;
use reqwest::Client;
use serde_json::Value;
pub static HTTP_CLIENT: Lazy<Client> = Lazy::new(|| {
Client::builder()
.timeout(std::time::Duration::from_secs(10))
.user_agent(concat!("mostro/", env!("CARGO_PKG_VERSION")))
.build()
.expect("valid reqwest Client")
});
async fn extract_lnurl(address: &str) -> Result<String, MostroError> {
let url = if address.to_lowercase().starts_with("lnurl") {
let lnurl = LnUrl::decode(address.to_string())
.map_err(|_| MostroInternalErr(ServiceError::LnAddressParseError))?;
lnurl.url
} else {
let (user, domain) = match address.split_once('@') {
Some((user, domain)) => (user, domain),
None => return Err(MostroInternalErr(ServiceError::LnAddressParseError)),
};
let base_url = if cfg!(test) {
format!("http://{domain}:8080")
} else {
format!("https://{domain}")
};
format!("{base_url}/.well-known/lnurlp/{user}")
};
Ok(url)
}
pub async fn ln_exists(address: &str) -> Result<(), MostroError> {
let url = extract_lnurl(address).await?;
let res = HTTP_CLIENT
.get(url)
.send()
.await
.map_err(|_| MostroInternalErr(ServiceError::NoAPIResponse))?;
let status = res.status();
if status.is_success() {
let body = res
.text()
.await
.map_err(|_| MostroInternalErr(ServiceError::NoAPIResponse))?;
let body: Value = serde_json::from_str(&body)
.map_err(|_| MostroInternalErr(ServiceError::MalformedAPIRes))?;
let tag = body["tag"].as_str().unwrap_or("");
if tag == "payRequest" {
return Ok(());
}
Err(MostroInternalErr(ServiceError::LnAddressParseError))
} else {
Err(MostroInternalErr(ServiceError::LnAddressParseError))
}
}
pub async fn resolv_ln_address(address: &str, amount: u64) -> Result<String, MostroError> {
let url = extract_lnurl(address).await?;
let amount_msat = amount * 1000;
let res = HTTP_CLIENT
.get(url)
.send()
.await
.map_err(|_| MostroInternalErr(ServiceError::MalformedAPIRes))?;
let status = res.status();
if status.is_success() {
let body = res
.text()
.await
.map_err(|_| MostroInternalErr(ServiceError::MessageSerializationError))?;
let body: Value = serde_json::from_str(&body)
.map_err(|_| MostroInternalErr(ServiceError::MessageSerializationError))?;
let tag = body["tag"].as_str().unwrap_or("");
if tag != "payRequest" {
return Ok("".to_string());
}
let min = body["minSendable"].as_u64().unwrap_or(0);
let max = body["maxSendable"].as_u64().unwrap_or(0);
if min > amount_msat || max < amount_msat {
return Ok("".to_string());
}
let callback = body["callback"].as_str().unwrap_or("");
let callback = format!("{callback}?amount={amount_msat}");
let res = HTTP_CLIENT
.get(callback)
.send()
.await
.map_err(|_| MostroInternalErr(ServiceError::MalformedAPIRes))?;
let status = res.status();
if status.is_success() {
let body = res
.text()
.await
.map_err(|_| MostroInternalErr(ServiceError::MessageSerializationError))?;
let body: Value = serde_json::from_str(&body)
.map_err(|_| MostroInternalErr(ServiceError::MessageSerializationError))?;
let pr = body["pr"].as_str().unwrap_or("");
return Ok(pr.to_string());
}
Ok("".to_string())
} else {
Ok("".to_string())
}
}