use std::collections::HashMap;
use crate::core::{
hmac_sha256, encode_base64, timestamp_iso8601,
Credentials, ExchangeResult, ExchangeError,
};
#[derive(Clone)]
pub struct OkxAuth {
api_key: String,
api_secret: String,
pub passphrase: String, }
impl OkxAuth {
pub fn new(credentials: &Credentials) -> ExchangeResult<Self> {
let passphrase = credentials.passphrase.clone()
.ok_or_else(|| ExchangeError::Auth("OKX requires passphrase".to_string()))?;
Ok(Self {
api_key: credentials.api_key.clone(),
api_secret: credentials.api_secret.clone(),
passphrase,
})
}
pub fn sign_request(
&self,
method: &str,
endpoint: &str,
body: &str,
) -> HashMap<String, String> {
let timestamp = timestamp_iso8601();
let prehash = format!("{}{}{}{}", timestamp, method.to_uppercase(), endpoint, body);
let signature = encode_base64(&hmac_sha256(
self.api_secret.as_bytes(),
prehash.as_bytes(),
));
let mut headers = HashMap::new();
headers.insert("OK-ACCESS-KEY".to_string(), self.api_key.clone());
headers.insert("OK-ACCESS-SIGN".to_string(), signature);
headers.insert("OK-ACCESS-TIMESTAMP".to_string(), timestamp);
headers.insert("OK-ACCESS-PASSPHRASE".to_string(), self.passphrase.clone());
headers.insert("Content-Type".to_string(), "application/json".to_string());
headers
}
pub fn sign_request_testnet(
&self,
method: &str,
endpoint: &str,
body: &str,
) -> HashMap<String, String> {
let mut headers = self.sign_request(method, endpoint, body);
headers.insert("x-simulated-trading".to_string(), "1".to_string());
headers
}
pub fn api_key(&self) -> &str {
&self.api_key
}
pub fn sign_websocket_login(&self, timestamp: &str) -> String {
let prehash = format!("{}GET/users/self/verify", timestamp);
encode_base64(&hmac_sha256(
self.api_secret.as_bytes(),
prehash.as_bytes(),
))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_sign_request() {
let credentials = Credentials::new("test_key", "test_secret")
.with_passphrase("test_pass");
let auth = OkxAuth::new(&credentials).unwrap();
let headers = auth.sign_request("GET", "/api/v5/account/balance", "");
assert!(headers.contains_key("OK-ACCESS-KEY"));
assert!(headers.contains_key("OK-ACCESS-SIGN"));
assert!(headers.contains_key("OK-ACCESS-TIMESTAMP"));
assert!(headers.contains_key("OK-ACCESS-PASSPHRASE"));
assert_eq!(headers.get("OK-ACCESS-KEY"), Some(&"test_key".to_string()));
assert_eq!(headers.get("OK-ACCESS-PASSPHRASE"), Some(&"test_pass".to_string()));
}
#[test]
fn test_sign_request_testnet() {
let credentials = Credentials::new("test_key", "test_secret")
.with_passphrase("test_pass");
let auth = OkxAuth::new(&credentials).unwrap();
let headers = auth.sign_request_testnet("POST", "/api/v5/trade/order", r#"{"instId":"BTC-USDT"}"#);
assert_eq!(headers.get("x-simulated-trading"), Some(&"1".to_string()));
}
#[test]
fn test_websocket_login_signature() {
let credentials = Credentials::new("test_key", "test_secret")
.with_passphrase("test_pass");
let auth = OkxAuth::new(&credentials).unwrap();
let timestamp = "2020-12-08T09:08:57.715Z";
let signature = auth.sign_websocket_login(timestamp);
assert!(!signature.is_empty());
assert!(signature.chars().all(|c| c.is_alphanumeric() || c == '+' || c == '/' || c == '='));
}
}