binance_async_api/rest/
mod.rs1pub mod coinm;
2pub mod spot;
3pub mod usdm;
4
5use std::str::FromStr;
6
7use crate::{
8 client::{BinanceClient, Product},
9 errors::{BinanceError, BinanceResponse, BinanceResponseContent},
10};
11use chrono::Utc;
12use hex::encode as hexify;
13use hmac::{Hmac, Mac};
14use reqwest::{
15 header::{HeaderMap, HeaderName, HeaderValue, CONTENT_TYPE, USER_AGENT},
16 Method, Response,
17};
18use serde::{de::DeserializeOwned, Serialize};
19use serde_json::{from_str, Value};
20use sha2::Sha256;
21
22pub trait Request: Serialize {
23 const PRODUCT: Product;
24 const ENDPOINT: &'static str;
25 const METHOD: Method;
26 const KEYED: bool = false; const SIGNED: bool = false;
28 type Response: DeserializeOwned;
29}
30
31impl BinanceClient {
32 pub async fn request<R>(
33 &self,
34 req: R,
35 api_key: Option<&str>,
36 api_secret: Option<&str>,
37 ) -> Result<BinanceResponse<R::Response>, BinanceError>
38 where
39 R: Request,
40 {
41 let mut params = if matches!(R::METHOD, Method::GET) {
42 serde_qs::to_string(&req).unwrap()
43 } else {
44 String::new()
45 };
46
47 let body = if !matches!(R::METHOD, Method::GET) {
48 serde_qs::to_string(&req).unwrap()
49 } else {
50 String::new()
51 };
52
53 if R::SIGNED {
54 let secret = match api_secret {
55 Some(s) => s,
56 None => return Err(BinanceError::MissingApiSecret),
57 };
58 if !params.is_empty() {
59 params.push('&');
60 }
61 params.push_str(&format!("timestamp={}", Utc::now().timestamp_millis()));
62
63 let signature = signature(¶ms, &body, secret);
64 params.push_str(&format!("&signature={}", signature));
65 }
66
67 let path = R::ENDPOINT;
68
69 let base = match R::PRODUCT {
70 Product::Spot => self.config.rest_api_endpoint,
71 Product::UsdMFutures => self.config.usdm_futures_rest_api_endpoint,
72 Product::CoinMFutures => self.config.coinm_futures_rest_api_endpoint,
73 };
74 let url = format!("{base}{path}?{params}");
75
76 let mut custom_headers = HeaderMap::new();
77 custom_headers.insert(USER_AGENT, HeaderValue::from_static("binance-async-api"));
78 if !body.is_empty() {
79 custom_headers.insert(
80 CONTENT_TYPE,
81 HeaderValue::from_static("application/x-www-form-urlencoded"),
82 );
83 }
84 if R::SIGNED || R::KEYED {
85 let key = match api_key {
86 Some(key) => key,
87 None => return Err(BinanceError::MissingApiKey),
88 };
89 custom_headers.insert(
90 HeaderName::from_static("x-mbx-apikey"),
91 HeaderValue::from_str(key).map_err(|_| BinanceError::InvalidApiKey)?,
92 );
93 }
94
95 let resp = self
96 .client
97 .request(R::METHOD, url.as_str())
98 .headers(custom_headers)
99 .body(body)
100 .send()
101 .await?;
102
103 handle_response(resp).await
104 }
105}
106
107fn signature(params: &str, body: &str, secret: &str) -> String {
108 let mut mac = Hmac::<Sha256>::new_from_slice(secret.as_bytes()).unwrap();
110 let sign_message = format!("{}{}", params, body);
111 mac.update(sign_message.as_bytes());
112 hexify(mac.finalize().into_bytes())
113}
114
115async fn handle_response<O: DeserializeOwned>(resp: Response) -> Result<BinanceResponse<O>, BinanceError> {
116 let status_code = resp.status();
117 let headers = resp.headers().clone();
118 let resp_text = resp.text().await?;
119 match from_str(&resp_text) {
120 Ok(BinanceResponseContent::Success(content)) => Ok(BinanceResponse {
121 status_code,
122 headers,
123 content,
124 }),
125 Ok(BinanceResponseContent::Error(content)) => Err(BinanceError::BinanceResponse {
126 status_code,
127 headers,
128 content,
129 }),
130 Err(e) => {
131 let val = Value::from_str(&resp_text).unwrap();
132 eprintln!("Failed to parse response:");
133 eprintln!("{:#?}", val.as_object().unwrap());
134 panic!("parsing error: {}", e);
135 },
136 }
137}