use std::collections::HashMap;
use crate::core::{
hmac_sha512, sha512,
Credentials, ExchangeResult,
timestamp_millis,
};
#[derive(Clone)]
pub struct GateioAuth {
api_key: String,
api_secret: String,
time_offset: i64,
}
impl GateioAuth {
pub fn new(credentials: &Credentials) -> ExchangeResult<Self> {
Ok(Self {
api_key: credentials.api_key.clone(),
api_secret: credentials.api_secret.clone(),
time_offset: 0,
})
}
pub fn sync_time(&mut self, server_time_seconds: i64) {
let local_time_seconds = (timestamp_millis() / 1000) as i64;
self.time_offset = server_time_seconds - local_time_seconds;
}
fn get_timestamp(&self) -> u64 {
let local_seconds = (timestamp_millis() / 1000) as i64;
(local_seconds + self.time_offset) as u64
}
fn hash_payload(&self, payload: &str) -> String {
let hash_bytes = sha512(payload.as_bytes());
hex::encode(hash_bytes)
}
pub fn sign_request(
&self,
method: &str,
url: &str,
query_string: &str,
body: &str,
) -> HashMap<String, String> {
let timestamp = self.get_timestamp();
let timestamp_str = timestamp.to_string();
let payload_hash = self.hash_payload(body);
let prehash = format!(
"{}\n{}\n{}\n{}\n{}",
method.to_uppercase(),
url,
query_string,
payload_hash,
timestamp_str
);
let signature_bytes = hmac_sha512(
self.api_secret.as_bytes(),
prehash.as_bytes(),
);
let signature = hex::encode(signature_bytes);
let mut headers = HashMap::new();
headers.insert("KEY".to_string(), self.api_key.clone());
headers.insert("SIGN".to_string(), signature);
headers.insert("Timestamp".to_string(), timestamp_str);
if method.to_uppercase() == "POST" || method.to_uppercase() == "PUT" || method.to_uppercase() == "PATCH" {
headers.insert("Content-Type".to_string(), "application/json".to_string());
}
headers
}
pub fn api_key(&self) -> &str {
&self.api_key
}
pub fn sign_ws(&self, sign_str: &str) -> ExchangeResult<String> {
let signature_bytes = hmac_sha512(
self.api_secret.as_bytes(),
sign_str.as_bytes(),
);
let signature = hex::encode(signature_bytes);
Ok(signature)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_sign_request() {
let credentials = Credentials::new("test_key", "test_secret");
let auth = GateioAuth::new(&credentials).unwrap();
let headers = auth.sign_request("GET", "/api/v4/spot/accounts", "", "");
assert!(headers.contains_key("KEY"));
assert!(headers.contains_key("SIGN"));
assert!(headers.contains_key("Timestamp"));
assert_eq!(headers.get("KEY").unwrap(), "test_key");
}
#[test]
fn test_post_request_headers() {
let credentials = Credentials::new("test_key", "test_secret");
let auth = GateioAuth::new(&credentials).unwrap();
let body = r#"{"currency_pair":"BTC_USDT","side":"buy"}"#;
let headers = auth.sign_request("POST", "/api/v4/spot/orders", "", body);
assert!(headers.contains_key("Content-Type"));
assert_eq!(headers.get("Content-Type").unwrap(), "application/json");
}
#[test]
fn test_hash_payload() {
let credentials = Credentials::new("test_key", "test_secret");
let auth = GateioAuth::new(&credentials).unwrap();
let empty_hash = auth.hash_payload("");
assert_eq!(
empty_hash,
"cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e"
);
}
}