1use reqwest::{self, Method, RequestBuilder, StatusCode, header::HeaderMap};
2
3use crate::{
4 SensitiveString,
5 crypto::make_sign,
6 spot::{
7 AccountInformation, AggregateTrade, CurrentAveragePrice, GetAccountInformationParams,
8 GetAggregateTradesParams, GetCurrentAveragePriceParams, GetKlineListParams,
9 GetOlderTradesParams, GetOrderBookParams, GetRecentTradesParams,
10 GetTickerPriceChangeStatisticsParams, Kline, NewOrderParams, NewOrderResponse,
11 NewOrderResponseAck, NewOrderResponseFull, NewOrderResponseResult, Order, OrderBook,
12 QueryOrderParams, RecentTrade, TestCommissionRates, TestCommissionRatesEmpty,
13 TestCommissionRatesFull, TestConnectivity, TickerPriceChangeStatistic,
14 },
15};
16
17use super::{
18 Error, ExchangeInfo, GetExchangeInfoParams, Headers, Response, ServerTime,
19 serde::deserialize_str, url::*,
20};
21
22pub struct GeneralClient {
23 base_url: String,
24}
25
26impl GeneralClient {
27 pub fn new(base_url: String) -> Self {
28 Self { base_url }
29 }
30}
31
32impl GeneralClient {
33 pub async fn test_connectivity(&self) -> Result<Response<TestConnectivity>, Error> {
35 let url = format!("{}{}", self.base_url, Path::Time);
36
37 let client = reqwest::Client::builder().build()?;
38 let request = client.request(Method::GET, url);
39
40 let response = send(request).await?;
41 Ok(response)
42 }
43
44 pub async fn get_server_time(&self) -> Result<Response<ServerTime>, Error> {
45 let url = format!("{}{}", self.base_url, Path::Time);
46
47 let client = reqwest::Client::builder().build()?;
48 let request = client.request(Method::GET, url);
49
50 let response = send(request).await?;
51 Ok(response)
52 }
53
54 pub async fn get_exchange_info(
55 &self,
56 params: GetExchangeInfoParams,
57 ) -> Result<Response<ExchangeInfo>, Error> {
58 let query = serde_urlencoded::to_string(¶ms)?;
59 let url = format!("{}{}?{query}", self.base_url, Path::ExchangeInfo);
60
61 let client = reqwest::Client::builder().build()?;
62 let request = client.request(Method::GET, url);
63
64 let response = send(request).await?;
65 Ok(response)
66 }
67}
68
69pub struct MarketClient {
70 base_url: String,
71}
72
73impl MarketClient {
74 pub fn new(base_url: String) -> Self {
75 Self { base_url }
76 }
77}
78
79impl MarketClient {
80 pub async fn get_order_book(
81 &self,
82 params: GetOrderBookParams,
83 ) -> Result<Response<OrderBook>, Error> {
84 let query = serde_urlencoded::to_string(¶ms)?;
85 let url = format!("{}{}?{query}", self.base_url, Path::ExchangeInfo);
86
87 let client = reqwest::Client::builder().build()?;
88 let request = client.request(Method::GET, url);
89
90 let response = send(request).await?;
91 Ok(response)
92 }
93
94 pub async fn recent_trades_list(
96 &self,
97 params: GetRecentTradesParams,
98 ) -> Result<Response<Vec<RecentTrade>>, Error> {
99 let query = serde_urlencoded::to_string(¶ms)?;
100 let url = format!("{}{}?{query}", self.base_url, Path::Trades);
101
102 let client = reqwest::Client::builder().build()?;
103 let request = client.request(Method::GET, url);
104
105 let response = send(request).await?;
106 Ok(response)
107 }
108
109 pub async fn old_trade_lookup(
111 &self,
112 params: GetOlderTradesParams,
113 ) -> Result<Response<Vec<RecentTrade>>, Error> {
114 let query = serde_urlencoded::to_string(¶ms)?;
115 let url = format!("{}{}?{query}", self.base_url, Path::HistoricalTrades);
116
117 let client = reqwest::Client::builder().build()?;
118 let request = client.request(Method::GET, url);
119
120 let response = send(request).await?;
121 Ok(response)
122 }
123
124 pub async fn aggregate_trades_list(
129 &self,
130 params: GetAggregateTradesParams,
131 ) -> Result<Response<Vec<AggregateTrade>>, Error> {
132 let query = serde_urlencoded::to_string(¶ms)?;
133 let url = format!("{}{}?{query}", self.base_url, Path::AggTrades);
134
135 let client = reqwest::Client::builder().build()?;
136 let request = client.request(Method::GET, url);
137
138 let response = send(request).await?;
139 Ok(response)
140 }
141
142 pub async fn get_kline_list(
152 &self,
153 params: GetKlineListParams,
154 ) -> Result<Response<Vec<Kline>>, Error> {
155 let query = serde_urlencoded::to_string(¶ms)?;
156 let url = format!("{}{}?{query}", self.base_url, Path::KLines);
157
158 let client = reqwest::Client::builder().build()?;
159 let request = client.request(Method::GET, url);
160
161 let response = send(request).await?;
162 Ok(response)
163 }
164
165 pub async fn get_ui_kline_list(
178 &self,
179 params: GetKlineListParams,
180 ) -> Result<Response<Vec<Kline>>, Error> {
181 let query = serde_urlencoded::to_string(¶ms)?;
182 let url = format!("{}{}?{query}", self.base_url, Path::UIKLines);
183
184 let client = reqwest::Client::builder().build()?;
185 let request = client.request(Method::GET, url);
186
187 let response = send(request).await?;
188 Ok(response)
189 }
190
191 pub async fn get_current_average_price(
193 &self,
194 params: GetCurrentAveragePriceParams,
195 ) -> Result<Response<CurrentAveragePrice>, Error> {
196 let query = serde_urlencoded::to_string(¶ms)?;
197 let url = format!("{}{}?{query}", self.base_url, Path::AvgPrice);
198
199 let client = reqwest::Client::builder().build()?;
200 let request = client.request(Method::GET, url);
201
202 let response = send(request).await?;
203 Ok(response)
204 }
205
206 pub async fn ticker_price_change_statistics(
208 &self,
209 params: GetTickerPriceChangeStatisticsParams,
210 ) -> Result<Response<TickerPriceChangeStatistic>, Error> {
211 let query = serde_urlencoded::to_string(¶ms)?;
212 let url = format!("{}{}?{query}", self.base_url, Path::Ticker24hr);
213
214 let client = reqwest::Client::builder().build()?;
215 let request = client.request(Method::GET, url);
216
217 let response = send(request).await?;
218 Ok(response)
219 }
220}
221
222pub struct TradingClient {
223 base_url: String,
224 headers: HeaderMap,
225 sign: Box<dyn Fn(&str) -> String>,
226}
227
228impl TradingClient {
229 pub fn new(base_url: String, api_key: SensitiveString, api_secret: SensitiveString) -> Self {
230 let mut headers = HeaderMap::new();
231
232 let api_key = api_key.expose().parse().unwrap();
233 headers.append(HEADER_X_MBX_APIKEY, api_key);
234
235 Self {
236 base_url,
237 headers,
238 sign: Box::new(make_sign(api_secret)),
239 }
240 }
241}
242
243impl TradingClient {
244 pub async fn new_order(
255 &self,
256 params: NewOrderParams,
257 ) -> Result<Response<NewOrderResponse>, Error> {
258 let query = serde_urlencoded::to_string(¶ms)?;
259 let body = (*self.sign)(&query);
260 let url = format!("{}{}?{query}", self.base_url, Path::Order);
261
262 let client = reqwest::Client::builder().build()?;
263 let request = client
264 .request(Method::POST, url)
265 .headers(self.headers.clone())
266 .body(body);
267
268 let response = send(request).await?;
269
270 Ok(response)
271 }
272
273 pub async fn test_new_order(
275 &self,
276 params: NewOrderParams,
277 compute_commission_rates: bool,
278 ) -> Result<Response<TestCommissionRates>, Error> {
279 let mut query = serde_urlencoded::to_string(¶ms)?;
280 if compute_commission_rates {
281 query.push_str("&computeCommissionRates=true");
282 }
283 let body = (*self.sign)(&query);
284 let url = format!("{}{}", self.base_url, Path::OrderTest);
285
286 let client = reqwest::Client::builder().build()?;
287 let request = client
288 .request(Method::POST, url)
289 .headers(self.headers.clone())
290 .body(body);
291
292 let response = send(request).await?;
293
294 Ok(response)
295 }
296}
297
298pub struct AccountClient {
299 base_url: String,
300 headers: HeaderMap,
301 sign: Box<dyn Fn(&str) -> String>,
302}
303
304impl AccountClient {
305 pub fn new(base_url: String, api_key: SensitiveString, api_secret: SensitiveString) -> Self {
306 let mut headers = HeaderMap::new();
307
308 let api_key = api_key.expose().parse().unwrap();
309 headers.append(HEADER_X_MBX_APIKEY, api_key);
310
311 Self {
312 base_url,
313 headers,
314 sign: Box::new(make_sign(api_secret)),
315 }
316 }
317}
318
319impl AccountClient {
320 pub async fn account_information(
322 &self,
323 params: GetAccountInformationParams,
324 ) -> Result<Response<AccountInformation>, Error> {
325 let query = serde_urlencoded::to_string(¶ms)?;
326 let query = (*self.sign)(&query);
327 let url = format!("{}{}?{query}", self.base_url, Path::Account);
328
329 let client = reqwest::Client::builder().build()?;
330 let request = client
331 .request(Method::GET, url)
332 .headers(self.headers.clone());
333
334 let response = send(request).await?;
335
336 Ok(response)
337 }
338
339 pub async fn query_order(&self, params: QueryOrderParams) -> Result<Response<Order>, Error> {
345 let query = serde_urlencoded::to_string(¶ms)?;
346 let query = (*self.sign)(&query);
347 let url = format!("{}{}?{query}", self.base_url, Path::Order);
348
349 let client = reqwest::Client::builder().build()?;
350 let request = client
351 .request(Method::GET, url)
352 .headers(self.headers.clone());
353
354 let response = send(request).await?;
355
356 Ok(response)
357 }
358}
359
360async fn send<T>(request: RequestBuilder) -> Result<Response<T>, Error>
361where
362 T: serde::de::DeserializeOwned,
363{
364 let response = request.send().await?;
365 let status = response.status();
366 let headers = parse_headers(&response.headers());
367 let json = response.text().await?;
368
369 #[cfg(debug_assertions)]
370 {
371 if status != StatusCode::OK {
372 println!("DEBUG: {status} {json}");
373 }
374 }
375
376 let result = deserialize_str(&json)?;
379 let response = Response { result, headers };
380 Ok(response)
381}
382
383fn parse_headers(headers: &HeaderMap) -> Headers {
385 let retry_after = headers
386 .get(HEADER_RETRY_AFTER)
387 .map(|h| h.to_str().unwrap_or_default().parse().ok())
388 .flatten();
389
390 Headers { retry_after }
391}