switchboard_utils/exchanges/
bitfinex.rs

1use crate::*;
2
3use serde::Deserialize;
4
5#[derive(Debug, Clone, Deserialize)]
6pub struct BitfinexTickerInfo {
7    pub bid: Decimal,
8    pub bid_size: Decimal,
9    pub ask: Decimal,
10    pub ask_size: Decimal,
11    pub daily_change: Decimal,
12    pub daily_change_relative: Decimal,
13    pub last_price: Decimal,
14    pub volume: Decimal,
15    pub high: Decimal,
16    pub low: Decimal,
17}
18
19pub struct BitfinexApi {}
20
21impl BitfinexApi {
22    // https://api-pub.bitfinex.com/v2/ticker/tBTCUSD
23    fn get_ticker_url(ticker: &str, url: Option<&str>) -> String {
24        format!(
25            "{}/v2/ticker/{}",
26            url.unwrap_or("https://api-pub.bitfinex.com"),
27            ticker
28        )
29    }
30
31    pub async fn fetch_ticker(
32        ticker: &str,
33        url: Option<&str>,
34    ) -> Result<BitfinexTickerInfo, SbError> {
35        let ticker_url = BitfinexApi::get_ticker_url(ticker, url);
36
37        let response: Vec<f64> = reqwest::get(&ticker_url)
38            .await
39            .map_err(handle_reqwest_err)?
40            .json()
41            .await
42            .map_err(handle_reqwest_err)?;
43
44        if response.len() < 10 {
45            return Err(SbError::Message("Bitfinex response too short"));
46        }
47
48        Ok(BitfinexTickerInfo {
49            bid: Decimal::from_f64(response[0]).unwrap(),
50            bid_size: Decimal::from_f64(response[1]).unwrap(),
51            ask: Decimal::from_f64(response[2]).unwrap(),
52            ask_size: Decimal::from_f64(response[3]).unwrap(),
53            daily_change: Decimal::from_f64(response[4]).unwrap(),
54            daily_change_relative: Decimal::from_f64(response[5]).unwrap(),
55            last_price: Decimal::from_f64(response[6]).unwrap(),
56            volume: Decimal::from_f64(response[7]).unwrap(),
57            high: Decimal::from_f64(response[8]).unwrap(),
58            low: Decimal::from_f64(response[9]).unwrap(),
59        })
60    }
61}
62
63#[cfg(test)]
64mod tests {
65    use super::*;
66
67    const MOCK_BITFINEX_RESPONSE: &str =
68        r#"[27686,18.95873976,27687,13.79181632,310,0.01132503,27683,800.37680489,27846,27211]"#;
69
70    #[tokio::test]
71    async fn test_bitfinex_api() {
72        // Request a new server from the pool
73        let mut server = mockito::Server::new();
74        let server_url = server.url();
75
76        // Create a mock
77        let mock = server
78            .mock("GET", "/v2/ticker/tBTCUSD")
79            .with_status(201)
80            .with_header("content-type", "application/json")
81            .with_body(MOCK_BITFINEX_RESPONSE)
82            .create();
83
84        let ticker = BitfinexApi::fetch_ticker("tBTCUSD", Some(server_url.as_str()))
85            .await
86            .unwrap();
87
88        // verify the mock was called
89        mock.assert();
90
91        assert_eq!(ticker.bid, Decimal::from_str("27686").unwrap());
92    }
93}