cbat 0.0.18

The unofficial Rust crate for the Coinbase Advanced Trade API
Documentation
use crate::{
    client::Client, contract_expiry_type::ContractExpiryType,
    expiring_contract_status::ExpiringContractStatus, granularity::Granularity,
    http_method::HttpMethod, market_trades::MarketTrades, price_books::PriceBooks,
    product::Product, product_candles::ProductCandles, products::Products,
};

pub struct ApiProducts;

impl ApiProducts {
    pub async fn get_best_bid_ask(
        client: &Client<'_>,
        product_ids: Option<Vec<&str>>,
    ) -> Result<PriceBooks, reqwest::Error> {
        let mut query_params = Vec::new();
        if let Some(product_ids) = product_ids {
            for product_id in product_ids {
                query_params.push(format!("product_ids={}", product_id));
            }
        }
        let query_string = if query_params.is_empty() {
            String::new()
        } else {
            format!("?{}", query_params.join("&"))
        };
        let url = &format!("{}{}", BEST_BID_ASK_URL, query_string);
        let response = client
            .get_auth(
                url,
                &client.create_jwt(HttpMethod::Get.as_str(), BEST_BID_ASK_ENDPOINT),
            )
            .await?;
        let price_books: PriceBooks = response.json().await?;
        Ok(price_books)
    }

    pub async fn get_product_book(
        client: &Client<'_>,
        product_id: &str,
        limit: Option<u32>,
        aggregation_price_increment: Option<&str>,
    ) -> Result<PriceBooks, reqwest::Error> {
        let mut query_params = Vec::new();
        query_params.push(format!("product_id={}", product_id));
        if let Some(limit) = limit {
            query_params.push(format!("limit={}", limit));
        }
        if let Some(aggregation_price_increment) = aggregation_price_increment {
            query_params.push(format!(
                "aggregation_price_increment={}",
                aggregation_price_increment
            ));
        }
        let query_string = if query_params.is_empty() {
            String::new()
        } else {
            format!("?{}", query_params.join("&"))
        };
        let url = &format!("{}{}", PRODUCT_BOOK_URL, query_string);
        let response = client
            .get_auth(
                url,
                &client.create_jwt(HttpMethod::Get.as_str(), PRODUCT_BOOK_ENDPOINT),
            )
            .await?;
        let price_books: PriceBooks = response.json().await?;
        Ok(price_books)
    }

    pub async fn list_products(
        client: &Client<'_>,
        limit: Option<u32>,
        offset: Option<u32>,
        product_type: Option<&str>,
        product_ids: Option<Vec<&str>>,
        contract_expiry_type: Option<ContractExpiryType>,
        expiring_contract_status: Option<ExpiringContractStatus>,
        get_tradability_status: Option<bool>,
        get_all_products: Option<bool>,
    ) -> Result<Products, reqwest::Error> {
        let mut query_params = Vec::new();
        if let Some(limit) = limit {
            query_params.push(format!("limit={}", limit));
        }
        if let Some(offset) = offset {
            query_params.push(format!("offset={}", offset));
        }
        if let Some(product_type) = product_type {
            query_params.push(format!("product_type={}", product_type));
        }
        if let Some(product_ids) = product_ids {
            for product_id in product_ids {
                query_params.push(format!("product_ids={}", product_id));
            }
        }
        if let Some(contract_expiry_type) = contract_expiry_type {
            query_params.push(format!("contract_expiry_type={}", contract_expiry_type));
        }
        if let Some(expiring_contract_status) = expiring_contract_status {
            query_params.push(format!(
                "expiring_contract_status={}",
                expiring_contract_status
            ));
        }
        if let Some(get_tradability_status) = get_tradability_status {
            query_params.push(format!("get_tradability_status={}", get_tradability_status));
        }
        if let Some(get_all_products) = get_all_products {
            query_params.push(format!("get_all_products={}", get_all_products));
        }
        let query_string = if query_params.is_empty() {
            String::new()
        } else {
            format!("?{}", query_params.join("&"))
        };
        let url = &format!("{}{}", PRODUCTS_URL, query_string);
        let response = client
            .get_auth(
                url,
                &client.create_jwt(HttpMethod::Get.as_str(), PRODUCTS_ENDPOINT),
            )
            .await?;
        let products: Products = response.json().await?;
        Ok(products)
    }

    pub async fn get_product(
        client: &Client<'_>,
        product_id: &str,
        get_tradability_status: Option<bool>,
    ) -> Result<Product, reqwest::Error> {
        let get_tradability_status = match get_tradability_status {
            Some(get_tradability_status) => {
                &format!("?get_tradability_status={}", get_tradability_status)
            }
            None => "",
        };
        let url = &format!("{}/{}{}", PRODUCTS_URL, product_id, get_tradability_status);
        let response = client
            .get_auth(
                url,
                &client.create_jwt(
                    HttpMethod::Get.as_str(),
                    &format!("{}/{}", PRODUCTS_ENDPOINT, product_id),
                ),
            )
            .await?;
        let products: Product = response.json().await?;
        Ok(products)
    }

    pub async fn get_product_candles(
        client: &Client<'_>,
        product_id: &str,
        start: &str,
        end: &str,
        granularity: Granularity,
        limit: Option<u32>,
    ) -> Result<ProductCandles, reqwest::Error> {
        let limit = match limit {
            Some(limit) => &format!("&limit={}", limit),
            None => "",
        };

        let url = &format!(
            "{}/{}/candles?start={}&end={}&granularity={}{}",
            PRODUCTS_URL, product_id, start, end, granularity, limit
        );
        let response = client
            .get_auth(
                url,
                &client.create_jwt(
                    HttpMethod::Get.as_str(),
                    &format!("{}/{}/candles", PRODUCTS_ENDPOINT, product_id),
                ),
            )
            .await?;
        let product_candles: ProductCandles = response.json().await?;
        Ok(product_candles)
    }

    pub async fn get_market_trades(
        client: &Client<'_>,
        product_id: &str,
        limit: u32,
        start: Option<String>,
        end: Option<String>,
    ) -> Result<MarketTrades, reqwest::Error> {
        let start = match start {
            Some(start) => &format!("&start={}", start),
            None => "",
        };
        let end = match end {
            Some(end) => &format!("&end={}", end),
            None => "",
        };
        let url = &format!(
            "{}/{}/ticker?limit={}{}{}",
            MARKET_TRADES_URL, product_id, limit, start, end
        );
        let response = client
            .get_auth(
                url,
                &client.create_jwt(
                    HttpMethod::Get.as_str(),
                    &format!("{}/{}/ticker", MARKET_TRADES_ENDPOINT, product_id),
                ),
            )
            .await?;
        let market_trades: MarketTrades = response.json().await?;
        Ok(market_trades)
    }
}

const BEST_BID_ASK_URL: &str = "https://api.coinbase.com/api/v3/brokerage/best_bid_ask";
const BEST_BID_ASK_ENDPOINT: &str = "/api/v3/brokerage/best_bid_ask";
const PRODUCT_BOOK_URL: &str = "https://api.coinbase.com/api/v3/brokerage/product_book";
const PRODUCT_BOOK_ENDPOINT: &str = "/api/v3/brokerage/product_book";
const PRODUCTS_URL: &str = "https://api.coinbase.com/api/v3/brokerage/products";
const PRODUCTS_ENDPOINT: &str = "/api/v3/brokerage/products";
const MARKET_TRADES_URL: &str = "https://api.coinbase.com/api/v3/brokerage/market/products";
const MARKET_TRADES_ENDPOINT: &str = "/api/v3/brokerage/market/products";