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}