transaction-decoder 0.1.13

A CLI tool for decoding EVM transactions
Documentation
use crate::types::FunctionResponse;
use alloy::{
    dyn_abi::{DynSolType, DynSolValue},
    hex,
};

/// Fetches and decodes the function name and input parameters.
///
/// # Arguments
/// * `input` - The transaction calldata.
///
/// # Returns
/// An optional tuple of function name and decoded parameter values.
pub async fn fetch_function_name(input: &str) -> Option<(String, Vec<DynSolValue>)> {
    if input.len() <= 2 {
        return Some(("This is likely a native asset transfer".to_string(), vec![]));
    }

    let selector = &input[0..10];
    let calldata = &input[10..];
    let url = format!(
        "https://api.openchain.xyz/signature-database/v1/lookup?function={}&filter=true",
        selector
    );

    match reqwest::get(&url).await {
        Ok(resp) => {
            let text = resp.text().await.unwrap_or_default();
            if let Ok(parsed) = serde_json::from_str::<FunctionResponse>(&text) {
                if let Some(functions) = parsed.result.function.get(selector) {
                    let func = functions.get(0).map(|f| f.name.clone());

                    if let Some(sig) = func.clone() {
                        if !calldata.is_empty() {
                            if let Some(types) = extract_params(&sig) {
                                let param_type = DynSolType::Tuple(types);
                                let decoded = param_type
                                    .abi_decode_params(&hex::decode(calldata).unwrap())
                                    .unwrap();

                                if let DynSolValue::Tuple(values) = decoded {
                                    return Some((sig, values));
                                }
                            }
                        }
                        return Some((sig, vec![]));
                    }
                }
            }
        }
        Err(_) => {}
    }

    if let Ok(bytes) = hex::decode(input.strip_prefix("0x").unwrap_or(input)) {
        if let Ok(utf8_str) = std::str::from_utf8(&bytes) {
            return Some((
                "Likely a raw UTF-8 message (not a function)".to_string(),
                vec![DynSolValue::String(utf8_str.to_string())],
            ));
        }
    }

    None
}

/// Parses function or event signature strings into dynamic Solidity types.
///
/// # Arguments
/// * `signature` - A string like `transfer(address,uint256)`.
///
/// # Returns
/// A list of parsed `DynSolType`s if successful.
pub fn extract_params(signature: &str) -> Option<Vec<DynSolType>> {
    let open = signature.find('(')?;
    let close = signature.rfind(')')?;
    let params = &signature[open + 1..close];

    if params.trim().is_empty() {
        return Some(vec![]);
    }

    let mut types = Vec::new();
    let mut current = String::new();
    let mut paren_level = 0;

    for ch in params.chars() {
        match ch {
            '(' => {
                paren_level += 1;
                current.push(ch);
            }
            ')' => {
                paren_level -= 1;
                current.push(ch);
            }
            ',' if paren_level == 0 => {
                if let Ok(parsed) = DynSolType::parse(current.trim()) {
                    types.push(parsed);
                } else {
                    return None;
                }
                current.clear();
            }
            _ => current.push(ch),
        }
    }

    if !current.trim().is_empty() {
        if let Ok(parsed) = DynSolType::parse(current.trim()) {
            types.push(parsed);
        } else {
            return None;
        }
    }

    Some(types)
}