crm_cli_utils/
token_list.rs

1use lazy_static::lazy_static;
2use reqwest;
3use serde::{Deserialize, Serialize};
4use serde_json;
5use solana_program::pubkey::Pubkey;
6use std::sync::Mutex;
7use std::{borrow::BorrowMut, collections::HashMap, io::Read};
8
9#[derive(Debug, Serialize, Deserialize)]
10pub struct TokenListResponse {
11    req_id: String,
12    code: String,
13    msg: String,
14    data: Vec<Token>,
15}
16
17#[derive(Debug, Serialize, Deserialize, Clone)]
18pub struct Token {
19    pub address: String,
20    pub symbol: String,
21    pub decimals: u16,
22    #[serde(rename = "logoURI")]
23    pub logo_url: String,
24}
25
26lazy_static! {
27    static ref TOKENS_MAP: Mutex<HashMap<String, Token>> = Mutex::new(HashMap::new());
28}
29
30#[allow(dead_code)]
31pub fn load_tokens(force_api: bool) {
32    let mut tokens_map = TOKENS_MAP.try_lock().unwrap();
33    if !tokens_map.is_empty() {
34        return;
35    }
36    let mut cache: Option<TokenListResponse>;
37    if force_api {
38        cache = load_tokens_from_api();
39    } else {
40        cache = load_tokens_from_cache();
41        if cache.is_none() {
42            cache = load_tokens_from_api();
43        }
44    }
45    let token_list = cache.unwrap();
46    save_token_to_cache(&token_list).unwrap();
47
48    for token in token_list.data {
49        tokens_map.borrow_mut().insert(token.address.clone(), token);
50    }
51}
52
53#[allow(dead_code)]
54pub fn fetch_token(address: String) -> Option<Token> {
55    load_tokens(false);
56    let tokens_map = TOKENS_MAP.lock().unwrap();
57    let token = tokens_map.get(&address);
58    if token.is_none() {
59        return None;
60    }
61    Some(token.unwrap().clone())
62}
63
64pub fn token_symbol(address: String) -> String {
65    let token = fetch_token(address);
66    if token.is_some() {
67        token.unwrap().symbol
68    } else {
69        String::from("?")
70    }
71}
72
73#[allow(dead_code)]
74pub fn pool_name(token_a: &Pubkey, token_b: &Pubkey) -> String {
75    let token_a_info = fetch_token(token_a.to_string());
76    let token_b_info = fetch_token(token_b.to_string());
77    let symbol_a = if token_a_info.is_some() {
78        token_a_info.unwrap().symbol
79    } else {
80        String::from("?")
81    };
82    let symbol_b = if token_b_info.is_some() {
83        token_b_info.unwrap().symbol
84    } else {
85        String::from("?")
86    };
87    format!("{}-{}", symbol_a, symbol_b)
88}
89
90fn load_tokens_from_cache() -> Option<TokenListResponse> {
91    let path = {
92        let mut path = dirs_next::home_dir().expect("home directory");
93        path.extend([".config", "solana", "token-list.json"]);
94        path.to_str().unwrap().to_string()
95    };
96    let f = std::fs::File::open(path);
97    if f.is_ok() {
98        let token_list: TokenListResponse = serde_json::from_reader(f.unwrap()).unwrap();
99        return Some(token_list);
100    }
101    None
102}
103
104fn load_tokens_from_api() -> Option<TokenListResponse> {
105    let mut res =
106        reqwest::blocking::get("https://api.crema.finance/config?name=token-list").unwrap();
107    let mut body = String::new();
108    let readed = res.read_to_string(&mut body);
109    if readed.is_ok() {
110        let token_list: TokenListResponse = serde_json::from_str(body.as_str()).unwrap();
111        if token_list.code == "OK" {
112            return Some(token_list);
113        }
114    }
115    None
116}
117
118fn save_token_to_cache(token_list: &TokenListResponse) -> Result<(), std::io::Error> {
119    let path = {
120        let mut path = dirs_next::home_dir().expect("home directory");
121        path.extend([".config", "solana", "token-list.json"]);
122        path.to_str().unwrap().to_string()
123    };
124    std::fs::write(path, serde_json::to_string(token_list).unwrap())
125}
126
127#[cfg(test)]
128mod token_list {
129    use crate::token_list::*;
130
131    #[test]
132    fn test_load_tokens_from_api() {
133        let ts = load_tokens_from_api();
134        if ts.is_none() {
135            println!("load tokens from api failed");
136        } else {
137            for token in ts.unwrap().data {
138                println!("{:?}", token);
139            }
140        }
141    }
142
143    #[test]
144    fn test_load_tokens() {
145        load_tokens(true);
146    }
147
148    #[test]
149    fn test_load_tokens_from_cache() {
150        let ts = load_tokens_from_cache();
151        if ts.is_none() {
152            println!("load tokens from api failed");
153        } else {
154            for token in ts.unwrap().data {
155                println!("{:?}", token);
156            }
157        }
158    }
159
160    #[test]
161    fn test_fetch_token() {
162        load_tokens(false);
163        let token = fetch_token(String::from("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"));
164        println!("{:?}", token);
165    }
166}