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