solagent_plugin_dexscreener/
lib.rs

1use serde::{Deserialize, Serialize};
2use std::error::Error;
3
4// Define the Txns struct to represent transaction information
5#[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// Define the BuysSells struct to represent the number of buys and sells
18#[derive(Debug, Serialize, Deserialize, Clone)]
19struct BuysSells {
20    #[serde(rename = "buys")]
21    buys: u64,
22    #[serde(rename = "sells")]
23    sells: u64,
24}
25
26// Define the Volume struct to represent trading volume
27#[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// Define the PriceChange struct to represent price changes
40#[derive(Debug, Serialize, Deserialize, Clone)]
41struct PriceChange {
42    #[serde(rename = "h6")]
43    h6: f64,
44}
45
46// Define the Liquidity struct to represent liquidity
47#[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// Define the Token struct to represent token information
58#[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// Define the Pair struct to represent trading pair information
69#[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// Define the Root struct to represent the root structure of the JSON
106#[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
135/// Get the token data for a given token ticker.
136///
137/// # Parameters
138///
139/// - `ticker`: Ticker of the token, e.g. 'USDC'
140///
141/// # Returns
142///
143/// A `Result`
144pub 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}