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