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 telegram: Option<String>,
pub twitter: 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(())
}