mod registry;
pub const HOLDINGS_CHAINS_NOTE: &str = "Portfolio, grant links and batch-sell support Base (8453), Arbitrum One (42161), BNB Smart Chain (56) and Robinhood Chain (4663). Batch-sell sells nothing on chains without prices, including Robinhood Chain (4663).";
pub const CHAIN_ID_HELP: &str = "Chain ID such as 8453; known aliases such as base are also accepted.";
pub const PROTOCOL_CHAINS_NOTE: &str = "Intents are available on Ethereum (1), Base (8453), Arbitrum One (42161), BNB Smart Chain (56), Robinhood Chain (4663), Arc (5042) and Arc Testnet (5042002). Ethereum uses V7 owner intents for swaps, with self-paid submission or broadcast and no sponsored relay; agent delegation is unavailable on Ethereum (1); trade and policy refuse chain 1. Other supported chains use V6.";
pub fn unknown_chain_id(value: &str) -> String {
format!("unknown chain id: {value}. {CHAIN_ID_HELP}")
}
pub fn chain_name_to_id(chain_id_or_alias: &str) -> Option<u64> {
match chain_id_or_alias.to_lowercase().as_str() {
"base" => Some(8453),
"arbitrum" | "arb" => Some(42161),
"bsc" | "bnb" | "binance" => Some(56),
"robinhood" | "robinhood-chain" | "rh" => Some(4663),
"arc" | "arc-mainnet" => Some(5042),
"arc-testnet" | "arct" => Some(5042002),
"ethereum" | "eth" | "mainnet" => Some(1),
"optimism" | "op" => Some(10),
_ => chain_id_or_alias.parse::<u64>().ok(),
}
}
pub fn chain_id_to_name(chain_id: u64) -> &'static str {
match chain_id {
1 => "Ethereum",
10 => "Optimism",
8453 => "Base",
42161 => "Arbitrum One",
56 => "BNB Smart Chain",
4663 => "Robinhood Chain",
5042 => "Arc",
5042002 => "Arc Testnet",
_ => "Unknown",
}
}
pub fn chain_id_to_short(chain_id: u64) -> &'static str {
match chain_id {
1 => "Eth",
10 => "Op",
8453 => "Base",
42161 => "Arb",
56 => "BSC",
4663 => "Robinhood",
5042 => "Arc",
5042002 => "Arc Testnet",
_ => "?",
}
}
pub fn chain_id_to_explorer(chain_id: u64) -> Option<&'static str> {
match chain_id {
1 => Some("https://etherscan.io"),
5042 => Some("https://explorer.arc.io"),
5042002 => Some("https://testnet.arcscan.app"),
_ => None,
}
}
struct TokenInfo {
chain_id: u64,
address: &'static str,
symbol: &'static str,
decimals: u8,
}
pub fn registry_addresses(chain_id: u64) -> Vec<&'static str> {
registry::iter().filter(|token| token.chain_id == chain_id).map(|token| token.address).collect()
}
pub fn resolve_token(input: &str, chain_id: u64) -> Option<(&'static str, &'static str, u8)> {
if input.starts_with("0x") || input.starts_with("0X") {
for t in registry::iter() {
if t.chain_id == chain_id && t.address.eq_ignore_ascii_case(input) {
return Some((t.address, t.symbol, t.decimals));
}
}
return None;
}
let needle = input.to_uppercase();
for t in registry::iter() {
if t.chain_id == chain_id && t.symbol.to_uppercase() == needle {
return Some((t.address, t.symbol, t.decimals));
}
}
None
}
#[allow(dead_code)]
pub fn address_to_symbol(address: &str, chain_id: u64) -> String {
for t in registry::iter() {
if t.chain_id == chain_id && t.address.eq_ignore_ascii_case(address) {
return t.symbol.to_string();
}
}
if address.len() > 10 {
format!("{}..{}", &address[..6], &address[address.len() - 4..])
} else {
address.to_string()
}
}
pub fn format_amount(raw: &str, decimals: u8) -> String {
let raw = raw.trim();
if raw.is_empty() || decimals == 0 {
return add_commas(raw);
}
let d = decimals as usize;
let len = raw.len();
let (integer, fraction) = if len <= d {
let zeros = "0".repeat(d - len);
("0".to_string(), format!("{zeros}{raw}"))
} else {
(raw[..len - d].to_string(), raw[len - d..].to_string())
};
let trimmed = fraction.trim_end_matches('0');
let frac = if trimmed.len() < 2 {
&fraction[..2.min(fraction.len())]
} else {
trimmed
};
format!("{}.{frac}", add_commas(&integer))
}
fn add_commas(s: &str) -> String {
let bytes = s.as_bytes();
let len = bytes.len();
if len <= 3 {
return s.to_string();
}
let mut result = String::with_capacity(len + len / 3);
let remainder = len % 3;
if remainder > 0 {
result.push_str(&s[..remainder]);
}
for (i, chunk) in s.as_bytes()[remainder..].chunks(3).enumerate() {
if i > 0 || remainder > 0 {
result.push(',');
}
result.push_str(std::str::from_utf8(chunk).unwrap_or(""));
}
result
}
#[cfg(test)]
mod tests;