1#![allow(clippy::result_large_err)]
3
4use bitcoin::secp256k1::ecdsa::Signature;
5use bitcoin::secp256k1::PublicKey;
6use std::time::Duration;
7
8use ureq::{Agent, Proxy};
9
10use crate::channel::ChannelResponse;
11use crate::lnurl::LnUrl;
12use crate::pay::{LnURLPayInvoice, PayResponse};
13use crate::withdraw::WithdrawalResponse;
14use crate::{decode_ln_url_response_from_json, Builder, Error, LnUrlResponse, Response};
15
16#[derive(Debug, Clone)]
17pub struct BlockingClient {
18 agent: Agent,
19}
20
21impl BlockingClient {
22 pub fn from_builder(builder: Builder) -> Result<Self, Error> {
24 let mut agent_builder = ureq::AgentBuilder::new();
25
26 if let Some(timeout) = builder.timeout {
27 agent_builder = agent_builder.timeout(Duration::from_secs(timeout));
28 }
29
30 if let Some(proxy) = &builder.proxy {
31 agent_builder = agent_builder.proxy(Proxy::new(proxy).unwrap());
32 }
33
34 Ok(Self::from_agent(agent_builder.build()))
35 }
36
37 pub fn from_agent(agent: Agent) -> Self {
39 BlockingClient { agent }
40 }
41
42 pub fn make_request(&self, url: &str) -> Result<LnUrlResponse, Error> {
43 let resp = self.agent.get(url).call();
44
45 match resp {
46 Ok(resp) => {
47 let json: serde_json::Value = resp.into_json()?;
48 decode_ln_url_response_from_json(json)
49 }
50 Err(ureq::Error::Status(code, _)) => Err(Error::HttpResponse(code)),
51 Err(e) => Err(Error::Ureq(e)),
52 }
53 }
54
55 pub fn get_invoice(
56 &self,
57 pay: &PayResponse,
58 msats: u64,
59 zap_request: Option<String>,
60 comment: Option<&str>,
61 ) -> Result<LnURLPayInvoice, Error> {
62 if msats < pay.min_sendable || msats > pay.max_sendable {
64 return Err(Error::InvalidAmount);
65 }
66
67 if let Some(comment) = comment {
69 if let Some(max_length) = pay.comment_allowed {
70 if comment.len() > max_length as usize {
71 return Err(Error::InvalidComment);
72 }
73 }
74 }
75
76 let symbol = if pay.callback.contains('?') { "&" } else { "?" };
77
78 let url = match (zap_request, comment) {
79 (Some(_), Some(_)) => return Err(Error::InvalidComment),
80 (Some(zap_request), None) => format!(
81 "{}{}amount={}&nostr={}",
82 pay.callback, symbol, msats, zap_request
83 ),
84 (None, Some(comment)) => format!(
85 "{}{}amount={}&comment={}",
86 pay.callback, symbol, msats, comment
87 ),
88 (None, None) => format!("{}{}amount={}", pay.callback, symbol, msats),
89 };
90
91 let resp = self.agent.get(&url).call();
92
93 match resp {
94 Ok(resp) => {
95 let json: serde_json::Value = resp.into_json()?;
96 let result = serde_json::from_value::<LnURLPayInvoice>(json.clone());
97
98 match result {
99 Ok(invoice) => {
100 invoice.verify_amount(msats)?;
102 Ok(invoice)
103 }
104 Err(_) => {
105 let response = serde_json::from_value::<Response<()>>(json)?;
106 match response {
107 Response::Error { reason } => Err(Error::Other(reason)),
108 Response::Ok { .. } => unreachable!("Ok response should be an invoice"),
109 }
110 }
111 }
112 }
113 Err(ureq::Error::Status(code, _)) => Err(Error::HttpResponse(code)),
114 Err(e) => Err(Error::Ureq(e)),
115 }
116 }
117
118 pub 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
134 let resp = self.agent.get(&url).call();
135
136 match resp {
137 Ok(resp) => Ok(resp.into_json()?),
138 Err(ureq::Error::Status(code, _)) => Err(Error::HttpResponse(code)),
139 Err(e) => Err(Error::Ureq(e)),
140 }
141 }
142
143 pub fn open_channel(
144 &self,
145 channel: &ChannelResponse,
146 node_pubkey: PublicKey,
147 private: bool,
148 ) -> Result<Response<()>, Error> {
149 let symbol = if channel.callback.contains('?') {
150 "&"
151 } else {
152 "?"
153 };
154
155 let url = format!(
156 "{}{}k1={}&remoteid={}&private={}",
157 channel.callback,
158 symbol,
159 channel.k1,
160 node_pubkey,
161 private as i32 );
163
164 let resp = self.agent.get(&url).call();
165
166 match resp {
167 Ok(resp) => Ok(resp.into_json()?),
168 Err(ureq::Error::Status(code, _)) => Err(Error::HttpResponse(code)),
169 Err(e) => Err(Error::Ureq(e)),
170 }
171 }
172
173 pub fn lnurl_auth(
174 &self,
175 lnurl: LnUrl,
176 sig: Signature,
177 key: PublicKey,
178 ) -> Result<Response<()>, Error> {
179 let url = format!("{}&sig={}&key={}", lnurl.url, sig, key);
180
181 let resp = self.agent.get(&url).call();
182
183 match resp {
184 Ok(resp) => Ok(resp.into_json()?),
185 Err(ureq::Error::Status(code, _)) => Err(Error::HttpResponse(code)),
186 Err(e) => Err(Error::Ureq(e)),
187 }
188 }
189}