use std::collections::HashMap;
use crate::core::{
hmac_sha256, timestamp_millis,
Credentials, ExchangeResult,
};
#[derive(Clone)]
pub struct BithumbAuth {
api_key: String,
api_secret: String,
}
impl BithumbAuth {
pub fn new(credentials: &Credentials) -> ExchangeResult<Self> {
Ok(Self {
api_key: credentials.api_key.clone(),
api_secret: credentials.api_secret.clone(),
})
}
pub fn sign_request(
&self,
params: &mut HashMap<String, String>,
) -> HashMap<String, String> {
params.insert("apiKey".to_string(), self.api_key.clone());
params.insert("timestamp".to_string(), timestamp_millis().to_string());
let mut keys: Vec<&String> = params.keys().collect();
keys.sort();
let signature_string: String = keys.iter()
.map(|k| format!("{}={}", k, params[*k]))
.collect::<Vec<_>>()
.join("&");
let signature_bytes = hmac_sha256(
self.api_secret.as_bytes(),
signature_string.as_bytes(),
);
let signature = signature_bytes.iter()
.map(|b| format!("{:02x}", b))
.collect::<String>();
params.insert("signature".to_string(), signature);
params.clone()
}
pub fn api_key(&self) -> &str {
&self.api_key
}
pub fn api_secret(&self) -> &str {
&self.api_secret
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_sign_request() {
let credentials = Credentials::new("test_key", "test_secret");
let auth = BithumbAuth::new(&credentials).unwrap();
let mut params = HashMap::new();
params.insert("symbol".to_string(), "BTC-USDT".to_string());
params.insert("quantity".to_string(), "0.5".to_string());
let signed_params = auth.sign_request(&mut params);
assert!(signed_params.contains_key("apiKey"));
assert!(signed_params.contains_key("timestamp"));
assert!(signed_params.contains_key("signature"));
assert!(signed_params.contains_key("symbol"));
assert!(signed_params.contains_key("quantity"));
let signature = signed_params.get("signature").unwrap();
assert_eq!(signature.len(), 64);
assert!(signature.chars().all(|c| c.is_ascii_hexdigit() && !c.is_uppercase()));
}
#[test]
fn test_signature_deterministic() {
let credentials = Credentials::new("test_key", "test_secret");
let auth = BithumbAuth::new(&credentials).unwrap();
let timestamp = timestamp_millis().to_string();
let mut params1 = HashMap::new();
params1.insert("symbol".to_string(), "BTC-USDT".to_string());
params1.insert("timestamp".to_string(), timestamp.clone());
params1.insert("apiKey".to_string(), auth.api_key().to_string());
let mut params2 = HashMap::new();
params2.insert("apiKey".to_string(), auth.api_key().to_string());
params2.insert("timestamp".to_string(), timestamp.clone());
params2.insert("symbol".to_string(), "BTC-USDT".to_string());
let mut keys1: Vec<&String> = params1.keys().collect();
keys1.sort();
let sig_str1: String = keys1.iter()
.map(|k| format!("{}={}", k, params1[*k]))
.collect::<Vec<_>>()
.join("&");
let mut keys2: Vec<&String> = params2.keys().collect();
keys2.sort();
let sig_str2: String = keys2.iter()
.map(|k| format!("{}={}", k, params2[*k]))
.collect::<Vec<_>>()
.join("&");
assert_eq!(sig_str1, sig_str2);
}
}