Skip to main content

scope/display/
format.rs

1//! Shared formatting utilities for consistent presentation across all chains.
2
3/// Formats a USD value with K, M, B suffixes.
4///
5/// # Examples
6///
7/// ```
8/// use scope::display::format::format_usd;
9///
10/// assert_eq!(format_usd(1_500_000_000.0), "$1.50B");
11/// assert_eq!(format_usd(1_500_000.0), "$1.50M");
12/// assert_eq!(format_usd(1_500.0), "$1.50K");
13/// assert_eq!(format_usd(15.50), "$15.50");
14/// ```
15/// Decimals for prices near `reference`: the one precision rule for
16/// every surface (M3). 2 from 1000, 6 near a USD peg (stablecoins), 4 from
17/// 1, 6 from 0.01, 8 from 0.0001, else 10. A list of prices (a book) takes
18/// its decimals from one reference, so its rows line up.
19pub fn price_decimals(reference: f64) -> usize {
20    let a = reference.abs();
21    if a >= 1000.0 {
22        2
23    } else if crate::tokens::is_stablecoin_price(a) {
24        6
25    } else if a >= 1.0 {
26        4
27    } else if a >= 0.01 {
28        6
29    } else if a >= 0.0001 {
30        8
31    } else {
32        10
33    }
34}
35
36/// A price at the decimals of its own size ([`price_decimals`]).
37pub fn format_price(price: f64) -> String {
38    format!("{price:.*}", price_decimals(price))
39}
40
41/// [`format_price`] in USD.
42pub fn format_price_usd(price: f64) -> String {
43    format!("${}", format_price(price))
44}
45
46pub fn format_usd(value: f64) -> String {
47    format!("${}", format_large_number(value))
48}
49
50/// Formats a large number with K, M, B suffixes (no currency prefix).
51///
52/// # Examples
53///
54/// ```
55/// use scope::display::format::format_large_number;
56///
57/// assert_eq!(format_large_number(1_500_000_000.0), "1.50B");
58/// assert_eq!(format_large_number(1_500_000.0), "1.50M");
59/// assert_eq!(format_large_number(1_500.0), "1.50K");
60/// assert_eq!(format_large_number(500.0), "500.00");
61/// ```
62pub fn format_large_number(value: f64) -> String {
63    if value >= 1_000_000_000.0 {
64        format!("{:.2}B", value / 1_000_000_000.0)
65    } else if value >= 1_000_000.0 {
66        format!("{:.2}M", value / 1_000_000.0)
67    } else if value >= 1_000.0 {
68        format!("{:.2}K", value / 1_000.0)
69    } else {
70        format!("{:.2}", value)
71    }
72}
73
74/// Formats a raw token balance string with proper decimals and K/M/B suffixes.
75///
76/// # Arguments
77///
78/// * `balance` - Raw balance in smallest units (e.g., wei, lamports)
79/// * `decimals` - Token decimal places
80///
81/// # Examples
82///
83/// ```
84/// use scope::display::format::format_token_balance;
85///
86/// assert!(format_token_balance("1000000000000000000", 18).starts_with("1.00"));
87/// assert!(format_token_balance("5000000000000000000000000", 18).contains("M"));
88/// ```
89pub fn format_token_balance(balance: &str, decimals: u8) -> String {
90    let balance_f64: f64 = balance.parse().unwrap_or(0.0);
91    let divisor = 10_f64.powi(decimals as i32);
92    let formatted = balance_f64 / divisor;
93
94    if formatted >= 1_000_000_000.0 {
95        format!("{:.2}B", formatted / 1_000_000_000.0)
96    } else if formatted >= 1_000_000.0 {
97        format!("{:.2}M", formatted / 1_000_000.0)
98    } else if formatted >= 1_000.0 {
99        format!("{:.2}K", formatted / 1_000.0)
100    } else {
101        format!("{:.4}", formatted)
102    }
103}
104
105#[cfg(test)]
106mod tests {
107    use super::*;
108
109    #[test]
110    fn test_format_usd() {
111        assert_eq!(format_usd(1_500_000_000.0), "$1.50B");
112        assert_eq!(format_usd(1_500_000.0), "$1.50M");
113        assert_eq!(format_usd(1_500.0), "$1.50K");
114        assert_eq!(format_usd(15.50), "$15.50");
115        assert_eq!(format_usd(0.0), "$0.00");
116    }
117
118    #[test]
119    fn test_format_large_number() {
120        assert_eq!(format_large_number(500.0), "500.00");
121        assert_eq!(format_large_number(1_500.0), "1.50K");
122        assert_eq!(format_large_number(1_500_000.0), "1.50M");
123        assert_eq!(format_large_number(1_500_000_000.0), "1.50B");
124    }
125
126    #[test]
127    fn test_format_token_balance() {
128        assert!(format_token_balance("1000000000000000000", 18).starts_with("1.00"));
129        assert!(format_token_balance("5000000000000000000", 18).contains("5.00"));
130        assert!(format_token_balance("0", 18).starts_with("0.00"));
131    }
132}