mexc_rs/spot/
mod.rs

1use async_trait::async_trait;
2use hmac::digest::InvalidLength;
3use hmac::{Hmac, Mac};
4use sha2::Sha256;
5
6pub mod v3;
7#[cfg(feature = "ws")]
8pub mod ws;
9
10#[derive(Debug, Clone)]
11pub enum MexcSpotApiEndpoint {
12    Base,
13    Custom(String),
14}
15
16impl AsRef<str> for MexcSpotApiEndpoint {
17    fn as_ref(&self) -> &str {
18        match self {
19            MexcSpotApiEndpoint::Base => "https://api.mexc.com",
20            MexcSpotApiEndpoint::Custom(endpoint) => endpoint,
21        }
22    }
23}
24
25#[derive(Clone)]
26pub struct MexcSpotApiClient {
27    endpoint: MexcSpotApiEndpoint,
28    reqwest_client: reqwest::Client,
29}
30
31#[async_trait]
32trait MexcSpotApiTrait {
33    fn endpoint(&self) -> &MexcSpotApiEndpoint;
34    fn reqwest_client(&self) -> &reqwest::Client;
35}
36
37impl MexcSpotApiClient {
38    pub fn new(endpoint: MexcSpotApiEndpoint) -> Self {
39        let reqwest_client = reqwest::Client::builder()
40            .build()
41            .expect("Failed to build reqwest client");
42        Self {
43            endpoint,
44            reqwest_client,
45        }
46    }
47
48    pub fn into_with_authentication(
49        self,
50        api_key: String,
51        secret_key: String,
52    ) -> MexcSpotApiClientWithAuthentication {
53        MexcSpotApiClientWithAuthentication::new(self.endpoint, api_key, secret_key)
54    }
55}
56
57impl Default for MexcSpotApiClient {
58    fn default() -> Self {
59        Self::new(MexcSpotApiEndpoint::Base)
60    }
61}
62
63impl MexcSpotApiTrait for MexcSpotApiClient {
64    fn endpoint(&self) -> &MexcSpotApiEndpoint {
65        &self.endpoint
66    }
67
68    fn reqwest_client(&self) -> &reqwest::Client {
69        &self.reqwest_client
70    }
71}
72
73#[derive(Clone)]
74pub struct MexcSpotApiClientWithAuthentication {
75    endpoint: MexcSpotApiEndpoint,
76    reqwest_client: reqwest::Client,
77    _api_key: String,
78    secret_key: String,
79}
80
81impl MexcSpotApiClientWithAuthentication {
82    pub fn new(endpoint: MexcSpotApiEndpoint, api_key: String, secret_key: String) -> Self {
83        let mut headers = reqwest::header::HeaderMap::new();
84        headers.insert(
85            "X-MEXC-APIKEY",
86            api_key.parse().expect("Failed to parse api key"),
87        );
88        let reqwest_client = reqwest::Client::builder()
89            .default_headers(headers)
90            .build()
91            .expect("Failed to build reqwest client");
92        Self {
93            endpoint,
94            reqwest_client,
95            _api_key: api_key,
96            secret_key,
97        }
98    }
99
100    fn sign_query<T>(&self, query: T) -> Result<QueryWithSignature<T>, SignQueryError>
101    where
102        T: serde::Serialize,
103    {
104        let query_string = serde_urlencoded::to_string(&query)?;
105        let mut mac = Hmac::<Sha256>::new_from_slice(self.secret_key.as_bytes())?;
106        mac.update(query_string.as_bytes());
107        let mac_result = mac.finalize();
108        let mac_bytes = mac_result.into_bytes();
109        let signature = hex::encode(mac_bytes);
110
111        Ok(QueryWithSignature::new(query, signature))
112    }
113
114    #[cfg(test)]
115    fn new_for_test() -> Self {
116        dotenv::dotenv().ok();
117        let api_key = std::env::var("MEXC_API_KEY").expect("MEXC_API_KEY not set");
118        let secret_key = std::env::var("MEXC_SECRET_KEY").expect("MEXC_SECRET_KEY not set");
119        Self::new(MexcSpotApiEndpoint::Base, api_key, secret_key)
120    }
121}
122
123impl MexcSpotApiTrait for MexcSpotApiClientWithAuthentication {
124    fn endpoint(&self) -> &MexcSpotApiEndpoint {
125        &self.endpoint
126    }
127
128    fn reqwest_client(&self) -> &reqwest::Client {
129        &self.reqwest_client
130    }
131}
132
133#[derive(Debug, serde::Serialize)]
134#[serde(rename_all = "camelCase")]
135pub struct QueryWithSignature<T> {
136    #[serde(flatten)]
137    pub query: T,
138    pub signature: String,
139}
140
141impl<T> QueryWithSignature<T> {
142    pub fn new(query: T, signature: String) -> Self {
143        Self { query, signature }
144    }
145}
146
147#[derive(Debug, thiserror::Error)]
148pub enum SignQueryError {
149    #[error("Serde url encoded error: {0}")]
150    SerdeUrlencodedError(#[from] serde_urlencoded::ser::Error),
151
152    #[error("Secret key invalid length")]
153    SecretKeyInvalidLength(#[from] InvalidLength),
154}