exc_okx/
key.rs

1use exc_core::Str;
2use hmac::{Hmac, Mac};
3use serde::{Deserialize, Serialize};
4use sha2::Sha256;
5use thiserror::Error;
6use time::{error::Format, format_description::well_known::Rfc3339, OffsetDateTime};
7
8type HmacSha256 = Hmac<Sha256>;
9
10/// Error type for signing.
11#[derive(Debug, Error)]
12pub enum SignError {
13    /// Format timestamp error.
14    #[error("format timestamp error: {0}")]
15    FormatTimestamp(#[from] Format),
16
17    /// Convert timetsamp error.
18    #[error("convert timestamp error: {0}")]
19    ConvertTimestamp(#[from] time::error::ComponentRange),
20
21    /// SecretKey length error.
22    #[error("secretkey length error")]
23    SecretKeyLength,
24}
25
26/// The APIKey definition of OKX.
27#[derive(Debug, Clone, Deserialize, Serialize)]
28pub struct OkxKey {
29    /// APIKey.
30    pub apikey: Str,
31    /// SecretKey.
32    pub secretkey: Str,
33    /// Passphrase.
34    pub passphrase: Str,
35}
36
37/// Signature
38#[derive(Debug, Clone, Deserialize, Serialize)]
39pub struct Signature {
40    /// Signature.
41    #[serde(rename = "sign")]
42    pub signature: Str,
43
44    /// Timestamp.
45    pub timestamp: Str,
46}
47
48impl OkxKey {
49    /// Create a new [`Key`].
50    pub fn new(apikey: &str, secretkey: &str, passphrase: &str) -> Self {
51        Self {
52            apikey: Str::new(apikey),
53            secretkey: Str::new(secretkey),
54            passphrase: Str::new(passphrase),
55        }
56    }
57
58    /// Sign with this [`Key`].
59    pub fn sign(
60        &self,
61        method: &str,
62        uri: &str,
63        timestamp: OffsetDateTime,
64        use_unix_timestamp: bool,
65    ) -> Result<Signature, SignError> {
66        use base64::{engine::general_purpose::STANDARD, Engine};
67
68        let secret = self.secretkey.as_str();
69        let timestamp = timestamp.replace_millisecond(timestamp.millisecond())?;
70        let timestamp = if use_unix_timestamp {
71            timestamp.unix_timestamp().to_string()
72        } else {
73            timestamp.format(&Rfc3339)?
74        };
75        let raw_sign = timestamp.clone() + method + uri;
76        tracing::debug!("message to sign: {}", raw_sign);
77        let mut mac = HmacSha256::new_from_slice(secret.as_bytes())
78            .map_err(|_| SignError::SecretKeyLength)?;
79        mac.update(raw_sign.as_bytes());
80
81        Ok(Signature {
82            signature: Str::new(STANDARD.encode(mac.finalize().into_bytes())),
83            timestamp: Str::new(timestamp),
84        })
85    }
86
87    /// Sign now.
88    pub fn sign_now(
89        &self,
90        method: &str,
91        uri: &str,
92        use_unix_timestamp: bool,
93    ) -> Result<Signature, SignError> {
94        self.sign(method, uri, OffsetDateTime::now_utc(), use_unix_timestamp)
95    }
96}