heliosphere_core/
address.rs

1//! Universal address representation
2use crate::error::Error;
3use alloc::string::String;
4use core::fmt::{Debug, Display};
5use core::str::FromStr;
6use serde::{Deserialize, Serialize};
7use zerocopy::AsBytes;
8
9/// Account address struct
10#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
11#[repr(transparent)]
12pub struct Address([u8; 21]);
13
14impl Address {
15    /// Construct new address from bytes (expected with 0x41 prefix)
16    pub fn new(bytes: [u8; 21]) -> Result<Self, Error> {
17        if bytes[0] == 0x41 {
18            Ok(Self(bytes))
19        } else {
20            Err(Error::InvalidAddress)
21        }
22    }
23
24    /// Get base58 representation
25    pub fn as_base58(&self) -> alloc::string::String {
26        bs58::encode(&self.0).with_check().into_string()
27    }
28
29    /// Get hex representation
30    pub fn as_hex(&self) -> alloc::string::String {
31        hex::encode(self.0)
32    }
33
34    /// Get raw address bytes (including 0x41 prefix)
35    pub fn as_bytes(&self) -> &[u8] {
36        &self.0
37    }
38}
39
40/// Parse address from base58 or hex string
41impl FromStr for Address {
42    type Err = Error;
43
44    fn from_str(s: &str) -> Result<Self, Self::Err> {
45        let bytes = bs58::decode(s)
46            .with_check(None)
47            .into_vec()
48            .or_else(|_| hex::decode(s))
49            .map_err(|_| Error::InvalidAddress)?;
50        Ok(Self(bytes.try_into().map_err(|_| Error::InvalidAddress)?))
51    }
52}
53
54impl Debug for Address {
55    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
56        write!(f, "{}", self.as_base58())
57    }
58}
59
60impl Display for Address {
61    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
62        write!(f, "{}", self.as_base58())
63    }
64}
65
66impl Serialize for Address {
67    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
68    where
69        S: serde::Serializer,
70    {
71        serializer.serialize_str(&self.as_base58())
72    }
73}
74
75impl<'de> Deserialize<'de> for Address {
76    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
77    where
78        D: serde::Deserializer<'de>,
79    {
80        let s = String::deserialize(deserializer)?;
81        s.parse().map_err(serde::de::Error::custom)
82    }
83}
84
85#[cfg(feature = "ethabi_compat")]
86impl From<ethabi::Address> for Address {
87    fn from(address: ethabi::Address) -> Self {
88        let mut buf = [0x41; 21];
89        buf[1..].copy_from_slice(address.as_bytes());
90        Self(buf)
91    }
92}
93
94#[cfg(feature = "ethabi_compat")]
95impl From<Address> for ethabi::Address {
96    fn from(address: Address) -> Self {
97        Self(address.0[1..].try_into().expect("Always 20 bytes"))
98    }
99}
100
101impl From<alloy_primitives::Address> for Address {
102    fn from(address: alloy_primitives::Address) -> Self {
103        let mut buf = [0x41; 21];
104        buf[1..].copy_from_slice(address.as_bytes());
105        Self(buf)
106    }
107}
108
109impl From<Address> for alloy_primitives::Address {
110    fn from(address: Address) -> Self {
111        Self(address.0[1..].try_into().expect("Always 20 bytes"))
112    }
113}
114
115#[cfg(test)]
116mod test {
117    use super::*;
118
119    #[test]
120    fn test_address_full() {
121        let hex = "418840E6C55B9ADA326D211D818C34A994AECED808";
122        let b58 = "TNPeeaaFB7K9cmo4uQpcU32zGK8G1NYqeL";
123        let bytes = hex::decode(hex).unwrap();
124
125        let a1 = Address::new(bytes.try_into().unwrap()).expect("Address::new");
126        let a2: Address = b58.parse().expect("b58 parse");
127        let a3: Address = hex.parse().expect("hex parse");
128
129        assert!(a1 == a2 && a2 == a3, "address mismatch");
130        assert_eq!(a1.as_base58(), b58, "b58 mismatch");
131        assert_eq!(a1.as_hex().to_ascii_uppercase(), hex, "hex mismatch");
132    }
133}