1mod models;
2mod pay;
3mod utils;
4mod withdraw;
5
6use self::models::{
7 LnUrlHttpClient, PayRequestCallbackResponse, PayRequestResponse, WithdrawRequestResponse,
8};
9use self::utils::{parse_invoice, parse_lnurl};
10use crate::node::ClnClient;
11use crate::pb::cln::{amount_or_any, Amount, AmountOrAny};
12use anyhow::{anyhow, Result};
13use models::LnUrlHttpClearnetClient;
14use pay::{resolve_lnurl_to_invoice, validate_invoice_from_callback_response};
15use url::Url;
16use withdraw::{build_withdraw_request_callback_url, parse_withdraw_request_response_from_url};
17
18pub struct LNURL<T: LnUrlHttpClient> {
19 http_client: T,
20}
21
22impl<T: LnUrlHttpClient> LNURL<T> {
23 pub fn new(http_client: T) -> Self {
24 LNURL { http_client }
25 }
26
27 pub fn new_with_clearnet_client() -> LNURL<LnUrlHttpClearnetClient> {
28 let http_client = LnUrlHttpClearnetClient::new();
29 LNURL { http_client }
30 }
31
32 pub async fn get_pay_request_response(&self, lnurl: &str) -> Result<PayRequestResponse> {
33 let url = parse_lnurl(lnurl)?;
34
35 let lnurl_pay_request_response: PayRequestResponse =
36 self.http_client.get_pay_request_response(&url).await?;
37
38 if lnurl_pay_request_response.tag != "payRequest" {
39 return Err(anyhow!("Expected tag to say 'payRequest'"));
40 }
41
42 Ok(lnurl_pay_request_response)
43 }
44
45 pub async fn get_pay_request_callback_response(
46 &self,
47 base_callback_url: &str,
48 amount_msats: u64,
49 metadata: &str,
50 ) -> Result<PayRequestCallbackResponse> {
51 let mut url = Url::parse(base_callback_url)?;
52 url.query_pairs_mut()
53 .append_pair("amount", &amount_msats.to_string());
54
55 let callback_response: PayRequestCallbackResponse = self
56 .http_client
57 .get_pay_request_callback_response(&url.to_string())
58 .await?;
59
60 let invoice = parse_invoice(&callback_response.pr)?;
61 validate_invoice_from_callback_response(&invoice, amount_msats, metadata)?;
62 Ok(callback_response)
63 }
64
65 pub async fn pay(
66 &self,
67 lnurl: &str,
68 amount_msats: u64,
69 node: &mut ClnClient,
70 ) -> Result<tonic::Response<crate::pb::cln::PayResponse>> {
71 let invoice = resolve_lnurl_to_invoice(&self.http_client, lnurl, amount_msats).await?;
72
73 node.pay(crate::pb::cln::PayRequest {
74 bolt11: invoice.to_string(),
75 ..Default::default()
76 })
77 .await
78 .map_err(|e| anyhow!(e))
79 }
80
81 pub async fn get_withdraw_request_response(
82 &self,
83 lnurl: &str,
84 ) -> Result<WithdrawRequestResponse> {
85 let url = parse_lnurl(lnurl)?;
86 let withdrawal_request_response = parse_withdraw_request_response_from_url(&url);
87
88 let withdrawal_request_response = match withdrawal_request_response {
90 Some(w) => w,
91 None => {
92 self.http_client
93 .get_withdrawal_request_response(&url)
94 .await?
95 }
96 };
97
98 Ok(withdrawal_request_response)
99 }
100
101 pub async fn withdraw(
102 &self,
103 lnurl: &str,
104 amount_msats: u64,
105 node: &mut ClnClient,
106 ) -> Result<()> {
107 let withdraw_request_response = self.get_withdraw_request_response(lnurl).await?;
108
109 let amount = AmountOrAny {
110 value: Some(amount_or_any::Value::Amount(Amount { msat: amount_msats })),
111 };
112 let invoice = node
113 .invoice(crate::pb::cln::InvoiceRequest {
114 amount_msat: Some(amount),
115 description: withdraw_request_response.default_description.clone(),
116 ..Default::default()
117 })
118 .await
119 .map_err(|e| anyhow!(e))?
120 .into_inner();
121
122 let callback_url =
123 build_withdraw_request_callback_url(&withdraw_request_response, invoice.bolt11)?;
124
125 let _ = self
126 .http_client
127 .send_invoice_for_withdraw_request(&callback_url);
128
129 Ok(())
130 }
131}