nautilus_coinbase_intx/common/
credential.rs1use base64::prelude::*;
17use ring::hmac;
18use ustr::Ustr;
19
20#[derive(Debug, Clone)]
24pub struct Credential {
25 pub api_key: Ustr,
26 pub api_passphrase: Ustr,
27 hmac_key: hmac::Key,
28}
29
30impl Credential {
31 #[must_use]
37 pub fn new(api_key: String, api_secret: String, api_passphrase: String) -> Self {
38 let decoded_secret = BASE64_STANDARD
39 .decode(api_secret)
40 .expect("Invalid base64 secret key");
41
42 Self {
43 api_key: api_key.into(),
44 api_passphrase: api_passphrase.into(),
45 hmac_key: hmac::Key::new(hmac::HMAC_SHA256, &decoded_secret),
46 }
47 }
48
49 pub fn sign(&self, timestamp: &str, method: &str, endpoint: &str, body: &str) -> String {
51 let request_path = match endpoint.find('?') {
53 Some(index) => &endpoint[..index],
54 None => endpoint,
55 };
56
57 let message = format!("{timestamp}{method}{request_path}{body}");
58 tracing::trace!("Signing message: {message}");
59 let signature = hmac::sign(&self.hmac_key, message.as_bytes());
60 BASE64_STANDARD.encode(signature)
61 }
62
63 pub fn sign_ws(&self, timestamp: &str) -> String {
64 let message = format!("{timestamp}{}CBINTLMD{}", self.api_key, self.api_passphrase);
65 tracing::trace!("Signing message: {message}");
66 let signature = hmac::sign(&self.hmac_key, message.as_bytes());
67 BASE64_STANDARD.encode(signature)
68 }
69}
70
71#[cfg(test)]
72mod tests {
73 use rstest::rstest;
74
75 use super::*;
76
77 const API_KEY: &str = "test_key_123";
78 const API_SECRET: &str = "dGVzdF9zZWNyZXRfYmFzZTY0"; const API_PASSPHRASE: &str = "test_pass";
80
81 #[rstest]
82 fn test_simple_get() {
83 let credential = Credential::new(
84 API_KEY.to_string(),
85 API_SECRET.to_string(),
86 API_PASSPHRASE.to_string(),
87 );
88 let timestamp = "1641890400"; let signature = credential.sign(timestamp, "GET", "/api/v1/fee-rate-tiers", "");
90
91 assert_eq!(signature, "h/9tnYzD/nsEbH1sV7dkB5uJ3Vygr4TjmOOxJNQB8ts=");
92 }
93}