use reqwest::{self, Method, RequestBuilder, header::HeaderMap};
use crate::{
SensitiveString,
crypto::sign_query,
serde::{deserialize_json, serialize_query},
spot::{
ApiError, Error, HEADER_RETRY_AFTER, HEADER_X_MBX_APIKEY, Path,
http::{
AccountInformation, AggregateTrade, CurrentAveragePrice, ExchangeInfo,
GetAccountInformationParams, GetAggregateTradesParams, GetCurrentAveragePriceParams,
GetExchangeInfoParams, GetKlineListParams, GetOlderTradesParams, GetOrderBookParams,
GetRecentTradesParams, GetTickerPriceChangeStatisticsParams, Headers, Kline,
NewOrderRequest, NewOrderResponse, Order, OrderBook, PrivateConfig, PublicConfig,
QueryOrderParams, RecentTrade, Response, ServerTime, TestCommissionRates,
TestConnectivity, TickerPriceChangeStatistic,
},
},
timestamp,
};
pub struct PublicClient {
base_url: String,
headers: HeaderMap,
}
impl PublicClient {
pub fn new(cfg: PublicConfig) -> Self {
Self {
base_url: cfg.base_url,
headers: cfg.headers.unwrap_or_default(),
}
}
}
impl PublicClient {
pub async fn test_connectivity(&self) -> Result<Response<TestConnectivity>, Error> {
let url = format!("{}{}", self.base_url, Path::Ping);
let client = reqwest::Client::builder().build()?;
let request = client
.request(Method::GET, url)
.headers(self.headers.clone());
send(request).await
}
pub async fn get_server_time(&self) -> Result<Response<ServerTime>, Error> {
let url = format!("{}{}", self.base_url, Path::Time);
let client = reqwest::Client::builder().build()?;
let request = client
.request(Method::GET, url)
.headers(self.headers.clone());
send(request).await
}
pub async fn get_exchange_info(
&self,
params: GetExchangeInfoParams,
) -> Result<Response<ExchangeInfo>, Error> {
let url = format!("{}{}", self.base_url, Path::ExchangeInfo);
let client = reqwest::Client::builder().build()?;
let request = client
.request(Method::GET, url)
.headers(self.headers.clone())
.query(¶ms);
send(request).await
}
}
impl PublicClient {
pub async fn get_order_book(
&self,
params: GetOrderBookParams,
) -> Result<Response<OrderBook>, Error> {
let url = format!("{}{}", self.base_url, Path::Depth);
let client = reqwest::Client::builder().build()?;
let request = client
.request(Method::GET, url)
.headers(self.headers.clone())
.query(¶ms);
send(request).await
}
pub async fn recent_trades_list(
&self,
params: GetRecentTradesParams,
) -> Result<Response<Vec<RecentTrade>>, Error> {
let url = format!("{}{}", self.base_url, Path::Trades);
let client = reqwest::Client::builder().build()?;
let request = client
.request(Method::GET, url)
.headers(self.headers.clone())
.query(¶ms);
send(request).await
}
pub async fn old_trade_lookup(
&self,
params: GetOlderTradesParams,
) -> Result<Response<Vec<RecentTrade>>, Error> {
let url = format!("{}{}", self.base_url, Path::HistoricalTrades);
let client = reqwest::Client::builder().build()?;
let request = client
.request(Method::GET, url)
.headers(self.headers.clone())
.query(¶ms);
send(request).await
}
pub async fn aggregate_trades_list(
&self,
params: GetAggregateTradesParams,
) -> Result<Response<Vec<AggregateTrade>>, Error> {
let url = format!("{}{}", self.base_url, Path::AggTrades);
let client = reqwest::Client::builder().build()?;
let request = client
.request(Method::GET, url)
.headers(self.headers.clone())
.query(¶ms);
send(request).await
}
pub async fn get_kline_list(
&self,
params: GetKlineListParams,
) -> Result<Response<Vec<Kline>>, Error> {
let url = format!("{}{}", self.base_url, Path::KLines);
let client = reqwest::Client::builder().build()?;
let request = client
.request(Method::GET, url)
.headers(self.headers.clone())
.query(¶ms);
send(request).await
}
pub async fn get_ui_kline_list(
&self,
params: GetKlineListParams,
) -> Result<Response<Vec<Kline>>, Error> {
let url = format!("{}{}", self.base_url, Path::UIKLines);
let client = reqwest::Client::builder().build()?;
let request = client
.request(Method::GET, url)
.headers(self.headers.clone())
.query(¶ms);
send(request).await
}
pub async fn get_current_average_price(
&self,
params: GetCurrentAveragePriceParams,
) -> Result<Response<CurrentAveragePrice>, Error> {
let url = format!("{}{}", self.base_url, Path::AvgPrice);
let client = reqwest::Client::builder().build()?;
let request = client
.request(Method::GET, url)
.headers(self.headers.clone())
.query(¶ms);
send(request).await
}
pub async fn ticker_price_change_statistics(
&self,
params: GetTickerPriceChangeStatisticsParams,
) -> Result<Response<TickerPriceChangeStatistic>, Error> {
let url = format!("{}{}", self.base_url, Path::Ticker24hr);
let client = reqwest::Client::builder().build()?;
let request = client
.request(Method::GET, url)
.headers(self.headers.clone())
.query(¶ms);
send(request).await
}
}
pub struct PrivateClient {
base_url: String,
headers: HeaderMap,
api_secret: SensitiveString,
}
impl PrivateClient {
pub fn new(cfg: PrivateConfig) -> Self {
let headers = {
let mut headers = HeaderMap::new();
let api_key = cfg.api_key.expose().parse().unwrap();
headers.append(HEADER_X_MBX_APIKEY, api_key);
if let Some(extra_headers) = cfg.headers {
headers.extend(extra_headers);
}
headers
};
Self {
base_url: cfg.base_url,
headers,
api_secret: cfg.api_secret,
}
}
}
impl PrivateClient {
pub async fn new_order(
&self,
params: NewOrderRequest,
) -> Result<Response<NewOrderResponse>, Error> {
let query = serialize_query(¶ms)?;
let query = sign_query(&self.api_secret, timestamp(), &query);
let url = format!("{}{}", self.base_url, Path::Order);
let client = reqwest::Client::builder().build()?;
let request = client
.request(Method::POST, url)
.headers(self.headers.clone())
.body(query);
send(request).await
}
pub async fn test_new_order(
&self,
params: NewOrderRequest,
) -> Result<Response<TestCommissionRates>, Error> {
let query = serialize_query(¶ms)?;
let query = sign_query(&self.api_secret, timestamp(), &query);
let url = format!("{}{}", self.base_url, Path::OrderTest);
let client = reqwest::Client::builder().build()?;
let request = client
.request(Method::POST, url)
.headers(self.headers.clone())
.body(query);
send(request).await
}
}
impl PrivateClient {
pub async fn account_information(
&self,
params: GetAccountInformationParams,
) -> Result<Response<AccountInformation>, Error> {
let query = serialize_query(¶ms)?;
let query = sign_query(&self.api_secret, timestamp(), &query);
let url = format!("{}{}?{query}", self.base_url, Path::Account);
let client = reqwest::Client::builder().build()?;
let request = client
.request(Method::GET, url)
.headers(self.headers.clone());
send(request).await
}
pub async fn query_order(&self, params: QueryOrderParams) -> Result<Response<Order>, Error> {
let query = serialize_query(¶ms)?;
let query = sign_query(&self.api_secret, timestamp(), &query);
let url = format!("{}{}?{query}", self.base_url, Path::Order);
let client = reqwest::Client::builder().build()?;
let request = client
.request(Method::GET, url)
.headers(self.headers.clone());
send(request).await
}
}
async fn send<T>(request: RequestBuilder) -> Result<Response<T>, Error>
where
T: serde::de::DeserializeOwned,
{
let response = request.send().await?;
let status = response.status();
let headers = parse_headers(response.headers());
let json = response.text().await?;
if !status.is_success() {
#[cfg(debug_assertions)]
tracing::debug!(?status, ?json, "request failed");
let api_err = deserialize_json::<ApiError>(&json)?;
return Err(Error::Api(api_err));
}
let result = deserialize_json(&json)?;
Ok(Response { result, headers })
}
fn parse_headers(headers: &HeaderMap) -> Headers {
let retry_after = headers
.get(HEADER_RETRY_AFTER)
.and_then(|h| h.to_str().unwrap_or_default().parse().ok());
Headers { retry_after }
}