xapi_binance/common/
signer.rs1use chrono::Utc;
2use hmac::{Hmac, Mac};
3use redact::Secret;
4use reqwest::Request;
5use serde::Deserialize;
6use sha2::Sha256;
7use xapi_shared::{signer::SharedSignerTrait, ws::error::SharedWsError};
8
9#[derive(Clone, Deserialize)]
10pub struct BnSigner {
11 apikey: String,
12 secret: Secret<String>,
13}
14
15impl SharedSignerTrait for BnSigner {
16 fn sign_request(&self, request: &mut Request) {
17 let url = request.url().clone();
18
19 let query = if let Some(query) = url.query() {
20 query.to_string()
21 } else {
22 "".to_string()
23 };
24
25 let timestamp = Utc::now().timestamp_millis();
26 let mut query_items = query.split("&").map(|x| x.to_string()).collect::<Vec<_>>();
27 query_items.push(format!("timestamp={timestamp}"));
28 query_items.sort();
29
30 let signature = self.sign_query_str(&query_items.join("&"));
31 query_items.push(format!("signature={}", signature));
32
33 request
34 .headers_mut()
35 .insert("X-MBX-APIKEY", self.apikey.as_str().parse().unwrap());
36
37 request.url_mut().set_query(Some(&query_items.join("&")));
40 }
41}
42
43impl BnSigner {
44 pub fn new(apikey: String, secret: Secret<String>) -> Self {
45 BnSigner { apikey, secret }
46 }
47
48 pub fn sign_ws_payload(
49 &self,
50 payload: Option<serde_json::Value>,
51 ) -> Result<serde_json::Value, SharedWsError> {
52 let mut payload =
53 payload.unwrap_or_else(|| serde_json::Value::Object(serde_json::Map::new()));
54
55 let payload_obj = match payload.as_object_mut() {
56 None => {
57 tracing::error!("payload should be an object");
58 return Err(SharedWsError::AppError(
59 "payload should be an object".to_string(),
60 ));
61 }
62 Some(obj) => obj,
63 };
64
65 payload_obj.insert(
66 "timestamp".to_string(),
67 Utc::now().timestamp_millis().into(),
68 );
69 payload_obj.insert("apiKey".to_string(), self.apikey.clone().into());
70
71 let query_string = serde_urlencoded::to_string(&payload_obj)
72 .inspect_err(|err| {
73 tracing::error!("failed to serialize payload into query string: {err:?}")
74 })
75 .map_err(|err| SharedWsError::SerdeError(err.to_string()))?;
76
77 let signature = self.sign_query_str(&query_string);
78
79 payload_obj.insert("signature".to_string(), signature.into());
80
81 Ok(payload)
82 }
83
84 fn sign_query_str(&self, query: &str) -> String {
85 let mut signed_key = Hmac::<Sha256>::new_from_slice(self.secret.expose_secret().as_bytes())
86 .expect("HMAC can take key of any size");
87 signed_key.update(query.as_bytes());
88 hex::encode(signed_key.finalize().into_bytes())
89 }
90}