1use reqwest::{self, Method, RequestBuilder, header::HeaderMap};
2
3use crate::{
4 SensitiveString,
5 crypto::sign_query,
6 serde::{deserialize_json, serialize_query},
7 timestamp,
8 wallet::{
9 ApiError, Error, HEADER_RETRY_AFTER, HEADER_X_MBX_APIKEY, Path,
10 http::{
11 AccountStatus, CoinInfo, Deposit, DepositAddress, GetAccountStatusParams,
12 GetAllCoinsParams, GetDepositAddressParams, GetDepositHistoryParams, GetTradeFeeParams,
13 GetWithdrawHistoryParams, Headers, PrivateConfig, Response, TradeFee, Withdraw,
14 },
15 },
16};
17
18pub struct PrivateClient {
24 base_url: String,
25 headers: HeaderMap,
26 api_secret: SensitiveString,
27}
28
29impl PrivateClient {
30 pub fn new(cfg: PrivateConfig) -> Self {
31 let headers = {
32 let mut headers = HeaderMap::new();
33 let api_key = cfg.api_key.expose().parse().unwrap();
34 headers.append(HEADER_X_MBX_APIKEY, api_key);
35 if let Some(extra) = cfg.headers {
36 headers.extend(extra);
37 }
38 headers
39 };
40 Self {
41 base_url: cfg.base_url,
42 headers,
43 api_secret: cfg.api_secret,
44 }
45 }
46}
47
48impl PrivateClient {
50 pub async fn get_all_coins(
53 &self,
54 params: GetAllCoinsParams,
55 ) -> Result<Response<Vec<CoinInfo>>, Error> {
56 let query = serialize_query(¶ms)?;
57 let query = sign_query(&self.api_secret, timestamp(), &query);
58 let url = format!("{}{}?{query}", self.base_url, Path::CapitalConfigGetAll);
59 let client = reqwest::Client::builder().build()?;
60 let request = client
61 .request(Method::GET, url)
62 .headers(self.headers.clone());
63 send(request).await
64 }
65
66 pub async fn get_deposit_address(
68 &self,
69 params: GetDepositAddressParams,
70 ) -> Result<Response<DepositAddress>, Error> {
71 let query = serialize_query(¶ms)?;
72 let query = sign_query(&self.api_secret, timestamp(), &query);
73 let url = format!("{}{}?{query}", self.base_url, Path::CapitalDepositAddress);
74 let client = reqwest::Client::builder().build()?;
75 let request = client
76 .request(Method::GET, url)
77 .headers(self.headers.clone());
78 send(request).await
79 }
80
81 pub async fn get_deposit_history(
83 &self,
84 params: GetDepositHistoryParams,
85 ) -> Result<Response<Vec<Deposit>>, Error> {
86 let query = serialize_query(¶ms)?;
87 let query = sign_query(&self.api_secret, timestamp(), &query);
88 let url = format!("{}{}?{query}", self.base_url, Path::CapitalDepositHistory);
89 let client = reqwest::Client::builder().build()?;
90 let request = client
91 .request(Method::GET, url)
92 .headers(self.headers.clone());
93 send(request).await
94 }
95
96 pub async fn get_withdraw_history(
98 &self,
99 params: GetWithdrawHistoryParams,
100 ) -> Result<Response<Vec<Withdraw>>, Error> {
101 let query = serialize_query(¶ms)?;
102 let query = sign_query(&self.api_secret, timestamp(), &query);
103 let url = format!("{}{}?{query}", self.base_url, Path::CapitalWithdrawHistory);
104 let client = reqwest::Client::builder().build()?;
105 let request = client
106 .request(Method::GET, url)
107 .headers(self.headers.clone());
108 send(request).await
109 }
110}
111
112impl PrivateClient {
114 pub async fn get_account_status(
116 &self,
117 params: GetAccountStatusParams,
118 ) -> Result<Response<AccountStatus>, Error> {
119 let query = serialize_query(¶ms)?;
120 let query = sign_query(&self.api_secret, timestamp(), &query);
121 let url = format!("{}{}?{query}", self.base_url, Path::AccountStatus);
122 let client = reqwest::Client::builder().build()?;
123 let request = client
124 .request(Method::GET, url)
125 .headers(self.headers.clone());
126 send(request).await
127 }
128}
129
130impl PrivateClient {
132 pub async fn get_trade_fee(
134 &self,
135 params: GetTradeFeeParams,
136 ) -> Result<Response<Vec<TradeFee>>, Error> {
137 let query = serialize_query(¶ms)?;
138 let query = sign_query(&self.api_secret, timestamp(), &query);
139 let url = format!("{}{}?{query}", self.base_url, Path::AssetTradeFee);
140 let client = reqwest::Client::builder().build()?;
141 let request = client
142 .request(Method::GET, url)
143 .headers(self.headers.clone());
144 send(request).await
145 }
146}
147
148async fn send<T>(request: RequestBuilder) -> Result<Response<T>, Error>
149where
150 T: serde::de::DeserializeOwned,
151{
152 let response = request.send().await?;
153 let status = response.status();
154 let headers = parse_headers(response.headers());
155 let json = response.text().await?;
156
157 if !status.is_success() {
158 #[cfg(debug_assertions)]
159 tracing::debug!(?status, ?json, "request failed");
160
161 let api_err = deserialize_json::<ApiError>(&json)?;
163 return Err(Error::Api(api_err));
164 }
165
166 let result = deserialize_json(&json)?;
167 Ok(Response { result, headers })
168}
169
170fn parse_headers(headers: &HeaderMap) -> Headers {
171 let retry_after = headers
172 .get(HEADER_RETRY_AFTER)
173 .and_then(|h| h.to_str().unwrap_or_default().parse().ok());
174 Headers { retry_after }
175}