coinbase_client/
public_client.rs

1use super::{
2    deserialize_response, deserialize_to_date, COINBASE_API_URL, COINBASE_SANDBOX_API_URL,
3};
4use crate::{configure_pagination, error::Error};
5use chrono::{DateTime, Utc};
6use reqwest;
7use serde;
8
9/// `PublicClient provides public market data
10pub struct PublicClient {
11    reqwest_client: reqwest::Client,
12    url: &'static str,
13}
14
15impl PublicClient {
16    async fn get_paginated<T>(
17        &self,
18        path: &str,
19        before: Option<&str>,
20        after: Option<&str>,
21        limit: Option<u16>,
22    ) -> Result<T, Error>
23    where
24        T: serde::de::DeserializeOwned,
25    {
26        let params = configure_pagination(before, after, limit);
27        self.get(&format!("{}{}", path, params)).await
28    }
29
30    async fn get<T>(&self, path: &str) -> Result<T, Error>
31    where
32        T: serde::de::DeserializeOwned,
33    {
34        let response = self
35            .reqwest_client
36            .get(format!("{}{}", self.url, path))
37            .header(reqwest::header::USER_AGENT, "coinbase_client")
38            .send()
39            .await?;
40        deserialize_response(response).await
41    }
42
43    /// Creates a `PublicClient`
44    /// <br>
45    /// ~~~~
46    /// let client = PublicClient::new();
47    /// ~~~~
48    pub fn new() -> Self {
49        Self {
50            reqwest_client: reqwest::Client::new(),
51            url: COINBASE_API_URL,
52        }
53    }
54
55    /// Creates a `PublicClient` to be used with the coinbase pro sandbox API
56    /// <br>
57    /// ~~~~
58    /// let client = PublicClient::new();
59    /// ~~~~
60    pub fn new_sandbox() -> Self {
61        Self {
62            reqwest_client: reqwest::Client::new(),
63            url: COINBASE_SANDBOX_API_URL,
64        }
65    }
66
67    /// Get a list of available currency pairs for trading
68    /// <br>
69    /// [api docs](https://docs.pro.coinbase.com/#get-products)
70    /// <br>
71    /// ~~~~
72    /// let client = PublicClient::new();
73    /// let products = client.get_products().await.unwrap();
74    /// ~~~~
75    pub async fn get_products(&self) -> Result<Vec<Product>, Error> {
76        let products: Vec<Product> = self.get("/products").await?;
77        Ok(products)
78    }
79
80    /// Get market data for a specific currency pair.
81    /// <br>
82    /// [api docs](https://docs.pro.coinbase.com/#get-single-product)
83    /// <br>
84    /// ~~~~
85    /// let client = PublicClient::new();
86    /// let product = client.get_product("BTC-USD").await.unwrap();
87    /// ~~~~
88    pub async fn get_product(&self, id: &str) -> Result<Product, Error> {
89        let product: Product = self.get(&format!("/products/{}", id)).await?;
90        Ok(product)
91    }
92
93    // Get a list of open orders for a product
94    async fn get_order_book(
95        &self,
96        id: &str,
97        level: OrderLevel,
98    ) -> Result<OrderBook<BookEntry>, Error> {
99        let book: OrderBook<BookEntry> = self
100            .get(&format!("/products/{}/book?level={}", id, level as u8))
101            .await?;
102        Ok(book)
103    }
104
105    /// Get a list of open orders for a product
106    /// <br>
107    /// Gets only the best bid and ask
108    /// <br>
109    /// [api docs](https://docs.pro.coinbase.com/#get-product-order-book)
110    /// <br>
111    /// ~~~~
112    /// let client = PublicClient::new();
113    /// let order_book = client.get_product_order_book("BTC-USD").await.unwrap();
114    /// ~~~~
115    pub async fn get_product_order_book(&self, id: &str) -> Result<OrderBook<BookEntry>, Error> {
116        Ok(self.get_order_book(id, OrderLevel::One).await?)
117    }
118
119    /// Get a list of open orders for a product
120    /// <br>
121    /// Gets top 50 bids and asks
122    /// <br>
123    /// [api docs](https://docs.pro.coinbase.com/#get-product-order-book)
124    /// <br>
125    /// ~~~~
126    /// let client = PublicClient::new();
127    /// let order_book = client
128    ///     .get_product_order_book_top50("BTC-USD")
129    ///     .await
130    ///     .unwrap();
131    /// ~~~~
132    pub async fn get_product_order_book_top50(
133        &self,
134        id: &str,
135    ) -> Result<OrderBook<BookEntry>, Error> {
136        Ok(self.get_order_book(id, OrderLevel::Two).await?)
137    }
138
139    /// Get a list of open orders for a product
140    /// <br>
141    /// Get Full order book
142    /// <br>
143    /// [api docs](https://docs.pro.coinbase.com/#get-product-order-book)
144    /// <br>
145    /// ~~~~
146    /// let client = PublicClient::new();
147    /// let order_book = client.get_product_order_book_all("BTC-USD").await.unwrap();
148    /// ~~~~
149    pub async fn get_product_order_book_all(
150        &self,
151        id: &str,
152    ) -> Result<OrderBook<FullBookEntry>, Error> {
153        let book: OrderBook<FullBookEntry> =
154            self.get(&format!("/products/{}/book?level=3", id)).await?;
155        Ok(book)
156    }
157
158    /// Get snapshot information about the last trade (tick), best bid/ask and 24h volume.
159    /// <br>
160    /// [api docs](https://docs.pro.coinbase.com/#get-product-ticker)
161    /// <br>
162    /// This request is [paginated](https://docs.pro.coinbase.com/#pagination)
163    /// <br>
164    /// ~~~~
165    /// let client = PublicClient::new();
166    /// let ticker = client
167    ///     .get_product_ticker("BTC-USD", Some("30902419"), None, None)
168    ///     .await
169    ///     .unwrap();    
170    /// ~~~~
171    pub async fn get_product_ticker(
172        &self,
173        id: &str,
174        before: Option<&str>,
175        after: Option<&str>,
176        limit: Option<u16>,
177    ) -> Result<Ticker, Error> {
178        let ticker = self
179            .get_paginated(&format!("/products/{}/ticker?", id), before, after, limit)
180            .await?;
181        Ok(ticker)
182    }
183
184    /// Get the latest trades for a product.
185    ///<br>
186    ///<br>
187    /// **PARAMETERS**
188    /// <br>
189    ///before: Request page before (newer) this pagination id.
190    ///<br>
191    ///after: Request page after (older) this pagination id.
192    ///<br>
193    ///limit: Number of results per request. Maximum 1000. (default 1000)
194    /// <br>
195    /// [api docs](https://docs.pro.coinbase.com/#get-trades)
196    /// <br>
197    /// This request is [paginated](https://docs.pro.coinbase.com/#pagination)
198    /// <br>
199    /// ~~~~
200    /// let client = PublicClient::new();
201    /// let trades = client
202    ///     .get_product_trades("BTC-USD", None, Some("30898635"), Some(100))
203    ///     .await
204    ///     .unwrap();
205    /// ~~~~
206    pub async fn get_product_trades(
207        &self,
208        id: &str,
209        before: Option<&str>,
210        after: Option<&str>,
211        limit: Option<u16>,
212    ) -> Result<Vec<Trade>, Error> {
213        let trades: Vec<Trade> = self
214            .get_paginated(&format!("/products/{}/trades?", id), before, after, limit)
215            .await?;
216        Ok(trades)
217    }
218
219    /// get historic rates for a product
220    /// <br>
221    /// <br>
222    /// **PARAMETERS**
223    /// <br>
224    /// *start*: Start time in ISO 8601
225    /// <br>
226    /// *end*: End time in ISO 8601
227    /// <br>
228    /// *granularity*: Desired timeslice in seconds
229    /// <br>
230    /// [api docs](https://docs.pro.coinbase.com/#get-historic-rates)
231    /// <br>
232    /// ~~~~
233    /// let client = PublicClient::new();
234    /// let historical_rates = client
235    ///     .get_product_historic_rates("BTC-USD", None, None, Some(Granularity::OneMinute))
236    ///     .await
237    ///     .unwrap();
238    /// ~~~~
239    pub async fn get_product_historic_rates(
240        &self,
241        id: &str,
242        start: Option<&str>,
243        end: Option<&str>,
244        granularity: Option<Granularity>,
245    ) -> Result<Vec<HistoricRate>, Error> {
246        let mut appended = false;
247        let mut path = format!("/products/{}/candles", id);
248        if let Some(n) = start {
249            appended = true;
250            path.push_str(&format!("?start={}", n));
251        }
252
253        if let Some(n) = end {
254            if appended {
255                path.push_str(&format!("&end={}", n));
256            } else {
257                path.push_str(&format!("?end={}", n));
258            }
259        }
260        if let Some(n) = granularity {
261            if appended {
262                path.push_str(&format!("&granularity={}", n as u32));
263            } else {
264                path.push_str(&format!("?granularity={}", n as u32));
265            }
266        }
267        let rates: Vec<HistoricRate> = self.get(&path).await?;
268        Ok(rates)
269    }
270
271    /// Get 24 hr stats for the product
272    /// <br>
273    /// [api docs](https://docs.pro.coinbase.com/#get-24hr-stats)
274    /// <br>
275    /// ~~~~
276    /// let client = PublicClient::new();
277    /// let twenty_four_hour_stats = client.get_product_24hr_stats("BTC-USD").await.unwrap();
278    /// ~~~~
279    pub async fn get_product_24hr_stats(&self, id: &str) -> Result<TwentyFourHourStats, Error> {
280        let stats: TwentyFourHourStats = self.get(&format!("/products/{}/stats", id)).await?;
281        Ok(stats)
282    }
283
284    /// Get known currencies
285    /// <br>
286    /// [api docs](https://docs.pro.coinbase.com/#get-currencies)
287    /// <br>
288    /// ~~~~
289    /// let client = PublicClient::new();
290    /// let currencies = client.get_currencies().await.unwrap();
291    /// ~~~~
292    pub async fn get_currencies(&self) -> Result<Vec<Currency>, Error> {
293        let currencies: Vec<Currency> = self.get("/currencies").await?;
294        Ok(currencies)
295    }
296
297    /// Get the currency for specified id
298    /// <br>
299    /// [api docs](https://docs.pro.coinbase.com/#get-a-currency)
300    /// <br>
301    /// ~~~~
302    /// let client = PublicClient::new();
303    /// let currency = client.get_currency("LINK").await.unwrap();
304    /// ~~~~
305    pub async fn get_currency(&self, id: &str) -> Result<Currency, Error> {
306        Ok(self.get(&format!("/currencies/{}", id)).await?)
307    }
308
309    /// Get the API server time
310    /// <br>
311    /// [api docs](https://docs.pro.coinbase.com/#time)
312    /// <br>
313    /// ~~~~
314    /// let client = PublicClient::new();
315    /// let time = client.get_time().await.unwrap();
316    /// ~~~~
317    pub async fn get_time(&self) -> Result<Time, Error> {
318        let time: Time = self.get("/time").await?;
319        Ok(time)
320    }
321}
322
323/// A structure that represents a product
324#[derive(serde::Deserialize, Debug)]
325pub struct Product {
326    pub id: String,
327    pub display_name: String,
328    pub base_currency: String,
329    pub quote_currency: String,
330    pub base_increment: String,
331    pub quote_increment: String,
332    pub base_min_size: String,
333    pub base_max_size: String,
334    pub min_market_funds: String,
335    pub max_market_funds: String,
336    pub status: String,
337    pub status_message: String,
338    pub cancel_only: bool,
339    pub limit_only: bool,
340    pub post_only: bool,
341    pub trading_disabled: bool,
342}
343
344#[derive(serde::Deserialize, Debug)]
345pub struct BookEntry {
346    pub price: String,
347    pub size: String,
348    pub num_orders: u64,
349}
350
351#[derive(serde::Deserialize, Debug)]
352pub struct FullBookEntry {
353    pub price: String,
354    pub size: String,
355    pub order_id: String,
356}
357
358/// A structure that represents the trade list of open orders for a product
359#[derive(serde::Deserialize, Debug)]
360pub struct OrderBook<T> {
361    pub bids: Vec<T>,
362    pub asks: Vec<T>,
363    pub sequence: u64,
364}
365
366/// A structure that represents a trade
367#[derive(serde::Deserialize, Debug)]
368pub struct Trade {
369    #[serde(deserialize_with = "deserialize_to_date")]
370    pub time: DateTime<Utc>,
371    pub trade_id: u64,
372    pub price: String,
373    pub size: String,
374    pub side: String,
375}
376
377/// A structure that represents latest trades for a product
378#[derive(serde::Deserialize, Debug)]
379pub struct Ticker {
380    pub trade_id: u64,
381    pub price: String,
382    pub size: String,
383    pub bid: String,
384    pub ask: String,
385    pub volume: String,
386    #[serde(deserialize_with = "deserialize_to_date")]
387    pub time: DateTime<Utc>,
388}
389
390/// A structure that represents rates for a product
391#[derive(serde::Deserialize, Debug)]
392pub struct HistoricRate {
393    pub time: u64,
394    pub low: f64,
395    pub high: f64,
396    pub open: f64,
397    pub close: f64,
398    pub volume: f64,
399}
400
401/// A structure that represents 24 hr stats for a product
402#[derive(serde::Deserialize, Debug)]
403pub struct TwentyFourHourStats {
404    pub open: String,
405    pub high: String,
406    pub low: String,
407    pub volume: String,
408    pub last: String,
409    pub volume_30day: String,
410}
411
412/// A structure that represents a currency
413#[derive(serde::Deserialize, Debug)]
414pub struct Currency {
415    pub id: String,
416    pub name: String,
417    pub min_size: String,
418    pub status: String,
419    pub message: Option<String>,
420    pub max_precision: String,
421    pub convertible_to: Option<Vec<String>>,
422    pub details: CurrencyDetails,
423}
424
425#[derive(serde::Deserialize, Debug)]
426pub struct CurrencyDetails {
427    pub r#type: String, // use raw identifier to allow reserved keyword
428    pub symbol: Option<String>,
429    pub network_confirmations: u64,
430    pub sort_order: u64,
431    pub crypto_address_link: String,
432    pub crypto_transaction_link: String,
433    pub push_payment_methods: Vec<String>,
434    pub group_types: Option<Vec<String>>,
435    pub display_name: Option<String>,
436    pub processing_time_seconds: Option<f64>,
437    pub min_withdrawal_amount: f64,
438    pub max_withdrawal_amount: f64,
439}
440
441/// A structure that represents the API server time.
442#[derive(serde::Deserialize, Debug)]
443pub struct Time {
444    #[serde(deserialize_with = "deserialize_to_date")]
445    pub iso: DateTime<Utc>,
446    pub epoch: f64,
447}
448
449enum OrderLevel {
450    One = 1,
451    Two = 2,
452}
453
454/// Desired timeslice in seconds {60, 300, 900, 3600, 21600, 86400}
455pub enum Granularity {
456    OneMinute = 60,
457    FiveMinutes = 300,
458    FifteenMinutes = 900,
459    OneHour = 3600,
460    SixHours = 21600,
461    OneDay = 86400,
462}