use std::fmt;
use std::str::FromStr;
use alloy::primitives::{Address, address};
use serde::{Deserialize, Serialize};
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum EvmChainName {
Tempo,
Base,
Ethereum,
Arbitrum,
Robinhood,
Hyperevm,
}
impl EvmChainName {
pub const ALL: [Self; 6] =
[Self::Tempo, Self::Base, Self::Ethereum, Self::Arbitrum, Self::Robinhood, Self::Hyperevm];
pub fn as_str(self) -> &'static str {
match self {
Self::Tempo => "tempo",
Self::Base => "base",
Self::Ethereum => "ethereum",
Self::Arbitrum => "arbitrum",
Self::Robinhood => "robinhood",
Self::Hyperevm => "hyperevm",
}
}
}
impl fmt::Display for EvmChainName {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
impl FromStr for EvmChainName {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::ALL.into_iter().find(|c| c.as_str() == s).ok_or_else(|| format!("unknown chain {s:?}"))
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct NativeCurrency {
pub name: &'static str,
pub symbol: &'static str,
pub decimals: u8,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub struct KnownNetwork {
pub chain: EvmChainName,
pub chain_id: u64,
pub name: &'static str,
pub testnet: bool,
pub rpc_url: Option<&'static str>,
pub explorer_url: Option<&'static str>,
pub native_currency: Option<NativeCurrency>,
pub usdc: Option<Address>,
pub tokens: &'static [(&'static str, Address)],
}
impl KnownNetwork {
pub fn token(&self, symbol: &str) -> Option<Address> {
self.tokens.iter().find(|(s, _)| *s == symbol).map(|(_, a)| *a)
}
}
const ETH: NativeCurrency = NativeCurrency { name: "Ether", symbol: "ETH", decimals: 18 };
const SEPOLIA_ETH: NativeCurrency = NativeCurrency { name: "Sepolia Ether", symbol: "ETH", decimals: 18 };
const HYPE: NativeCurrency = NativeCurrency { name: "HYPE", symbol: "HYPE", decimals: 18 };
const TEMPO_OUSD: Address = address!("20c0000000000000000000006a37DA5C996874BE");
const TEMPO_PATH_USD: Address = address!("20c0000000000000000000000000000000000000");
pub const KNOWN_NETWORKS: &[KnownNetwork] = &[
KnownNetwork {
chain: EvmChainName::Tempo,
chain_id: 4217,
name: "Tempo",
testnet: false,
rpc_url: Some("https://rpc.tempo.xyz"),
explorer_url: Some("https://explore.tempo.xyz"),
native_currency: None,
usdc: None,
tokens: &[
("OUSD", TEMPO_OUSD),
("pathUSD", TEMPO_PATH_USD),
("USDC.e", address!("20c000000000000000000000b9537d11c60e8b50")),
],
},
KnownNetwork {
chain: EvmChainName::Tempo,
chain_id: 42431,
name: "Tempo Testnet (Moderato)",
testnet: true,
rpc_url: Some("https://rpc.moderato.tempo.xyz"),
explorer_url: Some("https://explore.testnet.tempo.xyz"),
native_currency: None,
usdc: None,
tokens: &[
("OUSD", TEMPO_OUSD),
("pathUSD", TEMPO_PATH_USD),
("USDC.e", address!("20c0000000000000000000009e8d7eb59b783726")),
],
},
KnownNetwork {
chain: EvmChainName::Base,
chain_id: 8453,
name: "Base",
testnet: false,
rpc_url: Some("https://mainnet.base.org"),
explorer_url: Some("https://basescan.org"),
native_currency: Some(ETH),
usdc: Some(address!("833589fCD6eDb6E08f4c7C32D4f71b54bdA02913")),
tokens: &[("USDC", address!("833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"))],
},
KnownNetwork {
chain: EvmChainName::Base,
chain_id: 84532,
name: "Base Sepolia",
testnet: true,
rpc_url: Some("https://sepolia.base.org"),
explorer_url: Some("https://sepolia.basescan.org"),
native_currency: Some(ETH),
usdc: Some(address!("036CbD53842c5426634e7929541eC2318f3dCF7e")),
tokens: &[("USDC", address!("036CbD53842c5426634e7929541eC2318f3dCF7e"))],
},
KnownNetwork {
chain: EvmChainName::Ethereum,
chain_id: 1,
name: "Ethereum",
testnet: false,
rpc_url: None,
explorer_url: None,
native_currency: Some(ETH),
usdc: Some(address!("A0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48")),
tokens: &[("USDC", address!("A0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"))],
},
KnownNetwork {
chain: EvmChainName::Ethereum,
chain_id: 11155111,
name: "Sepolia",
testnet: true,
rpc_url: None,
explorer_url: Some("https://sepolia.etherscan.io"),
native_currency: Some(SEPOLIA_ETH),
usdc: Some(address!("1c7D4B196Cb0C7B01d743Fbc6116a902379C7238")),
tokens: &[("USDC", address!("1c7D4B196Cb0C7B01d743Fbc6116a902379C7238"))],
},
KnownNetwork {
chain: EvmChainName::Arbitrum,
chain_id: 42161,
name: "Arbitrum One",
testnet: false,
rpc_url: Some("https://arb1.arbitrum.io/rpc"),
explorer_url: Some("https://arbiscan.io"),
native_currency: Some(ETH),
usdc: Some(address!("af88d065e77c8cC2239327C5EDb3A432268e5831")),
tokens: &[("USDC", address!("af88d065e77c8cC2239327C5EDb3A432268e5831"))],
},
KnownNetwork {
chain: EvmChainName::Arbitrum,
chain_id: 421614,
name: "Arbitrum Sepolia",
testnet: true,
rpc_url: Some("https://sepolia-rollup.arbitrum.io/rpc"),
explorer_url: Some("https://sepolia.arbiscan.io"),
native_currency: Some(ETH),
usdc: Some(address!("75faf114eafb1BDbe2F0316DF893fd58CE46AA4d")),
tokens: &[("USDC", address!("75faf114eafb1BDbe2F0316DF893fd58CE46AA4d"))],
},
KnownNetwork {
chain: EvmChainName::Robinhood,
chain_id: 4663,
name: "Robinhood Chain",
testnet: false,
rpc_url: Some("https://rpc.mainnet.chain.robinhood.com"),
explorer_url: Some("https://robinhoodchain.blockscout.com"),
native_currency: Some(ETH),
usdc: None,
tokens: &[("USDG", address!("5fc5360D0400a0Fd4f2af552ADD042D716F1d168"))],
},
KnownNetwork {
chain: EvmChainName::Robinhood,
chain_id: 46630,
name: "Robinhood Chain Testnet",
testnet: true,
rpc_url: Some("https://rpc.testnet.chain.robinhood.com"),
explorer_url: Some("https://explorer.testnet.chain.robinhood.com"),
native_currency: Some(ETH),
usdc: None,
tokens: &[],
},
KnownNetwork {
chain: EvmChainName::Hyperevm,
chain_id: 999,
name: "HyperEVM",
testnet: false,
rpc_url: Some("https://rpc.hyperliquid.xyz/evm"),
explorer_url: Some("https://hyperevmscan.io"),
native_currency: Some(HYPE),
usdc: Some(address!("b88339CB7199b77E23DB6E890353E22632Ba630f")),
tokens: &[("USDC", address!("b88339CB7199b77E23DB6E890353E22632Ba630f"))],
},
KnownNetwork {
chain: EvmChainName::Hyperevm,
chain_id: 998,
name: "HyperEVM Testnet",
testnet: true,
rpc_url: Some("https://rpc.hyperliquid-testnet.xyz/evm"),
explorer_url: None,
native_currency: Some(HYPE),
usdc: Some(address!("2B3370eE501B4a559b57D449569354196457D8Ab")),
tokens: &[("USDC", address!("2B3370eE501B4a559b57D449569354196457D8Ab"))],
},
];
pub fn known_network(chain_id: u64) -> Option<&'static KnownNetwork> {
KNOWN_NETWORKS.iter().find(|n| n.chain_id == chain_id)
}
pub fn known_networks(chain: EvmChainName) -> impl Iterator<Item = &'static KnownNetwork> {
KNOWN_NETWORKS.iter().filter(move |n| n.chain == chain)
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ParalockDeployment {
pub adaptor_escrow: Address,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub chain: Option<String>,
pub chain_id: u64,
pub deployer: Address,
pub htlc: Address,
pub quote_token: String,
pub start_block: u64,
pub tokens: std::collections::BTreeMap<String, Address>,
}
impl ParalockDeployment {
pub fn quote_token_address(&self) -> Option<Address> {
self.tokens.get(&self.quote_token).copied()
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub struct TestnetDeployment {
pub chain: EvmChainName,
pub chain_id: u64,
pub adaptor_escrow: Address,
pub htlc: Address,
pub deployer: Address,
pub quote_token: &'static str,
pub start_block: u64,
pub tokens: &'static [(&'static str, Address)],
}
impl TestnetDeployment {
pub fn token(&self, symbol: &str) -> Option<Address> {
self.tokens.iter().find(|(s, _)| *s == symbol).map(|(_, a)| *a)
}
pub fn quote_token_address(&self) -> Option<Address> {
self.token(self.quote_token)
}
pub fn to_deployment(&self) -> ParalockDeployment {
ParalockDeployment {
adaptor_escrow: self.adaptor_escrow,
chain: Some(self.chain.as_str().to_owned()),
chain_id: self.chain_id,
deployer: self.deployer,
htlc: self.htlc,
quote_token: self.quote_token.to_owned(),
start_block: self.start_block,
tokens: self.tokens.iter().map(|(s, a)| ((*s).to_owned(), *a)).collect(),
}
}
}
const TESTNET_DEPLOYER: Address = address!("5e3958b05401C21C75D432d501E30da773a720dC");
pub const TESTNET_DEPLOYMENTS: &[TestnetDeployment] = &[
TestnetDeployment {
chain: EvmChainName::Tempo,
chain_id: 42431,
adaptor_escrow: address!("d2f352Fa313d23b3536dAA3d783263C03CED2673"),
htlc: address!("D9B278625B5946B8CD15dad6c0c776095B8D0513"),
deployer: TESTNET_DEPLOYER,
quote_token: "OUSD",
start_block: 39_235_852,
tokens: &[
("AlphaUSD", address!("20C0000000000000000000000000000000000001")),
("BetaUSD", address!("20C0000000000000000000000000000000000002")),
("OUSD", address!("20c0000000000000000000006a37DA5C996874BE")),
("PathUSD", address!("20C0000000000000000000000000000000000000")),
("ThetaUSD", address!("20C0000000000000000000000000000000000003")),
],
},
TestnetDeployment {
chain: EvmChainName::Base,
chain_id: 84532,
adaptor_escrow: address!("d2f352Fa313d23b3536dAA3d783263C03CED2673"),
htlc: address!("D9B278625B5946B8CD15dad6c0c776095B8D0513"),
deployer: TESTNET_DEPLOYER,
quote_token: "USDC",
start_block: 48_009_147,
tokens: &[("USDC", address!("036CbD53842c5426634e7929541eC2318f3dCF7e"))],
},
TestnetDeployment {
chain: EvmChainName::Ethereum,
chain_id: 11155111,
adaptor_escrow: address!("f44bc4Cc4B4b6b94E4142Bb403cCeEa82AFd55B7"),
htlc: address!("9a6f3F47423f590afd1Df06E2729057b10baB1a1"),
deployer: TESTNET_DEPLOYER,
quote_token: "USDC",
start_block: 11_897_116,
tokens: &[("USDC", address!("1c7D4B196Cb0C7B01d743Fbc6116a902379C7238"))],
},
TestnetDeployment {
chain: EvmChainName::Arbitrum,
chain_id: 421614,
adaptor_escrow: address!("d2f352Fa313d23b3536dAA3d783263C03CED2673"),
htlc: address!("D9B278625B5946B8CD15dad6c0c776095B8D0513"),
deployer: TESTNET_DEPLOYER,
quote_token: "USDC",
start_block: 318_328_857,
tokens: &[("USDC", address!("75faf114eafb1BDbe2F0316DF893fd58CE46AA4d"))],
},
TestnetDeployment {
chain: EvmChainName::Robinhood,
chain_id: 46630,
adaptor_escrow: address!("f44bc4Cc4B4b6b94E4142Bb403cCeEa82AFd55B7"),
htlc: address!("9a6f3F47423f590afd1Df06E2729057b10baB1a1"),
deployer: TESTNET_DEPLOYER,
quote_token: "USDC",
start_block: 133_451_919,
tokens: &[("USDC", address!("D9B278625B5946B8CD15dad6c0c776095B8D0513"))],
},
TestnetDeployment {
chain: EvmChainName::Hyperevm,
chain_id: 998,
adaptor_escrow: address!("d2f352Fa313d23b3536dAA3d783263C03CED2673"),
htlc: address!("D9B278625B5946B8CD15dad6c0c776095B8D0513"),
deployer: TESTNET_DEPLOYER,
quote_token: "USDC",
start_block: 66_684_352,
tokens: &[("USDC", address!("2B3370eE501B4a559b57D449569354196457D8Ab"))],
},
];
pub fn testnet_deployment(chain_id: u64) -> Option<&'static TestnetDeployment> {
TESTNET_DEPLOYMENTS.iter().find(|d| d.chain_id == chain_id)
}
#[cfg(test)]
mod tests {
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
use super::*;
#[test]
fn registry_is_consistent() {
let mut ids: Vec<u64> = KNOWN_NETWORKS.iter().map(|n| n.chain_id).collect();
ids.sort_unstable();
ids.dedup();
assert_eq!(ids.len(), KNOWN_NETWORKS.len(), "duplicate chain ids");
for network in KNOWN_NETWORKS {
if let Some(usdc) = network.usdc {
assert_eq!(network.token("USDC"), Some(usdc), "{}", network.name);
}
assert_eq!(network.native_currency.is_none(), network.chain == EvmChainName::Tempo);
}
for deployment in TESTNET_DEPLOYMENTS {
let network = known_network(deployment.chain_id).unwrap();
assert!(network.testnet && network.chain == deployment.chain);
assert!(deployment.quote_token_address().is_some());
}
assert_eq!(known_networks(EvmChainName::Base).count(), 2);
assert_eq!("hyperevm".parse::<EvmChainName>().unwrap(), EvmChainName::Hyperevm);
assert!("solana".parse::<EvmChainName>().is_err());
}
}