binance_async_api/rest/
mod.rs

1pub 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; // SIGNED imples KEYED no matter KEYED is true or false
27    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(&params, &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    // Signature: hex(HMAC_SHA256(queries + data))
109    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}