Skip to main content

eth_prices/token/
mod.rs

1//! Token metadata and identifier helpers.
2
3use alloy::{
4    primitives::{Address, U256},
5    providers::DynProvider,
6};
7
8use crate::{Result, token::erc20::ERC20};
9
10pub mod erc20;
11pub mod identity;
12
13pub use identity::TokenIdentifier;
14
15/// A resolved asset with display metadata and decimal precision.
16#[derive(Debug, Clone, PartialEq)]
17pub struct Token {
18    /// Canonical identifier for the asset.
19    pub identifier: TokenIdentifier,
20    /// Human-readable asset name.
21    pub name: String,
22    /// Short symbol used for display.
23    pub symbol: String,
24    /// Number of decimal places used by on-chain amounts.
25    pub decimals: u8,
26}
27
28const FIAT_DECIMALS: u8 = 6;
29
30impl Token {
31    /// Resolves token metadata for the provided identifier.
32    ///
33    /// ERC-20 metadata is loaded from chain, while fiat and native assets use local defaults.
34    pub async fn new(identifier: TokenIdentifier, provider: &DynProvider) -> Result<Self> {
35        let (name, symbol, decimals) = match &identifier {
36            TokenIdentifier::ERC20 { address } => {
37                let erc20 = ERC20::new(*address, provider);
38
39                (
40                    erc20.name().call().await?,
41                    erc20.symbol().call().await?,
42                    erc20.decimals().call().await?,
43                )
44            }
45            TokenIdentifier::Fiat { symbol } => (symbol.clone(), symbol.clone(), FIAT_DECIMALS),
46            TokenIdentifier::Native => ("Native".to_string(), "ETH".to_string(), 18),
47        };
48
49        Ok(Self {
50            identifier,
51            name,
52            symbol,
53            decimals,
54        })
55    }
56
57    /// Returns one nominal unit for this token in base precision.
58    pub async fn nominal_amount(&self) -> U256 {
59        U256::from(10).pow(U256::from(self.decimals))
60    }
61
62    /// Formats a raw integer amount into a human-readable decimal string.
63    pub fn format_amount(&self, amount: U256, precision: usize) -> crate::Result<String> {
64        let amount = amount
65            .to_string()
66            .parse::<f64>()
67            .map_err(|e| crate::error::EthPricesError::InvalidTokenAmount(e.to_string()))?;
68        let amount = amount / 10_f64.powf(self.decimals as f64);
69        Ok(format!("{:.precision$}", amount))
70    }
71
72    /// Returns the ERC-20 contract address.
73    ///
74    /// Returns the underlying contract address, if applicable.
75    pub fn address(&self) -> Option<Address> {
76        match &self.identifier {
77            TokenIdentifier::ERC20 { address } => Some(*address),
78            TokenIdentifier::Fiat { .. } => None,
79            TokenIdentifier::Native => None,
80        }
81    }
82}