switchboard_utils/exchanges/
coinbase.rs

1use crate::*;
2
3use chrono::{DateTime, Utc};
4use futures_util::TryFutureExt;
5use reqwest::{header, Client};
6use serde::Deserialize;
7
8#[derive(Debug, Clone, Deserialize)]
9pub struct CoinbaseTickerInfo {
10    pub ask: Decimal,
11    pub bid: Decimal,
12    pub volume: Decimal,
13    pub trade_id: u64,
14    pub price: Decimal,
15    pub size: Decimal,
16    pub time: DateTime<Utc>,
17}
18
19pub struct CoinbaseApi {}
20
21impl CoinbaseApi {
22    // https://api.exchange.coinbase.com/products/BTC-USD/ticker
23    fn get_ticker_url(ticker: &str, url: Option<&str>) -> String {
24        format!(
25            "{}/products/{}/ticker",
26            url.unwrap_or("https://api.exchange.coinbase.com"),
27            ticker
28        )
29    }
30
31    pub async fn fetch_ticker(
32        ticker: &str,
33        url: Option<&str>,
34    ) -> Result<CoinbaseTickerInfo, SbError> {
35        let client = Client::new();
36        let ticker_url = CoinbaseApi::get_ticker_url(ticker, url);
37
38        let response: CoinbaseTickerInfo = client
39            .get(&ticker_url)
40            .header(header::USER_AGENT, "null")
41            .send()
42            .and_then(|r| r.json())
43            .await
44            .map_err(handle_reqwest_err)?;
45
46        Ok(response)
47    }
48}
49
50#[cfg(test)]
51mod tests {
52    use super::*;
53
54    const MOCK_COINBASE_RESPONSE: &str = r#"{"ask":"27582.21","bid":"27581.23","volume":"9812.9333157","trade_id":566366195,"price":"27581.23","size":"0.40855232","time":"2023-10-04T20:04:04.969312Z"}"#;
55
56    #[tokio::test]
57    async fn test_coinbase_api() {
58        // Request a new server from the pool
59        let mut server = mockito::Server::new();
60        let server_url = server.url();
61
62        // Create a mock
63        let mock = server
64            .mock("GET", "/products/BTC-USD/ticker")
65            .with_status(201)
66            .with_header("content-type", "application/json")
67            .with_body(MOCK_COINBASE_RESPONSE)
68            .create();
69
70        let ticker = CoinbaseApi::fetch_ticker("BTC-USD", Some(server_url.as_str()))
71            .await
72            .unwrap();
73
74        // verify the mock was called
75        mock.assert();
76
77        assert_eq!(ticker.ask, Decimal::from_str("27582.21").unwrap());
78    }
79}