clmm-common 0.1.39

Blockchain, Clmm for Solana
Documentation
use lazy_static::lazy_static;
use reqwest;
use serde::{Deserialize, Serialize};
use serde_json;
use solana_program::pubkey::Pubkey;
use std::sync::Mutex;

use std::{borrow::BorrowMut, collections::HashMap, io::Read};

#[derive(Debug, Serialize, Deserialize)]
pub struct TokenListResponse {
    req_id: String,
    code: String,
    msg: String,
    data: Vec<Token>,
}

#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Token {
    pub address: String,
    pub symbol: String,
    pub decimals: u16,
    #[serde(rename = "logoURI")]
    pub logo_url: String,
}

lazy_static! {
    static ref TOKENS_MAP: Mutex<HashMap<String, Token>> = Mutex::new(HashMap::new());
}

pub fn load_tokens(force_api: bool) {
    let mut tokens_map = TOKENS_MAP.try_lock().unwrap();
    if !tokens_map.is_empty() {
        return;
    }
    let mut cache: Option<TokenListResponse>;
    if force_api {
        cache = load_tokens_from_api();
    } else {
        cache = load_tokens_from_cache();
        if cache.is_none() {
            cache = load_tokens_from_api();
        }
    }
    let token_list = cache.unwrap();
    save_token_to_cache(&token_list).unwrap();

    for token in token_list.data {
        tokens_map.borrow_mut().insert(token.address.clone(), token);
    }
}

pub fn fetch_token(address: String) -> Option<Token> {
    load_tokens(false);
    let tokens_map = TOKENS_MAP.lock().unwrap();
    let token = tokens_map.get(&address);
    if token.is_none() {
        return None;
    }
    Some(token.unwrap().clone())
}

pub fn pool_name(token_a: &Pubkey, token_b: &Pubkey) -> String {
    let token_a_info = fetch_token(token_a.to_string());
    let token_b_info = fetch_token(token_b.to_string());
    let symbol_a = if token_a_info.is_some() {
        token_a_info.unwrap().symbol
    } else {
        String::from("?")
    };
    let symbol_b = if token_b_info.is_some() {
        token_b_info.unwrap().symbol
    } else {
        String::from("?")
    };
    format!("{}-{}", symbol_a, symbol_b)
}

fn load_tokens_from_cache() -> Option<TokenListResponse> {
    let path = {
        let mut path = dirs_next::home_dir().expect("home directory");
        path.extend([".config", "solana", "token-list.json"]);
        path.to_str().unwrap().to_string()
    };
    let f = std::fs::File::open(path);
    if f.is_ok() {
        let token_list: TokenListResponse = serde_json::from_reader(f.unwrap()).unwrap();
        return Some(token_list);
    }
    None
}

fn load_tokens_from_api() -> Option<TokenListResponse> {
    let mut res =
        reqwest::blocking::get("https://api.crema.finance/config?name=token-list").unwrap();
    let mut body = String::new();
    let readed = res.read_to_string(&mut body);
    if readed.is_ok() {
        let token_list: TokenListResponse = serde_json::from_str(body.as_str()).unwrap();
        if token_list.code == "OK" {
            return Some(token_list);
        }
    }
    None
}

fn save_token_to_cache(token_list: &TokenListResponse) -> Result<(), std::io::Error> {
    let path = {
        let mut path = dirs_next::home_dir().expect("home directory");
        path.extend([".config", "solana", "token-list.json"]);
        path.to_str().unwrap().to_string()
    };
    std::fs::write(path, serde_json::to_string(token_list).unwrap())
}

#[cfg(test)]
mod token_list {
    use crate::token_list::*;

    #[test]
    fn test_load_tokens_from_api() {
        let ts = load_tokens_from_api();
        if ts.is_none() {
            println!("load tokens from api failed");
        } else {
            for token in ts.unwrap().data {
                println!("{:?}", token);
            }
        }
    }

    #[test]
    fn test_load_tokens() {
        load_tokens(true);
    }

    #[test]
    fn test_load_tokens_from_cache() {
        let ts = load_tokens_from_cache();
        if ts.is_none() {
            println!("load tokens from api failed");
        } else {
            for token in ts.unwrap().data {
                println!("{:?}", token);
            }
        }
    }

    #[test]
    fn test_fetch_token() {
        load_tokens(false);
        let token = fetch_token(String::from("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"));
        println!("{:?}", token);
    }
}