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#[derive(Debug, Error)]
12pub enum SignError {
13 #[error("format timestamp error: {0}")]
15 FormatTimestamp(#[from] Format),
16
17 #[error("convert timestamp error: {0}")]
19 ConvertTimestamp(#[from] time::error::ComponentRange),
20
21 #[error("secretkey length error")]
23 SecretKeyLength,
24}
25
26#[derive(Debug, Clone, Deserialize, Serialize)]
28pub struct OkxKey {
29 pub apikey: Str,
31 pub secretkey: Str,
33 pub passphrase: Str,
35}
36
37#[derive(Debug, Clone, Deserialize, Serialize)]
39pub struct Signature {
40 #[serde(rename = "sign")]
42 pub signature: Str,
43
44 pub timestamp: Str,
46}
47
48impl OkxKey {
49 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 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 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}