transaction-decoder 0.1.13

A CLI tool for decoding EVM transactions
Documentation
use ratatui::widgets::{ListState, TableState};

#[derive(Clone, Copy, PartialEq, Debug)]
pub enum ActiveTab {
    Overview,
    Input,
    Logs,
}

pub struct App {
    pub overview_details: Vec<(String, String)>,
    pub input_details: Vec<(String, String)>,
    pub logs: Vec<(String, Vec<(String, String)>)>,
    pub active_tab: ActiveTab,
    pub overview_scroll_state: TableState,
    pub input_scroll_state: TableState,
    pub logs_scroll_state: ListState,
    pub should_quit: bool,
    pub gas_used: u128,
    pub gas_limit: u128,
}

impl App {
    pub fn new(
        overview_details: Vec<(String, String)>,
        input_details: Vec<(String, String)>,
        logs: Vec<(String, Vec<(String, String)>)>,
        gas_used: u128,
        gas_limit: u128,
    ) -> App {
        let mut app = App {
            overview_details,
            input_details,
            logs,
            active_tab: ActiveTab::Overview,
            overview_scroll_state: TableState::default(),
            input_scroll_state: TableState::default(),
            logs_scroll_state: ListState::default(),
            should_quit: false,
            gas_used,
            gas_limit,
        };

        // Preselect first items so users see the highlight and understand the view is scrollable.
        if !app.overview_details.is_empty() {
            app.overview_scroll_state.select(Some(0));
        }
        if !app.input_details.is_empty() {
            app.input_scroll_state.select(Some(0));
        }
        if !app.logs.is_empty() {
            app.logs_scroll_state.select(Some(0));
        }

        app
    }

    pub fn on_tick(&mut self) {}

    pub fn quit(&mut self) {
        self.should_quit = true;
    }

    pub fn next_tab(&mut self) {
        self.active_tab = match self.active_tab {
            ActiveTab::Overview => ActiveTab::Input,
            ActiveTab::Input => ActiveTab::Logs,
            ActiveTab::Logs => ActiveTab::Overview,
        };
    }

    pub fn previous_tab(&mut self) {
        self.active_tab = match self.active_tab {
            ActiveTab::Overview => ActiveTab::Logs,
            ActiveTab::Logs => ActiveTab::Input,
            ActiveTab::Input => ActiveTab::Overview,
        };
    }

    pub fn scroll_down(&mut self) {
        match self.active_tab {
            ActiveTab::Overview => {
                let i = match self.overview_scroll_state.selected() {
                    Some(i) => {
                        if i >= self.overview_details.len() - 1 {
                            0
                        } else {
                            i + 1
                        }
                    }
                    None => 0,
                };
                self.overview_scroll_state.select(Some(i));
            }
            ActiveTab::Input => {
                let i = match self.input_scroll_state.selected() {
                    Some(i) => {
                        if i >= self.input_details.len() - 1 {
                            0
                        } else {
                            i + 1
                        }
                    }
                    None => 0,
                };
                self.input_scroll_state.select(Some(i));
            }
            ActiveTab::Logs => {
                let i = match self.logs_scroll_state.selected() {
                    Some(i) => {
                        if i >= self.logs.len() - 1 {
                            0
                        } else {
                            i + 1
                        }
                    }
                    None => 0,
                };
                self.logs_scroll_state.select(Some(i));
            }
        }
    }

    pub fn scroll_up(&mut self) {
        match self.active_tab {
            ActiveTab::Overview => {
                let i = match self.overview_scroll_state.selected() {
                    Some(i) => {
                        if i == 0 {
                            self.overview_details.len() - 1
                        } else {
                            i - 1
                        }
                    }
                    None => 0,
                };
                self.overview_scroll_state.select(Some(i));
            }
            ActiveTab::Input => {
                let i = match self.input_scroll_state.selected() {
                    Some(i) => {
                        if i == 0 {
                            self.input_details.len().saturating_sub(1)
                        } else {
                            i - 1
                        }
                    }
                    None => 0,
                };
                self.input_scroll_state.select(Some(i));
            }
            ActiveTab::Logs => {
                let i = match self.logs_scroll_state.selected() {
                    Some(i) => {
                        if i == 0 {
                            self.logs.len() - 1
                        } else {
                            i - 1
                        }
                    }
                    None => 0,
                };
                self.logs_scroll_state.select(Some(i));
            }
        }
    }
}

/// Split details into overview vs. input/param specific fields.
pub fn split_transaction_details(
    details: Vec<(String, String)>,
) -> (Vec<(String, String)>, Vec<(String, String)>) {
    let mut overview = Vec::new();
    let mut fn_rows = Vec::new();
    let mut param_rows = Vec::new();
    let mut input_rows = Vec::new();

    for (k, v) in details {
        match k.as_str() {
            "Function Name" => fn_rows.push((k, v)),
            _ if k.starts_with("Param ") => param_rows.push((k, v)),
            "Input" => input_rows.push((k, v)),
            _ => overview.push((k, v)),
        }
    }

    let mut input = Vec::new();
    input.extend(fn_rows);
    input.extend(param_rows);
    input.extend(input_rows);

    (overview, input)
}