dex_client/
lib.rs

1use reqwest::header::HeaderMap;
2use reqwest::{self, Client};
3use serde::{Deserialize, Serialize};
4use sha2::{Digest, Sha256};
5use std::error::Error as StdError;
6use std::fmt::{self, Display};
7
8#[derive(Deserialize, Debug)]
9pub struct CommonErrorResponse {
10    pub message: Option<String>,
11}
12
13#[derive(Deserialize, Debug)]
14pub struct TickerResponse {
15    pub symbol: Option<String>,
16    pub price: Option<String>,
17}
18
19#[derive(Deserialize, Debug)]
20pub struct FilledOrder {
21    pub order_id: Option<String>,
22    pub filled_size: Option<String>,
23    pub filled_value: Option<String>,
24    pub filled_fee: Option<String>,
25}
26
27#[derive(Deserialize, Debug)]
28pub struct FilledOrdersResponse {
29    pub orders: Vec<FilledOrder>,
30}
31
32#[derive(Serialize)]
33pub struct ClearFilledOrderPayload {
34    pub symbol: String,
35    pub order_id: String,
36}
37
38#[derive(Deserialize, Debug)]
39pub struct BalanceResponse {
40    pub equity: Option<String>,
41    pub balance: Option<String>,
42}
43
44#[derive(Serialize)]
45struct CreateOrderPayload {
46    symbol: String,
47    size: String,
48    side: String,
49    price: Option<String>,
50}
51
52#[derive(Deserialize, Debug)]
53pub struct CreateOrderResponse {
54    pub order_id: Option<String>,
55}
56
57#[derive(Serialize)]
58struct CancelOrderPayload {
59    pub order_id: String,
60}
61
62#[derive(Serialize)]
63struct CloseAllPositionsPayload {
64    symbol: Option<String>,
65}
66
67#[derive(Deserialize, Debug)]
68pub struct DefaultResponse {}
69
70#[derive(Clone, Debug)]
71pub struct DexClient {
72    client: Client,
73    base_url: String,
74}
75
76#[derive(Debug)]
77pub enum DexError {
78    Serde(serde_json::Error),
79    Reqwest(reqwest::Error),
80    ServerResponse(String),
81}
82
83impl Display for DexError {
84    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
85        match *self {
86            DexError::Serde(ref e) => write!(f, "Serde JSON error: {}", e),
87            DexError::Reqwest(ref e) => write!(f, "Reqwest error: {}", e),
88            DexError::ServerResponse(ref e) => write!(f, "Server response error: {}", e),
89        }
90    }
91}
92
93impl StdError for DexError {
94    fn source(&self) -> Option<&(dyn StdError + 'static)> {
95        match *self {
96            DexError::Serde(ref e) => Some(e),
97            DexError::Reqwest(ref e) => Some(e),
98            DexError::ServerResponse(_) => None,
99        }
100    }
101}
102
103impl From<serde_json::Error> for DexError {
104    fn from(err: serde_json::Error) -> DexError {
105        DexError::Serde(err)
106    }
107}
108
109impl From<reqwest::Error> for DexError {
110    fn from(err: reqwest::Error) -> DexError {
111        DexError::Reqwest(err)
112    }
113}
114
115impl DexClient {
116    pub async fn new(api_key: String, base_url: String) -> Result<Self, reqwest::Error> {
117        let client = Client::builder()
118            .default_headers(Self::headers_with_hashed_api_key(api_key))
119            .build()?;
120
121        Ok(DexClient { client, base_url })
122    }
123
124    fn headers_with_hashed_api_key(api_key: String) -> HeaderMap {
125        let mut hasher = Sha256::new();
126        hasher.update(api_key);
127        let hashed_api_key = format!("{:x}", hasher.finalize());
128
129        let mut headers = HeaderMap::new();
130        headers.insert("Authorization", hashed_api_key.parse().unwrap());
131        headers
132    }
133
134    async fn handle_request<T: serde::de::DeserializeOwned>(
135        &self,
136        result: Result<reqwest::Response, reqwest::Error>,
137        url: &str,
138    ) -> Result<T, DexError> {
139        let response = result.map_err(DexError::from)?;
140        let status = response.status();
141
142        if status.is_success() {
143            let headers = response.headers().clone();
144            let body = response.text().await.map_err(DexError::from)?;
145            log::trace!("Response body: {}", body);
146
147            serde_json::from_str(&body).map_err(|e| {
148                log::warn!("Response header: {:?}", headers);
149                log::error!("Failed to deserialize response: {}", e);
150                DexError::Serde(e)
151            })
152        } else {
153            let error_response: CommonErrorResponse = response
154                .json()
155                .await
156                .unwrap_or(CommonErrorResponse { message: None });
157            let error_message = format!(
158                "Server returned error: {}. Requested url: {}, message: {:?}",
159                status, url, error_response.message,
160            );
161            log::error!("{}", &error_message);
162            Err(DexError::ServerResponse(error_message))
163        }
164    }
165
166    pub async fn get_ticker(&self, dex: &str, symbol: &str) -> Result<TickerResponse, DexError> {
167        let url = format!("{}/ticker?dex={}&symbol={}", self.base_url, dex, symbol);
168        log::trace!("{:?}", url);
169        self.handle_request(self.client.get(&url).send().await, &url)
170            .await
171    }
172
173    pub async fn get_filled_orders(
174        &self,
175        dex: &str,
176        symbol: &str,
177    ) -> Result<FilledOrdersResponse, DexError> {
178        let url = format!(
179            "{}/get-filled-orders?dex={}&symbol={}",
180            self.base_url, dex, symbol
181        );
182        log::trace!("{:?}", url);
183        self.handle_request(self.client.get(&url).send().await, &url)
184            .await
185    }
186
187    pub async fn get_balance(&self, dex: &str) -> Result<BalanceResponse, DexError> {
188        let url = format!("{}/get-balance?dex={}", self.base_url, dex);
189        log::trace!("{:?}", url);
190        self.handle_request(self.client.get(&url).send().await, &url)
191            .await
192    }
193
194    pub async fn clear_filled_order(
195        &self,
196        dex: &str,
197        symbol: &str,
198        order_id: &str,
199    ) -> Result<DefaultResponse, DexError> {
200        let url = format!("{}/clear-filled-order?dex={}", self.base_url, dex);
201        log::trace!("{:?}", url);
202        let payload = ClearFilledOrderPayload {
203            symbol: symbol.to_string(),
204            order_id: order_id.to_string(),
205        };
206        self.handle_request(self.client.post(&url).json(&payload).send().await, &url)
207            .await
208    }
209
210    pub async fn create_order(
211        &self,
212        dex: &str,
213        symbol: &str,
214        size: &str,
215        side: &str,
216        price: Option<String>,
217    ) -> Result<CreateOrderResponse, DexError> {
218        let url = format!("{}/create-order?dex={}", self.base_url, dex);
219        log::trace!("{:?}", url);
220        let payload = CreateOrderPayload {
221            symbol: symbol.to_string(),
222            size: size.to_string(),
223            side: side.to_string(),
224            price,
225        };
226        self.handle_request(self.client.post(&url).json(&payload).send().await, &url)
227            .await
228    }
229
230    pub async fn cancel_order(
231        &self,
232        dex: &str,
233        order_id: &str,
234    ) -> Result<DefaultResponse, DexError> {
235        let url = format!("{}/cancel-order?dex={}", self.base_url, dex);
236        log::trace!("{:?}", url);
237        let payload = CancelOrderPayload {
238            order_id: order_id.to_string(),
239        };
240        self.handle_request(self.client.post(&url).json(&payload).send().await, &url)
241            .await
242    }
243
244    pub async fn close_all_positions(
245        &self,
246        dex: &str,
247        symbol: Option<String>,
248    ) -> Result<DefaultResponse, DexError> {
249        let url = format!("{}/close-all-positions?dex={}", self.base_url, dex);
250        log::trace!("{:?}", url);
251        let payload = CloseAllPositionsPayload { symbol };
252        self.handle_request(self.client.post(&url).json(&payload).send().await, &url)
253            .await
254    }
255}