From 6b971ebee699a4278aea153d58334dbe616597bc Mon Sep 17 00:00:00 2001 From: woxjro Date: Wed, 10 Jan 2024 01:40:39 +0900 Subject: [PATCH] [feat](tx): Displayed Decoded Input Data --- Cargo.toml | 1 + src/network.rs | 54 ++++++++++++++++++++++++++++++-- src/ui/home/transaction.rs | 63 +++++++++++++++++++++++++------------- 3 files changed, 93 insertions(+), 25 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 12ef5a4..85b6f7a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,6 +28,7 @@ simplelog = "0.12.1" toml = "0.8.4" serde = "1.0.189" url = "2.4.1" +tempfile = "3.9.0" [dependencies.crossterm] version = "0.26.1" diff --git a/src/network.rs b/src/network.rs index 6369522..3476673 100644 --- a/src/network.rs +++ b/src/network.rs @@ -12,7 +12,13 @@ use ethers::{ providers::{Http, Middleware, Provider}, }; use futures::future::{join_all, try_join, try_join3}; -use std::{error::Error, sync::Arc}; +use std::{ + fs::File, + io::Write, + process::Command, + {error::Error, sync::Arc}, +}; +use tempfile::tempdir; use tokio::sync::Mutex; const RATE_LIMIT: usize = 60; @@ -168,7 +174,14 @@ impl<'a> Network<'a> { app.is_loading = false; } IoEvent::GetTransactionWithReceipt { transaction_hash } => { - let res = Self::get_transaction_with_receipt(self.endpoint, transaction_hash).await; + let res = Self::get_transaction_with_receipt( + self.endpoint, + self.etherscan + .as_ref() + .and_then(|etherscan| etherscan.api_key.to_owned()), + transaction_hash, + ) + .await; let mut app = self.app.lock().await; if let Ok(some) = res { app.set_route(Route::new(RouteId::Transaction(some), ActiveBlock::Main)); @@ -363,6 +376,7 @@ impl<'a> Network<'a> { async fn get_transaction_with_receipt( endpoint: &'a str, + etherscan_api_key: Option, transaction_hash: TxHash, ) -> Result, Box> { let provider = Provider::::try_from(endpoint)?; @@ -370,10 +384,44 @@ impl<'a> Network<'a> { let transaction_receipt = provider.get_transaction_receipt(transaction_hash).await?; if let Some(transaction) = transaction { if let Some(transaction_receipt) = transaction_receipt { + let decoded_input_data = if let Some(api_key) = etherscan_api_key { + let client = Client::builder() + .with_api_key(api_key) + .chain(Chain::Mainnet)? + .build()?; + + if let Some(to) = transaction.to { + let abi = client.contract_abi(to).await?; + + let s = serde_json::to_string(&abi)?; + + let dir = tempdir()?; + let file_path = dir.path().join("lazy-etherscan.tmp.abi.json"); + let mut file = File::create(file_path.to_owned())?; + writeln!(file, "{}", s)?; + + let output = Command::new("ethereum-input-data-decoder") + .args([ + "--abi", + file_path.to_str().unwrap(), + &transaction.input.to_string(), + ]) + .output()?; + + drop(file); + dir.close()?; + Some(String::from_utf8(output.stdout)?) + } else { + None + } + } else { + None + }; + Ok(Some(TransactionWithReceipt { transaction, transaction_receipt, - decoded_input_data: None, + decoded_input_data, })) } else { Ok(None) diff --git a/src/ui/home/transaction.rs b/src/ui/home/transaction.rs index 8abf357..b37ffaf 100644 --- a/src/ui/home/transaction.rs +++ b/src/ui/home/transaction.rs @@ -23,7 +23,7 @@ pub fn render( let TransactionWithReceipt { transaction, transaction_receipt, - decoded_input_data: _, + decoded_input_data, } = transaction_with_receipt; let detail_block = Block::default() @@ -286,7 +286,16 @@ pub fn render( ])); } - let raw_decoded_input_data: Vec = vec![Line::from("")]; + let mut raw_decoded_input_data = vec![]; + + if let Some(decoded_input_data) = decoded_input_data { + for (idx, line) in decoded_input_data.split("\n").enumerate() { + raw_decoded_input_data.push(Line::from(vec![ + Span::raw(format!("{:>3} ", idx + 1)).fg(Color::Gray), + Span::raw(line.to_string()).fg(Color::White), + ])); + } + } app.input_data_scroll_state = app .input_data_scroll_state @@ -435,29 +444,39 @@ pub fn render( let block = Block::default().padding(Padding::new(1, 1, 0, 1)); f.render_widget( - Paragraph::new(raw_input_data.to_owned()) - .alignment(Alignment::Left) - .block( - Block::default() - .borders(Borders::RIGHT | Borders::LEFT | Borders::BOTTOM) - .border_style( - if let ActiveBlock::Main = - app.get_current_route().get_active_block() + Paragraph::new( + match app + .input_data_detail_list_state + .selected() + .map_or(SelectableInputDataDetailItem::InputData, |i| { + SelectableInputDataDetailItem::from(i) + }) { + SelectableInputDataDetailItem::InputData => raw_input_data.to_owned(), + SelectableInputDataDetailItem::DecodedInputData => { + raw_decoded_input_data.to_owned() + } + }, + ) + .alignment(Alignment::Left) + .block( + Block::default() + .borders(Borders::RIGHT | Borders::LEFT | Borders::BOTTOM) + .border_style( + if let ActiveBlock::Main = app.get_current_route().get_active_block() { + if let RouteId::InputDataOfTransaction(_) = + app.get_current_route().get_id() { - if let RouteId::InputDataOfTransaction(_) = - app.get_current_route().get_id() - { - Style::default().fg(Color::Green) - } else { - Style::default().fg(Color::White) - } + Style::default().fg(Color::Green) } else { Style::default().fg(Color::White) - }, - ), - ) - .scroll((app.input_data_scroll, 0)) - .wrap(Wrap { trim: false }), + } + } else { + Style::default().fg(Color::White) + }, + ), + ) + .scroll((app.input_data_scroll, 0)) + .wrap(Wrap { trim: false }), block.inner(chunks[1]), );