From 29484050020f8cf12bbb613b0d7c1a7a4b2bc743 Mon Sep 17 00:00:00 2001 From: Meet Mangukiya Date: Wed, 11 Dec 2024 21:33:41 +0530 Subject: [PATCH 1/5] feat: init tui --- Cargo.lock | 232 +++++++++++++++++++++++++++++++++++++- Cargo.toml | 1 + crates/tui/Cargo.toml | 3 + crates/tui/src/bin/nxm.rs | 207 ++++++++++++++++++++++++++++++++++ crates/tui/src/keys.rs | 2 + crates/tui/src/lib.rs | 1 + 6 files changed, 443 insertions(+), 3 deletions(-) create mode 100644 crates/tui/src/bin/nxm.rs create mode 100644 crates/tui/src/keys.rs create mode 100644 crates/tui/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 11c9dd8..8826f36 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -26,6 +26,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + [[package]] name = "alloy-chains" version = "0.1.47" @@ -476,6 +482,21 @@ version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b96ec4966b5813e2c0507c1f86115c8c5abaadc3980879c3424042a02fd1ad3" +[[package]] +name = "cassowary" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" + +[[package]] +name = "castaway" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0abae9be0aaf9ea96a3b1b8b1b55c602ca751eba1b1500220cea4ecbafe7c0d5" +dependencies = [ + "rustversion", +] + [[package]] name = "cc" version = "1.2.1" @@ -562,7 +583,7 @@ dependencies = [ "strsim", "terminal_size", "unicase", - "unicode-width", + "unicode-width 0.2.0", ] [[package]] @@ -632,6 +653,20 @@ dependencies = [ "memchr", ] +[[package]] +name = "compact_str" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6050c3a16ddab2e412160b31f2c871015704239bca62f72f6e5f0be631d3f644" +dependencies = [ + "castaway", + "cfg-if", + "itoa", + "rustversion", + "ryu", + "static_assertions", +] + [[package]] name = "config" version = "0.14.1" @@ -728,6 +763,31 @@ dependencies = [ "libc", ] +[[package]] +name = "crossterm" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" +dependencies = [ + "bitflags", + "crossterm_winapi", + "mio", + "parking_lot", + "rustix", + "signal-hook", + "signal-hook-mio", + "winapi", +] + +[[package]] +name = "crossterm_winapi" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" +dependencies = [ + "winapi", +] + [[package]] name = "crunchy" version = "0.2.2" @@ -756,6 +816,41 @@ dependencies = [ "typenum", ] +[[package]] +name = "darling" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.87", +] + +[[package]] +name = "darling_macro" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.87", +] + [[package]] name = "dashmap" version = "5.5.3" @@ -822,6 +917,12 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "diff" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" + [[package]] name = "digest" version = "0.9.0" @@ -1242,6 +1343,8 @@ version = "0.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a9bfc1af68b1726ea47d3d5109de126281def866b33970e10fbab11b5dafab3" dependencies = [ + "allocator-api2", + "equivalent", "foldhash", "serde", ] @@ -1523,6 +1626,12 @@ dependencies = [ "syn 2.0.87", ] +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + [[package]] name = "idna" version = "1.0.3" @@ -1581,6 +1690,12 @@ dependencies = [ "serde", ] +[[package]] +name = "indoc" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5" + [[package]] name = "injected" version = "0.1.0" @@ -1617,6 +1732,20 @@ dependencies = [ "web-sys", ] +[[package]] +name = "instability" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b829f37dead9dc39df40c2d3376c179fdfd2ac771f53f55d3c30dc096a3c0c6e" +dependencies = [ + "darling", + "indoc", + "pretty_assertions", + "proc-macro2", + "quote", + "syn 2.0.87", +] + [[package]] name = "interpolator" version = "0.5.0" @@ -1653,6 +1782,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.11" @@ -2149,6 +2287,15 @@ version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +[[package]] +name = "lru" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" +dependencies = [ + "hashbrown 0.15.1", +] + [[package]] name = "manyhow" version = "0.10.4" @@ -2220,6 +2367,7 @@ checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" dependencies = [ "hermit-abi", "libc", + "log", "wasi", "windows-sys 0.52.0", ] @@ -2488,6 +2636,16 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "pretty_assertions" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d" +dependencies = [ + "diff", + "yansi", +] + [[package]] name = "prettyplease" version = "0.2.25" @@ -2709,6 +2867,27 @@ dependencies = [ "rand_core", ] +[[package]] +name = "ratatui" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eabd94c2f37801c20583fc49dd5cd6b0ba68c716787c2dd6ed18571e1e63117b" +dependencies = [ + "bitflags", + "cassowary", + "compact_str", + "crossterm", + "indoc", + "instability", + "itertools 0.13.0", + "lru", + "paste", + "strum", + "unicode-segmentation", + "unicode-truncate", + "unicode-width 0.2.0", +] + [[package]] name = "redox_syscall" version = "0.5.7" @@ -3333,6 +3512,27 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "signal-hook" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-mio" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd" +dependencies = [ + "libc", + "mio", + "signal-hook", +] + [[package]] name = "signal-hook-registry" version = "1.4.2" @@ -3634,9 +3834,9 @@ dependencies = [ [[package]] name = "tokio" -version = "1.41.1" +version = "1.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22cfb5bee7a6a52939ca9224d6ac897bb669134078daa8735560897f69de4d33" +checksum = "5cec9b21b0450273377fc97bd4c33a8acffc8c996c987a7c5b319a0083707551" dependencies = [ "backtrace", "bytes", @@ -3858,6 +4058,15 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +[[package]] +name = "tui" +version = "0.1.0" +dependencies = [ + "ratatui", + "tokio", + "tracing", +] + [[package]] name = "typed-builder" version = "0.18.2" @@ -3926,6 +4135,23 @@ version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" +[[package]] +name = "unicode-truncate" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3644627a5af5fa321c95b9b235a72fd24cd29c648c2c379431e6628655627bf" +dependencies = [ + "itertools 0.13.0", + "unicode-segmentation", + "unicode-width 0.1.14", +] + +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + [[package]] name = "unicode-width" version = "0.2.0" diff --git a/Cargo.toml b/Cargo.toml index 6b29897..a52207c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,7 @@ members = [ "crates/extension/injected/", "crates/extension/injector/", "crates/extension/browser-ui/", + "crates/tui/", ] default-members = ["bin/rpc"] diff --git a/crates/tui/Cargo.toml b/crates/tui/Cargo.toml index 60b9659..a9e5366 100644 --- a/crates/tui/Cargo.toml +++ b/crates/tui/Cargo.toml @@ -9,3 +9,6 @@ repository.workspace = true exclude.workspace = true [dependencies] +ratatui = "0.29.0" +tokio = { version = "1.42.0", features = ["full"] } +tracing = { workspace = true } diff --git a/crates/tui/src/bin/nxm.rs b/crates/tui/src/bin/nxm.rs new file mode 100644 index 0000000..018b7b5 --- /dev/null +++ b/crates/tui/src/bin/nxm.rs @@ -0,0 +1,207 @@ +use ratatui::{ + buffer::Buffer, + crossterm::event::{self, Event, KeyCode, KeyEvent, KeyEventKind}, + layout::{Constraint, Direction, Layout, Rect}, + prelude::Stylize, + style::Style, + widgets::{Block, Borders, List, ListDirection, ListState, Paragraph, StatefulWidget, Widget}, + DefaultTerminal, +}; + +#[derive(Debug)] +struct App { + should_exit: bool, + rings: RingList, + pane: Pane, +} + +#[derive(PartialEq, Debug)] +enum Pane { + Rings, + Keys, + Actions, +} + +#[derive(Debug)] +struct RingList { + items: Vec, + state: ListState, +} + +#[derive(Debug)] +struct Ring { + name: String, + addresses: Vec, + state: ListState, +} + +impl Default for App { + fn default() -> Self { + Self { + should_exit: false, + rings: RingList { + items: vec![ + Ring { + name: "seed 1".to_string(), + addresses: vec!["address 1".to_string(), "address 2".to_string()], + state: ListState::default(), + }, + Ring { + name: "seed 2".to_string(), + addresses: vec!["address 1".to_string(), "address 2".to_string()], + state: ListState::default(), + }, + Ring { + name: "hote wallet 1".to_string(), + addresses: vec!["hot address 1".to_string()], + state: ListState::default(), + }, + ], + + state: ListState::default(), + }, + pane: Pane::Rings, + } + } +} + +impl App { + fn run(mut self, mut terminal: DefaultTerminal) -> std::io::Result<()> { + while !self.should_exit { + terminal.draw(|frame| frame.render_widget(&mut self, frame.area()))?; + if let Event::Key(key) = event::read()? { + self.handle_key(key); + }; + } + Ok(()) + } + + fn handle_key(&mut self, key: KeyEvent) { + if key.kind != KeyEventKind::Press { + return; + } + match key.code { + KeyCode::Char('q') | KeyCode::Esc => self.should_exit = true, + KeyCode::Char('h') | KeyCode::Left => self.prev_pane(), + KeyCode::Char('l') | KeyCode::Right => self.next_pane(), + KeyCode::Char('j') | KeyCode::Down => self.next_item(), + KeyCode::Char('k') | KeyCode::Up => self.prev_item(), + _ => {} + } + } + + fn prev_pane(&mut self) { + match self.pane { + Pane::Rings => self.pane = Pane::Actions, + Pane::Keys => self.pane = Pane::Rings, + Pane::Actions => self.pane = Pane::Keys, + } + } + + fn next_pane(&mut self) { + match self.pane { + Pane::Rings => self.pane = Pane::Keys, + Pane::Keys => self.pane = Pane::Actions, + Pane::Actions => self.pane = Pane::Rings, + } + } + + fn next_item(&mut self) { + if self.pane == Pane::Actions { + return; + } + + match self.pane { + Pane::Rings => self.rings.state.select_next(), + Pane::Keys => { + if let Some(key) = self.selected_ring() { + key.state.select_next() + } + } + _ => {} + } + } + + fn prev_item(&mut self) { + if self.pane == Pane::Actions { + return; + } + + match self.pane { + Pane::Rings => self.rings.state.select_previous(), + Pane::Keys => { + if let Some(key) = self.selected_ring() { + key.state.select_previous() + } + } + _ => {} + } + } + + fn selected_ring(&mut self) -> Option<&mut Ring> { + if let Some(offset) = self.rings.state.selected() { + self.rings.items.get_mut(offset) + } else { + None + } + } +} + +impl Widget for &mut App { + fn render(self, area: Rect, buf: &mut Buffer) { + let block = Block::default() + .title(format!(" Nexum, Pane: {:?} ", self.pane)) + .borders(Borders::ALL); + let inner = block.inner(area); + block.render(area, buf); + + let layout = Layout::default() + .direction(Direction::Horizontal) + .constraints(vec![ + Constraint::Fill(1), + Constraint::Fill(1), + Constraint::Fill(3), + ]) + .split(inner); + let [ring, key, window] = layout[..] else { + panic!("incorrect layout") + }; + + let ring_list = List::new( + self.rings + .items + .iter() + .map(|item| item.name.as_str()) + .collect::>(), + ) + .highlight_style(Style::new().black().on_white()) + .highlight_symbol(">>") + .direction(ListDirection::TopToBottom) + .block(Block::default().borders(Borders::ALL)); + + Paragraph::new(format!("selected offset: {}", self.rings.state.offset())).render(key, buf); + StatefulWidget::render(ring_list, ring, buf, &mut self.rings.state); + + if let Some(ring) = self.selected_ring() { + let keys_list = List::new( + ring.addresses + .iter() + .map(|item| item.as_str()) + .collect::>(), + ) + .highlight_style(Style::new().black().on_white()) + .highlight_symbol(">>") + .direction(ListDirection::TopToBottom) + .block(Block::default().borders(Borders::ALL)); + StatefulWidget::render(keys_list, key, buf, &mut ring.state); + } + } +} + +fn main() -> std::io::Result<()> { + let mut terminal = ratatui::init(); + terminal.clear()?; + let app_result = App::default().run(terminal); + ratatui::restore(); + app_result +} diff --git a/crates/tui/src/keys.rs b/crates/tui/src/keys.rs new file mode 100644 index 0000000..139597f --- /dev/null +++ b/crates/tui/src/keys.rs @@ -0,0 +1,2 @@ + + diff --git a/crates/tui/src/lib.rs b/crates/tui/src/lib.rs new file mode 100644 index 0000000..16821dc --- /dev/null +++ b/crates/tui/src/lib.rs @@ -0,0 +1 @@ +mod keys; From 14a9c5b4cf6edd76f6b467f456cc6ee3f8a33855 Mon Sep 17 00:00:00 2001 From: Meet Mangukiya Date: Sun, 29 Dec 2024 17:17:57 +0530 Subject: [PATCH 2/5] feat: keystore decryption --- Cargo.lock | 556 ++++++++++++++++++++++++++++++++-- Cargo.toml | 5 +- crates/tui/Cargo.toml | 4 + crates/tui/src/app.rs | 247 +++++++++++++++ crates/tui/src/bin/nxm.rs | 207 +------------ crates/tui/src/lib.rs | 2 + crates/tui/src/widgets/mod.rs | 0 7 files changed, 792 insertions(+), 229 deletions(-) create mode 100644 crates/tui/src/app.rs create mode 100644 crates/tui/src/widgets/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 8826f36..5a100bd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,17 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + [[package]] name = "aho-corasick" version = "1.1.3" @@ -44,11 +55,135 @@ dependencies = [ "strum", ] +[[package]] +name = "alloy-consensus" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e88e1edea70787c33e11197d3f32ae380f3db19e6e061e539a5bcf8184a6b326" +dependencies = [ + "alloy-eips", + "alloy-primitives", + "alloy-rlp", + "alloy-serde", + "alloy-trie", + "auto_impl", + "c-kzg", + "derive_more", + "serde", +] + +[[package]] +name = "alloy-consensus-any" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57b1bb53f40c0273cd1975573cd457b39213e68584e36d1401d25fd0398a1d65" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-primitives", + "alloy-rlp", + "alloy-serde", + "serde", +] + +[[package]] +name = "alloy-eip2930" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0069cf0642457f87a01a014f6dc29d5d893cd4fd8fddf0c3cdfad1bb3ebafc41" +dependencies = [ + "alloy-primitives", + "alloy-rlp", + "serde", +] + +[[package]] +name = "alloy-eip7702" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c986539255fb839d1533c128e190e557e52ff652c9ef62939e233a81dd93f7e" +dependencies = [ + "alloy-primitives", + "alloy-rlp", + "derive_more", + "serde", +] + +[[package]] +name = "alloy-eips" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f9fadfe089e9ccc0650473f2d4ef0a28bc015bbca5631d9f0f09e49b557fdb3" +dependencies = [ + "alloy-eip2930", + "alloy-eip7702", + "alloy-primitives", + "alloy-rlp", + "alloy-serde", + "c-kzg", + "derive_more", + "once_cell", + "serde", + "sha2", +] + +[[package]] +name = "alloy-json-rpc" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e29040b9d5fe2fb70415531882685b64f8efd08dfbd6cc907120650504821105" +dependencies = [ + "alloy-primitives", + "alloy-sol-types", + "serde", + "serde_json", + "thiserror 2.0.9", + "tracing", +] + +[[package]] +name = "alloy-network" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "510cc00b318db0dfccfdd2d032411cfae64fc144aef9679409e014145d3dacc4" +dependencies = [ + "alloy-consensus", + "alloy-consensus-any", + "alloy-eips", + "alloy-json-rpc", + "alloy-network-primitives", + "alloy-primitives", + "alloy-rpc-types-any", + "alloy-rpc-types-eth", + "alloy-serde", + "alloy-signer", + "alloy-sol-types", + "async-trait", + "auto_impl", + "futures-utils-wasm", + "serde", + "serde_json", + "thiserror 2.0.9", +] + +[[package]] +name = "alloy-network-primitives" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9081c099e798b8a2bba2145eb82a9a146f01fc7a35e9ab6e7b43305051f97550" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-primitives", + "alloy-serde", + "serde", +] + [[package]] name = "alloy-primitives" -version = "0.8.12" +version = "0.8.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fce5dbd6a4f118eecc4719eaa9c7ffc31c315e6c5ccde3642db927802312425" +checksum = "6259a506ab13e1d658796c31e6e39d2e2ee89243bcc505ddc613b35732e0a430" dependencies = [ "alloy-rlp", "bytes", @@ -78,10 +213,169 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da0822426598f95e45dd1ea32a738dac057529a709ee645fcc516ffa4cbde08f" dependencies = [ + "alloy-rlp-derive", "arrayvec", "bytes", ] +[[package]] +name = "alloy-rlp-derive" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a833d97bf8a5f0f878daf2c8451fff7de7f9de38baa5a45d936ec718d81255a" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + +[[package]] +name = "alloy-rpc-types-any" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed98e1af55a7d856bfa385f30f63d8d56be2513593655c904a8f4a7ec963aa3e" +dependencies = [ + "alloy-consensus-any", + "alloy-rpc-types-eth", + "alloy-serde", +] + +[[package]] +name = "alloy-rpc-types-eth" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8737d7a6e37ca7bba9c23e9495c6534caec6760eb24abc9d5ffbaaba147818e1" +dependencies = [ + "alloy-consensus", + "alloy-consensus-any", + "alloy-eips", + "alloy-network-primitives", + "alloy-primitives", + "alloy-rlp", + "alloy-serde", + "alloy-sol-types", + "derive_more", + "itertools 0.13.0", + "serde", + "serde_json", +] + +[[package]] +name = "alloy-serde" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5851bf8d5ad33014bd0c45153c603303e730acc8a209450a7ae6b4a12c2789e2" +dependencies = [ + "alloy-primitives", + "serde", + "serde_json", +] + +[[package]] +name = "alloy-signer" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e10ca565da6500cca015ba35ee424d59798f2e1b85bc0dd8f81dafd401f029a" +dependencies = [ + "alloy-primitives", + "async-trait", + "auto_impl", + "elliptic-curve", + "k256", + "thiserror 2.0.9", +] + +[[package]] +name = "alloy-signer-local" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47fababf5a745133490cde927d48e50267f97d3d1209b9fc9f1d1d666964d172" +dependencies = [ + "alloy-consensus", + "alloy-network", + "alloy-primitives", + "alloy-signer", + "async-trait", + "eth-keystore", + "k256", + "rand", + "thiserror 2.0.9", +] + +[[package]] +name = "alloy-sol-macro" +version = "0.8.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9d64f851d95619233f74b310f12bcf16e0cbc27ee3762b6115c14a84809280a" +dependencies = [ + "alloy-sol-macro-expander", + "alloy-sol-macro-input", + "proc-macro-error2", + "proc-macro2", + "quote", + "syn 2.0.87", +] + +[[package]] +name = "alloy-sol-macro-expander" +version = "0.8.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bf7ed1574b699f48bf17caab4e6e54c6d12bc3c006ab33d58b1e227c1c3559f" +dependencies = [ + "alloy-sol-macro-input", + "const-hex", + "heck", + "indexmap", + "proc-macro-error2", + "proc-macro2", + "quote", + "syn 2.0.87", + "syn-solidity", + "tiny-keccak", +] + +[[package]] +name = "alloy-sol-macro-input" +version = "0.8.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c02997ccef5f34f9c099277d4145f183b422938ed5322dc57a089fe9b9ad9ee" +dependencies = [ + "const-hex", + "dunce", + "heck", + "proc-macro2", + "quote", + "syn 2.0.87", + "syn-solidity", +] + +[[package]] +name = "alloy-sol-types" +version = "0.8.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1174cafd6c6d810711b4e00383037bdb458efc4fe3dbafafa16567e0320c54d8" +dependencies = [ + "alloy-primitives", + "alloy-sol-macro", + "const-hex", +] + +[[package]] +name = "alloy-trie" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e428104b2445a4f929030891b3dbf8c94433a8349ba6480946bf6af7975c2f6" +dependencies = [ + "alloy-primitives", + "alloy-rlp", + "arrayvec", + "derive_more", + "nybbles", + "serde", + "smallvec", + "tracing", +] + [[package]] name = "anstream" version = "0.6.18" @@ -266,6 +560,9 @@ name = "arrayvec" version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" +dependencies = [ + "serde", +] [[package]] name = "async-lock" @@ -426,6 +723,18 @@ dependencies = [ "generic-array", ] +[[package]] +name = "blst" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4378725facc195f1a538864863f6de233b500a8862747e7f165078a419d5e874" +dependencies = [ + "cc", + "glob", + "threadpool", + "zeroize", +] + [[package]] name = "browser-ui" version = "0.1.0" @@ -476,6 +785,21 @@ dependencies = [ "serde", ] +[[package]] +name = "c-kzg" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0307f72feab3300336fb803a57134159f6e20139af1357f36c54cb90d8e8928" +dependencies = [ + "blst", + "cc", + "glob", + "hex", + "libc", + "once_cell", + "serde", +] + [[package]] name = "camino" version = "1.1.9" @@ -561,11 +885,21 @@ dependencies = [ "half", ] +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + [[package]] name = "clap" -version = "4.5.21" +version = "4.5.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb3b4b9e5a7c7514dfa52869339ee98b3156b0bfb4e8a77c4ff4babb64b1604f" +checksum = "3135e7ec2ef7b10c6ed8950f0f792ed96ee093fa088608f1c76e569722700c84" dependencies = [ "clap_builder", "clap_derive", @@ -573,9 +907,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.21" +version = "4.5.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b17a95aa67cc7b5ebd32aa5370189aa0d79069ef1c64ce893bd30fb24bff20ec" +checksum = "30582fc632330df2bd26877bde0c1f4470d57c582bbc070376afcd04d8cb4838" dependencies = [ "anstream", "anstyle", @@ -600,9 +934,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afb84c814227b90d6895e01398aee0d8033c00e7466aca416fb6a8e0eb19d8a7" +checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" [[package]] name = "collection_literals" @@ -692,9 +1026,9 @@ dependencies = [ [[package]] name = "const-hex" -version = "1.13.1" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0121754e84117e65f9d90648ee6aa4882a6e63110307ab73967a4c5e7e69e586" +checksum = "4b0485bab839b018a8f1723fc5391819fea5f8f0f32288ef8a735fd096b6160c" dependencies = [ "cfg-if", "cpufeatures", @@ -816,6 +1150,15 @@ dependencies = [ "typenum", ] +[[package]] +name = "ctr" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +dependencies = [ + "cipher", +] + [[package]] name = "darling" version = "0.20.10" @@ -961,6 +1304,12 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "669a445ee724c5c69b1b06fe0b63e70a1c84bc9bb7d9696cd4f4e3ec45050408" +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + [[package]] name = "ecdsa" version = "0.16.9" @@ -1016,6 +1365,28 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "eth-keystore" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fda3bf123be441da5260717e0661c25a2fd9cb2b2c1d20bf2e05580047158ab" +dependencies = [ + "aes", + "ctr", + "digest 0.10.7", + "hex", + "hmac", + "pbkdf2", + "rand", + "scrypt", + "serde", + "serde_json", + "sha2", + "sha3", + "thiserror 1.0.69", + "uuid 0.8.2", +] + [[package]] name = "event-listener" version = "2.5.3" @@ -1197,6 +1568,12 @@ dependencies = [ "slab", ] +[[package]] +name = "futures-utils-wasm" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42012b0f064e01aa58b545fe3727f90f7dd4020f4a3ea735b50344965f5a57e9" + [[package]] name = "generic-array" version = "0.14.7" @@ -1248,7 +1625,7 @@ dependencies = [ "pin-project", "serde", "serde_json", - "thiserror", + "thiserror 1.0.69", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", @@ -1711,7 +2088,7 @@ dependencies = [ "serde", "serde-wasm-bindgen", "tracing", - "uuid", + "uuid 1.11.0", "wasm-bindgen", "wasm-bindgen-futures", "wasm-tracing", @@ -1732,6 +2109,15 @@ dependencies = [ "web-sys", ] +[[package]] +name = "inout" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +dependencies = [ + "generic-array", +] + [[package]] name = "instability" version = "0.3.3" @@ -1807,7 +2193,7 @@ dependencies = [ "combine", "jni-sys", "log", - "thiserror", + "thiserror 1.0.69", "walkdir", ] @@ -1861,7 +2247,7 @@ dependencies = [ "rustls-pki-types", "rustls-platform-verifier", "soketto", - "thiserror", + "thiserror 1.0.69", "tokio", "tokio-rustls", "tokio-util", @@ -1889,7 +2275,7 @@ dependencies = [ "rustc-hash 2.0.0", "serde", "serde_json", - "thiserror", + "thiserror 1.0.69", "tokio", "tokio-stream", "tracing", @@ -1914,7 +2300,7 @@ dependencies = [ "rustls-platform-verifier", "serde", "serde_json", - "thiserror", + "thiserror 1.0.69", "tokio", "tower 0.4.13", "tracing", @@ -1953,7 +2339,7 @@ dependencies = [ "serde", "serde_json", "soketto", - "thiserror", + "thiserror 1.0.69", "tokio", "tokio-stream", "tokio-util", @@ -1970,7 +2356,7 @@ dependencies = [ "http", "serde", "serde_json", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -2064,7 +2450,7 @@ dependencies = [ "config", "regex", "serde", - "thiserror", + "thiserror 1.0.69", "typed-builder", ] @@ -2136,7 +2522,7 @@ dependencies = [ "server_fn_macro", "syn 2.0.87", "tracing", - "uuid", + "uuid 1.11.0", ] [[package]] @@ -2173,7 +2559,7 @@ dependencies = [ "serde-wasm-bindgen", "serde_json", "slotmap", - "thiserror", + "thiserror 1.0.69", "tracing", "wasm-bindgen", "wasm-bindgen-futures", @@ -2199,7 +2585,7 @@ dependencies = [ "serde", "serde_json", "serde_qs 0.13.0", - "thiserror", + "thiserror 1.0.69", "tracing", "wasm-bindgen", "wasm-bindgen-futures", @@ -2218,7 +2604,7 @@ dependencies = [ "leptos_reactive", "serde", "server_fn", - "thiserror", + "thiserror 1.0.69", "tracing", ] @@ -2438,6 +2824,16 @@ dependencies = [ "libm", ] +[[package]] +name = "num_cpus" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +dependencies = [ + "hermit-abi", + "libc", +] + [[package]] name = "num_enum" version = "0.7.3" @@ -2458,6 +2854,17 @@ dependencies = [ "syn 2.0.87", ] +[[package]] +name = "nybbles" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55a62e678a89501192cc5ebf47dcbc656b608ae5e1c61c9251fe35230f119fe3" +dependencies = [ + "const-hex", + "serde", + "smallvec", +] + [[package]] name = "object" version = "0.32.2" @@ -2474,7 +2881,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c51ebcefb2f0b9a5e0bea115532c8ae4215d1b01eff176d0f4ba4192895c2708" dependencies = [ "serde", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -2568,6 +2975,15 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d61c5ce1153ab5b689d0c074c4e7fc613e942dfb7dd9eea5ab202d2ad91fe361" +[[package]] +name = "pbkdf2" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917" +dependencies = [ + "digest 0.10.7", +] + [[package]] name = "percent-encoding" version = "2.3.1" @@ -2581,7 +2997,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "879952a81a83930934cbf1786752d6dedc3b1f29e8f8fb2ad1d0a36f377cf442" dependencies = [ "memchr", - "thiserror", + "thiserror 1.0.69", "ucd-trie", ] @@ -2718,6 +3134,7 @@ dependencies = [ "proc-macro-error-attr2", "proc-macro2", "quote", + "syn 2.0.87", ] [[package]] @@ -3013,7 +3430,7 @@ dependencies = [ "quote", "syn 2.0.87", "syn_derive", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -3206,6 +3623,15 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" +[[package]] +name = "salsa20" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97a22f5af31f73a954c10289c93e8a50cc23d971e80ee446f1f6f7137a088213" +dependencies = [ + "cipher", +] + [[package]] name = "same-file" version = "1.0.6" @@ -3236,6 +3662,18 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "scrypt" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f9e24d2b632954ded8ab2ef9fea0a0c769ea56ea98bddbafbad22caeeadf45d" +dependencies = [ + "hmac", + "pbkdf2", + "salsa20", + "sha2", +] + [[package]] name = "sec1" version = "0.7.3" @@ -3370,7 +3808,7 @@ checksum = "0431a35568651e363364210c91983c1da5eb29404d9f0928b67d4ebcfa7d330c" dependencies = [ "percent-encoding", "serde", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -3381,7 +3819,7 @@ checksum = "cd34f36fe4c5ba9654417139a9b3a20d2e1de6012ee678ad14d240c22c78d8d6" dependencies = [ "percent-encoding", "serde", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -3422,7 +3860,7 @@ dependencies = [ "serde_json", "serde_qs 0.12.0", "server_fn_macro_default", - "thiserror", + "thiserror 1.0.69", "url", "wasm-bindgen", "wasm-bindgen-futures", @@ -3576,6 +4014,9 @@ name = "smallvec" version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" +dependencies = [ + "serde", +] [[package]] name = "socket2" @@ -3725,6 +4166,18 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "syn-solidity" +version = "0.8.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "219389c1ebe89f8333df8bdfb871f6631c552ff399c23cac02480b6088aad8f0" +dependencies = [ + "paste", + "proc-macro2", + "quote", + "syn 2.0.87", +] + [[package]] name = "syn_derive" version = "0.1.8" @@ -3789,7 +4242,16 @@ version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ - "thiserror-impl", + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f072643fd0190df67a8bab670c20ef5d8737177d6ac6b2e9a236cb096206b2cc" +dependencies = [ + "thiserror-impl 2.0.9", ] [[package]] @@ -3803,6 +4265,17 @@ dependencies = [ "syn 2.0.87", ] +[[package]] +name = "thiserror-impl" +version = "2.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b50fa271071aae2e6ee85f842e2e28ba8cd2c5fb67f11fcb1fd70b276f9e7d4" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + [[package]] name = "thread_local" version = "1.1.8" @@ -3813,6 +4286,15 @@ dependencies = [ "once_cell", ] +[[package]] +name = "threadpool" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa" +dependencies = [ + "num_cpus", +] + [[package]] name = "tiny-keccak" version = "2.0.2" @@ -4062,6 +4544,10 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" name = "tui" version = "0.1.0" dependencies = [ + "alloy-primitives", + "alloy-signer", + "alloy-signer-local", + "clap", "ratatui", "tokio", "tracing", @@ -4205,6 +4691,16 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" +[[package]] +name = "uuid" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" +dependencies = [ + "getrandom", + "serde", +] + [[package]] name = "uuid" version = "1.11.0" diff --git a/Cargo.toml b/Cargo.toml index a52207c..bc78176 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,7 +18,7 @@ members = [ "crates/extension/browser-ui/", "crates/tui/", ] -default-members = ["bin/rpc"] +default-members = ["bin/rpc", "crates/tui"] # Explicitly set the resolver to version 2, which is the default for packages with edition >= 2021 # https://doc.rust-lang.org/edition-guide/rust-2021/default-cargo-resolver.html @@ -49,6 +49,9 @@ gloo-timers = { version = "0.3.0", features = ["futures"] } console_error_panic_hook = "0.1.7" alloy-chains = { version = "0.1.47", features = ["serde"] } +alloy-signer = { version = "0.8.3" } +alloy-signer-local = { version = "0.8.3", features = ["keystore"] } +alloy-primitives = { version = "0.8.3" } chrome-sys = { path = "crates/extension/chrome-sys" } worker = { path = "crates/extension/worker" } diff --git a/crates/tui/Cargo.toml b/crates/tui/Cargo.toml index a9e5366..1a88a53 100644 --- a/crates/tui/Cargo.toml +++ b/crates/tui/Cargo.toml @@ -9,6 +9,10 @@ repository.workspace = true exclude.workspace = true [dependencies] +clap = { version = "4.5.23", features = ["derive"] } ratatui = "0.29.0" tokio = { version = "1.42.0", features = ["full"] } tracing = { workspace = true } +alloy-signer = { workspace = true } +alloy-signer-local = { workspace = true } +alloy-primitives = { workspace = true } diff --git a/crates/tui/src/app.rs b/crates/tui/src/app.rs new file mode 100644 index 0000000..97fbdba --- /dev/null +++ b/crates/tui/src/app.rs @@ -0,0 +1,247 @@ +use alloy_primitives::Address; +use alloy_signer::k256::ecdsa::SigningKey; +use alloy_signer_local::{LocalSigner, PrivateKeySigner}; +use ratatui::{ + buffer::Buffer, + crossterm::event::{self, Event, KeyCode, KeyEvent, KeyEventKind}, + layout::{Alignment, Constraint, Direction, Layout, Rect}, + style::{Style, Stylize}, + text::{Line, Span}, + widgets::{Block, Borders, List, ListDirection, ListState, Paragraph, StatefulWidget, Widget}, + DefaultTerminal, +}; + +#[derive(Debug)] +pub struct App { + should_exit: bool, + rings: RingList, + pane: Pane, + keystore_file: String, + view: View, + prompt_input: String, + signer: Option>, +} + +#[derive(Debug)] +enum View { + KeystorePassword, + Main, +} + +#[derive(PartialEq, Debug)] +enum Pane { + Rings, + Keys, + Actions, +} + +#[derive(Debug)] +struct RingList { + items: Vec, + state: ListState, +} + +#[derive(Debug)] +struct Ring { + name: String, + addresses: Vec, + state: ListState, +} + +impl Default for App { + fn default() -> Self { + Self { + should_exit: false, + rings: RingList { + items: vec![ + Ring { + name: "seed 1".to_string(), + addresses: vec!["address 1".to_string(), "address 2".to_string()], + state: ListState::default(), + }, + Ring { + name: "seed 2".to_string(), + addresses: vec!["address 1".to_string(), "address 2".to_string()], + state: ListState::default(), + }, + Ring { + name: "hote wallet 1".to_string(), + addresses: vec!["hot address 1".to_string()], + state: ListState::default(), + }, + ], + + state: ListState::default(), + }, + pane: Pane::Rings, + keystore_file: "".to_string(), + view: View::KeystorePassword, + prompt_input: "".to_string(), + signer: None, + } + } +} + +impl App { + pub fn run( + mut self, + mut terminal: DefaultTerminal, + keystore_file: String, + ) -> std::io::Result<()> { + self.keystore_file = keystore_file; + + while !self.should_exit { + terminal.draw(|frame| frame.render_widget(&mut self, frame.area()))?; + if let Event::Key(key) = event::read()? { + self.handle_key(key); + }; + } + Ok(()) + } + + fn handle_key(&mut self, key: KeyEvent) { + if key.kind != KeyEventKind::Press { + return; + } + match self.view { + View::KeystorePassword => match key.code { + // pasting wont work + KeyCode::Char(c) => { + self.prompt_input.push(c); + } + KeyCode::Enter => { + if let Ok(signer) = + PrivateKeySigner::decrypt_keystore(&self.keystore_file, &self.prompt_input) + { + self.view = View::Main; + self.signer = Some(signer); + } else { + self.prompt_input.truncate(0); + } + } + KeyCode::Esc => { + self.should_exit = true; + } + KeyCode::Backspace => { + self.prompt_input.pop(); + } + _ => {} + }, + View::Main => match key.code { + KeyCode::Char('q') | KeyCode::Esc => self.should_exit = true, + KeyCode::Char('h') | KeyCode::Left => self.prev_pane(), + KeyCode::Char('l') | KeyCode::Right => self.next_pane(), + KeyCode::Char('j') | KeyCode::Down => self.next_item(), + KeyCode::Char('k') | KeyCode::Up => self.prev_item(), + _ => {} + }, + } + } + + fn prev_pane(&mut self) { + match self.pane { + Pane::Rings => self.pane = Pane::Actions, + Pane::Keys => self.pane = Pane::Rings, + Pane::Actions => self.pane = Pane::Keys, + } + } + + fn next_pane(&mut self) { + match self.pane { + Pane::Rings => self.pane = Pane::Keys, + Pane::Keys => self.pane = Pane::Actions, + Pane::Actions => self.pane = Pane::Rings, + } + } + + fn next_item(&mut self) { + if self.pane == Pane::Actions { + return; + } + + match self.pane { + Pane::Rings => self.rings.state.select_next(), + Pane::Keys => { + if let Some(key) = self.selected_ring() { + key.state.select_next() + } + } + _ => {} + } + } + + fn prev_item(&mut self) { + if self.pane == Pane::Actions { + return; + } + + match self.pane { + Pane::Rings => self.rings.state.select_previous(), + Pane::Keys => { + if let Some(key) = self.selected_ring() { + key.state.select_previous() + } + } + _ => {} + } + } + + fn selected_ring(&mut self) -> Option<&mut Ring> { + if let Some(offset) = self.rings.state.selected() { + self.rings.items.get_mut(offset) + } else { + None + } + } + + fn address(&self) -> Option
{ + self.signer.as_ref().map(|signer| signer.address()) + } +} + +impl Widget for &mut App { + fn render(self, area: Rect, buf: &mut Buffer) { + match self.view { + View::KeystorePassword => { + let block = Block::default() + .title("Keystore Password: ") + .title_alignment(Alignment::Center) + .borders(Borders::ALL); + + let input_length = 32; + let block_area = { + let width = input_length + 6; + let height = 5; + let x = area.width / 2 - width / 2; + let y = area.height / 2 - height / 2; + + Rect::new(x, y, width, height) + }; + + let mut inner = block.inner(block_area); + inner.y += 1; + block.render(block_area, buf); + let password_text = Line::from(vec![Span::styled( + format!( + "{}{}", + "*".repeat(self.prompt_input.len()), + " ".repeat((input_length as usize) - self.prompt_input.len()) + ), + Style::default().underlined().black().on_gray(), + )]) + .alignment(Alignment::Center); + password_text.render(inner, buf); + } + View::Main => { + let block = Block::default() + .title(format!( + " Nexum, Address: {} ", + self.address().unwrap_or_default() + )) + .borders(Borders::ALL); + let inner = block.inner(area); + block.render(area, buf); + } + } + } +} diff --git a/crates/tui/src/bin/nxm.rs b/crates/tui/src/bin/nxm.rs index 018b7b5..c195aba 100644 --- a/crates/tui/src/bin/nxm.rs +++ b/crates/tui/src/bin/nxm.rs @@ -1,207 +1,18 @@ -use ratatui::{ - buffer::Buffer, - crossterm::event::{self, Event, KeyCode, KeyEvent, KeyEventKind}, - layout::{Constraint, Direction, Layout, Rect}, - prelude::Stylize, - style::Style, - widgets::{Block, Borders, List, ListDirection, ListState, Paragraph, StatefulWidget, Widget}, - DefaultTerminal, -}; +use alloy_signer_local::PrivateKeySigner; +use clap::Parser; +use tui::app::App; -#[derive(Debug)] -struct App { - should_exit: bool, - rings: RingList, - pane: Pane, -} - -#[derive(PartialEq, Debug)] -enum Pane { - Rings, - Keys, - Actions, -} - -#[derive(Debug)] -struct RingList { - items: Vec, - state: ListState, -} - -#[derive(Debug)] -struct Ring { - name: String, - addresses: Vec, - state: ListState, -} - -impl Default for App { - fn default() -> Self { - Self { - should_exit: false, - rings: RingList { - items: vec![ - Ring { - name: "seed 1".to_string(), - addresses: vec!["address 1".to_string(), "address 2".to_string()], - state: ListState::default(), - }, - Ring { - name: "seed 2".to_string(), - addresses: vec!["address 1".to_string(), "address 2".to_string()], - state: ListState::default(), - }, - Ring { - name: "hote wallet 1".to_string(), - addresses: vec!["hot address 1".to_string()], - state: ListState::default(), - }, - ], - - state: ListState::default(), - }, - pane: Pane::Rings, - } - } -} - -impl App { - fn run(mut self, mut terminal: DefaultTerminal) -> std::io::Result<()> { - while !self.should_exit { - terminal.draw(|frame| frame.render_widget(&mut self, frame.area()))?; - if let Event::Key(key) = event::read()? { - self.handle_key(key); - }; - } - Ok(()) - } - - fn handle_key(&mut self, key: KeyEvent) { - if key.kind != KeyEventKind::Press { - return; - } - match key.code { - KeyCode::Char('q') | KeyCode::Esc => self.should_exit = true, - KeyCode::Char('h') | KeyCode::Left => self.prev_pane(), - KeyCode::Char('l') | KeyCode::Right => self.next_pane(), - KeyCode::Char('j') | KeyCode::Down => self.next_item(), - KeyCode::Char('k') | KeyCode::Up => self.prev_item(), - _ => {} - } - } - - fn prev_pane(&mut self) { - match self.pane { - Pane::Rings => self.pane = Pane::Actions, - Pane::Keys => self.pane = Pane::Rings, - Pane::Actions => self.pane = Pane::Keys, - } - } - - fn next_pane(&mut self) { - match self.pane { - Pane::Rings => self.pane = Pane::Keys, - Pane::Keys => self.pane = Pane::Actions, - Pane::Actions => self.pane = Pane::Rings, - } - } - - fn next_item(&mut self) { - if self.pane == Pane::Actions { - return; - } - - match self.pane { - Pane::Rings => self.rings.state.select_next(), - Pane::Keys => { - if let Some(key) = self.selected_ring() { - key.state.select_next() - } - } - _ => {} - } - } - - fn prev_item(&mut self) { - if self.pane == Pane::Actions { - return; - } - - match self.pane { - Pane::Rings => self.rings.state.select_previous(), - Pane::Keys => { - if let Some(key) = self.selected_ring() { - key.state.select_previous() - } - } - _ => {} - } - } - - fn selected_ring(&mut self) -> Option<&mut Ring> { - if let Some(offset) = self.rings.state.selected() { - self.rings.items.get_mut(offset) - } else { - None - } - } -} - -impl Widget for &mut App { - fn render(self, area: Rect, buf: &mut Buffer) { - let block = Block::default() - .title(format!(" Nexum, Pane: {:?} ", self.pane)) - .borders(Borders::ALL); - let inner = block.inner(area); - block.render(area, buf); - - let layout = Layout::default() - .direction(Direction::Horizontal) - .constraints(vec![ - Constraint::Fill(1), - Constraint::Fill(1), - Constraint::Fill(3), - ]) - .split(inner); - let [ring, key, window] = layout[..] else { - panic!("incorrect layout") - }; - - let ring_list = List::new( - self.rings - .items - .iter() - .map(|item| item.name.as_str()) - .collect::>(), - ) - .highlight_style(Style::new().black().on_white()) - .highlight_symbol(">>") - .direction(ListDirection::TopToBottom) - .block(Block::default().borders(Borders::ALL)); - - Paragraph::new(format!("selected offset: {}", self.rings.state.offset())).render(key, buf); - StatefulWidget::render(ring_list, ring, buf, &mut self.rings.state); - - if let Some(ring) = self.selected_ring() { - let keys_list = List::new( - ring.addresses - .iter() - .map(|item| item.as_str()) - .collect::>(), - ) - .highlight_style(Style::new().black().on_white()) - .highlight_symbol(">>") - .direction(ListDirection::TopToBottom) - .block(Block::default().borders(Borders::ALL)); - StatefulWidget::render(keys_list, key, buf, &mut ring.state); - } - } +#[derive(Parser)] +struct Args { + keystore_file: String, } fn main() -> std::io::Result<()> { + let args = Args::parse(); + let mut terminal = ratatui::init(); terminal.clear()?; - let app_result = App::default().run(terminal); + let app_result = App::default().run(terminal, args.keystore_file); ratatui::restore(); app_result } diff --git a/crates/tui/src/lib.rs b/crates/tui/src/lib.rs index 16821dc..0cdb9e8 100644 --- a/crates/tui/src/lib.rs +++ b/crates/tui/src/lib.rs @@ -1 +1,3 @@ +pub mod app; mod keys; +mod widgets; diff --git a/crates/tui/src/widgets/mod.rs b/crates/tui/src/widgets/mod.rs new file mode 100644 index 0000000..e69de29 From aa40395350c06dd3724595bc4bb09c60b12d7a79 Mon Sep 17 00:00:00 2001 From: Meet Mangukiya Date: Sun, 5 Jan 2025 05:11:39 +0530 Subject: [PATCH 3/5] feat: integrate rpc into tui --- Cargo.lock | 1 + bin/rpc/src/lib.rs | 8 ++++++++ crates/tui/Cargo.toml | 1 + crates/tui/src/bin/nxm.rs | 8 +++++++- 4 files changed, 17 insertions(+), 1 deletion(-) create mode 100644 bin/rpc/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 5a100bd..ce86587 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4549,6 +4549,7 @@ dependencies = [ "alloy-signer-local", "clap", "ratatui", + "rpc", "tokio", "tracing", ] diff --git a/bin/rpc/src/lib.rs b/bin/rpc/src/lib.rs new file mode 100644 index 0000000..f28db34 --- /dev/null +++ b/bin/rpc/src/lib.rs @@ -0,0 +1,8 @@ +mod cli; +mod config; +mod logging; +mod namespaces; +mod rpc; + +pub use cli::Cli; +pub use rpc::run; diff --git a/crates/tui/Cargo.toml b/crates/tui/Cargo.toml index 1a88a53..232c273 100644 --- a/crates/tui/Cargo.toml +++ b/crates/tui/Cargo.toml @@ -16,3 +16,4 @@ tracing = { workspace = true } alloy-signer = { workspace = true } alloy-signer-local = { workspace = true } alloy-primitives = { workspace = true } +rpc = { path = "../../bin/rpc" } diff --git a/crates/tui/src/bin/nxm.rs b/crates/tui/src/bin/nxm.rs index c195aba..5a9176b 100644 --- a/crates/tui/src/bin/nxm.rs +++ b/crates/tui/src/bin/nxm.rs @@ -1,14 +1,20 @@ -use alloy_signer_local::PrivateKeySigner; use clap::Parser; use tui::app::App; #[derive(Parser)] struct Args { keystore_file: String, + #[clap(flatten)] + rpc: rpc::Cli, } fn main() -> std::io::Result<()> { let args = Args::parse(); + let runtime = tokio::runtime::Builder::new_multi_thread().build()?; + let server_handle = rpc::run(args.rpc.listen_addr.clone(), args.rpc.rpc_url.clone()); + let _ = std::thread::spawn(move || { + let _ = runtime.block_on(server_handle); + }); let mut terminal = ratatui::init(); terminal.clear()?; From c206325b06ff1219d18a3c836648e797096b2ea8 Mon Sep 17 00:00:00 2001 From: Meet Mangukiya Date: Sun, 5 Jan 2025 05:13:12 +0530 Subject: [PATCH 4/5] chore: fmt rpc --- bin/rpc/src/cli.rs | 14 +- bin/rpc/src/logging.rs | 2 +- bin/rpc/src/main.rs | 2 +- bin/rpc/src/namespaces.rs | 2 +- bin/rpc/src/namespaces/eth.rs | 16 +- bin/rpc/src/namespaces/net.rs | 8 +- bin/rpc/src/namespaces/wallet.rs | 5 +- bin/rpc/src/namespaces/web3.rs | 6 +- bin/rpc/src/rpc.rs | 290 +++++++++++++++++-------------- 9 files changed, 189 insertions(+), 156 deletions(-) diff --git a/bin/rpc/src/cli.rs b/bin/rpc/src/cli.rs index d920727..6c854ea 100644 --- a/bin/rpc/src/cli.rs +++ b/bin/rpc/src/cli.rs @@ -4,11 +4,21 @@ use clap::Parser; #[command(about)] pub struct Cli { /// Address to listen for browser extension WebSocket requests - #[arg(short, long, value_name = "ADDR", default_value = "ws://localhost:1248")] + #[arg( + short, + long, + value_name = "ADDR", + default_value = "ws://localhost:1248" + )] pub listen_addr: String, /// Node JSON-RPC URL capable of supporting WebSockets - #[arg(short, long, value_name = "RPC_URL", default_value = "ws://localhost:8546")] + #[arg( + short, + long, + value_name = "RPC_URL", + default_value = "ws://localhost:8546" + )] pub rpc_url: String, } diff --git a/bin/rpc/src/logging.rs b/bin/rpc/src/logging.rs index d89a494..ea7d1a6 100644 --- a/bin/rpc/src/logging.rs +++ b/bin/rpc/src/logging.rs @@ -1,6 +1,6 @@ +use anyhow::Result; use tracing_error::ErrorLayer; use tracing_subscriber::{fmt, prelude::*, EnvFilter}; -use anyhow::Result; use crate::config; diff --git a/bin/rpc/src/main.rs b/bin/rpc/src/main.rs index 25525f3..d4efc21 100644 --- a/bin/rpc/src/main.rs +++ b/bin/rpc/src/main.rs @@ -4,8 +4,8 @@ use clap::Parser; use cli::Cli; mod cli; -mod logging; mod config; +mod logging; mod namespaces; mod rpc; diff --git a/bin/rpc/src/namespaces.rs b/bin/rpc/src/namespaces.rs index 65195fc..f934a89 100644 --- a/bin/rpc/src/namespaces.rs +++ b/bin/rpc/src/namespaces.rs @@ -1,4 +1,4 @@ pub mod eth; pub mod net; +pub mod wallet; pub mod web3; -pub mod wallet; \ No newline at end of file diff --git a/bin/rpc/src/namespaces/eth.rs b/bin/rpc/src/namespaces/eth.rs index cb735af..0d02bbd 100644 --- a/bin/rpc/src/namespaces/eth.rs +++ b/bin/rpc/src/namespaces/eth.rs @@ -1,5 +1,5 @@ -use std::sync::Arc; use jsonrpsee::{core::RpcResult, ws_client::WsClient, RpcModule}; +use std::sync::Arc; use crate::rpc::upstream_request; @@ -42,12 +42,14 @@ pub fn init(_: EthContext, client: Arc) -> RpcModule { eth_methods.iter().for_each(|method| { let _ = eth_module.register_async_method(method, upstream_request(method, client.clone())); }); - - let _ = eth_module.register_method("eth_requestAccounts", |_, _, _| -> RpcResult> { - let addresses: Vec = vec!["0xE618050F1adb1F6bb7d03A3484346AC42F3E71EE".to_string()]; - Ok(addresses) - }); - + + let _ = + eth_module.register_method("eth_requestAccounts", |_, _, _| -> RpcResult> { + let addresses: Vec = + vec!["0xE618050F1adb1F6bb7d03A3484346AC42F3E71EE".to_string()]; + Ok(addresses) + }); + let _ = eth_module.register_method("eth_accounts", |_, _, _| -> RpcResult> { let addresses: Vec = vec!["0xE618050F1adb1F6bb7d03A3484346AC42F3E71EE".to_string()]; Ok(addresses) diff --git a/bin/rpc/src/namespaces/net.rs b/bin/rpc/src/namespaces/net.rs index ab17a99..3a2d3ed 100644 --- a/bin/rpc/src/namespaces/net.rs +++ b/bin/rpc/src/namespaces/net.rs @@ -1,5 +1,5 @@ -use std::sync::Arc; use jsonrpsee::{ws_client::WsClient, RpcModule}; +use std::sync::Arc; use crate::rpc::upstream_request; @@ -7,12 +7,10 @@ pub type NetContext = (); pub fn init(_: NetContext, client: Arc) -> RpcModule { let mut net_module = RpcModule::new(()); - let net_methods = vec![ - "net_version", - ]; + let net_methods = vec!["net_version"]; net_methods.iter().for_each(|method| { let _ = net_module.register_async_method(method, upstream_request(method, client.clone())); }); net_module -} \ No newline at end of file +} diff --git a/bin/rpc/src/namespaces/wallet.rs b/bin/rpc/src/namespaces/wallet.rs index 3a80b8a..28f5492 100644 --- a/bin/rpc/src/namespaces/wallet.rs +++ b/bin/rpc/src/namespaces/wallet.rs @@ -1,5 +1,5 @@ -use std::sync::Arc; use jsonrpsee::{ws_client::WsClient, RpcModule}; +use std::sync::Arc; use crate::rpc::upstream_request; @@ -9,7 +9,8 @@ pub fn init(_: WalletContext, client: Arc) -> RpcModule let mut wallet_module = RpcModule::new(()); let wallet_methods: Vec<&str> = vec![]; wallet_methods.iter().for_each(|method| { - let _ = wallet_module.register_async_method(method, upstream_request(method, client.clone())); + let _ = + wallet_module.register_async_method(method, upstream_request(method, client.clone())); }); wallet_module diff --git a/bin/rpc/src/namespaces/web3.rs b/bin/rpc/src/namespaces/web3.rs index 8e59ff1..7dde0da 100644 --- a/bin/rpc/src/namespaces/web3.rs +++ b/bin/rpc/src/namespaces/web3.rs @@ -1,5 +1,5 @@ -use std::sync::Arc; use jsonrpsee::{ws_client::WsClient, RpcModule}; +use std::sync::Arc; use crate::rpc::upstream_request; @@ -8,9 +8,7 @@ pub type Web3Context = (); pub fn init(c: Web3Context, client: Arc) -> RpcModule { let mut web3_module = RpcModule::new(c); - let web3_methods = vec![ - "web3_clientVersion", - ]; + let web3_methods = vec!["web3_clientVersion"]; web3_methods.iter().for_each(|method| { let _ = web3_module.register_async_method(method, upstream_request(method, client.clone())); }); diff --git a/bin/rpc/src/rpc.rs b/bin/rpc/src/rpc.rs index 69a5125..123a700 100644 --- a/bin/rpc/src/rpc.rs +++ b/bin/rpc/src/rpc.rs @@ -8,7 +8,9 @@ use jsonrpsee::core::client::ClientT; use jsonrpsee::core::traits::ToRpcParams; use jsonrpsee::core::{ClientError, RpcResult}; use jsonrpsee::server::middleware::rpc::{RpcServiceBuilder, RpcServiceT}; -use jsonrpsee::server::{serve_with_graceful_shutdown, stop_channel, ServerHandle, StopHandle, TowerServiceBuilder}; +use jsonrpsee::server::{ + serve_with_graceful_shutdown, stop_channel, ServerHandle, StopHandle, TowerServiceBuilder, +}; use jsonrpsee::types::{ErrorCode, ErrorObject, Params, Request}; use jsonrpsee::ws_client::{WsClient, WsClientBuilder}; use jsonrpsee::{MethodResponse, Methods, RpcModule}; @@ -18,14 +20,14 @@ use tokio::net::TcpListener; use tower::Service; use tracing::trace; -use crate::namespaces::{eth, net, web3, wallet}; +use crate::namespaces::{eth, net, wallet, web3}; #[derive(Clone, Debug, Default)] struct Metrics { - opened_ws_connections: Arc, - closed_ws_connections: Arc, - http_calls: Arc, - success_http_calls: Arc, + opened_ws_connections: Arc, + closed_ws_connections: Arc, + http_calls: Arc, + success_http_calls: Arc, } /// Request parameters @@ -61,29 +63,29 @@ impl ToRpcParams for RequestParams { // by using the low-level API. #[derive(Clone)] pub struct CallerContext { - service: S, + service: S, } impl<'a, S> RpcServiceT<'a> for CallerContext where - S: RpcServiceT<'a> + Send + Sync + Clone + 'static, + S: RpcServiceT<'a> + Send + Sync + Clone + 'static, { - type Future = BoxFuture<'a, MethodResponse>; - - fn call(&self, req: Request<'a>) -> Self::Future { - let service = self.service.clone(); - - async move { - trace!("Request: {:?}", req); - let rp = service.call(req).await; - rp - } - .boxed() - } + type Future = BoxFuture<'a, MethodResponse>; + + fn call(&self, req: Request<'a>) -> Self::Future { + let service = self.service.clone(); + + async move { + trace!("Request: {:?}", req); + let rp = service.call(req).await; + rp + } + .boxed() + } } pub async fn run(addr: String, rpc_url: String) -> anyhow::Result { - let addr = addr.parse::()?; + let addr = addr.parse::()?; run_server(addr, rpc_url.as_str()).await } @@ -91,17 +93,28 @@ pub async fn run(addr: String, rpc_url: String) -> anyhow::Result pub fn upstream_request( method_name: &'static str, shared_client: Arc, -) -> impl Fn(Params<'static>, Arc<()>, jsonrpsee::Extensions) -> BoxFuture<'static, RpcResult> + Send + Sync + Clone + 'static { +) -> impl Fn(Params<'static>, Arc<()>, jsonrpsee::Extensions) -> BoxFuture<'static, RpcResult> + + Send + + Sync + + Clone + + 'static { let method_name: Arc = Arc::new(method_name.to_string()); let shared_client = Arc::clone(&shared_client); - move |params: Params<'static>, _: Arc<()>, _: jsonrpsee::Extensions| -> BoxFuture<'static, RpcResult> { + move |params: Params<'static>, + _: Arc<()>, + _: jsonrpsee::Extensions| + -> BoxFuture<'static, RpcResult> { let client = Arc::clone(&shared_client); let method_name = Arc::clone(&method_name); let params: Result = params.parse(); trace!("Received request extension"); - trace!("Received request: {} with params: {:?}", *method_name, params); + trace!( + "Received request: {} with params: {:?}", + *method_name, + params + ); async move { let params: RequestParams = match params { @@ -122,124 +135,135 @@ pub fn upstream_request( } } - async fn run_server(listen_addr: SocketAddr, rpc_url: &str) -> anyhow::Result { let listener = TcpListener::bind(listen_addr).await?; let rpc: Arc = Arc::new(WsClientBuilder::default().build(rpc_url).await?); - // This state is cloned for every connection all these types based on Arcs and it should - // be relatively cheap to clone them. - // - // Make sure that nothing expensive is cloned here when doing this or use an `Arc`. - #[derive(Clone)] - struct PerConnection { - methods: Methods, - stop_handle: StopHandle, - metrics: Metrics, - svc_builder: TowerServiceBuilder, - } - - // Each RPC call/connection get its own `stop_handle` to able to determine whether the server - // has been stopped or not. To keep the server running the `server_handle` must be kept and it + // This state is cloned for every connection all these types based on Arcs and it should + // be relatively cheap to clone them. + // + // Make sure that nothing expensive is cloned here when doing this or use an `Arc`. + #[derive(Clone)] + struct PerConnection { + methods: Methods, + stop_handle: StopHandle, + metrics: Metrics, + svc_builder: TowerServiceBuilder, + } + + // Each RPC call/connection get its own `stop_handle` to able to determine whether the server + // has been stopped or not. To keep the server running the `server_handle` must be kept and it // can also be used to stop the server. - let (stop_handle, server_handle) = stop_channel(); + let (stop_handle, server_handle) = stop_channel(); let mut methods = RpcModule::new(()); methods.merge(eth::init((), rpc.clone())).unwrap(); - methods.merge(net::init((), rpc.clone())).unwrap(); + methods.merge(net::init((), rpc.clone())).unwrap(); methods.merge(web3::init((), rpc.clone())).unwrap(); - methods.merge(wallet::init((), rpc.clone())).unwrap(); - - let per_conn_template = PerConnection { - methods: methods.into(), - stop_handle: stop_handle.clone(), - svc_builder: jsonrpsee::server::Server::builder() - .max_connections(33) - .to_service_builder(), - metrics: Metrics::default(), - }; - - tokio::spawn(async move { - loop { - // The `tokio::select!` macro is used to wait for either of the - // listeners to accept a new connection or for the server to be - // stopped. - let sock = tokio::select! { - res = listener.accept() => { - match res { - Ok((stream, _remote_addr)) => stream, - Err(e) => { - tracing::error!("failed to accept v4 connection: {:?}", e); - continue; - } - } - } - _ = per_conn_template.stop_handle.clone().shutdown() => break, - }; - let per_conn = per_conn_template.clone(); - - let svc = tower::service_fn(move |req: hyper::Request| { - let is_websocket = jsonrpsee::server::ws::is_upgrade_request(&req); - let transport_label = if is_websocket { "ws" } else { "http" }; - let PerConnection { methods, stop_handle, metrics, svc_builder, .. } = per_conn.clone(); + methods.merge(wallet::init((), rpc.clone())).unwrap(); + + let per_conn_template = PerConnection { + methods: methods.into(), + stop_handle: stop_handle.clone(), + svc_builder: jsonrpsee::server::Server::builder() + .max_connections(33) + .to_service_builder(), + metrics: Metrics::default(), + }; + + tokio::spawn(async move { + loop { + // The `tokio::select!` macro is used to wait for either of the + // listeners to accept a new connection or for the server to be + // stopped. + let sock = tokio::select! { + res = listener.accept() => { + match res { + Ok((stream, _remote_addr)) => stream, + Err(e) => { + tracing::error!("failed to accept v4 connection: {:?}", e); + continue; + } + } + } + _ = per_conn_template.stop_handle.clone().shutdown() => break, + }; + let per_conn = per_conn_template.clone(); + + let svc = tower::service_fn(move |req: hyper::Request| { + let is_websocket = jsonrpsee::server::ws::is_upgrade_request(&req); + let transport_label = if is_websocket { "ws" } else { "http" }; + let PerConnection { + methods, + stop_handle, + metrics, + svc_builder, + .. + } = per_conn.clone(); let rpc_middleware = RpcServiceBuilder::new() - .rpc_logger(1024) - .layer_fn(move |service| { - CallerContext { service } - }); - - let mut svc = svc_builder - // .set_http_middleware(http_middleware) - .set_rpc_middleware(rpc_middleware) - .build(methods, stop_handle); - - if is_websocket { - // Utilize the session close future to know when the actual WebSocket - // session was closed. - let session_close = svc.on_session_closed(); - - // A little bit weird API but the response to HTTP request must be returned below - // and we spawn a task to register when the session is closed. - tokio::spawn(async move { - session_close.await; - tracing::info!("Closed WebSocket connection"); - metrics.closed_ws_connections.fetch_add(1, Ordering::Relaxed); - }); - - async move { - tracing::info!("Opened WebSocket connection"); - metrics.opened_ws_connections.fetch_add(1, Ordering::Relaxed); - // https://github.com/rust-lang/rust/issues/102211 the error type can't be inferred - // to be `Box` so we need to convert it to a concrete type - // as workaround. - svc.call(req).await.map_err(|e| anyhow::anyhow!("{:?}", e)) - } - .boxed() - } else { - // HTTP. - async move { - tracing::info!("Opened HTTP connection"); - metrics.http_calls.fetch_add(1, Ordering::Relaxed); - let rp = svc.call(req).await; - - if rp.is_ok() { - metrics.success_http_calls.fetch_add(1, Ordering::Relaxed); - } - - tracing::info!("Closed HTTP connection"); - // https://github.com/rust-lang/rust/issues/102211 the error type can't be inferred - // to be `Box` so we need to convert it to a concrete type - // as workaround. - rp.map_err(|e| anyhow::anyhow!("{:?}", e)) - } - .boxed() - } - }); - - tokio::spawn(serve_with_graceful_shutdown(sock, svc, stop_handle.clone().shutdown())); - } - }); - - Ok(server_handle) + .rpc_logger(1024) + .layer_fn(move |service| CallerContext { service }); + + let mut svc = svc_builder + // .set_http_middleware(http_middleware) + .set_rpc_middleware(rpc_middleware) + .build(methods, stop_handle); + + if is_websocket { + // Utilize the session close future to know when the actual WebSocket + // session was closed. + let session_close = svc.on_session_closed(); + + // A little bit weird API but the response to HTTP request must be returned below + // and we spawn a task to register when the session is closed. + tokio::spawn(async move { + session_close.await; + tracing::info!("Closed WebSocket connection"); + metrics + .closed_ws_connections + .fetch_add(1, Ordering::Relaxed); + }); + + async move { + tracing::info!("Opened WebSocket connection"); + metrics + .opened_ws_connections + .fetch_add(1, Ordering::Relaxed); + // https://github.com/rust-lang/rust/issues/102211 the error type can't be inferred + // to be `Box` so we need to convert it to a concrete type + // as workaround. + svc.call(req).await.map_err(|e| anyhow::anyhow!("{:?}", e)) + } + .boxed() + } else { + // HTTP. + async move { + tracing::info!("Opened HTTP connection"); + metrics.http_calls.fetch_add(1, Ordering::Relaxed); + let rp = svc.call(req).await; + + if rp.is_ok() { + metrics.success_http_calls.fetch_add(1, Ordering::Relaxed); + } + + tracing::info!("Closed HTTP connection"); + // https://github.com/rust-lang/rust/issues/102211 the error type can't be inferred + // to be `Box` so we need to convert it to a concrete type + // as workaround. + rp.map_err(|e| anyhow::anyhow!("{:?}", e)) + } + .boxed() + } + }); + + tokio::spawn(serve_with_graceful_shutdown( + sock, + svc, + stop_handle.clone().shutdown(), + )); + } + }); + + Ok(server_handle) } From 06350bdcaa48c07009387575b806498528411e17 Mon Sep 17 00:00:00 2001 From: Meet Mangukiya Date: Sun, 2 Feb 2025 15:18:44 +0530 Subject: [PATCH 5/5] feat: prompts --- crates/tui/src/app.rs | 52 +++- crates/tui/src/bin/nxm.rs | 29 ++- crates/tui/src/lib.rs | 2 +- crates/tui/src/widgets/mod.rs | 1 + crates/tui/src/widgets/prompts.rs | 408 ++++++++++++++++++++++++++++++ 5 files changed, 480 insertions(+), 12 deletions(-) create mode 100644 crates/tui/src/widgets/prompts.rs diff --git a/crates/tui/src/app.rs b/crates/tui/src/app.rs index 97fbdba..a731785 100644 --- a/crates/tui/src/app.rs +++ b/crates/tui/src/app.rs @@ -11,15 +11,18 @@ use ratatui::{ DefaultTerminal, }; +use crate::widgets::prompts::{PendingPromptsState, PendingPromptsWidget}; + #[derive(Debug)] pub struct App { - should_exit: bool, - rings: RingList, - pane: Pane, - keystore_file: String, - view: View, - prompt_input: String, - signer: Option>, + pub should_exit: bool, + pub rings: RingList, + pub pane: Pane, + pub keystore_file: String, + pub view: View, + pub prompt_input: String, + pub signer: Option>, + pub pending_prompts_state: PendingPromptsState, } #[derive(Debug)] @@ -78,6 +81,7 @@ impl Default for App { view: View::KeystorePassword, prompt_input: "".to_string(), signer: None, + pending_prompts_state: PendingPromptsState::default(), } } } @@ -131,8 +135,8 @@ impl App { KeyCode::Char('q') | KeyCode::Esc => self.should_exit = true, KeyCode::Char('h') | KeyCode::Left => self.prev_pane(), KeyCode::Char('l') | KeyCode::Right => self.next_pane(), - KeyCode::Char('j') | KeyCode::Down => self.next_item(), - KeyCode::Char('k') | KeyCode::Up => self.prev_item(), + KeyCode::Char('j') | KeyCode::Down => self.next_prompt(), + KeyCode::Char('k') | KeyCode::Up => self.prev_prompt(), _ => {} }, } @@ -186,6 +190,33 @@ impl App { } } + fn next_prompt(&mut self) { + if let Some(offset) = self.pending_prompts_state.prompts_list_state.selected() { + let len = self.pending_prompts_state.prompts.len(); + if offset == len - 1 { + self.pending_prompts_state.prompts_list_state.select_first(); + } else { + self.pending_prompts_state.prompts_list_state.select_next(); + } + } else { + self.pending_prompts_state.prompts_list_state.select_first(); + } + } + + fn prev_prompt(&mut self) { + if let Some(offset) = self.pending_prompts_state.prompts_list_state.selected() { + if offset == 0 { + self.pending_prompts_state.prompts_list_state.select_last(); + } else { + self.pending_prompts_state + .prompts_list_state + .select_previous(); + } + } else { + self.pending_prompts_state.prompts_list_state.select_last(); + } + } + fn selected_ring(&mut self) -> Option<&mut Ring> { if let Some(offset) = self.rings.state.selected() { self.rings.items.get_mut(offset) @@ -201,6 +232,7 @@ impl App { impl Widget for &mut App { fn render(self, area: Rect, buf: &mut Buffer) { + buf.reset(); match self.view { View::KeystorePassword => { let block = Block::default() @@ -241,6 +273,8 @@ impl Widget for &mut App { .borders(Borders::ALL); let inner = block.inner(area); block.render(area, buf); + let pending_prompts_widget = PendingPromptsWidget; + pending_prompts_widget.render(inner, buf, &mut self.pending_prompts_state); } } } diff --git a/crates/tui/src/bin/nxm.rs b/crates/tui/src/bin/nxm.rs index 5a9176b..90979ec 100644 --- a/crates/tui/src/bin/nxm.rs +++ b/crates/tui/src/bin/nxm.rs @@ -1,5 +1,8 @@ use clap::Parser; -use tui::app::App; +use tui::{ + app::App, + widgets::prompts::{ConnectionPrompt, PendingPromptsState, Prompt, TransactionRequestPrompt}, +}; #[derive(Parser)] struct Args { @@ -18,7 +21,29 @@ fn main() -> std::io::Result<()> { let mut terminal = ratatui::init(); terminal.clear()?; - let app_result = App::default().run(terminal, args.keystore_file); + + let app = App { + pending_prompts_state: PendingPromptsState { + prompts: vec![ + Prompt::Connection(ConnectionPrompt { + origin: "https://swap.cow.fi".into(), + }), + Prompt::Connection(ConnectionPrompt { + origin: "https://app.safe.global".into(), + }), + Prompt::TransactionRequest(TransactionRequestPrompt { + to: Default::default(), + gas_limit: 0, + gas_price: 0, + value: Default::default(), + data: Default::default(), + }), + ], + prompts_list_state: Default::default(), + }, + ..Default::default() + }; + let app_result = app.run(terminal, args.keystore_file); ratatui::restore(); app_result } diff --git a/crates/tui/src/lib.rs b/crates/tui/src/lib.rs index 0cdb9e8..f439328 100644 --- a/crates/tui/src/lib.rs +++ b/crates/tui/src/lib.rs @@ -1,3 +1,3 @@ pub mod app; mod keys; -mod widgets; +pub mod widgets; diff --git a/crates/tui/src/widgets/mod.rs b/crates/tui/src/widgets/mod.rs index e69de29..f1e5cd0 100644 --- a/crates/tui/src/widgets/mod.rs +++ b/crates/tui/src/widgets/mod.rs @@ -0,0 +1 @@ +pub mod prompts; diff --git a/crates/tui/src/widgets/prompts.rs b/crates/tui/src/widgets/prompts.rs new file mode 100644 index 0000000..d494ce9 --- /dev/null +++ b/crates/tui/src/widgets/prompts.rs @@ -0,0 +1,408 @@ +use core::fmt; + +use alloy_primitives::{Address, Bytes, FixedBytes, Uint}; +use ratatui::{ + layout::{Constraint, Direction, Layout, Margin, Position}, + prelude::{Buffer, Rect}, + style::{Style, Stylize}, + text::{Line, Text}, + widgets::{ + Block, Borders, Cell, List, ListDirection, ListItem, ListState, Paragraph, Row, + StatefulWidget, Table, TableState, Widget, + }, +}; + +pub struct TransactionPromptWidget; +pub struct TransactionPromptState { + active_field_idx: usize, + fields: Vec, +} + +impl Default for TransactionPromptState { + fn default() -> Self { + Self { + active_field_idx: Default::default(), + fields: vec![ + Field { + name: "to".into(), + value: FieldValue::Address(Address::ZERO), + }, + Field { + name: "gas_price".into(), + value: FieldValue::Number(0), + }, + Field { + name: "gas".into(), + value: FieldValue::Number(0), + }, + Field { + name: "nonce".into(), + value: FieldValue::Number(0), + }, + Field { + name: "data".into(), + value: FieldValue::Bytes(Bytes::default()), + }, + ], + } + } +} + +impl TransactionPromptState { + pub fn max_field_name_length(&self) -> usize { + self.fields + .iter() + .map(|f| f.name.len()) + .max() + .unwrap_or_default() + } +} + +pub enum FieldValue { + Address(Address), + Boolean(bool), + Number(u64), + Bytes(Bytes), +} + +impl std::fmt::Display for FieldValue { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + FieldValue::Address(address) => write!(f, "{}", address), + FieldValue::Boolean(value) => write!(f, "{}", value), + FieldValue::Number(value) => write!(f, "{}", value), + FieldValue::Bytes(bytes) => write!(f, "{}", bytes), + } + } +} + +pub struct Field { + name: String, + value: FieldValue, +} + +#[derive(Clone, Debug)] +pub struct ConnectionPrompt { + pub origin: String, +} + +#[derive(Clone, Debug)] +pub struct TransactionRequestPrompt { + pub to: Address, + pub gas_limit: u64, + pub gas_price: u64, + pub value: Uint<256, 4>, + pub data: Bytes, +} + +#[derive(Clone, Debug)] +pub struct TypedSignatureRequestPrompt {} + +#[derive(Clone, Debug)] +pub struct RawSignatureRequestPrompt { + hash: FixedBytes<32>, +} + +#[derive(Clone, Debug)] +pub enum Prompt { + Connection(ConnectionPrompt), + TransactionRequest(TransactionRequestPrompt), + TypedSignatureRequest(TypedSignatureRequestPrompt), + RawSignatureRequest(RawSignatureRequestPrompt), +} + +impl Prompt { + fn lines(&self) -> Vec { + match self { + Prompt::Connection(c) => { + vec!["Connect to".into(), c.origin.clone().into()] + } + Prompt::TransactionRequest(r) => { + let min_len = ["to", "gas_limit", "gas_price", "value", "data"] + .iter() + .map(|f| f.len()) + .max() + .unwrap_or_default() + + 2; + + let text = format!( + "to{}: {}\ngas_limit{}: {}\ngas_price{}: {}\nvalue{}: {}\ndata{}: {}", + " ".repeat(min_len - "to".len()), + r.to, + " ".repeat(min_len - "gas_limit".len()), + r.gas_limit, + " ".repeat(min_len - "gas_price".len()), + r.gas_price, + " ".repeat(min_len - "value".len()), + r.value, + " ".repeat(min_len - "data".len()), + r.data + ); + let lines = text.split("\n").collect::>(); + // println!( + // "lnes: {:?}", + // lines.iter().map(|f| f.len()).collect::>() + // ); + lines.into_iter().map(|f| f.to_owned().into()).collect() + } + _ => vec![], + } + } +} + +const PIPE_CORNER_TOP_LEFT: char = '┌'; +const PIPE_CORNER_BOTTOM_LEFT: char = '└'; +const PIPE_CORNER_TOP_RIGHT: char = '┐'; +const PIPE_CORNER_BOTTOM_RIGHT: char = '┘'; + +const PIPE_HORIZONTAL: char = '─'; +const PIPE_VERTICAL: char = '│'; + +const PIPE_MID_LEFT: char = '├'; +const PIPE_MID_RIGHT: char = '┤'; +const PIPE_MID_TOP: char = '┬'; +const PIPE_MID_BOTTOM: char = '┴'; + +const PIPE_MID: char = '┼'; + +impl Widget for Prompt { + fn render(self, area: Rect, buf: &mut Buffer) + where + Self: Sized, + { + let lines = self.lines(); + let max_width = lines.iter().map(|f| f.width()).max().unwrap_or_default() as u16; + + // needs to be odd to get an equal split on the actions button area + let min_width = (23).max(max_width + 2); + let prompt_content_height = lines.len() as u16; + let actions_content_height = 1; + let height = prompt_content_height + actions_content_height + 3; + let prompt_area = Rect::new(area.x + 1, area.y + 1, min_width, prompt_content_height); + let actions_area = Rect::new( + area.x + 1, + area.y + prompt_content_height + 2, + min_width, + actions_content_height, + ); + let outer_area = Rect::new(area.x, area.y, min_width + 3, height); + + // draw the box + for (yidx, _) in (0..height).enumerate() { + for (xidx, _) in (0..min_width).enumerate() { + let yidx = yidx as u16; + let xidx = xidx as u16; + let cell_position = Position { + x: area.x + xidx, + y: area.y + yidx, + }; + let cell = buf.cell_mut(cell_position).expect("!cell"); + let cell_char = if yidx == 0 && xidx == 0 { + PIPE_CORNER_TOP_LEFT + } else if yidx == 0 && xidx == min_width - 1 { + PIPE_CORNER_TOP_RIGHT + } else if yidx == height - 1 && xidx == 0 { + PIPE_CORNER_BOTTOM_LEFT + } else if yidx == height - 1 && xidx == min_width - 1 { + PIPE_CORNER_BOTTOM_RIGHT + } else if yidx == prompt_content_height + 1 && xidx == 0 { + PIPE_MID_LEFT + } else if yidx == prompt_content_height + 1 && xidx == min_width - 1 { + PIPE_MID_RIGHT + } else if yidx == prompt_content_height + 1 && xidx == (min_width - 1) / 2 { + PIPE_MID_TOP + } else if yidx == height - 1 && xidx == (min_width - 1) / 2 { + PIPE_MID_BOTTOM + } else if yidx == 0 || yidx == height - 1 || yidx == prompt_content_height + 1 { + PIPE_HORIZONTAL + } else if xidx == 0 + || xidx == min_width - 1 + || ((xidx == (min_width - 1) / 2) && yidx > prompt_content_height + 1) + { + PIPE_VERTICAL + } else { + ' ' + }; + cell.set_char(cell_char); + } + } + + // render the prompt + for (line_idx, line) in lines.iter().enumerate() { + let line_area = Rect::new( + prompt_area.x, + prompt_area.y + (line_idx as u16), + prompt_area.width, + 1, + ); + line.render(line_area, buf); + } + + // render the actions + let accept_area = Rect::new( + actions_area.x, + actions_area.y, + (actions_area.width - 1) / 2, + actions_area.height, + ); + Text::from("[a]ccept").centered().render(accept_area, buf); + let reject_area = Rect::new( + actions_area.x + ((actions_area.width - 1) / 2), + actions_area.y, + (actions_area.width - 1) / 2, + actions_area.height, + ); + Text::from("[r]eject").centered().render(reject_area, buf); + + // let max_len = self + // .lines() + // .iter() + // .map(|l| l.width()) + // .max() + // .unwrap_or_default(); + // + // let block_area = Rect::new( + // area.x, + // area.y, + // ((max_len as u16) + 2).max(20), + // (self.lines().len() as u16) + 2, + // ); + // + // let block = Block::default().borders(Borders::ALL); + // block.render(block_area, buf); + // + // let mut line_area: Rect = Rect::default(); + // for (idx, line) in self.lines().iter().enumerate() { + // line_area = Rect::new( + // area.x + 1, + // area.y + 1 + (idx as u16), + // line.width() as u16, + // 1, + // ); + // line.render(line_area, buf); + // } + // + // let buttons_area = Rect::new(block_area.x, line_area.y + 2, block_area.width, 5); + // + // if let [reject, accept] = Layout::default() + // .constraints(vec![Constraint::Fill(1), Constraint::Fill(1)]) + // .direction(Direction::Horizontal) + // .split(buttons_area)[..] + // { + // Block::default().borders(Borders::ALL).render(reject, buf); + // Text::from("Reject").centered().render( + // reject.inner(Margin { + // horizontal: 1, + // vertical: 1, + // }), + // buf, + // ); + // + // Block::default() + // .borders(Borders::TOP | Borders::BOTTOM | Borders::RIGHT) + // .render(accept, buf); + // Text::from("Accept").centered().render( + // accept.inner(Margin { + // horizontal: 1, + // vertical: 1, + // }), + // buf, + // ); + // } + // + // // let inner = block.inner(block_area); + } +} + +#[derive(Default, Debug)] +pub struct PendingPromptsState { + pub prompts: Vec, + pub prompts_list_state: ListState, +} + +pub struct PendingPromptsWidget; + +impl StatefulWidget for PendingPromptsWidget { + type State = PendingPromptsState; + + fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) { + // let block = Block::default() + // .title("Transaction Prompt") + // .borders(Borders::ALL); + // let inner = block.inner(area); + // block.render(area, buf); + // let target_len = state.max_field_name_length(); + // let para = Paragraph::new( + // state + // .fields + // .iter() + // .map(|f| { + // format!( + // "{}{} : {}", + // f.name, + // " ".repeat(target_len - f.name.len()), + // f.value + // ) + // }) + // .collect::>() + // .join("\n"), + // ); + // para.render(inner, buf); + // let mut table_state = TableState::default().with_selected(Some(0)); + // let rows = state + // .fields + // .iter() + // .map(|f| { + // Row::new(vec![ + // Cell::new(f.name.clone()), + // Cell::new(f.value.to_string()), + // ]) + // }) + // .collect::>(); + // StatefulWidget::render( + // Table::new(rows, [Constraint::Length(7), Constraint::Length(30)]) + // .block(Block::new()) + // .header(Row::new(vec!["Qty", "Ingredient"])) + // .row_highlight_style(Style::new().light_yellow()), + // area, + // buf, + // &mut table_state, + // ); + + let layout = Layout::default() + .direction(Direction::Horizontal) + .constraints(vec![Constraint::Length(5), Constraint::Fill(5)]); + if let [selector, viewer] = layout.split(area.inner(Margin { + horizontal: 1, + vertical: 1, + }))[..] + { + let selector_block = Block::default().borders(Borders::RIGHT); + let selector_block_inner = selector_block.inner(selector); + selector_block.render(selector, buf); + + let selector_list = List::new( + (1..=state.prompts.len()) + .collect::>() + .iter() + .map(|i| ListItem::new(Text::from(i.to_string()).right_aligned())) + .collect::>(), + ) + .direction(ListDirection::TopToBottom) + .highlight_style(Style::new().on_gray().black()); + StatefulWidget::render( + selector_list, + selector_block_inner, + buf, + &mut state.prompts_list_state, + ); + + if let Some(selected) = state.prompts_list_state.selected() { + let prompt = state.prompts[selected].clone(); + prompt.render(viewer, buf); + } + } else { + Paragraph::new("layout split error").render(area, buf); + } + } +}