1#![allow(clippy::result_large_err)]
3
4use bitcoin::secp256k1::ecdsa::Signature;
5use bitcoin::secp256k1::PublicKey;
6use reqwest::Client;
7
8use crate::api::*;
9use crate::channel::ChannelResponse;
10use crate::lnurl::LnUrl;
11use crate::pay::{LnURLPayInvoice, PayResponse, VerifyResponse};
12use crate::withdraw::WithdrawalResponse;
13use crate::{Builder, Error};
14
15#[derive(Debug, Clone)]
16pub struct AsyncClient {
17 pub client: Client,
18}
19
20impl Default for AsyncClient {
21 fn default() -> Self {
22 Self::new()
23 }
24}
25
26impl AsyncClient {
27 pub fn new() -> Self {
28 Self {
29 client: Client::new(),
30 }
31 }
32
33 pub fn from_builder(builder: Builder) -> Result<Self, Error> {
35 let mut client_builder = Client::builder();
36
37 #[cfg(not(target_arch = "wasm32"))]
38 if let Some(proxy) = &builder.proxy {
39 client_builder = client_builder.proxy(reqwest::Proxy::all(proxy)?);
40 }
41
42 #[cfg(not(target_arch = "wasm32"))]
43 if let Some(timeout) = builder.timeout {
44 client_builder = client_builder.timeout(core::time::Duration::from_secs(timeout));
45 }
46
47 Ok(Self::from_client(client_builder.build()?))
48 }
49
50 pub fn from_client(client: Client) -> Self {
52 AsyncClient { client }
53 }
54
55 pub async fn make_request(&self, url: &str) -> Result<LnUrlResponse, Error> {
56 let resp = self.client.get(url).send().await?;
57
58 let txt = resp.error_for_status()?.text().await?;
59 decode_ln_url_response(&txt)
60 }
61
62 pub async fn get_invoice(
63 &self,
64 pay: &PayResponse,
65 msats: u64,
66 zap_request: Option<String>,
67 comment: Option<&str>,
68 ) -> Result<LnURLPayInvoice, Error> {
69 if msats < pay.min_sendable || msats > pay.max_sendable {
71 return Err(Error::InvalidAmount);
72 }
73
74 if let Some(comment) = comment {
76 if let Some(max_length) = pay.comment_allowed {
77 if comment.len() > max_length as usize {
78 return Err(Error::InvalidComment);
79 }
80 }
81 }
82
83 let symbol = if pay.callback.contains('?') { "&" } else { "?" };
84
85 let url = match (zap_request, comment) {
86 (Some(_), Some(_)) => return Err(Error::InvalidComment),
87 (Some(zap_request), None) => format!(
88 "{}{}amount={}&nostr={}",
89 pay.callback, symbol, msats, zap_request
90 ),
91 (None, Some(comment)) => format!(
92 "{}{}amount={}&comment={}",
93 pay.callback, symbol, msats, comment
94 ),
95 (None, None) => format!("{}{}amount={}", pay.callback, symbol, msats),
96 };
97
98 let resp = self.client.get(&url).send().await?;
99
100 let invoice: LnURLPayInvoice = resp.error_for_status()?.json().await?;
101
102 invoice.verify_amount(msats)?;
104
105 Ok(invoice)
106 }
107
108 pub async fn verify(&self, url: &str) -> Result<VerifyResponse, Error> {
109 let resp = self.client.get(url).send().await?;
110
111 let rsp: Response<VerifyResponse> = resp.error_for_status()?.json().await?;
112 match rsp {
113 Response::Error { reason } => Err(Error::Other(reason)),
114 Response::Ok(r) => Ok(r),
115 }
116 }
117
118 pub async fn do_withdrawal(
119 &self,
120 withdrawal: &WithdrawalResponse,
121 invoice: &str,
122 ) -> Result<Response<()>, Error> {
123 let symbol = if withdrawal.callback.contains('?') {
124 "&"
125 } else {
126 "?"
127 };
128
129 let url = format!(
130 "{}{}k1={}&pr={}",
131 withdrawal.callback, symbol, withdrawal.k1, invoice
132 );
133 let resp = self.client.get(url).send().await?;
134
135 Ok(resp.error_for_status()?.json().await?)
136 }
137
138 pub async fn open_channel(
139 &self,
140 channel: &ChannelResponse,
141 node_pubkey: PublicKey,
142 private: bool,
143 ) -> Result<Response<()>, Error> {
144 let symbol = if channel.callback.contains('?') {
145 "&"
146 } else {
147 "?"
148 };
149
150 let url = format!(
151 "{}{}k1={}&remoteid={}&private={}",
152 channel.callback,
153 symbol,
154 channel.k1,
155 node_pubkey,
156 private as i32 );
158
159 let resp = self.client.get(url).send().await?;
160
161 Ok(resp.error_for_status()?.json().await?)
162 }
163
164 pub async fn lnurl_auth(
165 &self,
166 lnurl: LnUrl,
167 sig: Signature,
168 key: PublicKey,
169 ) -> Result<Response<()>, Error> {
170 let url = format!("{}&sig={}&key={}", lnurl.url, sig, key);
171
172 let resp = self.client.get(url).send().await?;
173
174 Ok(resp.error_for_status()?.json().await?)
175 }
176}