#[cfg(not(target_arch = "wasm32"))]
use std::sync::OnceLock;
pub struct ChainConfig {
pub name: &'static str,
pub rpc_url: &'static str,
pub chain_id: u64,
pub diamond: &'static str,
pub lh_token: &'static str,
pub fee_token: &'static str,
pub explorer_url: &'static str,
}
#[cfg(any(not(target_arch = "wasm32"), not(feature = "mainnet")))]
pub const MODERATO: ChainConfig = ChainConfig {
name: "Tempo Moderato",
rpc_url: "https://rpc.moderato.tempo.xyz",
chain_id: 42431,
diamond: "0x6c31c01e10C44f4813FffDC7D5e671c1b26Da30c",
lh_token: "0x90B84c7234Aae89BadA7f69160B9901B9bc37B17",
fee_token: "0x20c0000000000000000000000000000000000001", explorer_url: "https://moderato.tempo.xyz",
};
pub const MAINNET: ChainConfig = ChainConfig {
name: "Tempo mainnet",
rpc_url: "https://rpc.tempo.xyz",
chain_id: 4217,
diamond: "0x8ab4f3a57643410cdf4022cdaf1faeef234f3a77",
lh_token: "0x7ba3c9a39596e438b05c56dfc779700b58aea814",
fee_token: "0x20c000000000000000000000b9537d11c60e8b50",
explorer_url: "https://explore.tempo.xyz",
};
pub fn active() -> &'static ChainConfig {
#[cfg(target_arch = "wasm32")]
{
#[cfg(feature = "mainnet")]
{
&MAINNET
}
#[cfg(not(feature = "mainnet"))]
{
&MODERATO
}
}
#[cfg(not(target_arch = "wasm32"))]
{
static ACTIVE_CHAIN: OnceLock<&'static ChainConfig> = OnceLock::new();
ACTIVE_CHAIN.get_or_init(|| {
resolve_chain(std::env::var("LH_CHAIN").ok().as_deref()).unwrap_or_else(|e| panic!("{e}"))
})
}
}
#[cfg(not(target_arch = "wasm32"))]
pub(crate) fn resolve_chain(lh_chain: Option<&str>) -> Result<&'static ChainConfig, String> {
match lh_chain {
None | Some("mainnet") => Ok(&MAINNET),
Some("testnet") | Some("moderato") | Some("dev") => Ok(&MODERATO),
Some(other) => Err(format!(
"unrecognized LH_CHAIN '{other}' — use 'mainnet' (default) or \
'testnet'/'moderato'/'dev' for the dev chain"
)),
}
}
#[cfg(not(target_arch = "wasm32"))]
pub fn validate_chain_env() -> Result<(), String> {
resolve_chain(std::env::var("LH_CHAIN").ok().as_deref()).map(|_| ())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
#[cfg(not(target_arch = "wasm32"))]
fn resolve_chain_defaults_to_mainnet() {
let a = resolve_chain(None).expect("unset LH_CHAIN is valid");
assert_eq!(a.chain_id, 4217);
assert_eq!(a.rpc_url, "https://rpc.tempo.xyz");
assert_eq!(a.diamond, "0x8ab4f3a57643410cdf4022cdaf1faeef234f3a77");
}
#[test]
#[cfg(not(target_arch = "wasm32"))]
fn lh_chain_env_selects_chain() {
assert_eq!(resolve_chain(None).unwrap().chain_id, 4217);
assert_eq!(resolve_chain(Some("mainnet")).unwrap().chain_id, 4217);
assert_eq!(resolve_chain(Some("testnet")).unwrap().chain_id, 42431);
assert_eq!(resolve_chain(Some("moderato")).unwrap().chain_id, 42431);
assert_eq!(resolve_chain(Some("dev")).unwrap().chain_id, 42431);
assert!(resolve_chain(Some("")).is_err());
assert!(resolve_chain(Some("MAINNET")).is_err()); assert!(resolve_chain(Some("prod")).is_err());
assert!(resolve_chain(Some("xyz")).is_err());
}
#[test]
fn mainnet_addresses_pinned() {
assert_eq!(MAINNET.chain_id, 4217);
assert_eq!(MAINNET.rpc_url, "https://rpc.tempo.xyz");
assert_eq!(MAINNET.diamond, "0x8ab4f3a57643410cdf4022cdaf1faeef234f3a77");
assert_eq!(MAINNET.lh_token, "0x7ba3c9a39596e438b05c56dfc779700b58aea814");
assert_eq!(MAINNET.fee_token, "0x20c000000000000000000000b9537d11c60e8b50");
}
#[test]
#[cfg(not(target_arch = "wasm32"))]
fn proxy_chain_ts_defaults_match_moderato() {
let path = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("proxy/api/_chain.ts");
let Ok(src) = std::fs::read_to_string(&path) else {
eprintln!("skip: {} not present (packaged crate?)", path.display());
return;
};
let chain_id = MODERATO.chain_id.to_string();
let want = [
("TEMPO_RPC", MODERATO.rpc_url),
("REGISTRY", MODERATO.diamond),
("CHAIN_ID", chain_id.as_str()),
("LH_TOKEN", MODERATO.lh_token),
];
for (key, expect) in want {
let line = src
.lines()
.find(|l| l.contains(&format!("export const {key} ")))
.unwrap_or_else(|| panic!("_chain.ts missing `export const {key}`"));
assert!(
line.contains(expect),
"_chain.ts default for {key} drifted from Rust MODERATO (expected `{expect}`).\n \
line: {line}\n Update proxy/api/_chain.ts (or MODERATO) so they match."
);
}
}
}