crm_cli_utils/
token_list.rs1use 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}