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
9pub 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 pub fn new() -> Self {
49 Self {
50 reqwest_client: reqwest::Client::new(),
51 url: COINBASE_API_URL,
52 }
53 }
54
55 pub fn new_sandbox() -> Self {
61 Self {
62 reqwest_client: reqwest::Client::new(),
63 url: COINBASE_SANDBOX_API_URL,
64 }
65 }
66
67 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 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 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 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 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 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 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 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 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 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 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 pub async fn get_currency(&self, id: &str) -> Result<Currency, Error> {
306 Ok(self.get(&format!("/currencies/{}", id)).await?)
307 }
308
309 pub async fn get_time(&self) -> Result<Time, Error> {
318 let time: Time = self.get("/time").await?;
319 Ok(time)
320 }
321}
322
323#[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#[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#[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#[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#[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#[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#[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, 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#[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
454pub enum Granularity {
456 OneMinute = 60,
457 FiveMinutes = 300,
458 FifteenMinutes = 900,
459 OneHour = 3600,
460 SixHours = 21600,
461 OneDay = 86400,
462}