solagent_plugin_dexscreener/
lib.rs1use serde::{Deserialize, Serialize};
2use std::error::Error;
3
4#[derive(Debug, Serialize, Deserialize, Clone)]
6struct Txns {
7 #[serde(rename = "m5")]
8 m5: BuysSells,
9 #[serde(rename = "h1")]
10 h1: BuysSells,
11 #[serde(rename = "h6")]
12 h6: BuysSells,
13 #[serde(rename = "h24")]
14 h24: BuysSells,
15}
16
17#[derive(Debug, Serialize, Deserialize, Clone)]
19struct BuysSells {
20 #[serde(rename = "buys")]
21 buys: u64,
22 #[serde(rename = "sells")]
23 sells: u64,
24}
25
26#[derive(Debug, Serialize, Deserialize, Clone)]
28struct Volume {
29 #[serde(rename = "h24")]
30 h24: f64,
31 #[serde(rename = "h6")]
32 h6: f64,
33 #[serde(rename = "h1")]
34 h1: f64,
35 #[serde(rename = "m5")]
36 m5: f64,
37}
38
39#[derive(Debug, Serialize, Deserialize, Clone)]
41struct PriceChange {
42 #[serde(rename = "h6")]
43 h6: f64,
44}
45
46#[derive(Debug, Serialize, Deserialize, Clone)]
48struct Liquidity {
49 #[serde(rename = "usd")]
50 usd: f64,
51 #[serde(rename = "base")]
52 base: u64,
53 #[serde(rename = "quote")]
54 quote: f64,
55}
56
57#[derive(Debug, Serialize, Deserialize, Clone)]
59struct Token {
60 #[serde(rename = "address")]
61 address: String,
62 #[serde(rename = "name")]
63 name: String,
64 #[serde(rename = "symbol")]
65 symbol: String,
66}
67
68#[derive(Debug, Serialize, Deserialize, Clone)]
70struct Pair {
71 #[serde(rename = "chainId")]
72 chain_id: String,
73 #[serde(rename = "dexId")]
74 dex_id: String,
75 #[serde(rename = "url")]
76 url: String,
77 #[serde(rename = "pairAddress")]
78 pair_address: String,
79 #[serde(rename = "labels")]
80 labels: Option<Vec<String>>,
81 #[serde(rename = "baseToken")]
82 base_token: Token,
83 #[serde(rename = "quoteToken")]
84 quote_token: Token,
85 #[serde(rename = "priceNative")]
86 price_native: String,
87 #[serde(rename = "priceUsd")]
88 price_usd: String,
89 #[serde(rename = "txns")]
90 txns: Txns,
91 #[serde(rename = "volume")]
92 volume: Volume,
93 #[serde(rename = "priceChange")]
94 price_change: PriceChange,
95 #[serde(rename = "liquidity")]
96 liquidity: Liquidity,
97 #[serde(rename = "fdv")]
98 fdv: u64,
99 #[serde(rename = "marketCap")]
100 market_cap: u64,
101 #[serde(rename = "pairCreatedAt")]
102 pair_created_at: Option<u64>,
103}
104
105#[derive(Debug, Serialize, Deserialize, Clone)]
107struct SearchTokenData {
108 #[serde(rename = "schemaVersion")]
109 schema_version: String,
110 #[serde(rename = "pairs")]
111 pairs: Vec<Pair>,
112}
113
114#[derive(Debug, Serialize, Deserialize)]
115pub struct DexTokenData {
116 pub address: String,
117 pub name: String,
118 pub symbol: String,
119 pub decimals: u8,
120 pub tags: Vec<String>,
121 pub logo_uri: String,
122 pub daily_volume: f64,
123 pub freeze_authority: Option<String>,
124 pub mint_authority: Option<String>,
125 pub permanent_delegate: Option<String>,
126 pub extensions: Extensions,
127}
128
129#[derive(Debug, Serialize, Deserialize)]
130pub struct Extensions {
131 #[serde(skip_serializing_if = "Option::is_none")]
132 pub coingecko_id: Option<String>,
133}
134
135pub async fn get_token_data_by_ticker(ticker: &str) -> Result<DexTokenData, Box<dyn Error>> {
145 let client = reqwest::Client::new();
146 let address = get_token_address_from_ticker(&client, ticker).await?;
147
148 get_token_data_by_address(&client, &address).await
149}
150
151async fn get_token_address_from_ticker(
152 client: &reqwest::Client,
153 ticker: &str,
154) -> Result<String, Box<dyn Error>> {
155 let url = format!(
156 "https://api.dexscreener.com/latest/dex/search?q=${}",
157 ticker
158 );
159 let response = client.get(&url).send().await?;
160 if !response.status().is_success() {
161 return Err(format!(
162 "Failed to get token address from ticker: {}",
163 response.status()
164 )
165 .into());
166 }
167
168 let data: SearchTokenData = response.json().await?;
169
170 let mut solana_pairs: Vec<Pair> = data
171 .pairs
172 .iter()
173 .filter(|&pair| pair.chain_id == "solana")
174 .cloned()
175 .collect();
176
177 solana_pairs.sort_by_key(|pair| std::cmp::Reverse(pair.fdv));
178
179 let filtered_pairs: Vec<Pair> = solana_pairs
180 .into_iter()
181 .filter(|pair| pair.base_token.symbol.to_lowercase() == ticker.to_lowercase())
182 .collect();
183
184 filtered_pairs
185 .into_iter()
186 .next()
187 .map(|pair| pair.base_token.address)
188 .ok_or("get address error".into())
189}
190
191async fn get_token_data_by_address(
192 client: &reqwest::Client,
193 address: &str,
194) -> Result<DexTokenData, Box<dyn Error>> {
195 let url = format!("https://tokens.jup.ag/token/${}", address);
196 let response = client.get(&url).send().await?;
197 if !response.status().is_success() {
198 return Err(format!(
199 "Failed to get token address from ticker: {}",
200 response.status()
201 )
202 .into());
203 }
204
205 let data: DexTokenData = response.json().await?;
206 Ok(data)
207}