const RESET: &str = "\x1b[0m";
const BOLD: &str = "\x1b[1m";
const DIM: &str = "\x1b[2m";
const GREEN: &str = "\x1b[32m";
const YELLOW: &str = "\x1b[33m";
const BLUE: &str = "\x1b[34m";
const MAGENTA: &str = "\x1b[35m";
const CYAN: &str = "\x1b[36m";
const RED: &str = "\x1b[31m";
const WHITE: &str = "\x1b[97m";
fn timestamp() -> String {
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default();
let secs = now.as_secs() % 86400;
let h = secs / 3600;
let m = (secs % 3600) / 60;
let s = secs % 60;
format!("{h:02}:{m:02}:{s:02}")
}
pub fn banner(
rpc_url: &str,
host: &str,
proxy_port: u16,
ui_port: u16,
separate_ui: bool,
exposed: bool,
) {
let display_host = if exposed { host } else { "localhost" };
eprintln!();
eprintln!(
" {BOLD}{MAGENTA}cipher-gate{RESET} {DIM}Proxy RPC with browser wallet signing{RESET}"
);
eprintln!(" {DIM}─────────────────────────────────────────{RESET}");
eprintln!(" {DIM}Upstream {RESET}{WHITE}{rpc_url}{RESET}");
eprintln!(" {DIM}Proxy {RESET}{CYAN}http://{display_host}:{proxy_port}{RESET}");
if separate_ui {
eprintln!(" {DIM}UI {RESET}{CYAN}http://{display_host}:{ui_port}{RESET}");
}
if exposed {
eprintln!(
" {RED}⚠{RESET} {RED}{BOLD}Exposed on {host} — reachable beyond this machine{RESET}"
);
}
eprintln!();
}
pub fn chain_id(cid: &str) {
let decimal = u64::from_str_radix(cid.trim_start_matches("0x"), 16)
.map(|n| n.to_string())
.unwrap_or_default();
let name = chain_name(&decimal);
if name.is_empty() {
eprintln!(" {DIM}Chain {RESET}{WHITE}{cid}{RESET} {DIM}({decimal}){RESET}");
} else {
eprintln!(" {DIM}Chain {RESET}{WHITE}{name}{RESET} {DIM}({cid}){RESET}");
}
eprintln!();
}
pub fn chain_id_failed() {
eprintln!(" {YELLOW}⚠{RESET} {DIM}Could not fetch chain ID from upstream{RESET}");
eprintln!();
}
pub fn ready() {
eprintln!(" {GREEN}●{RESET} {BOLD}Ready{RESET} {DIM}— waiting for connections{RESET}");
eprintln!();
}
pub fn ws_connected() {
eprintln!(
" {DIM}{}{RESET} {GREEN}◆{RESET} {GREEN}Frontend connected{RESET}",
timestamp()
);
}
pub fn ws_disconnected() {
eprintln!(
" {DIM}{}{RESET} {RED}◇{RESET} {DIM}Frontend disconnected{RESET}",
timestamp()
);
}
pub fn wallet_connected(address: &str) {
let short = shorten_address(address);
eprintln!(
" {DIM}{}{RESET} {GREEN}●{RESET} Wallet connected {CYAN}{short}{RESET}",
timestamp()
);
}
pub fn wallet_disconnected() {
eprintln!(
" {DIM}{}{RESET} {DIM}○{RESET} {DIM}Wallet disconnected{RESET}",
timestamp()
);
}
pub fn intercepted(method: &str) {
eprintln!(
" {DIM}{}{RESET} {YELLOW}⟶{RESET} Intercepted {BOLD}{method}{RESET}",
timestamp()
);
}
pub fn simulation_passed(gas: &str) {
eprintln!(
" {DIM}{}{RESET} {GREEN}✓{RESET} Simulation passed {DIM}gas={gas}{RESET}",
timestamp()
);
}
pub fn simulation_failed(reason: &str) {
eprintln!(
" {DIM}{}{RESET} {RED}✗{RESET} Simulation failed {RED}{reason}{RESET}",
timestamp()
);
}
pub fn decoded(signature: &str) {
eprintln!(
" {DIM}{}{RESET} {BLUE}ƒ{RESET} Decoded {BLUE}{signature}{RESET}",
timestamp()
);
}
pub fn calldata_warning(msg: &str) {
eprintln!(
" {DIM}{}{RESET} {RED}⚠{RESET} {RED}{BOLD}{msg}{RESET}",
timestamp()
);
}
pub fn signed(method: &str) {
eprintln!(
" {DIM}{}{RESET} {GREEN}✓{RESET} Signed {GREEN}{method}{RESET}",
timestamp()
);
}
pub fn rejected(method: &str) {
eprintln!(
" {DIM}{}{RESET} {YELLOW}✗{RESET} Rejected {YELLOW}{method}{RESET}",
timestamp()
);
}
pub fn timeout(method: &str) {
eprintln!(
" {DIM}{}{RESET} {RED}⏱{RESET} Timed out {DIM}{method}{RESET}",
timestamp()
);
}
pub fn waiting_for_frontend() {
eprintln!(
" {DIM}{}{RESET} {YELLOW}…{RESET} {DIM}Waiting for signing UI to connect{RESET}",
timestamp()
);
}
fn shorten_address(addr: &str) -> String {
if addr.len() > 12 {
format!("{}...{}", &addr[..6], &addr[addr.len() - 4..])
} else {
addr.to_string()
}
}
fn chain_name(decimal: &str) -> &'static str {
match decimal {
"1" => "Ethereum",
"5" => "Goerli",
"10" => "Optimism",
"56" => "BSC",
"100" => "Gnosis",
"137" => "Polygon",
"250" => "Fantom",
"8453" => "Base",
"42161" => "Arbitrum",
"42170" => "Arbitrum Nova",
"43114" => "Avalanche",
"11155111" => "Sepolia",
"11155420" => "OP Sepolia",
"84532" => "Base Sepolia",
"421614" => "Arb Sepolia",
_ => "",
}
}