use anyhow::Result;
use reqwest::Client;
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct DexScreenerResponseRaw {
#[serde(rename = "schemaVersion")]
pub schema_version: String,
pub pairs: Vec<PairInfoRaw>,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct PairInfoRaw {
#[serde(rename = "chainId")]
pub chain_id: String,
#[serde(rename = "dexId")]
pub dex_id: String,
pub url: String,
#[serde(rename = "pairAddress")]
pub pair_address: String,
pub labels: Option<Vec<String>>,
pub base_token: TokenRaw,
pub quote_token: TokenRaw,
#[serde(rename = "priceNative")]
pub price_native: String,
#[serde(rename = "priceUsd")]
pub price_usd: Option<String>,
pub liquidity: Option<LiquidityRaw>,
pub volume: Option<VolumeRaw>,
pub price_change: Option<PriceChangeRaw>,
pub txns: Option<TransactionsRaw>,
#[serde(rename = "marketCap")]
pub market_cap: Option<f64>,
#[serde(rename = "fdv")]
pub fdv: Option<f64>,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct LiquidityRaw {
pub usd: Option<f64>,
pub base: Option<f64>,
pub quote: Option<f64>,
}
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
pub struct VolumeRaw {
#[serde(default)]
pub h24: Option<f64>,
#[serde(default)]
pub h6: Option<f64>,
#[serde(default)]
pub h1: Option<f64>,
#[serde(default)]
pub m5: Option<f64>,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct PriceChangeRaw {
#[serde(default)]
pub h24: Option<f64>,
#[serde(default)]
pub h6: Option<f64>,
#[serde(default)]
pub h1: Option<f64>,
#[serde(default)]
pub m5: Option<f64>,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct TransactionsRaw {
#[serde(default)]
pub h24: Option<TransactionStatsRaw>,
#[serde(default)]
pub h6: Option<TransactionStatsRaw>,
#[serde(default)]
pub h1: Option<TransactionStatsRaw>,
#[serde(default)]
pub m5: Option<TransactionStatsRaw>,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct TransactionStatsRaw {
pub buys: Option<u64>,
pub sells: Option<u64>,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct TokenRaw {
pub address: String,
pub name: String,
pub symbol: String,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct TokenProfile {
pub url: String,
#[serde(rename = "chainId")]
pub chain_id: String,
#[serde(rename = "tokenAddress")]
pub token_address: String,
pub icon: String,
pub header: Option<String>,
pub description: Option<String>,
pub links: Option<Vec<TokenLink>>,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct TokenLink {
#[serde(rename = "type")]
pub link_type: Option<String>,
pub label: Option<String>,
pub url: String,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct BoostsResponse {
pub url: String,
#[serde(rename = "chainId")]
pub chain_id: String,
#[serde(rename = "tokenAddress")]
pub token_address: String,
pub amount: f64,
#[serde(rename = "totalAmount")]
pub total_amount: f64,
pub icon: Option<String>,
pub header: Option<String>,
pub description: Option<String>,
pub links: Option<Vec<TokenLink>>,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct OrdersResponse {
pub orders: Vec<Order>,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct Order {
#[serde(rename = "type")]
pub order_type: OrderType,
pub status: OrderStatus,
#[serde(rename = "paymentTimestamp")]
pub payment_timestamp: Option<f64>,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
pub enum OrderType {
TokenProfile,
CommunityTakeover,
TokenAd,
TrendingBarAd,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(rename_all = "kebab-case")]
pub enum OrderStatus {
Processing,
Cancelled,
#[serde(rename = "on-hold")]
OnHold,
Approved,
Rejected,
}
pub async fn search_ticker(ticker: String) -> Result<DexScreenerResponseRaw> {
let client = Client::new();
let url = format!(
"https://api.dexscreener.com/latest/dex/search/?q={}&limit=8",
ticker
);
let response = client.get(&url).send().await?;
if response.status().is_client_error() {
let res = response.text().await?;
tracing::error!("DexScreener API error: {:?}", res);
return Err(anyhow::anyhow!("DexScreener API error: {:?}", res));
}
let data: serde_json::Value = response.json().await?;
let mut dex_response: DexScreenerResponseRaw = serde_json::from_value(data)?;
dex_response.pairs.truncate(8);
Ok(dex_response)
}
pub async fn get_pairs_by_token_v1(
chain_id: &str,
token_address: &str,
) -> Result<DexScreenerResponseRaw> {
let client = Client::new();
let url = format!(
"https://api.dexscreener.com/tokens/v1/{}/{}",
chain_id, token_address
);
let response = client.get(&url).send().await?;
if !response.status().is_success() {
let res = response.text().await?;
return Err(anyhow::anyhow!("Failed to fetch token pairs: {}", res));
}
let data: serde_json::Value = response.json().await?;
let dex_response: DexScreenerResponseRaw = serde_json::from_value(data)?;
Ok(dex_response)
}
pub async fn get_pair_by_address_v1(chain_id: &str, pair_address: &str) -> Result<PairInfoRaw> {
let client = Client::new();
let url = format!(
"https://api.dexscreener.com/latest/dex/pairs/{}/{}",
chain_id, pair_address
);
let response = client.get(&url).send().await?;
if !response.status().is_success() {
let res = response.text().await?;
return Err(anyhow::anyhow!("Failed to fetch pair: {}", res));
}
let data: serde_json::Value = response.json().await?;
let pair_response: DexScreenerResponseRaw = serde_json::from_value(data)?;
pair_response
.pairs
.into_iter()
.next()
.ok_or_else(|| anyhow::anyhow!("No pair found for address: {}", pair_address))
}
pub async fn get_pairs_by_token(token_address: &str) -> Result<DexScreenerResponseRaw> {
get_pairs_by_token_v1("ethereum", token_address).await
}
pub async fn get_pair_by_address(pair_address: &str) -> Result<PairInfoRaw> {
let chain_id = if pair_address.starts_with("solana_") {
"solana"
} else if pair_address.starts_with("bsc_") {
"bsc"
} else {
"ethereum"
};
get_pair_by_address_v1(chain_id, pair_address).await
}
pub async fn get_token_pairs_v1(
chain_id: &str,
token_address: &str,
) -> Result<DexScreenerResponseRaw> {
let client = Client::new();
let url = format!(
"https://api.dexscreener.com/token-pairs/v1/{}/{}",
chain_id, token_address
);
let response = client.get(&url).send().await?;
if !response.status().is_success() {
let res = response.text().await?;
return Err(anyhow::anyhow!("Failed to fetch token pairs: {}", res));
}
let data: serde_json::Value = response.json().await?;
let dex_response: DexScreenerResponseRaw = serde_json::from_value(data)?;
Ok(dex_response)
}
pub async fn get_latest_token_profiles() -> Result<Vec<TokenProfile>> {
let client = Client::new();
let url = "https://api.dexscreener.com/token-profiles/latest/v1";
let response = client.get(url).send().await?;
if !response.status().is_success() {
let res = response.text().await?;
return Err(anyhow::anyhow!("Failed to fetch token profiles: {}", res));
}
let profiles: Vec<TokenProfile> = response.json().await?;
Ok(profiles)
}
pub async fn get_latest_token_boosts() -> Result<Vec<BoostsResponse>> {
let client = Client::new();
let url = "https://api.dexscreener.com/token-boosts/latest/v1";
let response = client.get(url).send().await?;
if !response.status().is_success() {
let res = response.text().await?;
return Err(anyhow::anyhow!("Failed to fetch latest boosts: {}", res));
}
let boosts: Vec<BoostsResponse> = response.json().await?;
Ok(boosts)
}
pub async fn get_top_token_boosts() -> Result<Vec<BoostsResponse>> {
let client = Client::new();
let url = "https://api.dexscreener.com/token-boosts/top/v1";
let response = client.get(url).send().await?;
if !response.status().is_success() {
let res = response.text().await?;
return Err(anyhow::anyhow!("Failed to fetch top boosts: {}", res));
}
let boosts: Vec<BoostsResponse> = response.json().await?;
Ok(boosts)
}
pub async fn get_token_orders(chain_id: &str, token_address: &str) -> Result<Vec<Order>> {
let client = Client::new();
let url = format!(
"https://api.dexscreener.com/orders/v1/{}/{}",
chain_id, token_address
);
let response = client.get(&url).send().await?;
if !response.status().is_success() {
let res = response.text().await?;
return Err(anyhow::anyhow!("Failed to fetch token orders: {}", res));
}
let orders: Vec<Order> = response.json().await?;
Ok(orders)
}
pub fn find_best_liquidity_pair(mut pairs: Vec<PairInfoRaw>) -> Option<PairInfoRaw> {
if pairs.is_empty() {
return None;
}
pairs.sort_by(|a, b| {
let a_liq = a.liquidity.as_ref().and_then(|l| l.usd).unwrap_or(0.0);
let b_liq = b.liquidity.as_ref().and_then(|l| l.usd).unwrap_or(0.0);
b_liq
.partial_cmp(&a_liq)
.unwrap_or(std::cmp::Ordering::Equal)
});
pairs.into_iter().next()
}
pub fn get_token_price(pairs: &[PairInfoRaw], token_address: &str) -> Option<String> {
let mut matching_pairs: Vec<_> = pairs
.iter()
.filter(|p| p.base_token.address.eq_ignore_ascii_case(token_address))
.collect();
matching_pairs.sort_by(|a, b| {
let a_liq = a.liquidity.as_ref().and_then(|l| l.usd).unwrap_or(0.0);
let b_liq = b.liquidity.as_ref().and_then(|l| l.usd).unwrap_or(0.0);
b_liq
.partial_cmp(&a_liq)
.unwrap_or(std::cmp::Ordering::Equal)
});
matching_pairs.first().and_then(|p| p.price_usd.clone())
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DexScreenerResponse {
pub schema_version: String,
pub pairs: Vec<PairInfo>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PairInfo {
pub chain_id: String,
pub dex_id: String,
pub url: String,
pub pair_address: String,
pub labels: Vec<String>,
pub base_token: Token,
pub quote_token: Token,
pub price_native: f64,
pub price_usd: Option<f64>,
pub liquidity: Option<Liquidity>,
pub volume: Option<Volume>,
pub price_change: Option<PriceChange>,
pub txns: Option<Transactions>,
pub market_cap: Option<f64>,
pub fdv: Option<f64>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Token {
pub address: String,
pub name: String,
pub symbol: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Liquidity {
pub usd: Option<f64>,
pub base: Option<f64>,
pub quote: Option<f64>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct Volume {
pub h24: Option<f64>,
pub h6: Option<f64>,
pub h1: Option<f64>,
pub m5: Option<f64>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PriceChange {
pub h24: Option<f64>,
pub h6: Option<f64>,
pub h1: Option<f64>,
pub m5: Option<f64>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Transactions {
pub h24: Option<TransactionStats>,
pub h6: Option<TransactionStats>,
pub h1: Option<TransactionStats>,
pub m5: Option<TransactionStats>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TransactionStats {
pub buys: Option<u64>,
pub sells: Option<u64>,
}
impl From<DexScreenerResponseRaw> for DexScreenerResponse {
fn from(raw: DexScreenerResponseRaw) -> Self {
Self {
schema_version: raw.schema_version,
pairs: raw.pairs.into_iter().map(Into::into).collect(),
}
}
}
impl From<PairInfoRaw> for PairInfo {
fn from(raw: PairInfoRaw) -> Self {
Self {
chain_id: raw.chain_id,
dex_id: raw.dex_id,
url: raw.url,
pair_address: raw.pair_address,
labels: raw.labels.unwrap_or_default(),
base_token: raw.base_token.into(),
quote_token: raw.quote_token.into(),
price_native: raw.price_native.parse().unwrap_or(0.0),
price_usd: raw.price_usd.and_then(|p| p.parse().ok()),
liquidity: raw.liquidity.map(Into::into),
volume: raw.volume.map(Into::into),
price_change: raw.price_change.map(Into::into),
txns: raw.txns.map(Into::into),
market_cap: raw.market_cap,
fdv: raw.fdv,
}
}
}
impl From<TokenRaw> for Token {
fn from(raw: TokenRaw) -> Self {
Self {
address: raw.address,
name: raw.name,
symbol: raw.symbol,
}
}
}
impl From<LiquidityRaw> for Liquidity {
fn from(raw: LiquidityRaw) -> Self {
Self {
usd: raw.usd,
base: raw.base,
quote: raw.quote,
}
}
}
impl From<VolumeRaw> for Volume {
fn from(raw: VolumeRaw) -> Self {
Self {
h24: raw.h24,
h6: raw.h6,
h1: raw.h1,
m5: raw.m5,
}
}
}
impl From<PriceChangeRaw> for PriceChange {
fn from(raw: PriceChangeRaw) -> Self {
Self {
h24: raw.h24,
h6: raw.h6,
h1: raw.h1,
m5: raw.m5,
}
}
}
impl From<TransactionsRaw> for Transactions {
fn from(raw: TransactionsRaw) -> Self {
Self {
h24: raw.h24.map(Into::into),
h6: raw.h6.map(Into::into),
h1: raw.h1.map(Into::into),
m5: raw.m5.map(Into::into),
}
}
}
impl From<TransactionStatsRaw> for TransactionStats {
fn from(raw: TransactionStatsRaw) -> Self {
Self {
buys: raw.buys,
sells: raw.sells,
}
}
}
pub fn aggregate_token_info(
pairs: Vec<PairInfo>,
token_address: &str,
) -> Option<crate::dexscreener::TokenInfo> {
let token_pairs: Vec<PairInfo> = pairs
.into_iter()
.filter(|p| p.base_token.address.eq_ignore_ascii_case(token_address))
.collect();
if token_pairs.is_empty() {
return None;
}
let primary_pair = token_pairs.iter().max_by_key(|p| {
p.liquidity
.as_ref()
.and_then(|l| l.usd)
.map_or(0, |usd| (usd * 1000.0) as u64)
})?;
let total_volume_24h: f64 = token_pairs
.iter()
.filter_map(|p| p.volume.as_ref())
.filter_map(|v| v.h24)
.sum();
Some(crate::dexscreener::TokenInfo {
address: token_address.to_string(),
name: primary_pair.base_token.name.clone(),
symbol: primary_pair.base_token.symbol.clone(),
decimals: 18, price_usd: primary_pair.price_usd,
market_cap: primary_pair.market_cap,
volume_24h: Some(total_volume_24h),
price_change_24h: primary_pair.price_change.as_ref().and_then(|pc| pc.h24),
price_change_1h: primary_pair.price_change.as_ref().and_then(|pc| pc.h1),
price_change_5m: primary_pair.price_change.as_ref().and_then(|pc| pc.m5),
circulating_supply: None,
total_supply: None,
pair_count: token_pairs.len() as u32,
pairs: token_pairs
.iter()
.map(|p| convert_to_token_pair(p))
.collect(),
chain: crate::dexscreener::ChainInfo {
id: primary_pair.chain_id.clone(),
name: crate::dexscreener::format_chain_name(&primary_pair.chain_id),
logo: None,
native_token: crate::dexscreener::get_native_token(&primary_pair.chain_id),
},
security: crate::dexscreener::SecurityInfo {
is_verified: false,
liquidity_locked: None,
audit_status: None,
honeypot_status: None,
ownership_status: None,
risk_score: None,
},
socials: vec![],
updated_at: chrono::Utc::now(),
})
}
fn convert_to_token_pair(pair: &PairInfo) -> crate::dexscreener::TokenPair {
crate::dexscreener::TokenPair {
pair_id: pair.pair_address.clone(),
dex: crate::dexscreener::DexInfo {
id: pair.dex_id.clone(),
name: crate::dexscreener::format_dex_name(&pair.dex_id),
url: Some(pair.url.clone()),
logo: None,
},
base_token: crate::dexscreener::PairToken {
address: pair.base_token.address.clone(),
name: pair.base_token.name.clone(),
symbol: pair.base_token.symbol.clone(),
},
quote_token: crate::dexscreener::PairToken {
address: pair.quote_token.address.clone(),
name: pair.quote_token.name.clone(),
symbol: pair.quote_token.symbol.clone(),
},
price_usd: pair.price_usd,
price_native: Some(pair.price_native),
volume_24h: pair.volume.as_ref().and_then(|v| v.h24),
price_change_24h: pair.price_change.as_ref().and_then(|pc| pc.h24),
liquidity_usd: pair.liquidity.as_ref().and_then(|l| l.usd),
fdv: pair.fdv,
created_at: None,
last_trade_at: chrono::Utc::now(),
txns_24h: {
let buys = pair
.txns
.as_ref()
.and_then(|t| t.h24.as_ref())
.and_then(|h| h.buys.map(|b| b as u32));
let sells = pair
.txns
.as_ref()
.and_then(|t| t.h24.as_ref())
.and_then(|h| h.sells.map(|s| s as u32));
let total = match (buys, sells) {
(Some(b), Some(s)) => Some(b + s),
_ => None,
};
let (buy_volume_usd, sell_volume_usd) = if let (Some(volume), Some(b), Some(s)) =
(pair.volume.as_ref().and_then(|v| v.h24), buys, sells)
{
let total_txns = (b + s) as f64;
if total_txns > 0.0 {
let buy_ratio = b as f64 / total_txns;
let sell_ratio = s as f64 / total_txns;
(Some(volume * buy_ratio), Some(volume * sell_ratio))
} else {
(None, None)
}
} else {
(None, None)
};
crate::dexscreener::TransactionStats {
buys,
sells,
total,
buy_volume_usd,
sell_volume_usd,
}
},
url: pair.url.clone(),
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
fn create_test_token() -> TokenRaw {
TokenRaw {
address: "0x1234567890123456789012345678901234567890".to_string(),
name: "Test Token".to_string(),
symbol: "TEST".to_string(),
}
}
fn create_test_pair_info() -> PairInfoRaw {
PairInfoRaw {
chain_id: "ethereum".to_string(),
dex_id: "uniswap".to_string(),
url: "https://dexscreener.com/test".to_string(),
pair_address: "0xabcdef1234567890".to_string(),
labels: Some(vec!["test".to_string()]),
base_token: create_test_token(),
quote_token: TokenRaw {
address: "0x9876543210987654321098765432109876543210".to_string(),
name: "Quote Token".to_string(),
symbol: "QUOTE".to_string(),
},
price_native: "0.001".to_string(),
price_usd: Some("1.50".to_string()),
liquidity: Some(LiquidityRaw {
usd: Some(100000.0),
base: Some(50000.0),
quote: Some(50000.0),
}),
volume: Some(VolumeRaw {
h24: Some(10000.0),
h6: Some(2500.0),
h1: Some(416.0),
m5: Some(35.0),
}),
price_change: Some(PriceChangeRaw {
h24: Some(5.5),
h6: Some(2.1),
h1: Some(0.8),
m5: Some(0.1),
}),
txns: Some(TransactionsRaw {
h24: Some(TransactionStatsRaw {
buys: Some(100),
sells: Some(80),
}),
h6: Some(TransactionStatsRaw {
buys: Some(25),
sells: Some(20),
}),
h1: Some(TransactionStatsRaw {
buys: Some(4),
sells: Some(3),
}),
m5: Some(TransactionStatsRaw {
buys: Some(1),
sells: Some(0),
}),
}),
market_cap: Some(1000000.0),
fdv: Some(1500000.0),
}
}
#[test]
fn test_token_serialization() {
let token = create_test_token();
let serialized = serde_json::to_string(&token).unwrap();
let deserialized: TokenRaw = serde_json::from_str(&serialized).unwrap();
assert_eq!(token.address, deserialized.address);
assert_eq!(token.name, deserialized.name);
assert_eq!(token.symbol, deserialized.symbol);
}
#[test]
fn test_liquidity_serialization_with_all_fields() {
let liquidity = LiquidityRaw {
usd: Some(100000.0),
base: Some(50000.0),
quote: Some(50000.0),
};
let serialized = serde_json::to_string(&liquidity).unwrap();
let deserialized: LiquidityRaw = serde_json::from_str(&serialized).unwrap();
assert_eq!(liquidity.usd, deserialized.usd);
assert_eq!(liquidity.base, deserialized.base);
assert_eq!(liquidity.quote, deserialized.quote);
}
#[test]
fn test_liquidity_serialization_with_none_fields() {
let liquidity = LiquidityRaw {
usd: None,
base: None,
quote: None,
};
let serialized = serde_json::to_string(&liquidity).unwrap();
let deserialized: LiquidityRaw = serde_json::from_str(&serialized).unwrap();
assert_eq!(liquidity.usd, deserialized.usd);
assert_eq!(liquidity.base, deserialized.base);
assert_eq!(liquidity.quote, deserialized.quote);
}
#[test]
fn test_volume_default_serialization() {
let volume = VolumeRaw::default();
let serialized = serde_json::to_string(&volume).unwrap();
let deserialized: VolumeRaw = serde_json::from_str(&serialized).unwrap();
assert_eq!(volume.h24, deserialized.h24);
assert_eq!(volume.h6, deserialized.h6);
assert_eq!(volume.h1, deserialized.h1);
assert_eq!(volume.m5, deserialized.m5);
}
#[test]
fn test_volume_serialization_with_values() {
let volume = VolumeRaw {
h24: Some(10000.0),
h6: Some(2500.0),
h1: Some(416.0),
m5: Some(35.0),
};
let serialized = serde_json::to_string(&volume).unwrap();
let deserialized: VolumeRaw = serde_json::from_str(&serialized).unwrap();
assert_eq!(volume.h24, deserialized.h24);
assert_eq!(volume.h6, deserialized.h6);
assert_eq!(volume.h1, deserialized.h1);
assert_eq!(volume.m5, deserialized.m5);
}
#[test]
fn test_price_change_serialization() {
let price_change = PriceChangeRaw {
h24: Some(5.5),
h6: Some(2.1),
h1: Some(0.8),
m5: Some(0.1),
};
let serialized = serde_json::to_string(&price_change).unwrap();
let deserialized: PriceChangeRaw = serde_json::from_str(&serialized).unwrap();
assert_eq!(price_change.h24, deserialized.h24);
assert_eq!(price_change.h6, deserialized.h6);
assert_eq!(price_change.h1, deserialized.h1);
assert_eq!(price_change.m5, deserialized.m5);
}
#[test]
fn test_transaction_stats_serialization() {
let stats = TransactionStatsRaw {
buys: Some(100),
sells: Some(80),
};
let serialized = serde_json::to_string(&stats).unwrap();
let deserialized: TransactionStatsRaw = serde_json::from_str(&serialized).unwrap();
assert_eq!(stats.buys, deserialized.buys);
assert_eq!(stats.sells, deserialized.sells);
}
#[test]
fn test_transaction_stats_serialization_with_none() {
let stats = TransactionStatsRaw {
buys: None,
sells: None,
};
let serialized = serde_json::to_string(&stats).unwrap();
let deserialized: TransactionStatsRaw = serde_json::from_str(&serialized).unwrap();
assert_eq!(stats.buys, deserialized.buys);
assert_eq!(stats.sells, deserialized.sells);
}
#[test]
fn test_transactions_serialization() {
let transactions = TransactionsRaw {
h24: Some(TransactionStatsRaw {
buys: Some(100),
sells: Some(80),
}),
h6: None,
h1: Some(TransactionStatsRaw {
buys: None,
sells: Some(3),
}),
m5: None,
};
let serialized = serde_json::to_string(&transactions).unwrap();
let deserialized: TransactionsRaw = serde_json::from_str(&serialized).unwrap();
assert!(deserialized.h24.is_some());
assert!(deserialized.h6.is_none());
assert!(deserialized.h1.is_some());
assert!(deserialized.m5.is_none());
}
#[test]
fn test_pair_info_serialization_complete() {
let pair = create_test_pair_info();
let serialized = serde_json::to_string(&pair).unwrap();
let deserialized: PairInfoRaw = serde_json::from_str(&serialized).unwrap();
assert_eq!(pair.chain_id, deserialized.chain_id);
assert_eq!(pair.dex_id, deserialized.dex_id);
assert_eq!(pair.url, deserialized.url);
assert_eq!(pair.pair_address, deserialized.pair_address);
assert_eq!(pair.labels, deserialized.labels);
assert_eq!(pair.price_native, deserialized.price_native);
assert_eq!(pair.price_usd, deserialized.price_usd);
assert!(deserialized.liquidity.is_some());
assert!(deserialized.volume.is_some());
assert!(deserialized.price_change.is_some());
assert!(deserialized.txns.is_some());
assert_eq!(pair.market_cap, deserialized.market_cap);
assert_eq!(pair.fdv, deserialized.fdv);
}
#[test]
fn test_pair_info_serialization_minimal() {
let pair = PairInfoRaw {
chain_id: "ethereum".to_string(),
dex_id: "uniswap".to_string(),
url: "https://dexscreener.com/test".to_string(),
pair_address: "0xabcdef1234567890".to_string(),
labels: None,
base_token: create_test_token(),
quote_token: create_test_token(),
price_native: "0.001".to_string(),
price_usd: None,
liquidity: None,
volume: None,
price_change: None,
txns: None,
market_cap: None,
fdv: None,
};
let serialized = serde_json::to_string(&pair).unwrap();
let deserialized: PairInfoRaw = serde_json::from_str(&serialized).unwrap();
assert_eq!(pair.chain_id, deserialized.chain_id);
assert_eq!(pair.dex_id, deserialized.dex_id);
assert!(deserialized.labels.is_none());
assert!(deserialized.price_usd.is_none());
assert!(deserialized.liquidity.is_none());
assert!(deserialized.volume.is_none());
assert!(deserialized.price_change.is_none());
assert!(deserialized.txns.is_none());
assert!(deserialized.market_cap.is_none());
assert!(deserialized.fdv.is_none());
}
#[test]
fn test_dexscreener_response_serialization() {
let response = DexScreenerResponseRaw {
schema_version: "1.0.0".to_string(),
pairs: vec![create_test_pair_info()],
};
let serialized = serde_json::to_string(&response).unwrap();
let deserialized: DexScreenerResponseRaw = serde_json::from_str(&serialized).unwrap();
assert_eq!(response.schema_version, deserialized.schema_version);
assert_eq!(response.pairs.len(), deserialized.pairs.len());
}
#[test]
fn test_dexscreener_response_empty_pairs() {
let response = DexScreenerResponseRaw {
schema_version: "1.0.0".to_string(),
pairs: vec![],
};
let serialized = serde_json::to_string(&response).unwrap();
let deserialized: DexScreenerResponseRaw = serde_json::from_str(&serialized).unwrap();
assert_eq!(response.schema_version, deserialized.schema_version);
assert!(deserialized.pairs.is_empty());
}
#[test]
fn test_find_best_liquidity_pair_when_empty_should_return_none() {
let pairs = vec![];
let result = find_best_liquidity_pair(pairs);
assert!(result.is_none());
}
#[test]
fn test_find_best_liquidity_pair_when_no_liquidity_should_return_first() {
let mut pair1 = create_test_pair_info();
pair1.liquidity = None;
let mut pair2 = create_test_pair_info();
pair2.liquidity = None;
let pairs = vec![pair1.clone(), pair2];
let result = find_best_liquidity_pair(pairs);
assert!(result.is_some());
assert_eq!(result.unwrap().pair_address, pair1.pair_address);
}
#[test]
fn test_find_best_liquidity_pair_when_liquidity_none_usd_should_return_first() {
let mut pair1 = create_test_pair_info();
pair1.liquidity = Some(LiquidityRaw {
usd: None,
base: Some(100.0),
quote: Some(200.0),
});
let mut pair2 = create_test_pair_info();
pair2.liquidity = Some(LiquidityRaw {
usd: None,
base: Some(300.0),
quote: Some(400.0),
});
let pairs = vec![pair1.clone(), pair2];
let result = find_best_liquidity_pair(pairs);
assert!(result.is_some());
assert_eq!(result.unwrap().pair_address, pair1.pair_address);
}
#[test]
fn test_find_best_liquidity_pair_when_has_liquidity_should_return_highest() {
let mut pair1 = create_test_pair_info();
pair1.pair_address = "low_liquidity".to_string();
pair1.liquidity = Some(LiquidityRaw {
usd: Some(50000.0),
base: Some(25000.0),
quote: Some(25000.0),
});
let mut pair2 = create_test_pair_info();
pair2.pair_address = "high_liquidity".to_string();
pair2.liquidity = Some(LiquidityRaw {
usd: Some(200000.0),
base: Some(100000.0),
quote: Some(100000.0),
});
let mut pair3 = create_test_pair_info();
pair3.pair_address = "medium_liquidity".to_string();
pair3.liquidity = Some(LiquidityRaw {
usd: Some(100000.0),
base: Some(50000.0),
quote: Some(50000.0),
});
let pairs = vec![pair1, pair2.clone(), pair3];
let result = find_best_liquidity_pair(pairs);
assert!(result.is_some());
assert_eq!(result.unwrap().pair_address, "high_liquidity");
}
#[test]
fn test_find_best_liquidity_pair_when_mixed_liquidity_should_return_highest() {
let mut pair1 = create_test_pair_info();
pair1.pair_address = "no_liquidity".to_string();
pair1.liquidity = None;
let mut pair2 = create_test_pair_info();
pair2.pair_address = "has_liquidity".to_string();
pair2.liquidity = Some(LiquidityRaw {
usd: Some(150000.0),
base: Some(75000.0),
quote: Some(75000.0),
});
let mut pair3 = create_test_pair_info();
pair3.pair_address = "none_usd".to_string();
pair3.liquidity = Some(LiquidityRaw {
usd: None,
base: Some(50000.0),
quote: Some(50000.0),
});
let pairs = vec![pair1, pair2.clone(), pair3];
let result = find_best_liquidity_pair(pairs);
assert!(result.is_some());
assert_eq!(result.unwrap().pair_address, "has_liquidity");
}
#[test]
fn test_get_token_price_when_empty_pairs_should_return_none() {
let pairs = vec![];
let result = get_token_price(&pairs, "0x1234567890123456789012345678901234567890");
assert!(result.is_none());
}
#[test]
fn test_get_token_price_when_no_matching_address_should_return_none() {
let pairs = vec![create_test_pair_info()];
let result = get_token_price(&pairs, "0xnonexistent");
assert!(result.is_none());
}
#[test]
fn test_get_token_price_when_matching_address_case_insensitive_should_return_price() {
let mut pair = create_test_pair_info();
pair.base_token.address = "0xABCDEF1234567890".to_string();
pair.price_usd = Some("2.50".to_string());
let pairs = vec![pair];
let result = get_token_price(&pairs, "0xabcdef1234567890");
assert!(result.is_some());
assert_eq!(result.unwrap(), "2.50");
}
#[test]
fn test_get_token_price_when_matching_address_uppercase_should_return_price() {
let mut pair = create_test_pair_info();
pair.base_token.address = "0xabcdef1234567890".to_string();
pair.price_usd = Some("3.75".to_string());
let pairs = vec![pair];
let result = get_token_price(&pairs, "0XABCDEF1234567890");
assert!(result.is_some());
assert_eq!(result.unwrap(), "3.75");
}
#[test]
fn test_get_token_price_when_no_price_usd_should_return_none() {
let mut pair = create_test_pair_info();
pair.base_token.address = "0x1234567890123456789012345678901234567890".to_string();
pair.price_usd = None;
let pairs = vec![pair];
let result = get_token_price(&pairs, "0x1234567890123456789012345678901234567890");
assert!(result.is_none());
}
#[test]
fn test_get_token_price_when_multiple_pairs_should_return_highest_liquidity() {
let mut pair1 = create_test_pair_info();
pair1.base_token.address = "0x1234567890123456789012345678901234567890".to_string();
pair1.price_usd = Some("1.00".to_string());
pair1.liquidity = Some(LiquidityRaw {
usd: Some(50000.0),
base: Some(25000.0),
quote: Some(25000.0),
});
let mut pair2 = create_test_pair_info();
pair2.base_token.address = "0x1234567890123456789012345678901234567890".to_string();
pair2.price_usd = Some("1.05".to_string());
pair2.liquidity = Some(LiquidityRaw {
usd: Some(150000.0),
base: Some(75000.0),
quote: Some(75000.0),
});
let pairs = vec![pair1, pair2];
let result = get_token_price(&pairs, "0x1234567890123456789012345678901234567890");
assert!(result.is_some());
assert_eq!(result.unwrap(), "1.05");
}
#[test]
fn test_get_token_price_when_multiple_pairs_no_liquidity_should_return_first_match() {
let mut pair1 = create_test_pair_info();
pair1.base_token.address = "0x1234567890123456789012345678901234567890".to_string();
pair1.price_usd = Some("1.00".to_string());
pair1.liquidity = None;
let mut pair2 = create_test_pair_info();
pair2.base_token.address = "0x1234567890123456789012345678901234567890".to_string();
pair2.price_usd = Some("1.05".to_string());
pair2.liquidity = None;
let pairs = vec![pair1, pair2];
let result = get_token_price(&pairs, "0x1234567890123456789012345678901234567890");
assert!(result.is_some());
assert_eq!(result.unwrap(), "1.00");
}
#[test]
fn test_get_token_price_when_mixed_liquidity_should_prefer_with_liquidity() {
let mut pair1 = create_test_pair_info();
pair1.base_token.address = "0x1234567890123456789012345678901234567890".to_string();
pair1.price_usd = Some("1.00".to_string());
pair1.liquidity = None;
let mut pair2 = create_test_pair_info();
pair2.base_token.address = "0x1234567890123456789012345678901234567890".to_string();
pair2.price_usd = Some("1.05".to_string());
pair2.liquidity = Some(LiquidityRaw {
usd: Some(100000.0),
base: Some(50000.0),
quote: Some(50000.0),
});
let pairs = vec![pair1, pair2];
let result = get_token_price(&pairs, "0x1234567890123456789012345678901234567890");
assert!(result.is_some());
assert_eq!(result.unwrap(), "1.05");
}
#[test]
fn test_deserialize_pair_info_with_camel_case() {
let json = json!({
"chainId": "ethereum",
"dexId": "uniswap",
"url": "https://dexscreener.com/test",
"pairAddress": "0xabcdef1234567890",
"labels": ["test"],
"baseToken": {
"address": "0x1234567890123456789012345678901234567890",
"name": "Test Token",
"symbol": "TEST"
},
"quoteToken": {
"address": "0x9876543210987654321098765432109876543210",
"name": "Quote Token",
"symbol": "QUOTE"
},
"priceNative": "0.001",
"priceUsd": "1.50",
"liquidity": {
"usd": 100000.0,
"base": 50000.0,
"quote": 50000.0
},
"volume": {
"h24": 10000.0,
"h6": 2500.0,
"h1": 416.0,
"m5": 35.0
},
"priceChange": {
"h24": 5.5,
"h6": 2.1,
"h1": 0.8,
"m5": 0.1
},
"txns": {
"h24": {
"buys": 100,
"sells": 80
},
"h6": {
"buys": 25,
"sells": 20
},
"h1": {
"buys": 4,
"sells": 3
},
"m5": {
"buys": 1,
"sells": 0
}
},
"marketCap": 1000000.0,
"fdv": 1500000.0
});
let pair: PairInfoRaw = serde_json::from_value(json).unwrap();
assert_eq!(pair.chain_id, "ethereum");
assert_eq!(pair.dex_id, "uniswap");
assert_eq!(pair.pair_address, "0xabcdef1234567890");
assert_eq!(
pair.base_token.address,
"0x1234567890123456789012345678901234567890"
);
assert_eq!(pair.quote_token.symbol, "QUOTE");
assert_eq!(pair.price_native, "0.001");
assert_eq!(pair.price_usd, Some("1.50".to_string()));
assert!(pair.liquidity.is_some());
assert!(pair.volume.is_some());
assert!(pair.price_change.is_some());
assert!(pair.txns.is_some());
assert_eq!(pair.market_cap, Some(1000000.0));
assert_eq!(pair.fdv, Some(1500000.0));
}
#[test]
fn test_deserialize_dexscreener_response_with_camel_case() {
let json = json!({
"schemaVersion": "1.0.0",
"pairs": []
});
let response: DexScreenerResponseRaw = serde_json::from_value(json).unwrap();
assert_eq!(response.schema_version, "1.0.0");
assert!(response.pairs.is_empty());
}
#[test]
fn test_volume_default_values() {
let volume = VolumeRaw::default();
assert!(volume.h24.is_none());
assert!(volume.h6.is_none());
assert!(volume.h1.is_none());
assert!(volume.m5.is_none());
}
#[test]
fn test_volume_deserialization_with_missing_fields() {
let json = json!({});
let volume: VolumeRaw = serde_json::from_value(json).unwrap();
assert!(volume.h24.is_none());
assert!(volume.h6.is_none());
assert!(volume.h1.is_none());
assert!(volume.m5.is_none());
}
#[test]
fn test_price_change_deserialization_with_missing_fields() {
let json = json!({});
let price_change: PriceChangeRaw = serde_json::from_value(json).unwrap();
assert!(price_change.h24.is_none());
assert!(price_change.h6.is_none());
assert!(price_change.h1.is_none());
assert!(price_change.m5.is_none());
}
#[test]
fn test_transactions_deserialization_with_missing_fields() {
let json = json!({});
let transactions: TransactionsRaw = serde_json::from_value(json).unwrap();
assert!(transactions.h24.is_none());
assert!(transactions.h6.is_none());
assert!(transactions.h1.is_none());
assert!(transactions.m5.is_none());
}
#[test]
fn test_find_best_liquidity_pair_with_single_pair() {
let pair = create_test_pair_info();
let pairs = vec![pair.clone()];
let result = find_best_liquidity_pair(pairs);
assert!(result.is_some());
assert_eq!(result.unwrap().pair_address, pair.pair_address);
}
#[test]
fn test_find_best_liquidity_pair_with_zero_liquidity() {
let mut pair1 = create_test_pair_info();
pair1.pair_address = "zero_liquidity_1".to_string();
pair1.liquidity = Some(LiquidityRaw {
usd: Some(0.0),
base: Some(0.0),
quote: Some(0.0),
});
let mut pair2 = create_test_pair_info();
pair2.pair_address = "zero_liquidity_2".to_string();
pair2.liquidity = Some(LiquidityRaw {
usd: Some(0.0),
base: Some(100.0),
quote: Some(200.0),
});
let pairs = vec![pair1.clone(), pair2];
let result = find_best_liquidity_pair(pairs);
assert!(result.is_some());
assert_eq!(result.unwrap().pair_address, "zero_liquidity_1");
}
#[test]
fn test_find_best_liquidity_pair_with_negative_liquidity() {
let mut pair = create_test_pair_info();
pair.pair_address = "negative_liquidity".to_string();
pair.liquidity = Some(LiquidityRaw {
usd: Some(-1000.0),
base: Some(-500.0),
quote: Some(-500.0),
});
let pairs = vec![pair.clone()];
let result = find_best_liquidity_pair(pairs);
assert!(result.is_some());
assert_eq!(result.unwrap().pair_address, "negative_liquidity");
}
#[test]
fn test_get_token_price_with_empty_string_address() {
let pairs = vec![create_test_pair_info()];
let result = get_token_price(&pairs, "");
assert!(result.is_none());
}
#[test]
fn test_get_token_price_with_partial_match() {
let mut pair = create_test_pair_info();
pair.base_token.address = "0x1234567890123456789012345678901234567890".to_string();
let pairs = vec![pair];
let result = get_token_price(&pairs, "0x1234567890");
assert!(result.is_none());
}
#[test]
fn test_get_token_price_with_special_characters() {
let mut pair = create_test_pair_info();
pair.base_token.address = "0x!@#$%^&*()_+".to_string();
pair.price_usd = Some("1.00".to_string());
let pairs = vec![pair];
let result = get_token_price(&pairs, "0x!@#$%^&*()_+");
assert!(result.is_some());
assert_eq!(result.unwrap(), "1.00");
}
#[test]
fn test_get_token_price_case_sensitivity_mixed() {
let mut pair = create_test_pair_info();
pair.base_token.address = "0xaBcDeF1234567890".to_string();
pair.price_usd = Some("2.50".to_string());
let pairs = vec![pair];
let result = get_token_price(&pairs, "0xAbCdEf1234567890");
assert!(result.is_some());
assert_eq!(result.unwrap(), "2.50");
}
#[test]
fn test_token_debug_format() {
let token = create_test_token();
let debug_str = format!("{:?}", token);
assert!(debug_str.contains("Test Token"));
assert!(debug_str.contains("TEST"));
assert!(debug_str.contains("0x1234567890123456789012345678901234567890"));
}
#[test]
fn test_liquidity_debug_format() {
let liquidity = LiquidityRaw {
usd: Some(100000.0),
base: Some(50000.0),
quote: Some(50000.0),
};
let debug_str = format!("{:?}", liquidity);
assert!(debug_str.contains("100000"));
assert!(debug_str.contains("50000"));
}
#[test]
fn test_volume_debug_format() {
let volume = VolumeRaw {
h24: Some(10000.0),
h6: Some(2500.0),
h1: Some(416.0),
m5: Some(35.0),
};
let debug_str = format!("{:?}", volume);
assert!(debug_str.contains("10000"));
assert!(debug_str.contains("2500"));
assert!(debug_str.contains("416"));
assert!(debug_str.contains("35"));
}
#[test]
fn test_price_change_debug_format() {
let price_change = PriceChangeRaw {
h24: Some(5.5),
h6: Some(2.1),
h1: Some(0.8),
m5: Some(0.1),
};
let debug_str = format!("{:?}", price_change);
assert!(debug_str.contains("5.5"));
assert!(debug_str.contains("2.1"));
assert!(debug_str.contains("0.8"));
assert!(debug_str.contains("0.1"));
}
#[test]
fn test_transaction_stats_debug_format() {
let stats = TransactionStatsRaw {
buys: Some(100),
sells: Some(80),
};
let debug_str = format!("{:?}", stats);
assert!(debug_str.contains("100"));
assert!(debug_str.contains("80"));
}
#[test]
fn test_transactions_debug_format() {
let transactions = TransactionsRaw {
h24: Some(TransactionStatsRaw {
buys: Some(100),
sells: Some(80),
}),
h6: None,
h1: None,
m5: None,
};
let debug_str = format!("{:?}", transactions);
assert!(debug_str.contains("100"));
assert!(debug_str.contains("80"));
}
#[test]
fn test_pair_info_debug_format() {
let pair = create_test_pair_info();
let debug_str = format!("{:?}", pair);
assert!(debug_str.contains("ethereum"));
assert!(debug_str.contains("uniswap"));
assert!(debug_str.contains("0xabcdef1234567890"));
}
#[test]
fn test_dexscreener_response_debug_format() {
let response = DexScreenerResponseRaw {
schema_version: "1.0.0".to_string(),
pairs: vec![create_test_pair_info()],
};
let debug_str = format!("{:?}", response);
assert!(debug_str.contains("1.0.0"));
assert!(debug_str.contains("ethereum"));
}
#[test]
fn test_token_clone() {
let token = create_test_token();
let cloned = token.clone();
assert_eq!(token.address, cloned.address);
assert_eq!(token.name, cloned.name);
assert_eq!(token.symbol, cloned.symbol);
}
#[test]
fn test_liquidity_clone() {
let liquidity = LiquidityRaw {
usd: Some(100000.0),
base: Some(50000.0),
quote: Some(50000.0),
};
let cloned = liquidity.clone();
assert_eq!(liquidity.usd, cloned.usd);
assert_eq!(liquidity.base, cloned.base);
assert_eq!(liquidity.quote, cloned.quote);
}
#[test]
fn test_volume_clone() {
let volume = VolumeRaw {
h24: Some(10000.0),
h6: Some(2500.0),
h1: Some(416.0),
m5: Some(35.0),
};
let cloned = volume.clone();
assert_eq!(volume.h24, cloned.h24);
assert_eq!(volume.h6, cloned.h6);
assert_eq!(volume.h1, cloned.h1);
assert_eq!(volume.m5, cloned.m5);
}
#[test]
fn test_price_change_clone() {
let price_change = PriceChangeRaw {
h24: Some(5.5),
h6: Some(2.1),
h1: Some(0.8),
m5: Some(0.1),
};
let cloned = price_change.clone();
assert_eq!(price_change.h24, cloned.h24);
assert_eq!(price_change.h6, cloned.h6);
assert_eq!(price_change.h1, cloned.h1);
assert_eq!(price_change.m5, cloned.m5);
}
#[test]
fn test_transaction_stats_clone() {
let stats = TransactionStatsRaw {
buys: Some(100),
sells: Some(80),
};
let cloned = stats.clone();
assert_eq!(stats.buys, cloned.buys);
assert_eq!(stats.sells, cloned.sells);
}
#[test]
fn test_transactions_clone() {
let transactions = TransactionsRaw {
h24: Some(TransactionStatsRaw {
buys: Some(100),
sells: Some(80),
}),
h6: None,
h1: None,
m5: None,
};
let cloned = transactions.clone();
assert!(cloned.h24.is_some());
assert!(cloned.h6.is_none());
assert!(cloned.h1.is_none());
assert!(cloned.m5.is_none());
}
#[test]
fn test_pair_info_clone() {
let pair = create_test_pair_info();
let cloned = pair.clone();
assert_eq!(pair.chain_id, cloned.chain_id);
assert_eq!(pair.dex_id, cloned.dex_id);
assert_eq!(pair.url, cloned.url);
assert_eq!(pair.pair_address, cloned.pair_address);
}
#[test]
fn test_dexscreener_response_clone() {
let response = DexScreenerResponseRaw {
schema_version: "1.0.0".to_string(),
pairs: vec![create_test_pair_info()],
};
let cloned = response.clone();
assert_eq!(response.schema_version, cloned.schema_version);
assert_eq!(response.pairs.len(), cloned.pairs.len());
}
#[test]
fn test_find_best_liquidity_pair_with_very_large_numbers() {
let mut pair = create_test_pair_info();
pair.pair_address = "large_liquidity".to_string();
pair.liquidity = Some(LiquidityRaw {
usd: Some(f64::MAX),
base: Some(f64::MAX),
quote: Some(f64::MAX),
});
let pairs = vec![pair.clone()];
let result = find_best_liquidity_pair(pairs);
assert!(result.is_some());
assert_eq!(result.unwrap().pair_address, "large_liquidity");
}
#[test]
fn test_find_best_liquidity_pair_with_infinity() {
let mut pair = create_test_pair_info();
pair.pair_address = "infinity_liquidity".to_string();
pair.liquidity = Some(LiquidityRaw {
usd: Some(f64::INFINITY),
base: Some(100.0),
quote: Some(200.0),
});
let pairs = vec![pair.clone()];
let result = find_best_liquidity_pair(pairs);
assert!(result.is_some());
assert_eq!(result.unwrap().pair_address, "infinity_liquidity");
}
#[test]
fn test_find_best_liquidity_pair_with_nan() {
let mut pair = create_test_pair_info();
pair.pair_address = "nan_liquidity".to_string();
pair.liquidity = Some(LiquidityRaw {
usd: Some(f64::NAN),
base: Some(100.0),
quote: Some(200.0),
});
let pairs = vec![pair.clone()];
let result = find_best_liquidity_pair(pairs);
assert!(result.is_some());
assert_eq!(result.unwrap().pair_address, "nan_liquidity");
}
#[tokio::test]
async fn test_search_ticker_truncates_to_8_results() {
let response = search_ticker("ETH".to_string()).await.unwrap();
assert!(response.pairs.len() <= 8);
}
#[tokio::test]
async fn test_search_ticker() {
let response = search_ticker("BONK".to_string()).await.unwrap();
assert_eq!(response.schema_version, "1.0.0");
assert!(!response.pairs.is_empty());
}
#[tokio::test]
async fn test_search_by_mint() {
let response = search_ticker(
"DezXAZ8z7PnrnRJjz3wXBoRgixCa6xjnB7YaB1pPB263".to_string(), )
.await
.unwrap();
assert_eq!(response.schema_version, "1.0.0");
assert!(!response.pairs.is_empty());
}
#[tokio::test]
async fn test_get_pairs_by_token() {
let response = get_pairs_by_token("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48") .await
.unwrap();
assert!(!response.pairs.is_empty());
}
#[tokio::test]
async fn test_get_pair_by_address_success() {
let result =
get_pair_by_address("ethereum_0x0d4a11d5eeaac28ec3f61d100daf4d40471f1852").await;
match result {
Ok(pair) => {
assert!(!pair.pair_address.is_empty());
assert!(!pair.chain_id.is_empty());
assert!(!pair.dex_id.is_empty());
}
Err(_) => {
}
}
}
#[tokio::test]
async fn test_get_pair_by_address_nonexistent() {
let result = get_pair_by_address("invalid_pair_address_that_does_not_exist").await;
assert!(result.is_err());
}
}