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 TokenSearchResponse {
#[serde(default)]
pub results: Vec<TokenSearchItem>,
pub has_next_page: Option<bool>,
pub next_cursor: Option<String>,
}
#[derive(Debug, Deserialize, Serialize)]
pub struct TokenSearchItem {
pub address: Option<String>,
pub chain: Option<String>,
pub created_at: Option<String>,
pub price_usd: Option<f64>,
pub volume_usd_24h: Option<f64>,
pub volume_usd_7d: Option<f64>,
pub volume_usd_30d: Option<f64>,
pub liquidity_usd: Option<f64>,
pub fdv_usd: Option<f64>,
pub txns_24h: Option<i64>,
pub price_change_percentage_24h: Option<f64>,
}
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 order_by = crate::commands::search_mapping::map_token_sort_field(order_by);
let resp: TokenSearchResponse = client
.dexpaprika_get(
&format!("/networks/{network}/tokens/search"),
&[
("limit", limit_str.as_str()),
("order_by", order_by),
("sort", sort),
],
)
.await?;
match output {
OutputFormat::Table => {
crate::output::tokens::print_token_search_table(&resp.results);
crate::output::print_more_results_hint(resp.has_next_page, resp.next_cursor.as_deref());
}
OutputFormat::Json => {
crate::output::print_json_wrapped(
&resp,
crate::output::ResponseMeta::dexpaprika(&format!(
"/networks/{network}/tokens/search"
)),
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 order_by = crate::commands::search_mapping::map_token_sort_field(sort_by);
let mut params: Vec<(&str, String)> = vec![
("limit", limit_str),
("order_by", order_by.to_string()),
("sort", sort_dir.to_string()),
];
if let Some(v) = volume_24h_min {
params.push(("volume_usd_24h_min", v.to_string()));
}
if let Some(v) = volume_24h_max {
params.push(("volume_usd_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: TokenSearchResponse = client
.dexpaprika_get(&format!("/networks/{network}/tokens/search"), ¶m_refs)
.await?;
match output {
OutputFormat::Table => {
crate::output::tokens::print_token_search_table(&resp.results);
crate::output::print_more_results_hint(resp.has_next_page, resp.next_cursor.as_deref());
}
OutputFormat::Json => {
crate::output::print_json_wrapped(
&resp,
crate::output::ResponseMeta::dexpaprika(&format!(
"/networks/{network}/tokens/search"
)),
raw,
)?;
}
}
Ok(())
}