#![deny(missing_docs)]
use chrono::{DateTime, Utc};
use serde::{Deserialize, Deserializer, Serialize};
use serde_json::Value;
use solana_program::pubkey::Pubkey;
use std::{collections::BTreeMap, str::FromStr};
use url::Url;
fn ok_or_default<'a, T, D>(deserializer: D) -> Result<T, D::Error>
where
T: Deserialize<'a> + Default,
D: Deserializer<'a>,
{
let v: Value = Deserialize::deserialize(deserializer)?;
Ok(T::deserialize(v).unwrap_or_default())
}
fn default_token_list_logo() -> Url {
Url::from_str("https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/So11111111111111111111111111111111111111112/logo.png").unwrap()
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
#[repr(u32)]
pub enum ChainID {
MainnetBeta = 101,
Testnet = 102,
Devnet = 103,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct TokenExtensions {
#[serde(skip_serializing_if = "Option::is_none")]
pub website: Option<String>,
#[serde(skip_serializing_if = "Option::is_none", rename = "bridgeContract")]
pub bridge_contract: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(rename = "assetContract")]
pub asset_contract: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub explorer: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub twitter: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub github: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub medium: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tgann: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tggroup: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub discord: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(rename = "serumV3Usdt")]
pub serum_v3_usdt: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(rename = "serumV3Usdc")]
pub serum_v3_usdc: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(rename = "coingeckoId")]
pub coingecko_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(rename = "imageUrl")]
pub image_url: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none", rename = "underlyingTokens")]
pub underlying_tokens: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub source: Option<String>,
#[serde(flatten)]
pub extra: BTreeMap<String, Value>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct TokenInfo {
pub name: String,
pub symbol: String,
#[serde(
deserialize_with = "ok_or_default",
default = "Option::default",
skip_serializing_if = "Option::is_none",
rename = "logoURI"
)]
pub logo_uri: Option<Url>,
pub decimals: u8,
#[serde(with = "pubkey")]
pub address: Pubkey,
#[serde(rename = "chainId")]
pub chain_id: u32,
#[serde(skip_serializing_if = "Option::is_none")]
pub tags: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub extensions: Option<TokenExtensions>,
}
impl Eq for TokenInfo {}
impl PartialEq for TokenInfo {
fn eq(&self, other: &Self) -> bool {
self.address == other.address && self.chain_id == other.chain_id
}
}
impl PartialOrd for TokenInfo {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for TokenInfo {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
if self.chain_id == other.chain_id {
self.address.to_string().cmp(&other.address.to_string())
} else {
self.chain_id.cmp(&other.chain_id)
}
}
}
impl TokenInfo {
pub fn simplify(&mut self) {
self.tags = None;
self.extensions = None;
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct TagDetails {
pub name: String,
pub description: String,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct TokenList {
pub name: String,
#[serde(default = "default_token_list_logo", rename = "logoURI")]
pub logo_uri: Url,
pub tags: BTreeMap<String, TagDetails>,
pub timestamp: DateTime<Utc>,
pub tokens: Vec<TokenInfo>,
}
impl TokenList {
pub fn filter_chain(&self, chain_id: u32) -> TokenList {
TokenList {
tokens: self
.tokens
.clone()
.into_iter()
.filter(|t| t.chain_id == chain_id)
.collect(),
..self.clone()
}
}
pub fn simplify(&mut self) {
self.tags = BTreeMap::new();
self.tokens = self
.tokens
.iter()
.map(|token| {
let mut token = token.clone();
token.simplify();
token
})
.collect();
}
}