cefi_rs_bybit/
http.rs

1use crate::{
2    errors::{BybitError, BybitResult},
3    types::*,
4};
5use chrono::Utc;
6use hmac::{Hmac, Mac};
7use reqwest::{Client, RequestBuilder};
8use serde_json::{Map, Value};
9use sha2::Sha256;
10use std::collections::HashMap;
11
12static BYBIT_HOST: &'static str = "https://api.bybit.com";
13static RECV_WINDOW: &'static str = "5000";
14
15pub struct BybitHttp {
16    api_key: String,
17    api_secret: String,
18    client: Client,
19}
20
21impl BybitHttp {
22    pub fn new(api_key: String, api_secret: String) -> Self {
23        Self {
24            api_key,
25            api_secret,
26            client: Client::builder()
27                .timeout(std::time::Duration::from_secs(10))
28                .build()
29                .expect("reqwest client"),
30        }
31    }
32
33    fn generate_get_signature(
34        &self,
35        timestamp: &str,
36        params: &HashMap<&str, &str>,
37    ) -> BybitResult<String> {
38        let mut mac = Hmac::<Sha256>::new_from_slice(self.api_secret.as_bytes())
39            .expect("HMAC can take key of any size");
40        mac.update(timestamp.as_bytes());
41        mac.update(self.api_key.as_bytes());
42        mac.update(RECV_WINDOW.as_bytes());
43        mac.update(Self::generate_query_str(params).as_bytes());
44
45        let result = mac.finalize();
46        let code_bytes = result.into_bytes();
47        Ok(hex::encode(code_bytes))
48    }
49
50    fn generate_post_signature(
51        &self,
52        timestamp: &str,
53        params: &Map<String, Value>,
54    ) -> BybitResult<String> {
55        let mut mac = Hmac::<Sha256>::new_from_slice(self.api_secret.as_bytes())
56            .expect("HMAC can take key of any size");
57        mac.update(timestamp.as_bytes());
58        mac.update(self.api_key.as_bytes());
59        mac.update(RECV_WINDOW.as_bytes());
60        mac.update(serde_json::to_string(&params)?.as_bytes());
61
62        let result = mac.finalize();
63        let code_bytes = result.into_bytes();
64        Ok(hex::encode(code_bytes))
65    }
66
67    fn generate_query_str(params: &HashMap<&str, &str>) -> String {
68        params
69            .iter()
70            .map(|(key, value)| format!("{}={}", key, value))
71            .collect::<Vec<String>>()
72            .join("&")
73    }
74
75    fn add_headers(
76        &self,
77        builder: RequestBuilder,
78        timestamp: &String,
79        signature: &String,
80    ) -> RequestBuilder {
81        builder
82            .header("X-BAPI-API-KEY", self.api_key.clone())
83            .header("X-BAPI-SIGN", signature)
84            .header("X-BAPI-SIGN-TYPE", "2")
85            .header("X-BAPI-TIMESTAMP", timestamp.clone())
86            .header("X-BAPI-RECV-WINDOW", RECV_WINDOW)
87            .header("Content-Type", "application/json")
88    }
89
90    pub(crate) async fn send_get_request<T>(
91        &self,
92        endpoint: &str,
93        params: HashMap<&str, &str>,
94        is_auth: bool,
95    ) -> BybitResult<T>
96    where
97        T: for<'a> serde::Deserialize<'a>,
98    {
99        let query_str = Self::generate_query_str(&params);
100
101        let mut builder = self
102            .client
103            .get(&format!("{}/{}?{}", BYBIT_HOST, endpoint, query_str));
104
105        if is_auth {
106            let timestamp = Utc::now().timestamp_millis().to_string();
107            let signature = self.generate_get_signature(&timestamp, &params)?;
108            builder = self.add_headers(builder, &timestamp, &signature)
109        }
110
111        let response = builder
112            .send()
113            .await
114            .map_err(|err| BybitError::Unknown(format!("{}", err)))?;
115        let res = &response
116            .text()
117            .await
118            .map_err(|err| BybitError::Unknown(format!("{}", err)))?;
119        let res = serde_json::from_str::<BybitHttpResponse>(res)
120            .map_err(|err| BybitError::DeserializeError(format!("{res}: {err}")))?;
121        match res.ret_code {
122            0 => {
123                let value_str = &res.result.to_string();
124                let res = serde_json::from_str::<T>(value_str)
125                    .map_err(|err| BybitError::DeserializeError(format!("{value_str}: {err}")))?;
126                Ok(res)
127            }
128            _ => Err(BybitError::ApiError(res.ret_code, res.ret_msg)),
129        }
130    }
131
132    pub(crate) async fn send_post_request<T>(
133        &self,
134        endpoint: &str,
135        params: Map<String, Value>,
136    ) -> BybitResult<T>
137    where
138        T: for<'a> serde::Deserialize<'a>,
139    {
140        let timestamp = Utc::now().timestamp_millis().to_string();
141        let signature = self.generate_post_signature(&timestamp, &params)?;
142
143        let builder = self
144            .client
145            .post(format!("{BYBIT_HOST}/{}", endpoint))
146            .json(&params);
147        let builder = self.add_headers(builder, &timestamp, &signature);
148        let response = builder
149            .send()
150            .await
151            .map_err(|err| BybitError::Unknown(format!("{}", err)))?;
152        let res = &response
153            .text()
154            .await
155            .map_err(|err| BybitError::Unknown(format!("{}", err)))?;
156        let res = serde_json::from_str::<BybitHttpResponse>(res)
157            .map_err(|err| BybitError::DeserializeError(format!("{res}: {err}")))?;
158        match res.ret_code {
159            0 => {
160                let value_str = &res.result.to_string();
161                let res = serde_json::from_str::<T>(value_str)
162                    .map_err(|err| BybitError::DeserializeError(format!("{value_str}: {err}")))?;
163                Ok(res)
164            }
165            _ => Err(BybitError::ApiError(res.ret_code, res.ret_msg)),
166        }
167    }
168}