binance/spot/
client.rs

1use reqwest::{self, Method, RequestBuilder, header::HeaderMap};
2
3use crate::spot::{
4    AggregateTrade, CurrentAveragePrice, GetAggregateTradesParams, GetCurrentAveragePriceParams,
5    GetKlineListParams, GetOlderTradesParams, GetOrderBookParams, GetRecentTradesParams, Kline,
6    OrderBook, RecentTrade, TestConnectivity,
7};
8
9use super::{
10    Error, ExchangeInfo, GetExchangeInfoParams, Headers, Response, ServerTime,
11    crypto::SensitiveString, serde::deserialize_str, url::*,
12};
13
14pub struct ClientConfig {
15    pub base_url: String,
16    pub api_key: Option<SensitiveString>,
17    pub api_secret: Option<SensitiveString>,
18}
19
20pub struct Client {
21    base_url: String,
22    cfg: ClientConfig,
23}
24
25impl Client {
26    pub fn new(cfg: ClientConfig) -> Self {
27        Self {
28            base_url: cfg.base_url.clone(),
29            cfg,
30        }
31    }
32}
33
34// General.
35impl Client {
36    /// Test connectivity to the Rest API.
37    pub async fn test_connectivity(&self) -> Result<Response<TestConnectivity>, Error> {
38        let url = format!("{}{}", self.base_url, Path::Time);
39
40        let client = reqwest::Client::builder().build()?;
41        let request = client.request(Method::GET, url);
42
43        let response = send(request).await?;
44        Ok(response)
45    }
46
47    pub async fn get_server_time(&self) -> Result<Response<ServerTime>, Error> {
48        let url = format!("{}{}", self.base_url, Path::Time);
49
50        let client = reqwest::Client::builder().build()?;
51        let request = client.request(Method::GET, url);
52
53        let response = send(request).await?;
54        Ok(response)
55    }
56
57    pub async fn get_exchange_info(
58        &self,
59        params: GetExchangeInfoParams,
60    ) -> Result<Response<ExchangeInfo>, Error> {
61        let query = serde_urlencoded::to_string(&params)?;
62        let url = format!("{}{}?{query}", self.base_url, Path::ExchangeInfo);
63
64        let client = reqwest::Client::builder().build()?;
65        let request = client.request(Method::GET, url);
66
67        let response = send(request).await?;
68        Ok(response)
69    }
70}
71
72// Market Data.
73impl Client {
74    pub async fn get_order_book(
75        &self,
76        params: GetOrderBookParams,
77    ) -> Result<Response<OrderBook>, Error> {
78        let query = serde_urlencoded::to_string(&params)?;
79        let url = format!("{}{}?{query}", self.base_url, Path::ExchangeInfo);
80
81        let client = reqwest::Client::builder().build()?;
82        let request = client.request(Method::GET, url);
83
84        let response = send(request).await?;
85        Ok(response)
86    }
87
88    /// Get recent trades.
89    pub async fn recent_trades_list(
90        &self,
91        params: GetRecentTradesParams,
92    ) -> Result<Response<Vec<RecentTrade>>, Error> {
93        let query = serde_urlencoded::to_string(&params)?;
94        let url = format!("{}{}?{query}", self.base_url, Path::Trades);
95
96        let client = reqwest::Client::builder().build()?;
97        let request = client.request(Method::GET, url);
98
99        let response = send(request).await?;
100        Ok(response)
101    }
102
103    /// Get older trades.
104    pub async fn old_trade_lookup(
105        &self,
106        params: GetOlderTradesParams,
107    ) -> Result<Response<Vec<RecentTrade>>, Error> {
108        let query = serde_urlencoded::to_string(&params)?;
109        let url = format!("{}{}?{query}", self.base_url, Path::HistoricalTrades);
110
111        let client = reqwest::Client::builder().build()?;
112        let request = client.request(Method::GET, url);
113
114        let response = send(request).await?;
115        Ok(response)
116    }
117
118    /// Compressed/Aggregate trades list.
119    /// Get compressed, aggregate trades. Trades that fill at the time, from the same taker order, with the same price will have the quantity aggregated.
120    ///
121    /// If fromId, startTime, and endTime are not sent, the most recent aggregate trades will be returned.
122    pub async fn aggregate_trades_list(
123        &self,
124        params: GetAggregateTradesParams,
125    ) -> Result<Response<Vec<AggregateTrade>>, Error> {
126        let query = serde_urlencoded::to_string(&params)?;
127        let url = format!("{}{}?{query}", self.base_url, Path::AggTrades);
128
129        let client = reqwest::Client::builder().build()?;
130        let request = client.request(Method::GET, url);
131
132        let response = send(request).await?;
133        Ok(response)
134    }
135
136    /// Kline/candlestick bars for a symbol. Klines are uniquely identified by their open time.
137    ///
138    /// If startTime and endTime are not sent, the most recent klines are returned.
139    /// Supported values for timeZone:
140    /// Hours and minutes (e.g. -1:00, 05:45)
141    /// Only hours (e.g. 0, 8, 4)
142    /// Accepted range is strictly [-12:00 to +14:00] inclusive
143    /// If timeZone provided, kline intervals are interpreted in that timezone instead of UTC.
144    /// Note that startTime and endTime are always interpreted in UTC, regardless of timeZone.
145    pub async fn get_kline_list(
146        &self,
147        params: GetKlineListParams,
148    ) -> Result<Response<Vec<Kline>>, Error> {
149        let query = serde_urlencoded::to_string(&params)?;
150        let url = format!("{}{}?{query}", self.base_url, Path::KLines);
151
152        let client = reqwest::Client::builder().build()?;
153        let request = client.request(Method::GET, url);
154
155        let response = send(request).await?;
156        Ok(response)
157    }
158
159    /// UIKlines
160    ///
161    /// The request is similar to klines having the same parameters and response.
162    /// uiKlines return modified kline data, optimized for presentation of candlestick charts.
163    ///
164    /// If startTime and endTime are not sent, the most recent klines are returned.
165    /// Supported values for timeZone:
166    /// Hours and minutes (e.g. -1:00, 05:45)
167    /// Only hours (e.g. 0, 8, 4)
168    /// Accepted range is strictly [-12:00 to +14:00] inclusive
169    /// If timeZone provided, kline intervals are interpreted in that timezone instead of UTC.
170    /// Note that startTime and endTime are always interpreted in UTC, regardless of timeZone.
171    pub async fn get_ui_kline_list(
172        &self,
173        params: GetKlineListParams,
174    ) -> Result<Response<Vec<Kline>>, Error> {
175        let query = serde_urlencoded::to_string(&params)?;
176        let url = format!("{}{}?{query}", self.base_url, Path::UIKLines);
177
178        let client = reqwest::Client::builder().build()?;
179        let request = client.request(Method::GET, url);
180
181        let response = send(request).await?;
182        Ok(response)
183    }
184
185    /// Current average price for a symbol.
186    pub async fn get_current_average_price(
187        &self,
188        params: GetCurrentAveragePriceParams,
189    ) -> Result<Response<CurrentAveragePrice>, Error> {
190        let query = serde_urlencoded::to_string(&params)?;
191        let url = format!("{}{}?{query}", self.base_url, Path::AvgPrice);
192
193        let client = reqwest::Client::builder().build()?;
194        let request = client.request(Method::GET, url);
195
196        let response = send(request).await?;
197        Ok(response)
198    }
199}
200
201async fn send<T>(request: RequestBuilder) -> Result<Response<T>, Error>
202where
203    T: serde::de::DeserializeOwned,
204{
205    let response = request.send().await?;
206    let headers = parse_headers(&response.headers());
207    let json = response.text().await?;
208
209    let result = deserialize_str(&json)?;
210    let response = Response { result, headers };
211    Ok(response)
212}
213
214/// Parse response headers: Retry-After
215fn parse_headers(headers: &HeaderMap) -> Headers {
216    let retry_after = headers
217        .get(HEADER_RETRY_AFTER)
218        .map(|h| h.to_str().unwrap_or_default().parse().ok())
219        .flatten();
220
221    Headers { retry_after }
222}