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);
}
}