use tiny_keccak::{Hasher, Keccak};
pub fn hex_to_dec(hex: &str) -> Option<u64> {
let hex = hex.strip_prefix("0x").unwrap_or(hex);
if hex.is_empty() {
return Some(0);
}
u64::from_str_radix(hex, 16).ok()
}
pub fn hex_to_dec_u128(hex: &str) -> Option<u128> {
let hex = hex.strip_prefix("0x").unwrap_or(hex);
if hex.is_empty() {
return Some(0);
}
u128::from_str_radix(hex, 16).ok()
}
pub fn hex_to_dec_string(hex: &str) -> Option<String> {
let hex = hex.strip_prefix("0x").unwrap_or(hex);
if hex.is_empty() {
return Some("0".to_string());
}
let padded = format!("{:0>64}", hex);
let bytes = hex::decode(&padded).ok()?;
let value = primitive_types::U256::from_big_endian(&bytes);
Some(value.to_string())
}
pub fn normalize_address(address: Option<&str>) -> Option<String> {
address.map(|a| {
let addr = a.strip_prefix("0x").unwrap_or(a);
format!("0x{}", addr.to_lowercase())
})
}
pub fn keccak256(input: &str) -> String {
let mut hasher = Keccak::v256();
let mut output = [0u8; 32];
hasher.update(input.as_bytes());
hasher.finalize(&mut output);
format!("0x{}", hex::encode(output))
}
pub fn keccak256_selector(input: &str) -> String {
let mut hasher = Keccak::v256();
let mut output = [0u8; 32];
hasher.update(input.as_bytes());
hasher.finalize(&mut output);
format!("0x{}", hex::encode(&output[..4]))
}
pub fn timestamp_to_iso(timestamp: u64) -> String {
use chrono::{DateTime, Utc};
let dt = DateTime::<Utc>::from_timestamp(timestamp as i64, 0);
dt.map(|d| d.format("%Y-%m-%d %H:%M:%S").to_string())
.unwrap_or_default()
}
pub fn dec_to_hex(value: u64) -> String {
format!("0x{:x}", value)
}
pub fn parse_bool(value: &serde_json::Value) -> Option<bool> {
match value {
serde_json::Value::Bool(b) => Some(*b),
serde_json::Value::String(s) => match s.to_lowercase().as_str() {
"true" | "1" => Some(true),
"false" | "0" => Some(false),
_ => None,
},
serde_json::Value::Number(n) => n.as_u64().map(|v| v != 0),
_ => None,
}
}
pub const TRANSFER_EVENT_TOPIC: &str =
"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef";
pub fn is_transfer_event(topic: &str) -> bool {
topic.to_lowercase() == TRANSFER_EVENT_TOPIC
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_hex_to_dec() {
assert_eq!(hex_to_dec("0x1"), Some(1));
assert_eq!(hex_to_dec("0xff"), Some(255));
assert_eq!(hex_to_dec("0x"), Some(0));
assert_eq!(hex_to_dec(""), Some(0));
assert_eq!(hex_to_dec("0x10"), Some(16));
assert_eq!(hex_to_dec("10"), Some(16));
}
#[test]
fn test_normalize_address() {
assert_eq!(
normalize_address(Some("0xABC123")),
Some("0xabc123".to_string())
);
assert_eq!(
normalize_address(Some("ABC123")),
Some("0xabc123".to_string())
);
assert_eq!(normalize_address(None), None);
}
#[test]
fn test_keccak256() {
let hash = keccak256("Transfer(address,address,uint256)");
assert_eq!(hash, TRANSFER_EVENT_TOPIC);
}
#[test]
fn test_keccak256_selector() {
let selector = keccak256_selector("transfer(address,uint256)");
assert_eq!(selector, "0xa9059cbb");
}
}