Skip to main content

aleph_types/
chain.rs

1use serde::de::Deserializer;
2use serde::{Deserialize, Serialize};
3
4#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
5pub enum Chain {
6    #[serde(rename = "ARB")]
7    Arbitrum,
8    #[serde(rename = "AURORA")]
9    Aurora,
10    #[serde(rename = "AVAX")]
11    Avax,
12    #[serde(rename = "BASE")]
13    Base,
14    #[serde(rename = "BLAST")]
15    Blast,
16    #[serde(rename = "BOB")]
17    Bob,
18    #[serde(rename = "BSC")]
19    Bsc,
20    #[serde(rename = "CSDK")]
21    Csdk,
22    #[serde(rename = "CYBER")]
23    Cyber,
24    #[serde(rename = "DOT")]
25    Polkadot,
26    #[serde(rename = "ES")]
27    Eclipse,
28    #[serde(rename = "ETH")]
29    Ethereum,
30    #[serde(rename = "ETHERLINK")]
31    Etherlink,
32    #[serde(rename = "FRAX")]
33    Fraxtal,
34    #[serde(rename = "HYPE")]
35    Hype,
36    #[serde(rename = "INK")]
37    Ink,
38    #[serde(rename = "LENS")]
39    Lens,
40    #[serde(rename = "LINEA")]
41    Linea,
42    #[serde(rename = "LISK")]
43    Lisk,
44    #[serde(rename = "METIS")]
45    Metis,
46    #[serde(rename = "MODE")]
47    Mode,
48    #[serde(rename = "NEO")]
49    Neo,
50    #[serde(rename = "NULS")]
51    Nuls,
52    #[serde(rename = "NULS2")]
53    Nuls2,
54    #[serde(rename = "OP")]
55    Optimism,
56    #[serde(rename = "POL")]
57    Pol,
58    #[serde(rename = "SOL")]
59    Sol,
60    #[serde(rename = "STT")]
61    Somnia,
62    #[serde(rename = "SONIC")]
63    Sonic,
64    #[serde(rename = "TEZOS")]
65    Tezos,
66    #[serde(rename = "UNICHAIN")]
67    Unichain,
68    #[serde(rename = "WLD")]
69    Worldchain,
70    #[serde(rename = "ZORA")]
71    Zora,
72}
73
74impl std::fmt::Display for Chain {
75    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
76        let s = match self {
77            Chain::Arbitrum => "ARB",
78            Chain::Aurora => "AURORA",
79            Chain::Avax => "AVAX",
80            Chain::Base => "BASE",
81            Chain::Blast => "BLAST",
82            Chain::Bob => "BOB",
83            Chain::Bsc => "BSC",
84            Chain::Csdk => "CSDK",
85            Chain::Cyber => "CYBER",
86            Chain::Polkadot => "DOT",
87            Chain::Eclipse => "ES",
88            Chain::Ethereum => "ETH",
89            Chain::Etherlink => "ETHERLINK",
90            Chain::Fraxtal => "FRAX",
91            Chain::Hype => "HYPE",
92            Chain::Ink => "INK",
93            Chain::Lens => "LENS",
94            Chain::Linea => "LINEA",
95            Chain::Lisk => "LISK",
96            Chain::Metis => "METIS",
97            Chain::Mode => "MODE",
98            Chain::Neo => "NEO",
99            Chain::Nuls => "NULS",
100            Chain::Nuls2 => "NULS2",
101            Chain::Optimism => "OP",
102            Chain::Pol => "POL",
103            Chain::Sol => "SOL",
104            Chain::Somnia => "STT",
105            Chain::Sonic => "SONIC",
106            Chain::Tezos => "TEZOS",
107            Chain::Unichain => "UNICHAIN",
108            Chain::Worldchain => "WLD",
109            Chain::Zora => "ZORA",
110        };
111        f.write_str(s)
112    }
113}
114
115impl Chain {
116    /// Returns true if this chain uses EVM-compatible signature verification
117    /// (secp256k1 + EIP-191 personal sign).
118    ///
119    /// Uses an allow-list so that new chains added to the enum default to
120    /// unsupported rather than silently attempting EVM verification.
121    pub fn is_evm(&self) -> bool {
122        matches!(
123            self,
124            Chain::Arbitrum
125                | Chain::Aurora
126                | Chain::Avax
127                | Chain::Base
128                | Chain::Blast
129                | Chain::Bob
130                | Chain::Bsc
131                | Chain::Cyber
132                | Chain::Ethereum
133                | Chain::Etherlink
134                | Chain::Fraxtal
135                | Chain::Hype
136                | Chain::Ink
137                | Chain::Lens
138                | Chain::Linea
139                | Chain::Lisk
140                | Chain::Metis
141                | Chain::Mode
142                | Chain::Optimism
143                | Chain::Pol
144                | Chain::Somnia
145                | Chain::Sonic
146                | Chain::Unichain
147                | Chain::Worldchain
148                | Chain::Zora
149        )
150    }
151
152    /// Returns true if this chain uses SVM-compatible signature verification
153    /// (Ed25519).
154    ///
155    /// Uses an allow-list so that new chains added to the enum default to
156    /// unsupported rather than silently attempting Ed25519 verification.
157    pub fn is_svm(&self) -> bool {
158        matches!(self, Chain::Eclipse | Chain::Sol)
159    }
160}
161
162#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
163pub struct Address(String);
164
165impl std::fmt::Display for Address {
166    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
167        write!(f, "{}", self.0)
168    }
169}
170
171impl Address {
172    pub fn as_str(&self) -> &str {
173        &self.0
174    }
175}
176
177impl From<String> for Address {
178    fn from(value: String) -> Self {
179        Self(value)
180    }
181}
182
183/// Macro for creating Address instances from string literals.
184///
185/// # Example
186///
187/// ```
188/// use aleph_types::address;
189/// let address = address!("0x238224C744F4b90b4494516e074D2676ECfC6803");
190/// ```
191#[macro_export]
192macro_rules! address {
193    ($address:expr) => {{ $crate::chain::Address::from($address.to_string()) }};
194}
195
196/// Cryptographic signature of a message.
197///
198/// Handles two formats:
199/// - **Plain string** (EVM chains): `"0x636728db..."` — a hex-encoded ECDSA signature.
200/// - **Structured object** (Solana): `{"signature": "5HH5Z...", "publicKey": "5SwCe..."}`
201///   — a base58-encoded Ed25519 signature plus the signer's public key.
202#[derive(Clone, Debug, PartialEq, Eq, Hash)]
203pub struct Signature {
204    /// The signature value (hex for EVM, base58 for Solana).
205    value: String,
206    /// The signer's public key, present for chains that include it
207    /// alongside the signature (e.g., Solana).
208    public_key: Option<String>,
209}
210
211impl Signature {
212    /// Returns the signature value as a string.
213    pub fn as_str(&self) -> &str {
214        &self.value
215    }
216
217    /// Returns the embedded public key, if present (e.g., Solana signatures).
218    pub fn public_key(&self) -> Option<&str> {
219        self.public_key.as_deref()
220    }
221}
222
223impl From<String> for Signature {
224    fn from(value: String) -> Self {
225        Self {
226            value,
227            public_key: None,
228        }
229    }
230}
231
232impl Serialize for Signature {
233    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
234    where
235        S: serde::Serializer,
236    {
237        match &self.public_key {
238            None => serializer.serialize_str(&self.value),
239            Some(pk) => {
240                use serde::ser::SerializeStruct;
241                let mut state = serializer.serialize_struct("Signature", 2)?;
242                state.serialize_field("signature", &self.value)?;
243                state.serialize_field("publicKey", pk)?;
244                state.end()
245            }
246        }
247    }
248}
249
250impl<'de> Deserialize<'de> for Signature {
251    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
252    where
253        D: Deserializer<'de>,
254    {
255        #[derive(Deserialize)]
256        struct StructuredSig {
257            signature: String,
258            #[serde(rename = "publicKey")]
259            public_key: String,
260        }
261
262        #[derive(Deserialize)]
263        #[serde(untagged)]
264        enum SigFormat {
265            Plain(String),
266            Structured(StructuredSig),
267        }
268
269        match SigFormat::deserialize(deserializer)? {
270            SigFormat::Plain(s) => Ok(Signature {
271                value: s,
272                public_key: None,
273            }),
274            SigFormat::Structured(s) => Ok(Signature {
275                value: s.signature,
276                public_key: Some(s.public_key),
277            }),
278        }
279    }
280}
281
282/// Macro for creating Signature instances from string literals.
283///
284/// # Example
285///
286/// ```
287/// use aleph_types::signature;
288/// let signature = signature!("0x123456789");
289/// ```
290#[macro_export]
291macro_rules! signature {
292    ($signature:expr) => {{ $crate::chain::Signature::from($signature.to_string()) }};
293}
294
295#[cfg(test)]
296mod tests {
297    use super::*;
298
299    /// Ensures Display output matches serde serialization for every Chain variant.
300    /// If these drift, the verification buffer will be signed against a different
301    /// string than what the protocol expects.
302    #[test]
303    fn test_chain_display_matches_serde() {
304        let chains = [
305            Chain::Arbitrum,
306            Chain::Aurora,
307            Chain::Avax,
308            Chain::Base,
309            Chain::Blast,
310            Chain::Bob,
311            Chain::Bsc,
312            Chain::Csdk,
313            Chain::Cyber,
314            Chain::Polkadot,
315            Chain::Eclipse,
316            Chain::Ethereum,
317            Chain::Etherlink,
318            Chain::Fraxtal,
319            Chain::Hype,
320            Chain::Ink,
321            Chain::Lens,
322            Chain::Linea,
323            Chain::Lisk,
324            Chain::Metis,
325            Chain::Mode,
326            Chain::Neo,
327            Chain::Nuls,
328            Chain::Nuls2,
329            Chain::Optimism,
330            Chain::Pol,
331            Chain::Sol,
332            Chain::Somnia,
333            Chain::Sonic,
334            Chain::Tezos,
335            Chain::Unichain,
336            Chain::Worldchain,
337            Chain::Zora,
338        ];
339
340        for chain in &chains {
341            let display = chain.to_string();
342            let serde = serde_json::to_string(chain).unwrap();
343            let serde_unquoted = serde.trim_matches('"');
344            assert_eq!(
345                display, serde_unquoted,
346                "Display and serde disagree for {chain:?}: Display={display}, serde={serde_unquoted}"
347            );
348        }
349    }
350}