use anyhow::Result;
use serde::{Deserialize, Serialize};
use crate::client::ApiClient;
use crate::output::OutputFormat;
#[derive(Debug, Deserialize, Serialize)]
pub struct TokenDetail {
pub id: Option<String>,
pub name: Option<String>,
pub symbol: Option<String>,
pub chain: Option<String>,
pub decimals: Option<i64>,
pub total_supply: Option<f64>,
pub description: Option<String>,
pub website: Option<String>,
pub has_image: Option<bool>,
pub added_at: Option<String>,
pub price_stats: Option<TokenPriceStats>,
pub summary: Option<TokenSummary>,
pub last_updated: Option<String>,
}
#[derive(Debug, Deserialize, Serialize)]
pub struct TokenPriceStats {
pub high_24h: Option<f64>,
pub low_24h: Option<f64>,
pub ath: Option<f64>,
pub ath_date: Option<String>,
}
#[derive(Debug, Deserialize, Serialize)]
pub struct TokenSummary {
pub chain: Option<String>,
pub id: Option<String>,
pub price_usd: Option<f64>,
pub fdv: Option<f64>,
pub liquidity_usd: Option<f64>,
pub pools: Option<i64>,
#[serde(rename = "24h")]
pub h24: Option<TokenPeriodStats>,
#[serde(rename = "6h")]
pub h6: Option<TokenPeriodStats>,
#[serde(rename = "1h")]
pub h1: Option<TokenPeriodStats>,
#[serde(rename = "30m")]
pub m30: Option<TokenPeriodStats>,
#[serde(rename = "15m")]
pub m15: Option<TokenPeriodStats>,
#[serde(rename = "5m")]
pub m5: Option<TokenPeriodStats>,
#[serde(rename = "1m")]
pub m1: Option<TokenPeriodStats>,
}
#[derive(Debug, Deserialize, Serialize)]
pub struct TokenPeriodStats {
pub volume: Option<f64>,
pub volume_usd: Option<f64>,
pub sells: Option<i64>,
pub buys: Option<i64>,
pub txns: Option<i64>,
pub buy_usd: Option<f64>,
pub sell_usd: Option<f64>,
pub last_price_usd_change: Option<f64>,
}
#[derive(Debug, Deserialize, Serialize)]
pub struct TokenPoolItem {
pub id: Option<String>,
pub dex_id: Option<String>,
pub dex_name: Option<String>,
pub tokens: Option<Vec<TokenPoolToken>>,
pub price_usd: Option<f64>,
pub volume_usd: Option<f64>,
pub liquidity_usd: Option<f64>,
pub last_price_change_usd_24h: Option<f64>,
pub created_at: Option<String>,
}
#[derive(Debug, Deserialize, Serialize)]
pub struct TokenPoolsResponse {
pub pools: Vec<TokenPoolItem>,
}
#[derive(Debug, Deserialize, Serialize)]
pub struct TokenPoolToken {
pub id: Option<String>,
pub name: Option<String>,
pub symbol: Option<String>,
#[serde(flatten)]
pub extra: Option<std::collections::HashMap<String, serde_json::Value>>,
}
#[derive(Debug, Deserialize, Serialize)]
pub struct TokenPrice {
pub id: Option<String>,
pub chain: Option<String>,
pub price_usd: Option<f64>,
}
#[derive(Debug, Deserialize, Serialize)]
pub struct TokenFilterResponse {
pub results: Vec<TokenFilterResult>,
pub page_info: Option<PageInfo>,
}
#[derive(Debug, Deserialize, Serialize)]
pub struct TokenFilterResult {
pub chain: Option<String>,
pub address: Option<String>,
pub price_usd: Option<f64>,
pub volume_usd_24h: Option<f64>,
pub volume_usd_7d: Option<f64>,
pub liquidity_usd: Option<f64>,
pub fdv_usd: Option<f64>,
pub txns_24h: Option<i64>,
pub created_at: Option<String>,
}
#[derive(Debug, Deserialize, Serialize)]
pub struct PageInfo {
pub limit: Option<i64>,
pub page: Option<i64>,
pub total_items: Option<i64>,
pub total_pages: Option<i64>,
}
#[derive(Debug, Deserialize, Serialize)]
pub struct TopTokensResponse {
pub tokens: Vec<TopTokenApiItem>,
pub page_info: Option<PageInfo>,
}
#[derive(Debug, Deserialize, Serialize)]
pub struct TopTokenApiItem {
pub address: Option<String>,
pub name: Option<String>,
pub symbol: Option<String>,
pub chain: Option<String>,
pub decimals: Option<i64>,
pub has_image: Option<bool>,
pub price_usd: Option<f64>,
pub fdv: Option<f64>,
pub liquidity_usd: Option<f64>,
pub pools: Option<i64>,
#[serde(rename = "24h")]
pub h24: Option<TopTokenTimeData>,
#[serde(rename = "1h")]
pub h1: Option<TopTokenTimeData>,
#[serde(rename = "5m")]
pub m5: Option<TopTokenTimeData>,
}
#[derive(Debug, Deserialize, Serialize)]
pub struct TopTokenTimeData {
pub volume_usd: Option<f64>,
pub buys: Option<i64>,
pub sells: Option<i64>,
pub txns: Option<i64>,
pub last_price_usd_change: Option<f64>,
}
#[derive(Debug, Serialize)]
pub struct TopTokenEntry {
pub address: String,
pub name: String,
pub symbol: String,
pub price_usd: Option<f64>,
pub volume_usd_24h: Option<f64>,
pub change_24h: Option<f64>,
pub liquidity_usd: Option<f64>,
pub buys_24h: Option<i64>,
pub sells_24h: Option<i64>,
pub txns_24h: Option<i64>,
pub fdv: Option<f64>,
pub pools: Option<i64>,
}
pub async fn execute_top_tokens(
client: &ApiClient,
network: &str,
limit: usize,
page: usize,
order_by: &str,
sort: &str,
output: OutputFormat,
raw: bool,
) -> Result<()> {
let limit_str = limit.to_string();
let page_str = page.to_string();
let resp: TopTokensResponse = client.dexpaprika_get(
&format!("/networks/{network}/tokens/top"),
&[("limit", &limit_str), ("page", &page_str), ("order_by", order_by), ("sort", sort)],
).await?;
let entries: Vec<TopTokenEntry> = resp.tokens.iter().map(|t| {
let (vol, change, buys, sells, txns) = match &t.h24 {
Some(h) => (h.volume_usd, h.last_price_usd_change, h.buys, h.sells, h.txns),
None => (None, None, None, None, None),
};
TopTokenEntry {
address: t.address.clone().unwrap_or_default(),
name: t.name.clone().unwrap_or_default(),
symbol: t.symbol.clone().unwrap_or_default(),
price_usd: t.price_usd,
volume_usd_24h: vol,
change_24h: change,
liquidity_usd: t.liquidity_usd,
buys_24h: buys,
sells_24h: sells,
txns_24h: txns,
fdv: t.fdv,
pools: t.pools,
}
}).collect();
match output {
OutputFormat::Table => {
crate::output::tokens::print_top_tokens_table(&entries);
if let Some(pi) = &resp.page_info {
println!(" Page {}/{} ({} tokens total)",
pi.page.unwrap_or(0), pi.total_pages.unwrap_or(0), pi.total_items.unwrap_or(0));
}
}
OutputFormat::Json => {
crate::output::print_json_wrapped(&resp, crate::output::ResponseMeta::dexpaprika(&format!("/networks/{network}/tokens/top")), raw)?;
}
}
Ok(())
}
pub async fn execute_token(client: &ApiClient, network: &str, token_address: &str, output: OutputFormat, raw: bool) -> Result<()> {
let token: TokenDetail = client.dexpaprika_get(
&format!("/networks/{network}/tokens/{token_address}"),
&[],
).await?;
match output {
OutputFormat::Table => crate::output::tokens::print_token_detail(&token),
OutputFormat::Json => {
crate::output::print_json_wrapped(&token, crate::output::ResponseMeta::dexpaprika(&format!("/token/{network}/{token_address}")), raw)?;
}
}
Ok(())
}
pub async fn execute_token_pools(
client: &ApiClient,
network: &str,
token_address: &str,
limit: usize,
page: usize,
order_by: &str,
sort: &str,
output: OutputFormat,
raw: bool,
) -> Result<()> {
let limit_str = limit.to_string();
let page_str = page.to_string();
let resp: TokenPoolsResponse = client.dexpaprika_get(
&format!("/networks/{network}/tokens/{token_address}/pools"),
&[("limit", &limit_str), ("page", &page_str), ("order_by", order_by), ("sort", sort)],
).await?;
let pools = resp.pools;
match output {
OutputFormat::Table => crate::output::tokens::print_token_pools_table(&pools),
OutputFormat::Json => {
crate::output::print_json_wrapped(&pools, crate::output::ResponseMeta::dexpaprika(&format!("/token/{network}/{token_address}/pools")), raw)?;
}
}
Ok(())
}
pub async fn execute_prices(client: &ApiClient, network: &str, tokens: &str, output: OutputFormat, raw: bool) -> Result<()> {
let prices: Vec<TokenPrice> = client.dexpaprika_get(
&format!("/networks/{network}/multi/prices"),
&[("tokens", tokens)],
).await?;
if prices.is_empty() {
anyhow::bail!("No price data found. Check that the token addresses are valid on {network}.");
}
match output {
OutputFormat::Table => crate::output::tokens::print_prices_table(&prices),
OutputFormat::Json => {
crate::output::print_json_wrapped(&prices, crate::output::ResponseMeta::dexpaprika(&format!("/network/{network}/prices")), raw)?;
}
}
Ok(())
}
pub async fn execute_filter_tokens(
client: &ApiClient,
network: &str,
limit: usize,
page: usize,
sort_by: &str,
sort_dir: &str,
volume_24h_min: Option<f64>,
volume_24h_max: Option<f64>,
liquidity_usd_min: Option<f64>,
fdv_min: Option<f64>,
fdv_max: Option<f64>,
txns_24h_min: Option<u64>,
created_after: Option<u64>,
created_before: Option<u64>,
output: OutputFormat,
raw: bool,
) -> Result<()> {
let limit_str = limit.to_string();
let page_str = page.to_string();
let mut params: Vec<(&str, String)> = vec![
("limit", limit_str),
("page", page_str),
("sort_by", sort_by.to_string()),
("sort_dir", sort_dir.to_string()),
];
if let Some(v) = volume_24h_min { params.push(("volume_24h_min", v.to_string())); }
if let Some(v) = volume_24h_max { params.push(("volume_24h_max", v.to_string())); }
if let Some(v) = liquidity_usd_min { params.push(("liquidity_usd_min", v.to_string())); }
if let Some(v) = fdv_min { params.push(("fdv_min", v.to_string())); }
if let Some(v) = fdv_max { params.push(("fdv_max", v.to_string())); }
if let Some(v) = txns_24h_min { params.push(("txns_24h_min", v.to_string())); }
if let Some(v) = created_after { params.push(("created_after", v.to_string())); }
if let Some(v) = created_before { params.push(("created_before", v.to_string())); }
let param_refs: Vec<(&str, &str)> = params.iter().map(|(k, v)| (*k, v.as_str())).collect();
let resp: TokenFilterResponse = client.dexpaprika_get(
&format!("/networks/{network}/tokens/filter"),
¶m_refs,
).await?;
match output {
OutputFormat::Table => {
crate::output::tokens::print_token_filter_table(&resp.results);
if let Some(pi) = &resp.page_info {
println!(" Page {}/{} ({} tokens total)",
pi.page.unwrap_or(0), pi.total_pages.unwrap_or(0), pi.total_items.unwrap_or(0));
}
}
OutputFormat::Json => {
crate::output::print_json_wrapped(&resp, crate::output::ResponseMeta::dexpaprika(&format!("/networks/{network}/tokens/filter")), raw)?;
}
}
Ok(())
}