heliosphere_core/
address.rs1use 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#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
11#[repr(transparent)]
12pub struct Address([u8; 21]);
13
14impl Address {
15 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 pub fn as_base58(&self) -> alloc::string::String {
26 bs58::encode(&self.0).with_check().into_string()
27 }
28
29 pub fn as_hex(&self) -> alloc::string::String {
31 hex::encode(self.0)
32 }
33
34 pub fn as_bytes(&self) -> &[u8] {
36 &self.0
37 }
38}
39
40impl 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}