Skip to content

Commit

Permalink
✨ add CoinGecko API integration for fetching coin mappings
Browse files Browse the repository at this point in the history
  • Loading branch information
yezz123 committed Dec 12, 2024
1 parent 11ccd85 commit 70ed9f0
Show file tree
Hide file tree
Showing 4 changed files with 74 additions and 77 deletions.
62 changes: 62 additions & 0 deletions src/coingecko.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
use serde::{Deserialize, Serialize};
use std::collections::HashMap;

#[derive(Debug, Serialize, Deserialize)]
pub struct Coin {
id: String,
symbol: String,
}

fn to_usd_pair(symbol: String) -> String {
format!("{}/USD", symbol)
}

#[derive(Debug, thiserror::Error)]
pub enum CoinGeckoError {
#[error("Empty response from CoinGecko API")]
EmptyResponse,
#[error("Coingecko Coins API request failed with status: {0}")]
RequestFailed(String),
#[error("Coingecko Coins Failed to parse response: {0}")]
ParseError(#[from] reqwest::Error),
}

pub async fn get_coingecko_mappings() -> Result<HashMap<String, String>, CoinGeckoError> {
let client = reqwest::Client::new();
let mut mappings = HashMap::new();
let mut page = 1;

loop {
let url = format!(
"https://api.coingecko.com/api/v3/coins/markets?vs_currency=usd&order=market_cap_desc&per_page=250&page={}",
page
);

let response = client
.get(&url)
.header("User-Agent", "Crypto Data Fetcher")
.send()
.await
.map_err(CoinGeckoError::ParseError)?;

if !response.status().is_success() {
return Err(CoinGeckoError::RequestFailed(response.status().to_string()));
}

let coins: Vec<Coin> = response.json().await?;
if coins.is_empty() {
if page == 1 {
return Err(CoinGeckoError::EmptyResponse);
}
break;
}

coins.into_iter().for_each(|coin| {
mappings.insert(to_usd_pair(coin.symbol.to_uppercase()), coin.id);
});

page += 1;
}

Ok(mappings)
}
86 changes: 9 additions & 77 deletions src/constants.rs
Original file line number Diff line number Diff line change
@@ -1,94 +1,26 @@
use arc_swap::ArcSwap;
use lazy_static::lazy_static;
use prometheus::{opts, register_gauge_vec, register_int_gauge_vec, GaugeVec, IntGaugeVec};
use std::collections::HashMap;
use std::sync::Arc;

use serde::{Deserialize, Serialize};
use std::{collections::HashMap, error::Error};

use crate::coingecko::get_coingecko_mappings;
pub(crate) static LOW_SOURCES_THRESHOLD: usize = 6;

// Define the Coin struct to store the id and symbol of the coin.
#[derive(Debug, Serialize, Deserialize)]
pub struct Coin {
id: String,
symbol: String,
}

// We already have the Link for the CoinGecko API, so we can use it to fetch the data.
// We will use the CoinGecko API to fetch the data.
// Example: https://api.coingecko.com/api/v3/coins/markets?vs_currency=usd&order=market_cap_desc&per_page=100&page=1
// We will fetch the data for the first 100 coins.
// here is the data how its look like:
// [
// {
// "id": "bitcoin",
// "symbol": "btc",
// "name": "Bitcoin",
// "image": "https://coin-images.coingecko.com/coins/images/1/large/bitcoin.png?1696501400",
// "current_price": 100390,
// ...
// },
// ...
// ]
#[allow(dead_code)]
async fn get_coingecko_mappings() -> Result<HashMap<String, String>, Box<dyn Error>> {
let client = reqwest::Client::new();
let mut mappings = HashMap::new();
let mut page = 1;

loop {
let url = format!(
"https://api.coingecko.com/api/v3/coins/markets?vs_currency=usd&order=market_cap_desc&per_page=100&page={}",
page
);

let response = client
.get(&url)
.header("User-Agent", "Crypto Data Fetcher")
.send()
.await?;

if !response.status().is_success() {
return Err(format!("Coingecko Coins API request failed with status: {}", response.status()).into());
}

let coins: Vec<Coin> = response.json().await?;
if coins.is_empty() {
break;
}

for coin in coins {
// Convert symbol to uppercase and create pair format
let pair = format!("{}/USD", coin.symbol.to_uppercase());
mappings.insert(pair, coin.id);
}

page += 1;
}

Ok(mappings)
}

// Replace the static phf_map with a lazy_static ArcSwap
lazy_static! {
pub static ref COINGECKO_IDS: ArcSwap<HashMap<String, String>> =
ArcSwap::new(Arc::new(HashMap::new()));
}

#[allow(dead_code)]
pub async fn initialize_coingecko_mappings() {
match get_coingecko_mappings().await {
Ok(mappings) => {
COINGECKO_IDS.store(Arc::new(mappings));
tracing::info!("Successfully initialized CoinGecko mappings");
}
Err(e) => {
tracing::error!("Failed to initialize CoinGecko mappings: {}", e);
// You might want to panic here depending on how critical this is
// panic!("Failed to initialize CoinGecko mappings: {}", e);
}
}
let mappings = get_coingecko_mappings().await.unwrap_or_else(|e| {
tracing::error!("Failed to initialize CoinGecko mappings: {}", e);
panic!("Cannot start monitoring without CoinGecko mappings: {}", e);
});

COINGECKO_IDS.store(Arc::new(mappings));
tracing::info!("Successfully initialized CoinGecko mappings");
}

lazy_static! {
Expand Down
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
pub mod coingecko;
pub(crate) mod config;
pub mod constants;
pub mod models;
Expand Down
2 changes: 2 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ mod constants;
mod types;
// Utils
mod utils;
// coingecko
mod coingecko;

#[cfg(test)]
mod tests;
Expand Down

0 comments on commit 70ed9f0

Please sign in to comment.