use serde::de::Deserializer;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
pub enum Chain {
#[serde(rename = "ARB")]
Arbitrum,
#[serde(rename = "AURORA")]
Aurora,
#[serde(rename = "AVAX")]
Avax,
#[serde(rename = "BASE")]
Base,
#[serde(rename = "BLAST")]
Blast,
#[serde(rename = "BOB")]
Bob,
#[serde(rename = "BSC")]
Bsc,
#[serde(rename = "CSDK")]
Csdk,
#[serde(rename = "CYBER")]
Cyber,
#[serde(rename = "DOT")]
Polkadot,
#[serde(rename = "ES")]
Eclipse,
#[serde(rename = "ETH")]
Ethereum,
#[serde(rename = "ETHERLINK")]
Etherlink,
#[serde(rename = "FRAX")]
Fraxtal,
#[serde(rename = "HYPE")]
Hype,
#[serde(rename = "INK")]
Ink,
#[serde(rename = "LENS")]
Lens,
#[serde(rename = "LINEA")]
Linea,
#[serde(rename = "LISK")]
Lisk,
#[serde(rename = "METIS")]
Metis,
#[serde(rename = "MODE")]
Mode,
#[serde(rename = "NEO")]
Neo,
#[serde(rename = "NULS")]
Nuls,
#[serde(rename = "NULS2")]
Nuls2,
#[serde(rename = "OP")]
Optimism,
#[serde(rename = "POL")]
Pol,
#[serde(rename = "SOL")]
Sol,
#[serde(rename = "STT")]
Somnia,
#[serde(rename = "SONIC")]
Sonic,
#[serde(rename = "TEZOS")]
Tezos,
#[serde(rename = "UNICHAIN")]
Unichain,
#[serde(rename = "WLD")]
Worldchain,
#[serde(rename = "ZORA")]
Zora,
}
impl std::fmt::Display for Chain {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let s = match self {
Chain::Arbitrum => "ARB",
Chain::Aurora => "AURORA",
Chain::Avax => "AVAX",
Chain::Base => "BASE",
Chain::Blast => "BLAST",
Chain::Bob => "BOB",
Chain::Bsc => "BSC",
Chain::Csdk => "CSDK",
Chain::Cyber => "CYBER",
Chain::Polkadot => "DOT",
Chain::Eclipse => "ES",
Chain::Ethereum => "ETH",
Chain::Etherlink => "ETHERLINK",
Chain::Fraxtal => "FRAX",
Chain::Hype => "HYPE",
Chain::Ink => "INK",
Chain::Lens => "LENS",
Chain::Linea => "LINEA",
Chain::Lisk => "LISK",
Chain::Metis => "METIS",
Chain::Mode => "MODE",
Chain::Neo => "NEO",
Chain::Nuls => "NULS",
Chain::Nuls2 => "NULS2",
Chain::Optimism => "OP",
Chain::Pol => "POL",
Chain::Sol => "SOL",
Chain::Somnia => "STT",
Chain::Sonic => "SONIC",
Chain::Tezos => "TEZOS",
Chain::Unichain => "UNICHAIN",
Chain::Worldchain => "WLD",
Chain::Zora => "ZORA",
};
f.write_str(s)
}
}
impl Chain {
pub fn is_evm(&self) -> bool {
matches!(
self,
Chain::Arbitrum
| Chain::Aurora
| Chain::Avax
| Chain::Base
| Chain::Blast
| Chain::Bob
| Chain::Bsc
| Chain::Cyber
| Chain::Ethereum
| Chain::Etherlink
| Chain::Fraxtal
| Chain::Hype
| Chain::Ink
| Chain::Lens
| Chain::Linea
| Chain::Lisk
| Chain::Metis
| Chain::Mode
| Chain::Optimism
| Chain::Pol
| Chain::Somnia
| Chain::Sonic
| Chain::Unichain
| Chain::Worldchain
| Chain::Zora
)
}
pub fn is_svm(&self) -> bool {
matches!(self, Chain::Eclipse | Chain::Sol)
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct Address(String);
impl std::fmt::Display for Address {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
impl Address {
pub fn as_str(&self) -> &str {
&self.0
}
}
impl From<String> for Address {
fn from(value: String) -> Self {
Self(value)
}
}
#[macro_export]
macro_rules! address {
($address:expr) => {{ $crate::chain::Address::from($address.to_string()) }};
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Signature {
value: String,
public_key: Option<String>,
}
impl Signature {
pub fn as_str(&self) -> &str {
&self.value
}
pub fn public_key(&self) -> Option<&str> {
self.public_key.as_deref()
}
pub fn with_public_key(value: String, public_key: String) -> Self {
Self {
value,
public_key: Some(public_key),
}
}
}
impl From<String> for Signature {
fn from(value: String) -> Self {
Self {
value,
public_key: None,
}
}
}
impl Serialize for Signature {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
match &self.public_key {
None => serializer.serialize_str(&self.value),
Some(pk) => {
use serde::ser::SerializeStruct;
let mut state = serializer.serialize_struct("Signature", 2)?;
state.serialize_field("signature", &self.value)?;
state.serialize_field("publicKey", pk)?;
state.end()
}
}
}
}
impl<'de> Deserialize<'de> for Signature {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize)]
struct StructuredSig {
signature: String,
#[serde(rename = "publicKey")]
public_key: String,
}
#[derive(Deserialize)]
#[serde(untagged)]
enum SigFormat {
Plain(String),
Structured(StructuredSig),
}
match SigFormat::deserialize(deserializer)? {
SigFormat::Plain(s) => Ok(Signature {
value: s,
public_key: None,
}),
SigFormat::Structured(s) => Ok(Signature {
value: s.signature,
public_key: Some(s.public_key),
}),
}
}
}
#[macro_export]
macro_rules! signature {
($signature:expr) => {{ $crate::chain::Signature::from($signature.to_string()) }};
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_chain_display_matches_serde() {
let chains = [
Chain::Arbitrum,
Chain::Aurora,
Chain::Avax,
Chain::Base,
Chain::Blast,
Chain::Bob,
Chain::Bsc,
Chain::Csdk,
Chain::Cyber,
Chain::Polkadot,
Chain::Eclipse,
Chain::Ethereum,
Chain::Etherlink,
Chain::Fraxtal,
Chain::Hype,
Chain::Ink,
Chain::Lens,
Chain::Linea,
Chain::Lisk,
Chain::Metis,
Chain::Mode,
Chain::Neo,
Chain::Nuls,
Chain::Nuls2,
Chain::Optimism,
Chain::Pol,
Chain::Sol,
Chain::Somnia,
Chain::Sonic,
Chain::Tezos,
Chain::Unichain,
Chain::Worldchain,
Chain::Zora,
];
for chain in &chains {
let display = chain.to_string();
let serde = serde_json::to_string(chain).unwrap();
let serde_unquoted = serde.trim_matches('"');
assert_eq!(
display, serde_unquoted,
"Display and serde disagree for {chain:?}: Display={display}, serde={serde_unquoted}"
);
}
}
#[test]
fn test_signature_with_public_key() {
let sig = Signature::with_public_key("5HH5Z".to_string(), "5SwCe".to_string());
assert_eq!(sig.as_str(), "5HH5Z");
assert_eq!(sig.public_key(), Some("5SwCe"));
let json = serde_json::to_value(&sig).unwrap();
assert_eq!(json["signature"], "5HH5Z");
assert_eq!(json["publicKey"], "5SwCe");
}
}