kraken_sdk/
auth.rs

1use base64::{engine::general_purpose, Engine as _};
2use eyre::Result;
3use hmac::{Hmac, Mac};
4use reqwest::Client;
5use serde::Deserialize;
6use sha2::{Digest, Sha256, Sha512};
7use std::time::{SystemTime, UNIX_EPOCH};
8
9type HmacSha512 = Hmac<Sha512>;
10
11pub struct Authenticator {
12    api_key: String,
13    api_secret: String,
14    client: Client,
15}
16
17#[derive(Deserialize)]
18struct TokenResponse {
19    error: Vec<String>,
20    result: Option<TokenResult>,
21}
22
23#[derive(Deserialize)]
24struct TokenResult {
25    token: String,
26}
27
28impl Authenticator {
29    pub fn new(api_key: String, api_secret: String) -> Self {
30        Self {
31            api_key,
32            api_secret,
33            client: Client::new(),
34        }
35    }
36
37    pub async fn get_ws_token(&self) -> Result<String> {
38        let nonce = SystemTime::now()
39            .duration_since(UNIX_EPOCH)?
40            .as_millis()
41            .to_string();
42
43        let path = "/0/private/GetWebSocketsToken";
44        let url = format!("https://api.kraken.com{}", path);
45        let post_data = format!("nonce={}", nonce);
46
47        let signature = sign_request(&self.api_secret, path, &nonce, &post_data)?;
48
49        // 3. Send Request
50        let resp = self
51            .client
52            .post(&url)
53            .header("API-Key", &self.api_key)
54            .header("API-Sign", signature)
55            .body(post_data)
56            .send()
57            .await?
58            .json::<TokenResponse>()
59            .await?;
60
61        if !resp.error.is_empty() {
62            return Err(eyre::eyre!("Kraken API Error: {:?}", resp.error));
63        }
64
65        Ok(resp.result.unwrap().token)
66    }
67}
68
69pub fn sign_request(api_secret: &str, path: &str, nonce: &str, post_data: &str) -> Result<String> {
70    // 1. SHA256(nonce + POST data)
71    let mut sha256 = Sha256::new();
72    sha256.update(nonce.as_bytes());
73    sha256.update(post_data.as_bytes());
74    let sha256_digest = sha256.finalize();
75
76    // 2. HMAC-SHA512(path + sha256_digest, secret)
77    let secret_bytes = general_purpose::STANDARD.decode(api_secret)?;
78    let mut mac = HmacSha512::new_from_slice(&secret_bytes)?;
79    mac.update(path.as_bytes());
80    mac.update(&sha256_digest);
81    let sig_bytes = mac.finalize().into_bytes();
82
83    Ok(general_purpose::STANDARD.encode(sig_bytes))
84}