Skip to main content

scemadex_sdk/
primitives.rs

1use serde::{Deserialize, Serialize};
2
3use crate::error::{Result, ScemaDexError};
4
5/// A base58-encoded Solana account address.
6///
7/// The lean core stores it as a validated string so the published crate carries
8/// no `solana-sdk` dependency. The `scematica` feature bridges to
9/// `solana_sdk::pubkey::Pubkey` at the integration boundary.
10#[derive(Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
11pub struct Address(String);
12
13impl Address {
14    /// Validate and wrap a base58 32-byte address.
15    pub fn new(s: impl Into<String>) -> Result<Self> {
16        let s = s.into();
17        match bs58::decode(&s).into_vec() {
18            Ok(bytes) if bytes.len() == 32 => Ok(Self(s)),
19            _ => Err(ScemaDexError::InvalidAddress(s)),
20        }
21    }
22
23    pub fn as_str(&self) -> &str {
24        &self.0
25    }
26}
27
28impl std::fmt::Display for Address {
29    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
30        f.write_str(&self.0)
31    }
32}
33
34impl std::fmt::Debug for Address {
35    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
36        write!(f, "Address({})", self.0)
37    }
38}
39
40/// A token amount in base units plus its mint decimals.
41#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
42pub struct Amount {
43    pub raw: u64,
44    pub decimals: u8,
45}
46
47impl Amount {
48    pub fn new(raw: u64, decimals: u8) -> Self {
49        Self { raw, decimals }
50    }
51
52    /// Human-readable value (e.g. `1.5` SOL).
53    pub fn ui(&self) -> f64 {
54        self.raw as f64 / 10f64.powi(self.decimals as i32)
55    }
56}
57
58/// A micro-USDC amount (6 decimals) — the unit of account for x402 metering,
59/// inference fees, and Conviction Routing bonds.
60#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
61pub struct Usdc(pub u64);
62
63impl Usdc {
64    pub fn from_usdc(v: f64) -> Self {
65        Self((v * 1_000_000.0) as u64)
66    }
67
68    pub fn as_usdc(&self) -> f64 {
69        self.0 as f64 / 1_000_000.0
70    }
71}
72
73#[cfg(test)]
74mod tests {
75    use super::*;
76
77    #[test]
78    fn rejects_non_base58_address() {
79        assert!(Address::new("not-an-address!!").is_err());
80    }
81
82    #[test]
83    fn accepts_valid_mint() {
84        assert!(Address::new("So11111111111111111111111111111111111111112").is_ok());
85    }
86
87    #[test]
88    fn amount_ui_scales_by_decimals() {
89        assert_eq!(Amount::new(1_500_000_000, 9).ui(), 1.5);
90    }
91
92    #[test]
93    fn usdc_round_trips() {
94        assert_eq!(Usdc::from_usdc(2.5).as_usdc(), 2.5);
95    }
96}