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};
12use crate::withdraw::WithdrawalResponse;
13use crate::{Builder, Error};
14
15#[derive(Debug, Clone)]
16pub struct AsyncClient {
17 pub client: Client,
18}
19
20impl AsyncClient {
21 pub fn from_builder(builder: Builder) -> Result<Self, Error> {
23 let mut client_builder = Client::builder();
24
25 #[cfg(not(target_arch = "wasm32"))]
26 if let Some(proxy) = &builder.proxy {
27 client_builder = client_builder.proxy(reqwest::Proxy::all(proxy)?);
28 }
29
30 #[cfg(not(target_arch = "wasm32"))]
31 if let Some(timeout) = builder.timeout {
32 client_builder = client_builder.timeout(core::time::Duration::from_secs(timeout));
33 }
34
35 Ok(Self::from_client(client_builder.build()?))
36 }
37
38 pub fn from_client(client: Client) -> Self {
40 AsyncClient { client }
41 }
42
43 pub async fn make_request(&self, url: &str) -> Result<LnUrlResponse, Error> {
44 let resp = self.client.get(url).send().await?;
45
46 let txt = resp.error_for_status()?.text().await?;
47 decode_ln_url_response(&txt)
48 }
49
50 pub async fn get_invoice(
51 &self,
52 pay: &PayResponse,
53 msats: u64,
54 zap_request: Option<String>,
55 comment: Option<&str>,
56 ) -> Result<LnURLPayInvoice, Error> {
57 if msats < pay.min_sendable || msats > pay.max_sendable {
59 return Err(Error::InvalidAmount);
60 }
61
62 if let Some(comment) = comment {
64 if let Some(max_length) = pay.comment_allowed {
65 if comment.len() > max_length as usize {
66 return Err(Error::InvalidComment);
67 }
68 }
69 }
70
71 let symbol = if pay.callback.contains('?') { "&" } else { "?" };
72
73 let url = match (zap_request, comment) {
74 (Some(_), Some(_)) => return Err(Error::InvalidComment),
75 (Some(zap_request), None) => format!(
76 "{}{}amount={}&nostr={}",
77 pay.callback, symbol, msats, zap_request
78 ),
79 (None, Some(comment)) => format!(
80 "{}{}amount={}&comment={}",
81 pay.callback, symbol, msats, comment
82 ),
83 (None, None) => format!("{}{}amount={}", pay.callback, symbol, msats),
84 };
85
86 let resp = self.client.get(&url).send().await?;
87
88 Ok(resp.error_for_status()?.json().await?)
89 }
90
91 pub async fn do_withdrawal(
92 &self,
93 withdrawal: &WithdrawalResponse,
94 invoice: &str,
95 ) -> Result<Response, Error> {
96 let symbol = if withdrawal.callback.contains('?') {
97 "&"
98 } else {
99 "?"
100 };
101
102 let url = format!(
103 "{}{}k1={}&pr={}",
104 withdrawal.callback, symbol, withdrawal.k1, invoice
105 );
106 let resp = self.client.get(url).send().await?;
107
108 Ok(resp.error_for_status()?.json().await?)
109 }
110
111 pub async fn open_channel(
112 &self,
113 channel: &ChannelResponse,
114 node_pubkey: PublicKey,
115 private: bool,
116 ) -> Result<Response, Error> {
117 let symbol = if channel.callback.contains('?') {
118 "&"
119 } else {
120 "?"
121 };
122
123 let url = format!(
124 "{}{}k1={}&remoteid={}&private={}",
125 channel.callback,
126 symbol,
127 channel.k1,
128 node_pubkey,
129 private as i32 );
131
132 let resp = self.client.get(url).send().await?;
133
134 Ok(resp.error_for_status()?.json().await?)
135 }
136
137 pub async fn lnurl_auth(
138 &self,
139 lnurl: LnUrl,
140 sig: Signature,
141 key: PublicKey,
142 ) -> Result<Response, Error> {
143 let url = format!("{}&sig={}&key={}", lnurl.url, sig, key);
144
145 let resp = self.client.get(url).send().await?;
146
147 Ok(resp.error_for_status()?.json().await?)
148 }
149}