coinbase_client/private_client/private_client.rs
1use crate::configure_pagination;
2use crate::{
3 deserialize_option_to_date, deserialize_response, deserialize_to_date, Json, COINBASE_API_URL,
4 COINBASE_SANDBOX_API_URL,
5};
6
7use super::Order;
8use super::Report;
9
10use crate::error::{Error, ErrorKind, ErrorMessage, StatusError};
11use base64;
12use chrono::{DateTime, Utc};
13use core::f64;
14use crypto::{self, mac::Mac};
15use reqwest;
16use serde::{self, Deserialize};
17use std::str;
18use std::time::{SystemTime, SystemTimeError};
19
20/// `PrivateClient` requires authentication and provide access to placing orders and other account information
21pub struct PrivateClient {
22 reqwest_client: reqwest::Client,
23 secret: String,
24 passphrase: String,
25 key: String,
26 url: &'static str,
27}
28
29impl PrivateClient {
30 async fn get_paginated<T>(
31 &self,
32 path: &str,
33 before: Option<&str>,
34 after: Option<&str>,
35 limit: Option<u16>,
36 ) -> Result<T, Error>
37 where
38 T: serde::de::DeserializeOwned,
39 {
40 let pagination_params = configure_pagination(before, after, limit);
41 self.get(&format!("{}{}", path, pagination_params)).await
42 }
43
44 async fn get<T>(&self, path: &str) -> Result<T, Error>
45 where
46 T: serde::de::DeserializeOwned,
47 {
48 let headers = self.access_headers(path, None, "GET");
49 let response = self
50 .reqwest_client
51 .get(format!("{}{}", self.url, path))
52 .headers(headers)
53 .send()
54 .await?;
55 deserialize_response::<T>(response).await
56 }
57
58 async fn post_and_deserialize<T, K>(&self, path: &str, body: Option<K>) -> Result<T, Error>
59 where
60 K: serde::Serialize,
61 T: serde::de::DeserializeOwned,
62 {
63 deserialize_response::<T>(self.post(path, body).await?).await
64 }
65
66 async fn post<K>(&self, path: &str, body: Option<K>) -> Result<reqwest::Response, Error>
67 where
68 K: serde::Serialize,
69 {
70 let request_builder = self.reqwest_client.post(format!("{}{}", self.url, path));
71 Ok(if let Some(n) = body {
72 request_builder
73 .headers(self.access_headers(path, Some(&serde_json::to_string(&n)?), "POST"))
74 .json::<K>(&n)
75 .send()
76 } else {
77 request_builder
78 .headers(self.access_headers(path, None, "POST"))
79 .send()
80 }
81 .await?)
82 }
83
84 async fn delete<T>(&self, path: &str) -> Result<T, Error>
85 where
86 T: serde::de::DeserializeOwned,
87 {
88 let headers = self.access_headers(path, None, "DELETE");
89 let response = self
90 .reqwest_client
91 .delete(format!("{}{}", self.url, path))
92 .headers(headers)
93 .send()
94 .await?;
95 deserialize_response::<T>(response).await
96 }
97
98 fn get_current_timestamp() -> Result<String, SystemTimeError> {
99 Ok(SystemTime::now()
100 .duration_since(SystemTime::UNIX_EPOCH)?
101 .as_secs()
102 .to_string())
103 }
104
105 fn access_headers(
106 &self,
107 url: &str,
108 body: Option<&str>,
109 meathod: &str,
110 ) -> reqwest::header::HeaderMap {
111 let timestamp = PrivateClient::get_current_timestamp().unwrap();
112 let signature = self.sign_message(url, body, ×tamp, meathod);
113 let mut headers = reqwest::header::HeaderMap::new();
114 headers.insert(
115 reqwest::header::USER_AGENT,
116 reqwest::header::HeaderValue::from_str("coinbase-client")
117 .expect("invalid user agent value"),
118 );
119 headers.insert(
120 reqwest::header::HeaderName::from_static("cb-access-key"),
121 reqwest::header::HeaderValue::from_str(&self.key)
122 .expect("invalid user cb-access-key value"),
123 );
124 headers.insert(
125 reqwest::header::HeaderName::from_static("cb-access-sign"),
126 reqwest::header::HeaderValue::from_str(&signature)
127 .expect("invalid cb-access-sign value"),
128 );
129 headers.insert(
130 reqwest::header::HeaderName::from_static("cb-access-timestamp"),
131 reqwest::header::HeaderValue::from_str(×tamp)
132 .expect("invalid user cb-access-timestamp value"),
133 );
134 headers.insert(
135 reqwest::header::HeaderName::from_static("cb-access-passphrase"),
136 reqwest::header::HeaderValue::from_str(&self.passphrase)
137 .expect("invalid user cb-access-passphrase value"),
138 );
139
140 headers
141 }
142
143 fn sign_message(
144 &self,
145 url: &str,
146 body: Option<&str>,
147 timestamp: &str,
148 meathod: &str,
149 ) -> String {
150 let mut prehash = String::new();
151 // omit body if not supplied
152 match body {
153 Some(body) => {
154 prehash.push_str(×tamp);
155 prehash.push_str(&meathod);
156 prehash.push_str(&url);
157 prehash.push_str(&body);
158 }
159 None => {
160 prehash.push_str(×tamp);
161 prehash.push_str(&meathod);
162 prehash.push_str(&url);
163 }
164 }
165 // decode your coinbase api secret
166 let decoded_secret = base64::decode(&self.secret)
167 .expect("unable to decode secret, is your secret in base 64 encoding");
168 // hmac-sha256 it
169 let mut hmac = crypto::hmac::Hmac::new(crypto::sha2::Sha256::new(), &decoded_secret);
170 hmac.input(prehash.as_bytes());
171 let hmac_result = hmac.result();
172 let hmac_code = hmac_result.code();
173 let base64_encoding = base64::encode(hmac_code);
174 // return base64 encoded hmac result
175 base64_encoding
176 }
177
178 /// Creates a new `PrivateClient`
179 /// <br>
180 /// ~~~~
181 /// let client = PrivateClient::new("tGJSu7SuV3/HOR1/9DcFwO1s560BKI51SDEbnwuvTPbw4BbG5lYJLuKUFpD8TPU61R85dxJpGTygKZ5v+6wJdA==", "t9riylyad0r", "4a9f6de8bcdee641a0a207613dfb43ef");
182 /// ~~~~
183 pub fn new(secret: String, passphrase: String, key: String) -> Self {
184 Self {
185 reqwest_client: reqwest::Client::new(),
186 secret, // shared secret
187 key,
188 passphrase,
189 url: COINBASE_API_URL,
190 }
191 }
192
193 /// Creates a new `PrivateClient` for testing API connectivity and web trading
194 /// <br>
195 /// ~~~~
196 /// let client = PrivateClient::new("tGJSu7SuV3/HOR1/9DcFwO1s560BKI51SDEbnwuvTPbw4BbG5lYJLuKUFpD8TPU61R85dxJpGTygKZ5v+6wJdA==", "t9riylyad0r", "4a9f6de8bcdee641a0a207613dfb43ef");
197 /// ~~~~
198 pub fn new_sandbox(secret: String, passphrase: String, key: String) -> Self {
199 Self {
200 reqwest_client: reqwest::Client::new(),
201 secret,
202 key,
203 passphrase,
204 url: COINBASE_SANDBOX_API_URL,
205 }
206 }
207
208 /// Gets a list of trading accounts from the profile of the API key.
209 /// <br>
210 /// [API docs](https://docs.pro.coinbase.com/#account)
211 /// <br>
212 /// ~~~~
213 /// let client = PrivateClient::new("tGJSu7SuV3/HOR1/9DcFwO1s560BKI51SDEbnwuvTPbw4BbG5lYJLuKUFpD8TPU61R85dxJpGTygKZ5v+6wJdA==", "t9riylyad0r", "4a9f6de8bcdee641a0a207613dfb43ef");
214 /// let accounts = client.get_accounts().await.unwrap();
215 /// ~~~~
216 pub async fn get_accounts(&self) -> Result<Vec<Account>, Error> {
217 let accounts = self.get("/accounts").await?;
218 Ok(accounts)
219 }
220
221 /// Get trading account by account ID
222 /// <br>
223 /// [API docs](https://docs.pro.coinbase.com/#account)
224 /// <br>
225 /// ~~~~
226 /// let client = PrivateClient::new("tGJSu7SuV3/HOR1/9DcFwO1s560BKI51SDEbnwuvTPbw4BbG5lYJLuKUFpD8TPU61R85dxJpGTygKZ5v+6wJdA==", "t9riylyad0r", "4a9f6de8bcdee641a0a207613dfb43ef");
227 /// let account = client.get_account("1f6a7175-a89c-494f-986d-af9987e6dd69")
228 /// .await
229 /// .unwrap();
230 /// ~~~~
231 pub async fn get_account(&self, account_id: &str) -> Result<Account, Error> {
232 let account = self.get(&format!("/accounts/{}", account_id)).await?;
233 Ok(account)
234 }
235
236 /// Get account activity of the API key's profile.
237 /// <br>
238 /// [API docs](https://docs.pro.coinbase.com/#get-account-history)
239 /// <br>
240 /// ~~~~
241 /// let client = PrivateClient::new("tGJSu7SuV3/HOR1/9DcFwO1s560BKI51SDEbnwuvTPbw4BbG5lYJLuKUFpD8TPU61R85dxJpGTygKZ5v+6wJdA==", "t9riylyad0r", "4a9f6de8bcdee641a0a207613dfb43ef");
242 /// let history = client
243 /// .get_account_history(
244 /// "680f85f4-1a99-4108-93ce-a9066f9de246",
245 /// Some("297946691"),
246 /// Some("296147671"),
247 /// Some(100),
248 /// )
249 /// .await
250 /// .unwrap();
251 /// ~~~~
252 pub async fn get_account_history(
253 &self,
254 account_id: &str,
255 before: Option<&str>,
256 after: Option<&str>,
257 limit: Option<u16>,
258 ) -> Result<Vec<AccountHistory>, Error> {
259 let account = self
260 .get_paginated(
261 &format!("/accounts/{}/ledger?", account_id),
262 before,
263 after,
264 limit,
265 )
266 .await?;
267 Ok(account)
268 }
269
270 /// Get holds of an account that belong to the same profile as the API key.
271 /// <br>
272 /// [API docs](https://docs.pro.coinbase.com/#get-holds)\
273 /// <br>
274 /// ~~~~
275 /// let client = PrivateClient::new("tGJSu7SuV3/HOR1/9DcFwO1s560BKI51SDEbnwuvTPbw4BbG5lYJLuKUFpD8TPU61R85dxJpGTygKZ5v+6wJdA==", "t9riylyad0r", "4a9f6de8bcdee641a0a207613dfb43ef");
276 /// let _holds = client
277 /// .get_account_holds(
278 /// "680f85f4-1a99-4108-93ce-a9066f9de246",
279 /// None,
280 /// None,
281 /// Some(100),
282 /// )
283 /// .await
284 /// .unwrap();
285 /// ~~~~
286 pub async fn get_account_holds(
287 &self,
288 account_id: &str,
289 before: Option<&str>,
290 after: Option<&str>,
291 limit: Option<u16>,
292 ) -> Result<Vec<Hold>, Error> {
293 let account = self
294 .get_paginated(
295 &format!("/accounts/{}/holds?", account_id),
296 before,
297 after,
298 limit,
299 )
300 .await?;
301 Ok(account)
302 }
303
304 /// You can place three types of orders: limit, market and stop
305 /// <br>
306 /// [Overview of order types and settings](https://help.coinbase.com/en/pro/trading-and-funding/orders/overview-of-order-types-and-settings-stop-limit-market)
307 /// <br>
308 /// Create order order useing [`OrderBuilder`](https://docs.rs/coinbase-client/1.0.0-alpha/coinbase_client/private_client/struct.OrderBuilder.html)
309 /// <br>
310 /// [API docs](https://docs.pro.coinbase.com/#place-a-new-order)
311 /// ~~~~
312 /// let client = PrivateClient::new("tGJSu7SuV3/HOR1/9DcFwO1s560BKI51SDEbnwuvTPbw4BbG5lYJLuKUFpD8TPU61R85dxJpGTygKZ5v+6wJdA==", "t9riylyad0r", "4a9f6de8bcdee641a0a207613dfb43ef");
313 /// let order = OrderBuilder::market(OrderSide::Buy, "BTC-USD", SizeOrFunds::Funds(10.00))
314 /// .build();
315 /// let res = client.place_order(order).await.unwrap();
316 /// ~~~~
317 pub async fn place_order(&self, order: Order) -> Result<String, Error> {
318 #[derive(Deserialize, Debug)]
319 pub struct OrderID {
320 pub id: String,
321 }
322 Ok(self
323 .post_and_deserialize::<OrderID, _>("/orders", Some(order))
324 .await?
325 .id)
326 }
327
328 /// Cancel order specified by order ID
329 /// <br>
330 /// [API docs](https://docs.pro.coinbase.com/#cancel-an-order)
331 /// <br>
332 /// ~~~~
333 /// let client = PrivateClient::new("tGJSu7SuV3/HOR1/9DcFwO1s560BKI51SDEbnwuvTPbw4BbG5lYJLuKUFpD8TPU61R85dxJpGTygKZ5v+6wJdA==", "t9riylyad0r", "4a9f6de8bcdee641a0a207613dfb43ef");
334 /// let order = OrderBuilder::limit(OrderSide::Buy, "BTC-USD", 33000.0, 1.0)
335 /// .build();
336 /// let order_to_cancel_id = client.place_order(order).await.unwrap();
337 /// let canceled_order_id = client.cancel_order(&order_to_cancel_id)
338 /// .await.unwrap();
339 /// ~~~~
340 pub async fn cancel_order(&self, order_id: &str) -> Result<String, Error> {
341 Ok(self.delete(&format!("/orders/{}", order_id)).await?)
342 }
343
344 /// Cancel order specified by order OID
345 /// <br>
346 /// [API docs](https://docs.pro.coinbase.com/#cancel-an-order)
347 pub async fn cancel_order_by_oid(&self, oid: &str) -> Result<String, Error> {
348 Ok(self.delete(&format!("/orders/client:{}", oid)).await?)
349 }
350
351 /// Cancel all orders
352 /// <br>
353 /// [API docs](https://docs.pro.coinbase.com/#cancel-an-order)
354 /// <br>
355 /// ~~~~
356 /// let client = PrivateClient::new("tGJSu7SuV3/HOR1/9DcFwO1s560BKI51SDEbnwuvTPbw4BbG5lYJLuKUFpD8TPU61R85dxJpGTygKZ5v+6wJdA==", "t9riylyad0r", "4a9f6de8bcdee641a0a207613dfb43ef");
357 /// let canceled_orders_ids = client.cancel_orders().await.unwrap();
358 /// ~~~~
359 pub async fn cancel_orders(&self) -> Result<Vec<String>, Error> {
360 Ok(self.delete("/orders").await?)
361 }
362
363 /// Get open orders from the profile that the API key belongs
364 /// <br>
365 /// [API docs](https://docs.pro.coinbase.com/#list-orders)
366 /// <br>
367 /// This request is [paginated](https://docs.pro.coinbase.com/#pagination)
368 /// <br>
369 /// ~~~~
370 /// let client = PrivateClient::new("tGJSu7SuV3/HOR1/9DcFwO1s560BKI51SDEbnwuvTPbw4BbG5lYJLuKUFpD8TPU61R85dxJpGTygKZ5v+6wJdA==", "t9riylyad0r", "4a9f6de8bcdee641a0a207613dfb43ef");
371 /// let orders = client
372 /// .get_orders(
373 /// Some(OrderStatus::OpenActivePending),
374 /// Some("2021-06-19T20:24:20.467086Z"),
375 /// None,
376 /// None,
377 /// )
378 /// .await
379 /// .unwrap();
380 /// ~~~~
381 pub async fn get_orders(
382 &self,
383 order_status: Option<OrderStatus>,
384 before: Option<&str>,
385 after: Option<&str>,
386 limit: Option<u16>,
387 ) -> Result<Vec<OrderInfo>, Error> {
388 let path = match order_status {
389 Some(n) => {
390 let params = match n {
391 OrderStatus::Open => "status=open",
392 OrderStatus::Active => "status=active",
393 OrderStatus::Pending => "status=pending",
394 OrderStatus::OpenActive => "status=open&status=active",
395 OrderStatus::OpenPending => "status=open&status=pending",
396 OrderStatus::ActivePending => "status=active&status=pending",
397 OrderStatus::OpenActivePending => "status=open&status=active&status=pending",
398 };
399 format!("/orders?{}&", params)
400 }
401 None => String::from("/orders?"),
402 };
403
404 Ok(self.get_paginated(&path, before, after, limit).await?)
405 }
406
407 /// Get open order from the profile that the API key belongs
408 /// <br>
409 /// [API docs](https://docs.pro.coinbase.com/#get-an-order)
410 /// <br>
411 /// ~~~~
412 /// let client = PrivateClient::new("tGJSu7SuV3/HOR1/9DcFwO1s560BKI51SDEbnwuvTPbw4BbG5lYJLuKUFpD8TPU61R85dxJpGTygKZ5v+6wJdA==", "t9riylyad0r", "4a9f6de8bcdee641a0a207613dfb43ef");
413 /// let order = OrderBuilder::limit(OrderSide::Buy, "BTC-USD", 36000.0, 1.0)
414 /// .build();
415 /// let order_id = client.place_order(order).await.unwrap();
416 /// let order = client.get_order(&order_id).await.unwrap();
417 /// ~~~~
418 pub async fn get_order(&self, order_id: &str) -> Result<OrderInfo, Error> {
419 Ok(self.get(&format!("/orders/{}", order_id)).await?)
420 }
421
422 /// Gets order specified by order OID
423 /// <br>
424 /// [API docs](https://docs.pro.coinbase.com/#get-an-order)
425 pub async fn get_order_by_oid(&self, oid: &str) -> Result<OrderInfo, Error> {
426 Ok(self.get(&format!("/orders/client:{}", oid)).await?)
427 }
428
429 /// Get recent fills by specified order_id of the API key's profile
430 /// <br>
431 /// [API docs](https://docs.pro.coinbase.com/#fills)
432 /// <br>
433 /// ~~~~
434 /// let client = PrivateClient::new("tGJSu7SuV3/HOR1/9DcFwO1s560BKI51SDEbnwuvTPbw4BbG5lYJLuKUFpD8TPU61R85dxJpGTygKZ5v+6wJdA==", "t9riylyad0r", "4a9f6de8bcdee641a0a207613dfb43ef");
435 /// let fills = client
436 /// .get_fill_by_order_id("4f2756cf-dcb5-492b-83e5-5f2141892758", None, None, None)
437 /// .await
438 /// .unwrap();
439 /// ~~~~
440 pub async fn get_fill_by_order_id(
441 &self,
442 order_id: &str,
443 before: Option<&str>,
444 after: Option<&str>,
445 limit: Option<u16>,
446 ) -> Result<Vec<Fill>, Error> {
447 Ok(self
448 .get_paginated(
449 &format!("/fills?order_id={}&", order_id),
450 before,
451 after,
452 limit,
453 )
454 .await?)
455 }
456
457 /// Get recent fills by specified product_id of the API key's profile
458 /// <br>
459 /// [API docs](https://docs.pro.coinbase.com/#fills)
460 /// <br>
461 /// ~~~~
462 /// let client = PrivateClient::new("tGJSu7SuV3/HOR1/9DcFwO1s560BKI51SDEbnwuvTPbw4BbG5lYJLuKUFpD8TPU61R85dxJpGTygKZ5v+6wJdA==", "t9riylyad0r", "4a9f6de8bcdee641a0a207613dfb43ef");
463 /// let fills = client
464 /// .get_fills_by_product_id(&product_id, None, Some("29786034"), None)
465 /// .await
466 /// .unwrap();
467 /// ~~~~
468 pub async fn get_fills_by_product_id(
469 &self,
470 product_id: &str,
471 before: Option<&str>,
472 after: Option<&str>,
473 limit: Option<u16>,
474 ) -> Result<Vec<Fill>, Error> {
475 Ok(self
476 .get_paginated(
477 &format!("/fills?product_id={}&", product_id),
478 before,
479 after,
480 limit,
481 )
482 .await?)
483 }
484
485 /// Get information on your payment method transfer limits, as well as buy/sell limits per currency
486 /// <br>
487 /// [API docs](https://docs.pro.coinbase.com/#limits)
488 /// <br>
489 /// ~~~~
490 /// let client = PrivateClient::new("tGJSu7SuV3/HOR1/9DcFwO1s560BKI51SDEbnwuvTPbw4BbG5lYJLuKUFpD8TPU61R85dxJpGTygKZ5v+6wJdA==", "t9riylyad0r", "4a9f6de8bcdee641a0a207613dfb43ef");
491 /// let limits = client.get_limits().await.unwrap();
492 /// ~~~~
493 pub async fn get_limits(&self) -> Result<Json, Error> {
494 Ok(self.get(&format!("/users/self/exchange-limits")).await?)
495 }
496
497 /// Get deposits from the profile of the API key, in descending order by created time
498 /// <br>
499 /// **optional parameters**
500 /// <br>
501 /// *profile_id*: limit list of deposits to this profile_id. By default, it retrieves deposits using default profile
502 /// <br>
503 /// *before*: if before is set, then it returns deposits created after the before timestamp, sorted by oldest creation date
504 /// <br>
505 /// *after*: if after is set, then it returns deposits created before the after timestamp, sorted by newest
506 /// <br>
507 /// *limit*: truncate list to this many deposits, capped at 100. Default is 100.
508 /// <br>
509 /// [API docs](https://docs.pro.coinbase.com/#list-deposits)
510 /// <br>
511 /// This request is [paginated](https://docs.pro.coinbase.com/#pagination)
512 /// <br>
513 /// ~~~~
514 /// let client = PrivateClient::new("tGJSu7SuV3/HOR1/9DcFwO1s560BKI51SDEbnwuvTPbw4BbG5lYJLuKUFpD8TPU61R85dxJpGTygKZ5v+6wJdA==", "t9riylyad0r", "4a9f6de8bcdee641a0a207613dfb43ef");
515 /// let deposits = client
516 /// .get_deposits(
517 /// Some("b7482eaa-3eea-4065-9d81-1484257c5f92"),
518 /// None,
519 /// None,
520 /// None,
521 /// )
522 /// .await.unwrap();
523 /// ~~~~
524 pub async fn get_deposits(
525 &self,
526 profile_id: Option<&str>,
527 before: Option<&str>,
528 after: Option<&str>,
529 limit: Option<u16>,
530 ) -> Result<Json, Error> {
531 let path = match profile_id {
532 Some(n) => format!("/transfers?type=deposit&profile_id={}&", n),
533 None => String::from("/transfers?type=deposit&"),
534 };
535 Ok(self.get_paginated(&path, before, after, limit).await?)
536 }
537 /// Get internal deposits from the profile of the API key, in descending order by created time
538 /// <br>
539 /// **optional parameters**
540 /// <br>
541 /// *profile_id*: limit list of internal deposits to this profile_id. By default, it retrieves internal deposits using default profile
542 /// <br>
543 /// *before*: if before is set, then it returns internal deposits created after the before timestamp, sorted by oldest creation date
544 /// <br>
545 /// *after*: if after is set, then it returns internal deposits created before the after timestamp, sorted by newest
546 /// <br>
547 /// *limit*: truncate list to this many internal deposits, capped at 100. Default is 100.
548 /// <br>
549 /// [API docs](https://docs.pro.coinbase.com/#list-deposits)
550 /// <br>
551 /// This request is [paginated](https://docs.pro.coinbase.com/#pagination)
552 /// <br>
553 /// ~~~~
554 /// let client = PrivateClient::new("tGJSu7SuV3/HOR1/9DcFwO1s560BKI51SDEbnwuvTPbw4BbG5lYJLuKUFpD8TPU61R85dxJpGTygKZ5v+6wJdA==", "t9riylyad0r", "4a9f6de8bcdee641a0a207613dfb43ef");
555 /// let deposits = client
556 /// .get_internal_deposits(
557 /// Some("e1d7731f-b7e2-4285-b711-eeec76fc2aff"),
558 /// None,
559 /// None,
560 /// None,
561 /// )
562 /// .await.unwrap();
563 /// ~~~~
564 pub async fn get_internal_deposits(
565 &self,
566 profile_id: Option<&str>,
567 before: Option<&str>,
568 after: Option<&str>,
569 limit: Option<u16>,
570 ) -> Result<Json, Error> {
571 let path = match profile_id {
572 Some(n) => format!("/transfers?type=internal_deposit&profile_id={}&", n),
573 None => String::from("/transfers?type=internal_deposit&"),
574 };
575 Ok(self.get_paginated(&path, before, after, limit).await?)
576 }
577
578 /// Get information on a single deposit
579 /// <br>
580 /// [API docs](https://docs.pro.coinbase.com/#single-deposit)
581 /// <br>
582 /// ~~~~
583 /// let client = PrivateClient::new("tGJSu7SuV3/HOR1/9DcFwO1s560BKI51SDEbnwuvTPbw4BbG5lYJLuKUFpD8TPU61R85dxJpGTygKZ5v+6wJdA==", "t9riylyad0r", "4a9f6de8bcdee641a0a207613dfb43ef");
584 /// let deposit = client
585 /// .get_deposit("80259339-7bf9-498f-8200-ddbd32a1c545")
586 /// .await;
587 /// ~~~~
588 pub async fn get_deposit(&self, transfer_id: &str) -> Result<Json, Error> {
589 Ok(self.get(&format!("/transfers/{}", transfer_id)).await?)
590 }
591
592 /// Get your payment methods
593 /// <br>
594 /// [API docs](https://docs.pro.coinbase.com/#payment-methods)
595 /// <br>
596 /// ~~~~
597 /// let client = PrivateClient::new("tGJSu7SuV3/HOR1/9DcFwO1s560BKI51SDEbnwuvTPbw4BbG5lYJLuKUFpD8TPU61R85dxJpGTygKZ5v+6wJdA==", "t9riylyad0r", "4a9f6de8bcdee641a0a207613dfb43ef");
598 /// let payment_methods = client.get_payment_methods().await.unwrap();
599 /// ~~~~
600 pub async fn get_payment_methods(&self) -> Result<Json, Error> {
601 Ok(self.get("/payment-methods").await?)
602 }
603
604 /// Deposit funds from a payment method
605 /// <br>
606 /// [API docs](https://docs.pro.coinbase.com/#payment-method)
607 /// <br>
608 /// ~~~~
609 /// let client = PrivateClient::new("tGJSu7SuV3/HOR1/9DcFwO1s560BKI51SDEbnwuvTPbw4BbG5lYJLuKUFpD8TPU61R85dxJpGTygKZ5v+6wJdA==", "t9riylyad0r", "4a9f6de8bcdee641a0a207613dfb43ef");
610 /// let res = client
611 /// .deposit_funds(10.00, "USD", "1b4b4fbc-8921-5e7c-b362-a1c589a2cf20")
612 /// .await
613 /// .unwrap();
614 /// ~~~~
615 pub async fn deposit_funds(
616 &self,
617 amount: f64,
618 currency: &str,
619 payment_method_id: &str,
620 ) -> Result<DepositInfo, Error> {
621 Ok(self
622 .post_and_deserialize(
623 "/deposits/payment-method",
624 Some(serde_json::json!({
625 "amount": amount,
626 "currency": currency,
627 "payment_method_id": payment_method_id
628 })),
629 )
630 .await?)
631 }
632
633 /// Deposit funds from a coinbase account
634 /// <br>
635 /// [API docs](https://docs.pro.coinbase.com/#coinbase)
636 /// <br>
637 /// ~~~~
638 /// let client = PrivateClient::new("tGJSu7SuV3/HOR1/9DcFwO1s560BKI51SDEbnwuvTPbw4BbG5lYJLuKUFpD8TPU61R85dxJpGTygKZ5v+6wJdA==", "t9riylyad0r", "4a9f6de8bcdee641a0a207613dfb43ef");
639 /// let res = client
640 /// .deposit_funds_from_coinbase(10.00, "BTC", "95671473-4dda-5264-a654-fc6923e8a334")
641 /// .await
642 /// .unwrap();
643 /// ~~~~
644 pub async fn deposit_funds_from_coinbase(
645 &self,
646 amount: f64,
647 currency: &str,
648 coinbase_account_id: &str,
649 ) -> Result<DepositInfo, Error> {
650 Ok(self
651 .post_and_deserialize(
652 "/deposits/coinbase-account",
653 Some(serde_json::json!({
654 "amount": amount,
655 "currency": currency,
656 "coinbase_account_id": coinbase_account_id
657 })),
658 )
659 .await?)
660 }
661
662 /// Get a list of your coinbase accounts
663 /// <br>
664 /// [API docs](https://docs.pro.coinbase.com/#coinbase-accounts)
665 /// <br>
666 /// ~~~~
667 /// let client = PrivateClient::new("tGJSu7SuV3/HOR1/9DcFwO1s560BKI51SDEbnwuvTPbw4BbG5lYJLuKUFpD8TPU61R85dxJpGTygKZ5v+6wJdA==", "t9riylyad0r", "4a9f6de8bcdee641a0a207613dfb43ef");
668 /// let accounts = client.get_coinbase_accounts().await.unwrap();
669 /// ~~~~
670 pub async fn get_coinbase_accounts(&self) -> Result<Json, Error> {
671 Ok(self.get("/coinbase-accounts").await?)
672 }
673
674 /// Generate an address for crypto deposits
675 /// <br>
676 /// [API docs](https://docs.pro.coinbase.com/#generate-a-crypto-deposit-address)
677 /// <br>
678 /// ~~~~
679 /// let client = PrivateClient::new("tGJSu7SuV3/HOR1/9DcFwO1s560BKI51SDEbnwuvTPbw4BbG5lYJLuKUFpD8TPU61R85dxJpGTygKZ5v+6wJdA==", "t9riylyad0r", "4a9f6de8bcdee641a0a207613dfb43ef");
680 /// let address = client
681 /// .generate_crypto_deposit_address("95671473-4dda-5264-a654-fc6923e8a334")
682 /// .await
683 /// .unwrap();
684 /// ~~~~
685 pub async fn generate_crypto_deposit_address(
686 &self,
687 coinbase_account_id: &str,
688 ) -> Result<Json, Error> {
689 Ok(self
690 .post_and_deserialize::<_, Json>(
691 &format!("/coinbase-accounts/{}/addresses", coinbase_account_id),
692 None,
693 )
694 .await?)
695 }
696
697 /// Get withdrawals from the profile of the API key
698 /// <br>
699 /// **optional parameters**
700 /// <br>
701 /// *profile_id*: limit list of withdrawals to this profile_id. By default, it retrieves withdrawals using default profile
702 /// <br>
703 /// *before*: If before is set, then it returns withdrawals created after the before timestamp, sorted by oldest creation date
704 /// <br>
705 /// *after*: If after is set, then it returns withdrawals created before the after timestamp, sorted by newest
706 /// <br>
707 /// *limit*: truncate list to this many withdrawals, capped at 100. Default is 100
708 /// <br>
709 /// [API docs](https://docs.pro.coinbase.com/#list-withdrawals)
710 /// <br>
711 /// This request is [paginated](https://docs.pro.coinbase.com/#pagination)
712 /// <br>
713 /// ~~~~
714 /// let client = PrivateClient::new("tGJSu7SuV3/HOR1/9DcFwO1s560BKI51SDEbnwuvTPbw4BbG5lYJLuKUFpD8TPU61R85dxJpGTygKZ5v+6wJdA==", "t9riylyad0r", "4a9f6de8bcdee641a0a207613dfb43ef");
715 /// let withdrawls = client
716 /// .get_withdrawls(
717 /// Some("b7482eaa-3eea-4065-9d81-1484257c5f92"),
718 /// None,
719 /// None,
720 /// None,
721 /// )
722 /// .await
723 /// .unwrap();
724 /// ~~~~
725 pub async fn get_withdrawls(
726 &self,
727 profile_id: Option<&str>,
728 before: Option<&str>,
729 after: Option<&str>,
730 limit: Option<u16>,
731 ) -> Result<Json, Error> {
732 let path = match profile_id {
733 Some(n) => format!("/transfers?type=withdraw&profile_id={}&", n),
734 None => String::from("/transfers?type=withdraw&"),
735 };
736 Ok(self.get_paginated(&path, before, after, limit).await?)
737 }
738
739 /// Get withdrawals from the profile of the API key
740 /// <br>
741 /// **optional parameters**
742 /// <br>
743 /// *profile_id*: limit list of internal withdrawals to this profile_id. By default, it retrieves internal withdrawals using default profile
744 /// <br>
745 /// <br>
746 /// *before*: If before is set, then it returns internal withdrawals created after the before timestamp, sorted by oldest creation date
747 /// <br>
748 /// <br>
749 /// *after*: If after is set, then it returns internal withdrawals created before the after timestamp, sorted by newest
750 /// <br>
751 /// <br>
752 /// *limit*: truncate list to this many internal withdrawals, capped at 100. Default is 100
753 /// <br>
754 /// [API docs](https://docs.pro.coinbase.com/#list-withdrawals)
755 /// <br>
756 /// This request is [paginated](https://docs.pro.coinbase.com/#pagination)
757 /// <br>
758 /// ~~~~
759 /// let client = PrivateClient::new("tGJSu7SuV3/HOR1/9DcFwO1s560BKI51SDEbnwuvTPbw4BbG5lYJLuKUFpD8TPU61R85dxJpGTygKZ5v+6wJdA==", "t9riylyad0r", "4a9f6de8bcdee641a0a207613dfb43ef");
760 /// let withdrawls = client
761 /// .get_internal_withdrawls(
762 /// Some("b7482eaa-3eea-4065-9d81-1484257c5f92"),
763 /// None,
764 /// None,
765 /// None,
766 /// )
767 /// .await
768 /// .unwrap();
769 /// ~~~~
770 pub async fn get_internal_withdrawls(
771 &self,
772 profile_id: Option<&str>,
773 before: Option<&str>,
774 after: Option<&str>,
775 limit: Option<u16>,
776 ) -> Result<Json, Error> {
777 let path = match profile_id {
778 Some(n) => format!("/transfers?type=internal_withdraw&profile_id={}&", n),
779 None => String::from("/transfers?type=internal_withdraw&"),
780 };
781 Ok(self.get_paginated(&path, before, after, limit).await?)
782 }
783
784 /// Get information on a single withdrawal
785 /// <br>
786 /// [API docs](https://docs.pro.coinbase.com/#single-withdrawal)
787 /// <br>
788 /// ~~~~
789 /// let client = PrivateClient::new("tGJSu7SuV3/HOR1/9DcFwO1s560BKI51SDEbnwuvTPbw4BbG5lYJLuKUFpD8TPU61R85dxJpGTygKZ5v+6wJdA==", "t9riylyad0r", "4a9f6de8bcdee641a0a207613dfb43ef");
790 /// let withdrawl = client
791 /// .get_withdrawl("0e94a87f-9d50-4ead-86ac-7898830c5edf")
792 /// .await
793 /// .unwrap();
794 /// ~~~~
795 pub async fn get_withdrawl(&self, transfer_id: &str) -> Result<Json, Error> {
796 Ok(self.get(&format!("/transfers/{}", transfer_id)).await?)
797 }
798
799 /// Withdraw funds to a payment method
800 /// <br>
801 /// [API docs](https://docs.pro.coinbase.com/#payment-method55)
802 pub async fn withdraw_funds(
803 &self,
804 amount: f64,
805 currency: &str,
806 payment_method_id: &str,
807 ) -> Result<WithdrawInfo, Error> {
808 Ok(self
809 .post_and_deserialize(
810 "/withdrawals/payment-method",
811 Some(serde_json::json!({
812 "amount": amount,
813 "currency": currency,
814 "payment_method_id": payment_method_id
815 })),
816 )
817 .await?)
818 }
819
820 /// Withdraw funds to a coinbase account
821 /// <br>
822 /// [API docs](https://docs.pro.coinbase.com/#coinbase56)
823 /// <br>
824 /// ~~~~
825 /// let client = PrivateClient::new("tGJSu7SuV3/HOR1/9DcFwO1s560BKI51SDEbnwuvTPbw4BbG5lYJLuKUFpD8TPU61R85dxJpGTygKZ5v+6wJdA==", "t9riylyad0r", "4a9f6de8bcdee641a0a207613dfb43ef");
826 /// let res = client
827 /// .withdraw_to_coinbase(1.0, "ADA", "91bdfea7-f2sd-5waa-bb0d-5b93c9f09ffc")
828 /// .await
829 /// .unwrap();
830 /// ~~~~
831 pub async fn withdraw_to_coinbase(
832 &self,
833 amount: f64,
834 currency: &str,
835 coinbase_account_id: &str,
836 ) -> Result<WithdrawInfo, Error> {
837 Ok(self
838 .post_and_deserialize(
839 "/withdrawals/coinbase-account",
840 Some(serde_json::json!({
841 "amount": amount,
842 "currency": currency,
843 "coinbase_account_id": coinbase_account_id
844 })),
845 )
846 .await?)
847 }
848
849 /// Withdraw funds to a crypto address.
850 /// <br>
851 /// **parameters**
852 /// <br>
853 /// amount: The amount to withdraw
854 /// <br>
855 /// currency: The type of currency
856 /// <br>
857 /// crypto_address: A crypto address of the recipient
858 /// <br>
859 /// destination_tag: A destination tag for currencies that support one
860 /// <br>
861 /// no_destination_tag: A boolean flag to opt out of using a destination tag for currencies that support one. This is required when not providing a destination tag.
862 /// <br>
863 /// add_network_fee_to_total: A boolean flag to add the network fee on top of the amount. If this is blank, it will default to deducting the network fee from the amount.
864 /// <br>
865 /// [API docs](https://docs.pro.coinbase.com/#crypto)
866 /// <br>
867 /// ~~~~
868 /// let client = PrivateClient::new("tGJSu7SuV3/HOR1/9DcFwO1s560BKI51SDEbnwuvTPbw4BbG5lYJLuKUFpD8TPU61R85dxJpGTygKZ5v+6wJdA==", "t9riylyad0r", "4a9f6de8bcdee641a0a207613dfb43ef");
869 /// let res = client.withdraw_to_crypto_address(6.0, "ADA", "addr1qyk0yr3ht9d6hcqwp8q8j38nxs04npyjauzz9wp5jcfr95h64lvegfk57zmzltj3nmpjff6490ayyvjh0g6sne6hm3hspnnscy", None, None, None).await.unwrap();
870 /// ~~~~
871 pub async fn withdraw_to_crypto_address(
872 &self,
873 amount: f64,
874 currency: &str,
875 crypto_address: &str,
876 destination_tag: Option<&str>,
877 no_destination_tag: Option<bool>,
878 add_network_fee_to_total: Option<bool>,
879 ) -> Result<Json, Error> {
880 Ok(self
881 .post_and_deserialize(
882 "/withdrawals/crypto",
883 Some(serde_json::json!({
884 "amount": amount,
885 "currency": currency,
886 "crypto_address": crypto_address,
887 "destination_tag": destination_tag,
888 "no_destination_tag": no_destination_tag,
889 "add_network_fee_to_total": add_network_fee_to_total
890 })),
891 )
892 .await?)
893 }
894
895 /// Get your current maker & taker fee rates, as well as your 30-day trailing volume
896 /// <br>
897 /// [API docs](https://docs.pro.coinbase.com/#get-current-fees)
898 /// <br>
899 /// ~~~~
900 /// let client = PrivateClient::new("tGJSu7SuV3/HOR1/9DcFwO1s560BKI51SDEbnwuvTPbw4BbG5lYJLuKUFpD8TPU61R85dxJpGTygKZ5v+6wJdA==", "t9riylyad0r", "4a9f6de8bcdee641a0a207613dfb43ef");
901 /// let fees = client.get_fees().await.unwrap();
902 /// ~~~~
903 pub async fn get_fees(&self) -> Result<Fees, Error> {
904 Ok(self.get("/fees").await?)
905 }
906
907 /// Get the network fee estimate when sending to the given address
908 /// <br>
909 /// [API docs](https://docs.pro.coinbase.com/#fee-estimate)
910 /// <br>
911 /// ~~~~
912 /// let client = PrivateClient::new("tGJSu7SuV3/HOR1/9DcFwO1s560BKI51SDEbnwuvTPbw4BbG5lYJLuKUFpD8TPU61R85dxJpGTygKZ5v+6wJdA==", "t9riylyad0r", "4a9f6de8bcdee641a0a207613dfb43ef");
913 /// let fee = client
914 /// .get_fee_estimate("ETH", "0x82289D45Ee8E806C63Ba0DC94a22d4238525d815")
915 /// .await
916 /// .unwrap();
917 /// ~~~~
918 pub async fn get_fee_estimate(
919 &self,
920 currency: &str,
921 crypto_address: &str,
922 ) -> Result<f64, Error> {
923 #[derive(serde::Deserialize)]
924 struct Fee {
925 fee: f64,
926 }
927 let fee = self
928 .get::<Fee>(&format!(
929 "/withdrawals/fee-estimate?currency={}&crypto_address={}",
930 currency, crypto_address
931 ))
932 .await?;
933 Ok(fee.fee)
934 }
935
936 /// Convert between stablecoins
937 /// <br>
938 /// [API docs](https://docs.pro.coinbase.com/#stablecoin-conversions)
939 /// <br>
940 /// ~~~~
941 /// let client = PrivateClient::new("tGJSu7SuV3/HOR1/9DcFwO1s560BKI51SDEbnwuvTPbw4BbG5lYJLuKUFpD8TPU61R85dxJpGTygKZ5v+6wJdA==", "t9riylyad0r", "4a9f6de8bcdee641a0a207613dfb43ef");
942 /// let convertion = client
943 /// .convert_stablecoin("USD", "USDC", 10.00)
944 /// .await
945 /// .unwrap();
946 /// ~~~~
947 pub async fn convert_stablecoin(
948 &self,
949 from_currency_id: &str,
950 to_currency_id: &str,
951 amount: f64,
952 ) -> Result<StablecoinConversion, Error> {
953 Ok(self
954 .post_and_deserialize(
955 "/conversions",
956 Some(serde_json::json!({
957 "from": from_currency_id,
958 "to": to_currency_id,
959 "amount": amount
960 })),
961 )
962 .await?)
963 }
964
965 /// Reports provide batches of historic information about your profile in various human and machine readable forms
966 /// <br>
967 /// Create a `Report` useing [`ReportBuilder`](https://docs.rs/coinbase-client/1.0.0-alpha/coinbase_client/private_client/struct.ReportBuilder.html)
968 /// <br>
969 /// [API docs](https://docs.pro.coinbase.com/#create-a-new-report)
970 /// <br>
971 /// ~~~~
972 /// let client = PrivateClient::new("tGJSu7SuV3/HOR1/9DcFwO1s560BKI51SDEbnwuvTPbw4BbG5lYJLuKUFpD8TPU61R85dxJpGTygKZ5v+6wJdA==", "t9riylyad0r", "4a9f6de8bcdee641a0a207613dfb43ef");
973 /// let report = Report::account_builder(
974 /// "2014-11-01T00:00:00.000Z",
975 /// "2021-06-11T02:48:15.853Z",
976 /// "1f6a7175-a89c-494f-986d-af9987e6dd69",
977 /// )
978 /// .email("willstanhope@gmail.com")
979 /// .format(Format::CSV)
980 /// .build();
981 /// let res = client.create_report(report).await.unwrap();
982 /// ~~~~
983 pub async fn create_report<'a>(&self, report: Report) -> Result<ReportInfo, Error> {
984 Ok(self.post_and_deserialize("/reports", Some(report)).await?)
985 }
986
987 /// Get report status
988 /// <br>
989 /// Once a report request has been accepted for processing, the status becomes available
990 /// <br>
991 /// [API docs](https://docs.pro.coinbase.com/#get-report-status)
992 /// <br>
993 /// ~~~~
994 /// let client = PrivateClient::new("tGJSu7SuV3/HOR1/9DcFwO1s560BKI51SDEbnwuvTPbw4BbG5lYJLuKUFpD8TPU61R85dxJpGTygKZ5v+6wJdA==", "t9riylyad0r", "4a9f6de8bcdee641a0a207613dfb43ef");
995 /// let report = client
996 /// .get_report("d4a3e847-b618-454d-bcb3-e77b0ad61600")
997 /// .await
998 /// .unwrap();
999 /// ~~~~
1000 pub async fn get_report(&self, report_id: &str) -> Result<ReportInfo, Error> {
1001 Ok(self.get(&format!("/reports/{}", report_id)).await?)
1002 }
1003
1004 /// Get your profiles
1005 /// <br>
1006 /// [API docs](https://docs.pro.coinbase.com/#list-profiles)
1007 /// <br>
1008 /// ~~~~
1009 /// let client = PrivateClient::new("tGJSu7SuV3/HOR1/9DcFwO1s560BKI51SDEbnwuvTPbw4BbG5lYJLuKUFpD8TPU61R85dxJpGTygKZ5v+6wJdA==", "t9riylyad0r", "4a9f6de8bcdee641a0a207613dfb43ef");
1010 /// let profiles = client.get_profiles().await.unwrap();
1011 /// ~~~~
1012 pub async fn get_profiles(&self) -> Result<Vec<Profile>, Error> {
1013 Ok(self.get("/profiles").await?)
1014 }
1015
1016 /// Get a single profile by profile id
1017 /// <br>
1018 /// [API docs](https://docs.pro.coinbase.com/#get-a-profile)
1019 /// <br>
1020 /// ~~~~
1021 /// let client = PrivateClient::new("tGJSu7SuV3/HOR1/9DcFwO1s560BKI51SDEbnwuvTPbw4BbG5lYJLuKUFpD8TPU61R85dxJpGTygKZ5v+6wJdA==", "t9riylyad0r", "4a9f6de8bcdee641a0a207613dfb43ef");
1022 /// let profile = client
1023 /// .get_profile("e1d7731f-b7e2-4285-b711-eeec76fc2aff")
1024 /// .await
1025 /// .unwrap();
1026 /// ~~~~
1027 pub async fn get_profile(&self, profile_id: &str) -> Result<Profile, Error> {
1028 Ok(self.get(&format!("/profiles/{}", profile_id)).await?)
1029 }
1030
1031 /// Transfer funds from API key's profile to another user owned profile
1032 /// <br>
1033 /// [API docs](https://docs.pro.coinbase.com/#create-profile-transfer)
1034 /// <br>
1035 /// ~~~~
1036 /// let client = PrivateClient::new("tGJSu7SuV3/HOR1/9DcFwO1s560BKI51SDEbnwuvTPbw4BbG5lYJLuKUFpD8TPU61R85dxJpGTygKZ5v+6wJdA==", "t9riylyad0r", "4a9f6de8bcdee641a0a207613dfb43ef");
1037 /// let ok = client
1038 /// .create_profile_transfer(
1039 /// "e1d7731f-b7e2-4285-b711-eeec76fc2aff",
1040 /// "3510ac37-1a99-4c9c-9865-15f1bc5a832e",
1041 /// "USD",
1042 /// 100.00,
1043 /// )
1044 /// .await
1045 /// .unwrap();
1046 /// ~~~~
1047 pub async fn create_profile_transfer(
1048 &self,
1049 from: &str,
1050 to: &str,
1051 currency: &str,
1052 amount: f64,
1053 ) -> Result<String, Error> {
1054 let response = self
1055 .post(
1056 "/profiles/transfer",
1057 Some(serde_json::json!(
1058 {
1059 "from": from,
1060 "to": to,
1061 "currency": currency,
1062 "amount": amount
1063 }
1064 )),
1065 )
1066 .await?;
1067 let status = response.status();
1068 if !status.is_success() {
1069 let error_message = response.json::<ErrorMessage>().await?;
1070 return Err(Error::new(ErrorKind::Status(StatusError::new(
1071 status.as_u16(),
1072 error_message.message,
1073 ))));
1074 }
1075 Ok(response.text().await?)
1076 }
1077
1078 /// Get cryptographically signed prices ready to be posted on-chain using Open Oracle smart contracts.
1079 /// <br>
1080 /// [API docs](https://docs.pro.coinbase.com/#oracle)
1081 pub async fn oracle(&self) -> Result<Json, Error> {
1082 Ok(self.get("/oracle").await?)
1083 }
1084}
1085
1086/// Limit list of orders to these statuses. Passing `OpenActivePending` returns orders of all statuses.
1087pub enum OrderStatus {
1088 Open,
1089 Active,
1090 Pending,
1091 OpenActive,
1092 OpenPending,
1093 ActivePending,
1094 OpenActivePending,
1095}
1096
1097/// A structure that repersents a Stablecoin Conversion
1098#[derive(Deserialize, Debug)]
1099pub struct StablecoinConversion {
1100 id: String,
1101 amount: String,
1102 from_account_id: String,
1103 to_account_id: String,
1104 from: String,
1105 to: String,
1106}
1107
1108/// A structure that repersents an Account
1109#[derive(Deserialize, Debug)]
1110pub struct Account {
1111 pub id: String,
1112 pub currency: String,
1113 pub balance: String,
1114 pub available: String,
1115 pub hold: String,
1116 pub profile_id: String,
1117 pub trading_enabled: bool,
1118}
1119
1120/// A structure that repersents an Account History
1121#[derive(Deserialize, Debug)]
1122pub struct AccountHistory {
1123 id: String,
1124 #[serde(deserialize_with = "deserialize_to_date")]
1125 created_at: DateTime<Utc>,
1126 amount: String,
1127 balance: String,
1128 r#type: String,
1129 details: AccountHistoryDetails,
1130}
1131
1132/// A structure that repersents an Account Hold
1133#[derive(Deserialize, Debug)]
1134pub struct Hold {
1135 id: String,
1136 account_id: String,
1137 #[serde(deserialize_with = "deserialize_to_date")]
1138 created_at: DateTime<Utc>,
1139 #[serde(deserialize_with = "deserialize_to_date")]
1140 updated_at: DateTime<Utc>,
1141 amount: String,
1142 r#type: String,
1143 r#ref: String,
1144}
1145
1146/// A structure that repersents Account History Details
1147#[derive(Deserialize, Debug)]
1148pub struct AccountHistoryDetails {
1149 order_id: Option<String>,
1150 trade_id: Option<String>,
1151 product_id: Option<String>,
1152}
1153
1154/// A structure that repersents Deposit Info
1155#[derive(Deserialize, Debug)]
1156pub struct DepositInfo {
1157 id: String,
1158 amount: String,
1159 currency: String,
1160 payout_at: Option<String>,
1161}
1162
1163/// A structure that repersents Withdraw Info
1164#[derive(Deserialize, Debug)]
1165pub struct WithdrawInfo {
1166 id: String,
1167 amount: String,
1168 currency: String,
1169}
1170
1171/// A structure that repersents Order Info
1172#[derive(Debug, Deserialize)]
1173pub struct OrderInfo {
1174 id: String,
1175 price: String,
1176 size: String,
1177 product_id: String,
1178 side: String,
1179 stp: Option<String>,
1180 r#type: String,
1181 time_in_force: String,
1182 post_only: bool,
1183 #[serde(deserialize_with = "deserialize_to_date")]
1184 created_at: DateTime<Utc>,
1185 fill_fees: String,
1186 filled_size: String,
1187 executed_value: String,
1188 status: String,
1189 settled: bool,
1190}
1191
1192/// A structure that repersents Report Info
1193#[derive(Debug, Deserialize)]
1194pub struct ReportInfo {
1195 id: String,
1196 r#type: String,
1197 status: String,
1198 #[serde(default, deserialize_with = "deserialize_option_to_date")]
1199 created_at: Option<DateTime<Utc>>,
1200 #[serde(default, deserialize_with = "deserialize_option_to_date")]
1201 completed_at: Option<DateTime<Utc>>,
1202 #[serde(default, deserialize_with = "deserialize_option_to_date")]
1203 expires_at: Option<DateTime<Utc>>,
1204 file_url: Option<String>,
1205 params: Option<ReportParams>,
1206}
1207
1208/// A structure that repersents Report Info Params
1209#[derive(Debug, Deserialize)]
1210pub struct ReportParams {
1211 #[serde(deserialize_with = "deserialize_to_date")]
1212 start_date: DateTime<Utc>,
1213 #[serde(deserialize_with = "deserialize_to_date")]
1214 end_date: DateTime<Utc>,
1215}
1216
1217/// A structure that repersents a Fill
1218#[derive(Debug, Deserialize)]
1219pub struct Fill {
1220 trade_id: u64,
1221 product_id: String,
1222 price: String,
1223 size: String,
1224 order_id: String,
1225 #[serde(deserialize_with = "deserialize_to_date")]
1226 created_at: DateTime<Utc>,
1227 liquidity: String,
1228 fee: String,
1229 settled: bool,
1230 side: String,
1231}
1232
1233/// A structure that represents your current maker & taker fee rates, as well as your 30-day trailing volume
1234#[derive(Debug, Deserialize)]
1235pub struct Fees {
1236 maker_fee_rate: String,
1237 taker_fee_rate: String,
1238 usd_volume: String,
1239}
1240
1241/// A structure represents a single profile
1242#[derive(Debug, Deserialize)]
1243pub struct Profile {
1244 id: String,
1245 user_id: String,
1246 name: String,
1247 active: bool,
1248 is_default: bool,
1249 #[serde(deserialize_with = "deserialize_to_date")]
1250 created_at: DateTime<Utc>,
1251}