1use reqwest::{self, Method, RequestBuilder, header::HeaderMap};
2use tracing::debug;
3
4use crate::{
5 SensitiveString,
6 crypto::sign_query,
7 margin::{
8 ApiError, Error, HEADER_RETRY_AFTER, HEADER_X_MBX_APIKEY, Path,
9 http::{
10 EmptyResponse, GetAllMarginAssetsParams, GetMarginAccountParams,
11 GetMaxBorrowableParams, Headers, ListenKey, MarginAccount, MarginAsset, MaxBorrowable,
12 NewOrderRequest, NewOrderResponse, Order, PrivateConfig, QueryOrderParams, Response,
13 },
14 },
15 serde::{deserialize_json, serialize_query},
16 timestamp,
17};
18
19pub struct PrivateClient {
25 base_url: String,
26 headers: HeaderMap,
27 api_secret: SensitiveString,
28}
29
30impl PrivateClient {
31 pub fn new(cfg: PrivateConfig) -> Self {
32 let headers = {
33 let mut headers = HeaderMap::new();
34 let api_key = cfg.api_key.expose().parse().unwrap();
35 headers.append(HEADER_X_MBX_APIKEY, api_key);
36 if let Some(extra) = cfg.headers {
37 headers.extend(extra);
38 }
39 headers
40 };
41 Self {
42 base_url: cfg.base_url,
43 headers,
44 api_secret: cfg.api_secret,
45 }
46 }
47}
48
49impl PrivateClient {
51 pub async fn get_all_assets(
53 &self,
54 params: GetAllMarginAssetsParams,
55 ) -> Result<Response<Vec<MarginAsset>>, 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::AllAssets);
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
67impl PrivateClient {
69 pub async fn margin_account(
71 &self,
72 params: GetMarginAccountParams,
73 ) -> Result<Response<MarginAccount>, Error> {
74 let query = serialize_query(¶ms)?;
75 let query = sign_query(&self.api_secret, timestamp(), &query);
76 let url = format!("{}{}?{query}", self.base_url, Path::Account);
77 let client = reqwest::Client::builder().build()?;
78 let request = client
79 .request(Method::GET, url)
80 .headers(self.headers.clone());
81 send(request).await
82 }
83}
84
85impl PrivateClient {
87 pub async fn new_order(
94 &self,
95 params: NewOrderRequest,
96 ) -> Result<Response<NewOrderResponse>, Error> {
97 let query = serialize_query(¶ms)?;
98 let query = sign_query(&self.api_secret, timestamp(), &query);
99 let url = format!("{}{}", self.base_url, Path::Order);
100 let client = reqwest::Client::builder().build()?;
101 let request = client
102 .request(Method::POST, url)
103 .headers(self.headers.clone())
104 .body(query);
105 send(request).await
106 }
107
108 pub async fn query_order(&self, params: QueryOrderParams) -> Result<Response<Order>, Error> {
110 let query = serialize_query(¶ms)?;
111 let query = sign_query(&self.api_secret, timestamp(), &query);
112 let url = format!("{}{}?{query}", self.base_url, Path::Order);
113 let client = reqwest::Client::builder().build()?;
114 let request = client
115 .request(Method::GET, url)
116 .headers(self.headers.clone());
117 send(request).await
118 }
119}
120
121impl PrivateClient {
123 pub async fn max_borrowable(
128 &self,
129 params: GetMaxBorrowableParams,
130 ) -> Result<Response<MaxBorrowable>, Error> {
131 let query = serialize_query(¶ms)?;
132 let query = sign_query(&self.api_secret, timestamp(), &query);
133 let url = format!("{}{}?{query}", self.base_url, Path::MaxBorrowable);
134 let client = reqwest::Client::builder().build()?;
135 let request = client
136 .request(Method::GET, url)
137 .headers(self.headers.clone());
138 send(request).await
139 }
140}
141
142impl PrivateClient {
148 pub async fn create_listen_key(&self) -> Result<Response<ListenKey>, Error> {
154 let url = format!("{}{}", self.base_url, Path::UserDataStream);
155 let client = reqwest::Client::builder().build()?;
156 let request = client
157 .request(Method::POST, url)
158 .headers(self.headers.clone());
159 send(request).await
160 }
161
162 pub async fn keepalive_listen_key(
165 &self,
166 listen_key: &str,
167 ) -> Result<Response<EmptyResponse>, Error> {
168 let url = format!("{}{}", self.base_url, Path::UserDataStream);
169 let client = reqwest::Client::builder().build()?;
170 let request = client
171 .request(Method::PUT, url)
172 .headers(self.headers.clone())
173 .query(&[("listenKey", listen_key)]);
174 send(request).await
175 }
176
177 pub async fn close_listen_key(
180 &self,
181 listen_key: &str,
182 ) -> Result<Response<EmptyResponse>, Error> {
183 let url = format!("{}{}", self.base_url, Path::UserDataStream);
184 let client = reqwest::Client::builder().build()?;
185 let request = client
186 .request(Method::DELETE, url)
187 .headers(self.headers.clone())
188 .query(&[("listenKey", listen_key)]);
189 send(request).await
190 }
191}
192
193impl PrivateClient {
196 pub async fn create_isolated_listen_key(
199 &self,
200 symbol: &str,
201 ) -> Result<Response<ListenKey>, Error> {
202 let url = format!("{}{}", self.base_url, Path::UserDataStreamIsolated);
203 let client = reqwest::Client::builder().build()?;
204 let request = client
205 .request(Method::POST, url)
206 .headers(self.headers.clone())
207 .query(&[("symbol", symbol)]);
208 send(request).await
209 }
210
211 pub async fn keepalive_isolated_listen_key(
213 &self,
214 symbol: &str,
215 listen_key: &str,
216 ) -> Result<Response<EmptyResponse>, Error> {
217 let url = format!("{}{}", self.base_url, Path::UserDataStreamIsolated);
218 let client = reqwest::Client::builder().build()?;
219 let request = client
220 .request(Method::PUT, url)
221 .headers(self.headers.clone())
222 .query(&[("symbol", symbol), ("listenKey", listen_key)]);
223 send(request).await
224 }
225
226 pub async fn close_isolated_listen_key(
228 &self,
229 symbol: &str,
230 listen_key: &str,
231 ) -> Result<Response<EmptyResponse>, Error> {
232 let url = format!("{}{}", self.base_url, Path::UserDataStreamIsolated);
233 let client = reqwest::Client::builder().build()?;
234 let request = client
235 .request(Method::DELETE, url)
236 .headers(self.headers.clone())
237 .query(&[("symbol", symbol), ("listenKey", listen_key)]);
238 send(request).await
239 }
240}
241
242async fn send<T>(request: RequestBuilder) -> Result<Response<T>, Error>
243where
244 T: serde::de::DeserializeOwned,
245{
246 let response = request.send().await?;
247 let status = response.status();
248 let headers = parse_headers(response.headers());
249 let json = response.text().await?;
250
251 if !status.is_success() {
252 #[cfg(debug_assertions)]
253 debug!(?status, ?json, "request failed");
254
255 let api_err = deserialize_json::<ApiError>(&json)?;
256 return Err(Error::Api(api_err));
257 }
258
259 let result = deserialize_json(&json)?;
260 Ok(Response { result, headers })
261}
262
263fn parse_headers(headers: &HeaderMap) -> Headers {
264 let retry_after = headers
265 .get(HEADER_RETRY_AFTER)
266 .and_then(|h| h.to_str().unwrap_or_default().parse().ok());
267 Headers { retry_after }
268}