use super::SolClient;
use anyhow::Result;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Deserialize, Serialize, Default)]
pub struct JupAudit {
#[serde(rename = "mintAuthorityDisabled")]
pub mint_authority_disabled: Option<bool>,
#[serde(rename = "freezeAuthorityDisabled")]
pub freeze_authority_disabled: Option<bool>,
#[serde(rename = "topHoldersPercentage")]
pub top_holders_percentage: Option<f64>,
#[serde(rename = "devBalancePercentage")]
pub dev_balance_percentage: Option<f64>,
#[serde(rename = "devMints")]
pub dev_mints: Option<u64>,
}
#[derive(Debug, Clone, Deserialize, Serialize, Default)]
pub struct JupPool {
pub id: Option<String>,
#[serde(rename = "createdAt")]
pub created_at: Option<String>,
}
#[derive(Debug, Clone, Deserialize, Serialize, Default)]
pub struct JupToken {
pub id: String,
pub name: Option<String>,
pub symbol: Option<String>,
pub icon: Option<String>,
pub dev: Option<String>,
pub decimals: Option<u32>,
#[serde(rename = "totalSupply")]
pub total_supply: Option<f64>,
#[serde(rename = "tokenProgram")]
pub token_program: Option<String>,
pub launchpad: Option<String>,
#[serde(rename = "holderCount")]
pub holder_count: Option<u64>,
pub fdv: Option<f64>,
pub mcap: Option<f64>,
#[serde(rename = "usdPrice")]
pub usd_price: Option<f64>,
pub liquidity: Option<f64>,
#[serde(rename = "firstPool")]
pub first_pool: Option<JupPool>,
#[serde(default)]
pub audit: JupAudit,
#[serde(rename = "organicScore")]
pub organic_score: Option<f64>,
#[serde(rename = "organicScoreLabel")]
pub organic_score_label: Option<String>,
#[serde(rename = "isVerified")]
pub is_verified: Option<bool>,
#[serde(default)]
pub tags: Vec<String>,
#[serde(rename = "createdAt")]
pub created_at: Option<String>,
}
impl SolClient {
async fn jup(&self, path: &str) -> Result<Vec<JupToken>> {
let url = format!("{}{}", self.jupiter, path);
let mut last = anyhow::anyhow!("jupiter {path} failed");
for attempt in 0..3u32 {
if attempt > 0 {
tokio::time::sleep(std::time::Duration::from_millis(400 * attempt as u64)).await;
}
match self.http.get(&url).send().await {
Ok(resp) => {
let status = resp.status();
if status.as_u16() == 429 || status.is_server_error() {
last = anyhow::anyhow!("jupiter HTTP {status}");
continue;
}
if !status.is_success() {
return Err(anyhow::anyhow!("jupiter HTTP {status}"));
}
match resp.json::<Vec<JupToken>>().await {
Ok(r) => return Ok(r),
Err(e) => {
last = anyhow::anyhow!("jupiter decode: {e}");
continue;
}
}
}
Err(e) => last = anyhow::anyhow!("jupiter: {e}"),
}
}
Err(last)
}
pub async fn jupiter_token(&self, mint: &str) -> Result<Option<JupToken>> {
let hits = self.jup(&format!("/tokens/v2/search?query={mint}")).await?;
Ok(hits.into_iter().find(|t| t.id == mint))
}
pub async fn jupiter_recent(&self) -> Result<Vec<JupToken>> {
self.jup("/tokens/v2/recent").await
}
}