use std::collections::HashMap;
use crate::core::{
hmac_sha256, encode_hex_lower, timestamp_millis,
Credentials, ExchangeResult,
};
#[derive(Clone)]
pub struct BingxAuth {
api_key: String,
api_secret: String,
time_offset_ms: i64,
}
impl BingxAuth {
pub fn new(credentials: &Credentials) -> ExchangeResult<Self> {
Ok(Self {
api_key: credentials.api_key.clone(),
api_secret: credentials.api_secret.clone(),
time_offset_ms: 0,
})
}
pub fn sync_time(&mut self, server_time_ms: i64) {
let local_time = timestamp_millis() as i64;
self.time_offset_ms = server_time_ms - local_time;
}
fn get_timestamp(&self) -> u64 {
let local = timestamp_millis() as i64;
(local + self.time_offset_ms) as u64
}
pub fn sign_request(
&self,
params: &mut HashMap<String, String>,
) -> HashMap<String, String> {
let timestamp = self.get_timestamp();
params.insert("timestamp".to_string(), timestamp.to_string());
let param_string = self.build_query_string(params);
let signature_bytes = hmac_sha256(
self.api_secret.as_bytes(),
param_string.as_bytes(),
);
let signature = encode_hex_lower(&signature_bytes);
params.insert("signature".to_string(), signature);
let mut headers = HashMap::new();
headers.insert("X-BX-APIKEY".to_string(), self.api_key.clone());
headers
}
fn build_query_string(&self, params: &HashMap<String, String>) -> String {
let mut pairs: Vec<String> = params
.iter()
.filter(|(k, _)| k.as_str() != "signature") .map(|(k, v)| format!("{}={}", k, v))
.collect();
pairs.sort(); pairs.join("&")
}
pub fn api_key(&self) -> &str {
&self.api_key
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_sign_request() {
let credentials = Credentials::new("test_key", "test_secret");
let auth = BingxAuth::new(&credentials).unwrap();
let mut params = HashMap::new();
params.insert("symbol".to_string(), "BTC-USDT".to_string());
params.insert("side".to_string(), "BUY".to_string());
let headers = auth.sign_request(&mut params);
assert_eq!(headers.get("X-BX-APIKEY"), Some(&"test_key".to_string()));
assert!(params.contains_key("timestamp"));
assert!(params.contains_key("signature"));
assert_eq!(params.get("symbol"), Some(&"BTC-USDT".to_string()));
}
#[test]
fn test_query_string_sorted() {
let credentials = Credentials::new("test_key", "test_secret");
let auth = BingxAuth::new(&credentials).unwrap();
let mut params = HashMap::new();
params.insert("z_last".to_string(), "value1".to_string());
params.insert("a_first".to_string(), "value2".to_string());
params.insert("m_middle".to_string(), "value3".to_string());
let query = auth.build_query_string(¶ms);
assert_eq!(query, "a_first=value2&m_middle=value3&z_last=value1");
}
}