Skip to main content

eth_prices/asset/
mod.rs

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