Skip to main content

ftx_rs/
client.rs

1use crate::errors::*;
2use chrono::Local;
3use reqwest::header::{HeaderMap, HeaderName, HeaderValue, CONTENT_TYPE, USER_AGENT};
4use reqwest::{self, Response, StatusCode};
5use ring::hmac;
6use std::io::Read;
7
8static API_HOST: &'static str = "https://ftx.com/api";
9
10#[derive(Clone)]
11pub struct Client {
12    api_key: String,
13    secret_key: String,
14    client: reqwest::Client,
15}
16
17impl Client {
18    pub fn new(api_key: Option<String>, secret_key: Option<String>) -> Self {
19        Client {
20            api_key: api_key.unwrap_or("".into()),
21            secret_key: secret_key.unwrap_or("".into()),
22            client: reqwest::Client::new(),
23        }
24    }
25
26    pub fn get(&self, endpoint: String, request: String) -> Result<String> {
27        let mut url: String = format!("{}{}", API_HOST, String::from(endpoint));
28        if !request.is_empty() {
29            url.push_str(format!("?{}", request).as_str());
30        }
31        // let response = reqwest::get(url.as_str())?;
32        let response = self.client.get(url.as_str()).send()?;
33
34        self.handler(response)
35    }
36
37    pub fn get_signed(
38        &self,
39        endpoint: String,
40        payload: String,
41        subaccount: Option<String>,
42    ) -> Result<String> {
43        let url: String = format!("{}{}", API_HOST, String::from(&endpoint));
44        let response = self
45            .client
46            .get(url.as_str())
47            .headers(self.build_headers(
48                String::from("GET"),
49                endpoint,
50                payload.clone(),
51                subaccount,
52            )?)
53            .body(payload)
54            .send()?;
55
56        self.handler(response)
57    }
58
59    pub fn post_signed(
60        &self,
61        endpoint: String,
62        payload: String,
63        subaccount: Option<String>,
64    ) -> Result<String> {
65        let url: String = format!("{}{}", API_HOST, String::from(&endpoint));
66        // println!("{:?}", url);
67        let response = self
68            .client
69            .post(url.as_str())
70            .headers(self.build_headers(
71                String::from("POST"),
72                endpoint,
73                payload.clone(),
74                subaccount.clone(),
75            )?)
76            .body(payload)
77            .send()?;
78
79        self.handler(response)
80    }
81
82    pub fn delete_signed(
83        &self,
84        endpoint: String,
85        payload: String,
86        subaccount: Option<String>,
87    ) -> Result<String> {
88        let url: String = format!("{}{}", API_HOST, String::from(&endpoint));
89        let response = self
90            .client
91            .delete(url.as_str())
92            .headers(self.build_headers(
93                String::from("DELETE"),
94                endpoint,
95                payload.clone(),
96                subaccount.clone(),
97            )?)
98            .body(payload)
99            .send()?;
100
101        self.handler(response)
102    }
103
104    fn build_headers(
105        &self,
106        method: String,
107        endpoint: String,
108        payload: String,
109        subaccount: Option<String>,
110    ) -> Result<HeaderMap> {
111        let mut headers = HeaderMap::new();
112        headers.insert(USER_AGENT, HeaderValue::from_static("ftx-rs"));
113        headers.insert(
114            HeaderName::from_static("ftx-key"),
115            HeaderValue::from_str(self.api_key.as_str())?,
116        );
117
118        // signature
119        let ts = Local::now().timestamp() * 1000;
120        let signature_payload = format!("{}{}/api{}{}", ts, method, endpoint, payload);
121        let signed_key = hmac::Key::new(hmac::HMAC_SHA256, self.secret_key.as_bytes());
122        let signature = hex::encode(hmac::sign(&signed_key, signature_payload.as_bytes()).as_ref());
123        headers.insert(
124            HeaderName::from_static("ftx-sign"),
125            HeaderValue::from_str(signature.as_str())?,
126        );
127        headers.insert(HeaderName::from_static("ftx-ts"), HeaderValue::from(ts));
128
129        // set content type
130        headers.insert(CONTENT_TYPE, HeaderValue::from_static("application/json"));
131
132        // set sub account
133        if let Some(s) = subaccount {
134            headers.insert(
135                HeaderName::from_static("ftx-subaccount"),
136                HeaderValue::from_str(s.as_str())?,
137            );
138        }
139
140        // println!("{}, {:?}", signature_payload, headers);
141
142        Ok(headers)
143    }
144
145    fn handler(&self, mut response: Response) -> Result<String> {
146        match response.status() {
147            StatusCode::OK => {
148                let mut body = String::new();
149                response.read_to_string(&mut body)?;
150                return Ok(body);
151            }
152            StatusCode::INTERNAL_SERVER_ERROR => {
153                bail!("Internal Server Error");
154            }
155            StatusCode::SERVICE_UNAVAILABLE => {
156                bail!("Service Unavailable");
157            }
158            StatusCode::UNAUTHORIZED => {
159                bail!("Unauthorized");
160            }
161            StatusCode::BAD_REQUEST => {
162                bail!(format!("Bad Request: {:?}", response));
163            }
164            s => {
165                bail!(format!("Received response: {:?}", s));
166            }
167        };
168    }
169}