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    /// Creates a new Signature with an associated public key (e.g., for Solana).
223    pub fn with_public_key(value: String, public_key: String) -> Self {
224        Self {
225            value,
226            public_key: Some(public_key),
227        }
228    }
229}
230
231impl From<String> for Signature {
232    fn from(value: String) -> Self {
233        Self {
234            value,
235            public_key: None,
236        }
237    }
238}
239
240impl Serialize for Signature {
241    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
242    where
243        S: serde::Serializer,
244    {
245        match &self.public_key {
246            None => serializer.serialize_str(&self.value),
247            Some(pk) => {
248                use serde::ser::SerializeStruct;
249                let mut state = serializer.serialize_struct("Signature", 2)?;
250                state.serialize_field("signature", &self.value)?;
251                state.serialize_field("publicKey", pk)?;
252                state.end()
253            }
254        }
255    }
256}
257
258impl<'de> Deserialize<'de> for Signature {
259    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
260    where
261        D: Deserializer<'de>,
262    {
263        #[derive(Deserialize)]
264        struct StructuredSig {
265            signature: String,
266            #[serde(rename = "publicKey")]
267            public_key: String,
268        }
269
270        #[derive(Deserialize)]
271        #[serde(untagged)]
272        enum SigFormat {
273            Plain(String),
274            Structured(StructuredSig),
275        }
276
277        match SigFormat::deserialize(deserializer)? {
278            SigFormat::Plain(s) => Ok(Signature {
279                value: s,
280                public_key: None,
281            }),
282            SigFormat::Structured(s) => Ok(Signature {
283                value: s.signature,
284                public_key: Some(s.public_key),
285            }),
286        }
287    }
288}
289
290/// Macro for creating Signature instances from string literals.
291///
292/// # Example
293///
294/// ```
295/// use aleph_types::signature;
296/// let signature = signature!("0x123456789");
297/// ```
298#[macro_export]
299macro_rules! signature {
300    ($signature:expr) => {{ $crate::chain::Signature::from($signature.to_string()) }};
301}
302
303#[cfg(test)]
304mod tests {
305    use super::*;
306
307    /// Ensures Display output matches serde serialization for every Chain variant.
308    /// If these drift, the verification buffer will be signed against a different
309    /// string than what the protocol expects.
310    #[test]
311    fn test_chain_display_matches_serde() {
312        let chains = [
313            Chain::Arbitrum,
314            Chain::Aurora,
315            Chain::Avax,
316            Chain::Base,
317            Chain::Blast,
318            Chain::Bob,
319            Chain::Bsc,
320            Chain::Csdk,
321            Chain::Cyber,
322            Chain::Polkadot,
323            Chain::Eclipse,
324            Chain::Ethereum,
325            Chain::Etherlink,
326            Chain::Fraxtal,
327            Chain::Hype,
328            Chain::Ink,
329            Chain::Lens,
330            Chain::Linea,
331            Chain::Lisk,
332            Chain::Metis,
333            Chain::Mode,
334            Chain::Neo,
335            Chain::Nuls,
336            Chain::Nuls2,
337            Chain::Optimism,
338            Chain::Pol,
339            Chain::Sol,
340            Chain::Somnia,
341            Chain::Sonic,
342            Chain::Tezos,
343            Chain::Unichain,
344            Chain::Worldchain,
345            Chain::Zora,
346        ];
347
348        for chain in &chains {
349            let display = chain.to_string();
350            let serde = serde_json::to_string(chain).unwrap();
351            let serde_unquoted = serde.trim_matches('"');
352            assert_eq!(
353                display, serde_unquoted,
354                "Display and serde disagree for {chain:?}: Display={display}, serde={serde_unquoted}"
355            );
356        }
357    }
358
359    #[test]
360    fn test_signature_with_public_key() {
361        let sig = Signature::with_public_key("5HH5Z".to_string(), "5SwCe".to_string());
362        assert_eq!(sig.as_str(), "5HH5Z");
363        assert_eq!(sig.public_key(), Some("5SwCe"));
364
365        let json = serde_json::to_value(&sig).unwrap();
366        assert_eq!(json["signature"], "5HH5Z");
367        assert_eq!(json["publicKey"], "5SwCe");
368    }
369}