Skip to main content

ccdata_api/
lib.rs

1//! # CoinDesk API Wrapper
2//!
3//! `ccdata-api` is a wrapper for CoinDesk REST API endpoints (Formerly CCData). This crate supports non-exhausitve list of CoinDesk endpoints,
4//! you can check what endpoints are supported by checking the variants of the enum `APIEndpoint` - it contains all
5//! supported endpoint URLs.
6//!
7//! For documentation on CoinDesk REST API endpoints visit CoinDesk online documentation:
8//! - API Documentation: <https://developers.coindesk.com/documentation/data-api/introduction>
9//! - Legacy API Documentation: <https://developers.coindesk.com/documentation/legacy/Price/SingleSymbolPriceEndpoint>
10//!
11//! **Disclaimer:** This crate is an unofficial CoinDesk REST API wrapper, the maintainers of the crate are independent developers.
12//! The developers of the crate do not accept any responsibility or liability for the accuracy, security, or completeness of the code,
13//! or the information provided within the crate.
14//!
15//! # Errors
16//!
17//! The REST API functions in the crate will error if the data received does not fit into the pre-defined schemas provided
18//! in the crate. If you encounter any errors, please open an issue on GitHub with the parameters that you have used (e.g., asset symbol,
19//! timestamp, limit, etc.). **Do not provide your API key or any personal data!**
20//! 
21//! # Deprecated API Endpoints
22//! 
23//! Some API endpoints have been deprecated by CoinDesk, who strongly recommend migrating to newer alternatives suggested in their API documentation.
24//! 
25//! # Features
26//! - `debug`: If this feature is enabled, you can set `CCDATA_API_DEBUG` environment variable to `true`, which will print the response body
27//! for every request to the command line.
28//!
29//! # Examples
30//!
31//! To start making the REST API requests, define a data collection backend. This can be done by either directly passing an
32//! API key to the data collection backend, or by defining a `.env` file with an API key as an environment variable (Preferred
33//! method).
34//!
35//! ## Build Backend Explicitly Stating API Key (May expose API key)
36//!
37//! ```rust
38//! use ccdata_api::CoinDesk;
39//!
40//! let mut backend: CoinDesk = CoinDesk::new();
41//!
42//! let api_key: String = String::from("xxxxxxx");
43//! backend.update_api_key(api_key);
44//!
45//! assert_eq!(backend.api_key().unwrap(), "xxxxxxx");
46//! ```
47//!
48//! ## Build Backend Using .env File (Preferred method)
49//!
50//! ```rust
51//! use ccdata_api::CoinDesk;
52//!
53//! let mut backend: CoinDesk = CoinDesk::new();
54//! // Provide API key as the environment variable called API_KEY
55//! backend.build(&"API_KEY").unwrap();
56//!
57//! println!("{}", backend.api_key().unwrap());
58//! ```
59//!
60//! ## Making First API Call
61//!
62//! After the backend has been build, you can make API requests using the methods provided in the backend. For example, to
63//! get a daily spot OHLCV data for Bitcoin you can do the following (note that the calls use Rust's `async` functionality):
64//!
65//! ```rust
66//!
67//! use ccdata_api::{CoinDesk, Unit, SpotMarket};
68//!
69//! #[tokio::main]
70//! async fn main() -> () {
71//!
72//!     let mut backend: CoinDesk = CoinDesk::new();
73//!     // Provide API key as the environment variable called API_KEY
74//!     backend.build(&"API_KEY").unwrap();
75//!
76//!     // Define the API parameters
77//!     let market: SpotMarket = SpotMarket::KRAKEN;
78//!     let limit: usize = 2000;
79//!     let to_timestamp: Option<i64> = Some(1728860400);
80//!
81//!     // Make the API call
82//!     let ohlcv = backend.get_spot_ohlcv("BTC-USD", to_timestamp, Some(limit), market, Unit::Day).await.unwrap();
83//!     assert_eq!(ohlcv.data.unwrap().len(), limit);
84//!
85//! }
86//! ```
87//!
88//! # General information
89//! If you would like to add a commit or an issue, please do so using the GitHub link to the project:
90//! - <https://github.com/rsadykhov/ccdata-api>
91
92
93// Re-Exports
94pub use self::backend::CoinDesk;
95pub use self::utils::{Group, AssetLookupPriority, Param, call_api_endpoint};
96// Min-API Re-Exports
97pub use self::schemas::{CCMinResponse, CCMinWrapper, CCRateLimit, CCMaxCalls, CCCallsMade};
98pub use self::schemas::min_api::{BalanceDistribution, SupplyBand};
99// Data-API Re-Exports
100pub use self::schemas::{CoinDeskResponse, CCError, CCErrorOtherInfo};
101pub use self::schemas::data_api::indices_and_reference_rates::{IndicesMarket, IndicesOHLCV};
102pub use self::schemas::data_api::spot::{SpotMarket, SpotInstrumentStatus, SpotOHLCV, SpotInstrumentMetdata, SpotMarkets, SpotMarketsInstruments};
103pub use self::schemas::data_api::futures::{FuturesMarket, FuturesOHLCV, FuturesInstrumentMetadata, FuturesMarkets};
104pub use self::schemas::data_api::options::{OptionsMarket, OptionsOHLCV, OptionsInstrumentMetadata, OptionsMarkets};
105pub use self::schemas::data_api::derivatives_indices::{DerIndicesMarket, DerIndicesOHLCV, DerIndicesMarkets};
106pub use self::schemas::data_api::on_chain_dex::{OCDEXMarket, OCDEXOHLCV, OCDEXMarkets};
107pub use self::schemas::data_api::on_chain_core::{OCCoreETHBlock, OCCoreAssetByChain, OCCoreAssetByAddress, OCCoreSupply};
108pub use self::schemas::data_api::asset::{AssetMetadata, AssetEvent, AssetCodeRepoMetrics, AssetDiscord, AssetReddit, AssetTelegram, AssetTwitter};
109pub use self::schemas::data_api::news::{NewsStatus, NewsLang, NewsSourceID, NewsLatestArticle, NewsSourceType, NewsSource, NewsCategory};
110pub use self::schemas::data_api::overview::OverviewMktCapOHLCV;
111
112
113pub mod error;
114pub mod schemas;
115pub mod utils;
116pub mod backend;
117
118
119use std::fmt::Display;
120use serde::{Serialize, Deserialize};
121
122
123#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize)]
124/// Unit of the interval between successive data points.
125pub enum Unit {
126    #[default]
127    /// Daily data interval
128    Day,
129    /// Hourly data interval
130    Hour,
131    /// Minutely data interval
132    Minute,
133    /// Used for API endpoints that do not have a specified unit for data or where unit is unapplicable
134    NA,
135}
136
137impl Display for Unit {
138    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
139        match self {
140            Self::Day | Unit::NA => write!(f, "/days"),
141            Self::Hour => write!(f, "/hours"),
142            Self::Minute => write!(f, "/minutes"),
143        }
144    }
145}
146
147
148/// Trait that defines all required methods for API endpoint URL construction (prior to adding non-default parameters).
149/// This is useful for defining custom API endpoints that are not included with the crate.
150pub trait APIEndpointTrait {
151    /// Returns a vector of default groups to apply to match the pre-defined schemas.
152    /// 
153    /// Note: If there are no default groups defined the `None` variant is returned.
154    fn default_groups(&self) -> Option<Vec<Group>>;
155
156    /// Returns a vector of default API endpoint parameters to apply to match the pre-defined schemas.
157    /// 
158    ///  Note: If there are no default parameters defined the `None` variant is returned.
159    fn default_params(&self) -> Option<Vec<Param<'_>>>;
160
161    /// Produces URL for a given API endpoint.
162    ///
163    /// # Input
164    /// - `unit`: Unit of the interval between successive data points
165    fn url(&self, unit: &Unit) -> String;
166}
167
168
169#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
170/// All supported API endpoints.
171pub enum APIEndpoint {
172    #[deprecated(since="1.0.6", note="Deprecated by CoinDesk")]
173    // Legacy API Endpoint
174    /// Description: Retrieves the balance distribution for a specified asset over a specified time range at a daily interval
175    ///
176    /// Note: This is a legacy API endpoint and is not actively supported. Only data for BTC (Bitcoin) is available.
177    ///
178    /// URL: https://min-api.cryptocompare.com/data/blockchain/balancedistribution/histo/day
179    BalanceDistribution,
180    // Indices & Reference Rates
181    ///
182    /// Description: Provides historical candlestick data for various indices
183    ///
184    /// URL: https://data-api.coindesk.com/index/cc/v1/historical
185    IndicesOHLCV,
186    // Spot
187    /// Description: Provides candlestick data for specific cryptocurrency instruments across selected exchanges
188    ///
189    /// URL: https://data-api.coindesk.com/spot/v1/historical
190    SpotOHLCV,
191    /// Description: Delivers vital metadata about financial instruments traded on specified exchanges, focusing solely on non-price related information
192    ///
193    /// URL: https://data-api.coindesk.com/spot/v1/latest/instrument/metadata
194    SpotInstrumentMetadata,
195    /// Description: Provides comprehensive information about various cryptocurrency spot markets,
196    /// featuring extensive exchange metadata and operational details.
197    /// 
198    /// URL: https://data-api.coindesk.com/spot/v2/markets
199    SpotMarketsV2,
200    /// Description: Retrieves a comprehensive dictionary of mapped instruments across one or more spot markets, filtered by a specified state or status
201    ///
202    /// URL: https://data-api.coindesk.com/spot/v1/markets/instruments
203    SpotMarketsInstruments,
204    // Futures
205    /// Description: Provides aggregated candlestick data for specific futures instruments on designated exchanges
206    ///
207    /// URL: https://data-api.coindesk.com/futures/v1/historical
208    FuturesOHLCV,
209    /// Description: Provides essential metadata about futures instruments traded on various exchanges.
210    /// 
211    /// URL: https://data-api.coindesk.com/futures/v1/latest/instrument/metadata
212    FuturesInstrumentMetadata,
213    /// Description: Provides comprehensive information about various cryptocurrency futures markets,
214    /// featuring extensive exchange metadata and derivatives-specific operational details.
215    /// 
216    /// URL: https://data-api.coindesk.com/futures/v2/markets
217    FuturesMarketsV2,
218    // Options
219    /// Description: Provides historical OHLCV (open, high, low, close, volume) data for specified options instruments on a chosen exchange
220    ///
221    /// URL: https://data-api.coindesk.com/options/v1/historical
222    OptionsOHLCV,
223    /// Description: Provides detailed metadata about options instruments across various exchanges.
224    /// 
225    /// URL: https://data-api.coindesk.com/options/v1/latest/instrument/metadata
226    OptionsInstrumentMetadata,
227    /// Description: Provides comprehensive information about various cryptocurrency options markets,
228    /// featuring extensive exchange metadata and derivatives-specific operational details.
229    /// 
230    /// URL: https://data-api.coindesk.com/options/v2/markets
231    OptionsMarketsV2,
232    // Derivatives Indices
233    /// Description: Provides historical OHLC (open, high, low, close) data for specified index instruments on a selected market
234    ///
235    /// URL: https://data-api.coindesk.com/index/v1/historical
236    DerIndicesOHLCV,
237    /// Description: Provides comprehensive information about various derivatives index markets,
238    /// featuring extensive exchange metadata and index-specific operational details.
239    /// 
240    /// URL: https://data-api.coindesk.com/index/v2/markets
241    DerIndicesMarketsV2,
242    // On-Chain DEX
243    /// Description: Retrieves aggregated candlestick data for AMM swap transactions
244    ///
245    /// URL: https://data-api.coindesk.com/onchain/v1/amm/historical/swap
246    OCDEXOHLCV,
247    /// Description: Provides comprehensive information about various on-chain decentralized exchange markets,
248    /// featuring extensive exchange metadata and DEX-specific operational details.
249    /// 
250    /// URL: https://data-api.coindesk.com/onchain/v2/amm/markets
251    OCDEXMarketsV2,
252    // On-Chain Core
253    /// Description: Delivers exhaustive details on a specific Ethereum block in a meticulously processed format, complete with detailed explanations for each field
254    ///
255    /// URL: https://data-api.coindesk.com/onchain/v1/block/2
256    OCCoreETHBlocks,
257    /// Description: Retrieves a comprehensive summary of chain asset information for a specified blockchain, identified by its chain symbol
258    ///
259    /// URL: https://data-api.coindesk.com/onchain/v3/summary/by/chain
260    OCCoreAssetsByChain,
261    /// Description: Retrieves comprehensive asset information for a specific asset identified by its smart contract address and associated blockchain asset
262    ///
263    /// URL: https://data-api.coindesk.com/onchain/v2/data/by/address
264    OCCoreAssetByAddress,
265    /// Description: Retrieves comprehensive historical supply data for various digital assets identified by either their CoinDesk asset ID or unique asset symbol
266    ///
267    /// URL: https://data-api.coindesk.com/onchain/v2/historical/supply/days
268    OCCoreSupply,
269    /// Descriptions: Returns an object that provides detailed and comprehensive information about multiple cryptocurrency assets in response to a request
270    /// 
271    /// URL: https://data-api.coindesk.com/asset/v2/metadata
272    AssetMetadataV2,
273    /// Description: Retrieves an array of significant events related to digital assets, such as security incidents, rebrandings, blockchain forks, and other impactful developments
274    ///
275    /// URL: https://data-api.coindesk.com/asset/v1/events
276    AssetEvents,
277    /// Description: Provides an in-depth, daily snapshot of a digital asset's code repositories
278    ///
279    /// URL: https://data-api.coindesk.com/asset/v1/historical/code-repository/days
280    AssetCodeRepo,
281    /// Description: Aggregates detailed daily metrics from all Discord servers related to a specific digital asset, offering a multifaceted view into community engagement and the asset's standing within Discord communities
282    ///
283    /// URL: https://data-api.coindesk.com/asset/v1/historical/discord/days
284    AssetDiscord,
285    /// Description: Aggregates key performance indicators from all the subreddits related to a specific digital asset, providing a comprehensive understanding of the asset's footprint on Reddit
286    ///
287    /// URL: https://data-api.coindesk.com/asset/v1/historical/reddit/days
288    AssetReddit,
289    /// Description: Collates essential data points across all Telegram groups affiliated with a particular cryptocurrency asset
290    ///
291    /// URL: https://data-api.coindesk.com/asset/v1/historical/telegram/days
292    AssetTelegram,
293    /// Description: Aggregates essential metrics from all X (Twitter) accounts associated with a specific cryptocurrency asset
294    ///
295    /// URL: https://data-api.coindesk.com/asset/v1/historical/twitter/days
296    AssetTwitter,
297    // News
298    /// Description: Serves as the pulse of the crypto news landscape, providing users with instant access to the most recent articles across the industry
299    ///
300    /// URL: https://data-api.coindesk.com/news/v1/article/list
301    NewsLatestArticles,
302    /// Description: Offers a comprehensive listing of all news sources available through CoinDesk API
303    ///
304    /// URL: https://data-api.coindesk.com/news/v1/source/list
305    NewsSources,
306    /// Description: Provide a straightforward listing of all news categories available through CoinDesk API
307    ///
308    /// URL: https://data-api.coindesk.com/news/v1/category/list
309    NewsCategories,
310    // Overview
311    /// Description: Presents a thorough historical daily overview of market capitalisation for digital assets that meet the volume and listing criteria
312    ///
313    /// URL: https://data-api.coindesk.com/overview/v1/historical/marketcap/all/assets/days
314    OverviewMktCapOHLCV,
315}
316
317impl APIEndpoint {
318    fn resolve_url(&self) -> String {
319        match self {
320            // Legacy API
321            Self::BalanceDistribution => String::from("https://min-api.cryptocompare.com/data/blockchain/balancedistribution/histo/day"),
322            // Indices & Reference Rates
323            Self::IndicesOHLCV => String::from("https://data-api.coindesk.com/index/cc/v1/historical"),
324            // Spot
325            Self::SpotOHLCV => String::from("https://data-api.coindesk.com/spot/v1/historical"),
326            Self::SpotInstrumentMetadata => String::from("https://data-api.coindesk.com/spot/v1/latest/instrument/metadata"),
327            Self::SpotMarketsV2 => String::from("https://data-api.coindesk.com/spot/v2/markets"),
328            Self::SpotMarketsInstruments => String::from("https://data-api.coindesk.com/spot/v1/markets/instruments"),
329            // Futures
330            Self::FuturesOHLCV => String::from("https://data-api.coindesk.com/futures/v1/historical"),
331            Self::FuturesInstrumentMetadata => String::from("https://data-api.coindesk.com/futures/v1/latest/instrument/metadata"),
332            Self::FuturesMarketsV2 => String::from("https://data-api.coindesk.com/futures/v2/markets"),
333            // Options
334            Self::OptionsOHLCV => String::from("https://data-api.coindesk.com/options/v1/historical"),
335            Self::OptionsInstrumentMetadata => String::from("https://data-api.coindesk.com/options/v1/latest/instrument/metadata"),
336            Self::OptionsMarketsV2 => String::from("https://data-api.coindesk.com/options/v2/markets"),
337            // Derivatives Indices
338            Self::DerIndicesOHLCV => String::from("https://data-api.coindesk.com/index/v1/historical"),
339            Self::DerIndicesMarketsV2 => String::from("https://data-api.coindesk.com/index/v2/markets"),
340            // On-Chain DEX
341            Self::OCDEXOHLCV => String::from("https://data-api.coindesk.com/onchain/v1/amm/historical/swap"),
342            Self::OCDEXMarketsV2 => String::from("https://data-api.coindesk.com/onchain/v2/amm/markets"),
343            // On-Chain Core
344            Self::OCCoreETHBlocks => String::from("https://data-api.coindesk.com/onchain/v1/block/2"),
345            Self::OCCoreAssetsByChain => String::from("https://data-api.coindesk.com/onchain/v3/summary/by/chain"),
346            Self::OCCoreAssetByAddress => String::from("https://data-api.coindesk.com/onchain/v2/data/by/address"),
347            Self::OCCoreSupply => String::from("https://data-api.coindesk.com/onchain/v2/historical/supply/days"),
348            // Asset
349            Self::AssetMetadataV2 => String::from("https://data-api.coindesk.com/asset/v2/metadata"),
350            Self::AssetEvents => String::from("https://data-api.coindesk.com/asset/v1/events"),
351            Self::AssetCodeRepo => String::from("https://data-api.coindesk.com/asset/v1/historical/code-repository/days"),
352            Self::AssetDiscord => String::from("https://data-api.coindesk.com/asset/v1/historical/discord/days"),
353            Self::AssetReddit => String::from("https://data-api.coindesk.com/asset/v1/historical/reddit/days"),
354            Self::AssetTelegram => String::from("https://data-api.coindesk.com/asset/v1/historical/telegram/days"),
355            Self::AssetTwitter => String::from("https://data-api.coindesk.com/asset/v1/historical/twitter/days"),
356            // News
357            Self::NewsLatestArticles => String::from("https://data-api.coindesk.com/news/v1/article/list"),
358            Self::NewsSources => String::from("https://data-api.coindesk.com/news/v1/source/list"),
359            Self::NewsCategories => String::from("https://data-api.coindesk.com/news/v1/category/list"),
360            // Overview
361            Self::OverviewMktCapOHLCV => String::from("https://data-api.coindesk.com/overview/v1/historical/marketcap/all/assets/days"),
362        }
363    }
364
365    fn add_unit_to_url(&self, url: &mut String, unit: &Unit) -> () {
366        match self {
367            Self::IndicesOHLCV | Self::SpotOHLCV | Self::FuturesOHLCV |
368            Self::OptionsOHLCV | Self::DerIndicesOHLCV | Self::OCDEXOHLCV => {
369                url.push_str(&unit.to_string());
370            },
371            _ => (),
372        }
373    }
374}
375
376impl APIEndpointTrait for APIEndpoint {
377    fn default_groups(&self) -> Option<Vec<Group>> {
378        match self {
379            Self::FuturesOHLCV => Some(vec![ Group::Id, Group::Mapping, Group::OHLC, Group::Trade, Group::Volume, Group::MappingAdvanced, Group::OHLCTrade]),
380            Self::FuturesInstrumentMetadata => Some(vec![Group::Status, Group::General]),
381            Self::OptionsOHLCV => Some(vec![Group::Id, Group::Mapping, Group::OHLC, Group::Trade, Group::Volume, Group::MappingAdvanced, Group::OHLCTrade]),
382            Self::OptionsInstrumentMetadata => Some(vec![Group::Status, Group::General]),
383            Self::DerIndicesOHLCV => Some(vec![Group::Id, Group::OHLC, Group::OHLCMessage, Group::Message, Group::MappingAdvanced]),
384            Self::OCDEXOHLCV => Some(vec![Group::Id, Group::Mapping, Group::MappingAdvanced, Group::OHLC, Group::OHLCSwap, Group::Swap, Group::Volume]),
385            Self::OCCoreETHBlocks => Some(vec![Group::Id, Group::Metadata, Group::Transactions, Group::OrphanTraces, Group::Uncles, Group::Withdrawals]),
386            Self::OCCoreAssetByAddress => Some(vec![
387                Group::Id, Group::Basic, Group::SupportedPlatforms, Group::SecurityMetrics, Group::Supply, Group::SupplyAddresses,
388                Group::AssetTypeSpecificMetrics, Group::ResourceLinks, Group::Classification, Group::Price, Group::MktCap, Group::Volume,
389                Group::Change, Group::ToplistRank, Group::Description, Group::DescriptionSummary, Group::Contact, Group::SEO,
390            ]),
391            Self::AssetMetadataV2 => Some(vec![Group::Id, Group::Basic, Group::Supply, Group::SupplyAddresses, Group::Classification]),
392            Self::AssetCodeRepo => Some(vec![Group::Id, Group::General, Group::Activity, Group::Source]),
393            Self::AssetDiscord => Some(vec![Group::Id, Group::General, Group::Activity, Group::Source]),
394            Self::AssetReddit => Some(vec![Group::Id, Group::General, Group::Activity, Group::Source]),
395            Self::AssetTelegram => Some(vec![Group::Id, Group::General, Group::Source]),
396            Self::AssetTwitter => Some(vec![Group::Id, Group::General, Group::Activity, Group::Source]),
397            Self::OverviewMktCapOHLCV => Some(vec![Group::Id, Group::OHLC, Group::Volume]),
398            _ => None,
399        }
400    }
401
402    fn default_params(&self) -> Option<Vec<Param<'_>>> {
403        match self {
404            Self::FuturesInstrumentMetadata => Some(vec![Param::ApplyMapping { v: true, }]),
405            Self::OptionsInstrumentMetadata => Some(vec![Param::ApplyMapping { v: true, }]),
406            Self::OCCoreAssetsByChain => Some(vec![Param::AssetLookupPriority { v: AssetLookupPriority::Symbol, }]),
407            Self::OCCoreAssetByAddress => Some(vec![Param::AssetLookupPriority { v: AssetLookupPriority::Symbol, }]),
408            Self::AssetMetadataV2 => Some(vec![Param::AssetLookupPriority { v: AssetLookupPriority::Symbol, }, Param::QuoteAsset { v: "USD", }]),
409            _ => None,
410        }
411    }
412
413    fn url(&self, unit: &Unit) -> String {
414        let mut url: String = self.resolve_url();
415        self.add_unit_to_url(&mut url, unit);
416        url
417    }
418}
419
420
421#[cfg(test)]
422mod tests {
423
424    #[test]
425    fn unit_test_add_unit_to_url() -> () {
426        use crate::{Unit, APIEndpointTrait, APIEndpoint};
427        let unit: Unit = Unit::Hour;
428        let api_endpoint: APIEndpoint = APIEndpoint::IndicesOHLCV;
429        assert_eq!(api_endpoint.url(&unit), String::from("https://data-api.coindesk.com/index/cc/v1/historical/hours"));
430    }
431
432    #[test]
433    fn unit_test_custom_api_endpoint() -> () {
434        use crate::{Unit, APIEndpointTrait, call_api_endpoint};
435
436        enum CustomEndpoint { CustomSpotOHLCV, }
437
438        impl APIEndpointTrait for CustomEndpoint {
439            fn default_groups(&self) -> Option<Vec<crate::Group>> { None }
440
441            fn default_params(&self) -> Option<Vec<crate::Param<'_>>> { None }
442
443            fn url(&self, unit: &crate::Unit) -> String {
444                format!("https://data-api.coindesk.com/spot/v1/historical{}", unit.to_string())
445            }
446        }
447
448        assert_eq!(String::from("https://data-api.coindesk.com/spot/v1/historical/days"), CustomEndpoint::CustomSpotOHLCV.url(&Unit::Day))
449    }
450}