1use reqwest::{self, Method, RequestBuilder, header::HeaderMap};
2use tracing::debug;
3
4use crate::{
5 SensitiveString,
6 crypto::sign_query,
7 serde::{deserialize_json, serialize_query},
8 spot::{
9 ApiError, Error, HEADER_RETRY_AFTER, HEADER_X_MBX_APIKEY, Path,
10 http::{
11 AccountInformation, AggregateTrade, CurrentAveragePrice, ExchangeInfo,
12 GetAccountInformationParams, GetAggregateTradesParams, GetCurrentAveragePriceParams,
13 GetExchangeInfoParams, GetKlineListParams, GetOlderTradesParams, GetOrderBookParams,
14 GetRecentTradesParams, GetTickerPriceChangeStatisticsParams, Headers, Kline,
15 NewOrderRequest, NewOrderResponse, Order, OrderBook, PrivateConfig, PublicConfig,
16 QueryOrderParams, RecentTrade, Response, ServerTime, TestCommissionRates,
17 TestConnectivity, TickerPriceChangeStatistic,
18 },
19 },
20 timestamp,
21};
22
23pub struct PublicClient {
24 base_url: String,
25 headers: HeaderMap,
26}
27
28impl PublicClient {
29 pub fn new(cfg: PublicConfig) -> Self {
30 Self {
31 base_url: cfg.base_url,
32 headers: cfg.headers.unwrap_or_default(),
33 }
34 }
35}
36
37impl PublicClient {
39 pub async fn test_connectivity(&self) -> Result<Response<TestConnectivity>, Error> {
41 let url = format!("{}{}", self.base_url, Path::Ping);
42
43 let client = reqwest::Client::builder().build()?;
44 let request = client
45 .request(Method::GET, url)
46 .headers(self.headers.clone());
47
48 send(request).await
49 }
50
51 pub async fn get_server_time(&self) -> Result<Response<ServerTime>, Error> {
52 let url = format!("{}{}", self.base_url, Path::Time);
53
54 let client = reqwest::Client::builder().build()?;
55 let request = client
56 .request(Method::GET, url)
57 .headers(self.headers.clone());
58
59 send(request).await
60 }
61
62 pub async fn get_exchange_info(
63 &self,
64 params: GetExchangeInfoParams,
65 ) -> Result<Response<ExchangeInfo>, Error> {
66 let url = format!("{}{}", self.base_url, Path::ExchangeInfo);
67
68 let client = reqwest::Client::builder().build()?;
69 let request = client
70 .request(Method::GET, url)
71 .headers(self.headers.clone())
72 .query(¶ms);
73
74 send(request).await
75 }
76}
77
78impl PublicClient {
80 pub async fn get_order_book(
81 &self,
82 params: GetOrderBookParams,
83 ) -> Result<Response<OrderBook>, Error> {
84 let url = format!("{}{}", self.base_url, Path::ExchangeInfo);
85
86 let client = reqwest::Client::builder().build()?;
87 let request = client
88 .request(Method::GET, url)
89 .headers(self.headers.clone())
90 .query(¶ms);
91
92 send(request).await
93 }
94
95 pub async fn recent_trades_list(
97 &self,
98 params: GetRecentTradesParams,
99 ) -> Result<Response<Vec<RecentTrade>>, Error> {
100 let url = format!("{}{}", self.base_url, Path::Trades);
101
102 let client = reqwest::Client::builder().build()?;
103 let request = client
104 .request(Method::GET, url)
105 .headers(self.headers.clone())
106 .query(¶ms);
107
108 send(request).await
109 }
110
111 pub async fn old_trade_lookup(
113 &self,
114 params: GetOlderTradesParams,
115 ) -> Result<Response<Vec<RecentTrade>>, Error> {
116 let url = format!("{}{}", self.base_url, Path::HistoricalTrades);
117
118 let client = reqwest::Client::builder().build()?;
119 let request = client
120 .request(Method::GET, url)
121 .headers(self.headers.clone())
122 .query(¶ms);
123
124 send(request).await
125 }
126
127 pub async fn aggregate_trades_list(
132 &self,
133 params: GetAggregateTradesParams,
134 ) -> Result<Response<Vec<AggregateTrade>>, Error> {
135 let url = format!("{}{}", self.base_url, Path::AggTrades);
136
137 let client = reqwest::Client::builder().build()?;
138 let request = client
139 .request(Method::GET, url)
140 .headers(self.headers.clone())
141 .query(¶ms);
142
143 send(request).await
144 }
145
146 pub async fn get_kline_list(
156 &self,
157 params: GetKlineListParams,
158 ) -> Result<Response<Vec<Kline>>, Error> {
159 let url = format!("{}{}", self.base_url, Path::KLines);
160
161 let client = reqwest::Client::builder().build()?;
162 let request = client
163 .request(Method::GET, url)
164 .headers(self.headers.clone())
165 .query(¶ms);
166
167 send(request).await
168 }
169
170 pub async fn get_ui_kline_list(
183 &self,
184 params: GetKlineListParams,
185 ) -> Result<Response<Vec<Kline>>, Error> {
186 let url = format!("{}{}", self.base_url, Path::UIKLines);
187
188 let client = reqwest::Client::builder().build()?;
189 let request = client
190 .request(Method::GET, url)
191 .headers(self.headers.clone())
192 .query(¶ms);
193
194 send(request).await
195 }
196
197 pub async fn get_current_average_price(
199 &self,
200 params: GetCurrentAveragePriceParams,
201 ) -> Result<Response<CurrentAveragePrice>, Error> {
202 let url = format!("{}{}", self.base_url, Path::AvgPrice);
203
204 let client = reqwest::Client::builder().build()?;
205 let request = client
206 .request(Method::GET, url)
207 .headers(self.headers.clone())
208 .query(¶ms);
209
210 send(request).await
211 }
212
213 pub async fn ticker_price_change_statistics(
215 &self,
216 params: GetTickerPriceChangeStatisticsParams,
217 ) -> Result<Response<TickerPriceChangeStatistic>, Error> {
218 let url = format!("{}{}", self.base_url, Path::Ticker24hr);
219
220 let client = reqwest::Client::builder().build()?;
221 let request = client
222 .request(Method::GET, url)
223 .headers(self.headers.clone())
224 .query(¶ms);
225
226 send(request).await
227 }
228}
229
230pub struct PrivateClient {
231 base_url: String,
232 headers: HeaderMap,
233 api_secret: SensitiveString,
234}
235
236impl PrivateClient {
237 pub fn new(cfg: PrivateConfig) -> Self {
238 let headers = {
239 let mut headers = HeaderMap::new();
240
241 let api_key = cfg.api_key.expose().parse().unwrap();
242 headers.append(HEADER_X_MBX_APIKEY, api_key);
243
244 if let Some(extra_headers) = cfg.headers {
245 headers.extend(extra_headers);
246 }
247
248 headers
249 };
250
251 Self {
252 base_url: cfg.base_url,
253 headers,
254 api_secret: cfg.api_secret,
255 }
256 }
257}
258
259impl PrivateClient {
261 pub async fn new_order(
272 &self,
273 params: NewOrderRequest,
274 ) -> Result<Response<NewOrderResponse>, Error> {
275 let query = serialize_query(¶ms)?;
276 let query = sign_query(&self.api_secret, timestamp(), &query);
277 let url = format!("{}{}", self.base_url, Path::Order);
278
279 let client = reqwest::Client::builder().build()?;
280 let request = client
281 .request(Method::POST, url)
282 .headers(self.headers.clone())
283 .body(query);
284
285 send(request).await
286 }
287
288 pub async fn test_new_order(
290 &self,
291 params: NewOrderRequest,
292 ) -> Result<Response<TestCommissionRates>, Error> {
293 let query = serialize_query(¶ms)?;
294 let query = sign_query(&self.api_secret, timestamp(), &query);
295 let url = format!("{}{}", self.base_url, Path::OrderTest);
296
297 let client = reqwest::Client::builder().build()?;
298 let request = client
299 .request(Method::POST, url)
300 .headers(self.headers.clone())
301 .body(query);
302
303 send(request).await
304 }
305}
306
307impl PrivateClient {
309 pub async fn account_information(
311 &self,
312 params: GetAccountInformationParams,
313 ) -> Result<Response<AccountInformation>, Error> {
314 let query = serialize_query(¶ms)?;
315 let query = sign_query(&self.api_secret, timestamp(), &query);
316 let url = format!("{}{}?{query}", self.base_url, Path::Account);
317
318 let client = reqwest::Client::builder().build()?;
319 let request = client
320 .request(Method::GET, url)
321 .headers(self.headers.clone());
322
323 send(request).await
324 }
325
326 pub async fn query_order(&self, params: QueryOrderParams) -> Result<Response<Order>, Error> {
332 let query = serialize_query(¶ms)?;
333 let query = sign_query(&self.api_secret, timestamp(), &query);
334 let url = format!("{}{}?{query}", self.base_url, Path::Order);
335
336 let client = reqwest::Client::builder().build()?;
337 let request = client
338 .request(Method::GET, url)
339 .headers(self.headers.clone());
340
341 send(request).await
342 }
343}
344
345async fn send<T>(request: RequestBuilder) -> Result<Response<T>, Error>
346where
347 T: serde::de::DeserializeOwned,
348{
349 let response = request.send().await?;
350 let status = response.status();
351 let headers = parse_headers(response.headers());
352 let json = response.text().await?;
353
354 if !status.is_success() {
355 #[cfg(debug_assertions)]
356 debug!(?status, ?json, "request failed");
357
358 let api_err = deserialize_json::<ApiError>(&json)?;
360 return Err(Error::Api(api_err));
361 }
362
363 let result = deserialize_json(&json)?;
364 Ok(Response { result, headers })
365}
366
367fn parse_headers(headers: &HeaderMap) -> Headers {
369 let retry_after = headers
370 .get(HEADER_RETRY_AFTER)
371 .and_then(|h| h.to_str().unwrap_or_default().parse().ok());
372
373 Headers { retry_after }
374}