-
Notifications
You must be signed in to change notification settings - Fork 13
feat(cli): add serve subcommand for browsing decoded txs
#273
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
68635c9
613cb67
8cd5ba1
ef1fe20
cadccbf
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,5 @@ | ||
| use crate::chains::parse_chain; | ||
| use clap::Parser; | ||
| use clap::{Parser, Subcommand}; | ||
| use visualsign::registry::{Chain, TransactionConverterRegistry}; | ||
| use visualsign::vsptrait::{DeveloperConfig, VisualSignOptions}; | ||
| use visualsign::{SignablePayload, SignablePayloadField}; | ||
|
|
@@ -9,6 +9,21 @@ use visualsign::{SignablePayload, SignablePayloadField}; | |
| #[command(version = env!("VERSION"))] | ||
| #[command(about = "Converts raw transactions to visual signing properties")] | ||
| pub(crate) struct Args { | ||
| #[command(subcommand)] | ||
| pub(crate) command: Command, | ||
| } | ||
|
|
||
| #[derive(Subcommand, Debug)] | ||
| pub(crate) enum Command { | ||
| /// Decode a single transaction and print it. | ||
| Decode(DecodeArgs), | ||
| /// Serve a directory of raw-transaction files via a local web UI. | ||
| #[cfg(feature = "serve")] | ||
| Serve(crate::serve::ServeArgs), | ||
| } | ||
|
|
||
| #[derive(clap::Args, Debug)] | ||
| pub(crate) struct DecodeArgs { | ||
| #[arg(short, long, help = "Chain type")] | ||
| pub(crate) chain: String, | ||
|
|
||
|
|
@@ -19,16 +34,16 @@ pub(crate) struct Args { | |
| help = "Raw transaction string. Prefix with '@' to read from a file \ | ||
| (e.g. '@/path/to/tx.hex'), or use '@-' to read from stdin." | ||
| )] | ||
| transaction: String, | ||
| pub(crate) transaction: String, | ||
|
|
||
| #[arg(short, long, default_value = "text", help = "Output format")] | ||
| output: OutputFormat, | ||
| pub(crate) output: OutputFormat, | ||
|
|
||
| #[arg( | ||
| long, | ||
| help = "Show only condensed view (what hardware wallets display)" | ||
| )] | ||
| condensed_only: bool, | ||
| pub(crate) condensed_only: bool, | ||
|
|
||
| #[arg( | ||
| long, | ||
|
|
@@ -49,8 +64,19 @@ pub(crate) struct Args { | |
| pub(crate) solana: crate::solana::SolanaArgs, | ||
| } | ||
|
|
||
| impl DecodeArgs { | ||
| pub(crate) fn plugin_args(&self) -> crate::PluginArgs { | ||
| crate::PluginArgs { | ||
| #[cfg(feature = "ethereum")] | ||
| ethereum: self.ethereum.clone(), | ||
| #[cfg(feature = "solana")] | ||
| solana: self.solana.clone(), | ||
| } | ||
| } | ||
| } | ||
|
|
||
| #[derive(Debug, Clone, Copy)] | ||
| enum OutputFormat { | ||
| pub(crate) enum OutputFormat { | ||
| Text, | ||
| Json, | ||
| Human, | ||
|
|
@@ -257,70 +283,107 @@ fn parse_and_display( | |
| Ok(()) | ||
| } | ||
|
|
||
| /// CLI entry point. | ||
| pub struct Cli; | ||
| impl Cli { | ||
| /// Parse arguments and run the transaction visualizer. | ||
| pub fn execute() -> Result<(), String> { | ||
| let args = Args::parse(); | ||
| let chain = parse_chain(&args.chain); | ||
| let plugins = crate::build_plugins(&args); | ||
| /// Resolves chain + plugins + per-chain metadata, returning a ready-to-use registry. | ||
| /// | ||
| /// Shared by `decode` and `serve`. On invalid chain or metadata error, returns | ||
| /// an `Err(String)` with a user-facing message — the caller is responsible for | ||
| /// printing it and exiting non-zero. | ||
| pub(crate) struct Runtime { | ||
| pub registry: TransactionConverterRegistry, | ||
| pub options: VisualSignOptions, | ||
| } | ||
|
|
||
| let mut registry = TransactionConverterRegistry::new(); | ||
| for plugin in &plugins { | ||
| plugin.register(&mut registry); | ||
| } | ||
| pub(crate) fn prepare_runtime( | ||
| chain_str: &str, | ||
| network: Option<String>, | ||
| plugin_args: &crate::PluginArgs, | ||
| ) -> Result<Runtime, String> { | ||
| let chain = parse_chain(chain_str); | ||
| let plugins = crate::build_plugins(plugin_args); | ||
|
|
||
| let plugin = plugins.iter().find(|p| p.chain() == chain).ok_or_else(|| { | ||
| let supported: Vec<String> = plugins | ||
| .iter() | ||
| .map(|p| p.chain().as_str().to_lowercase()) | ||
| .collect(); | ||
| let supported_str = if supported.is_empty() { | ||
| "none".to_string() | ||
| } else { | ||
| supported.join(", ") | ||
| }; | ||
| if chain == Chain::Unspecified { | ||
| format!( | ||
| "unrecognized chain '{}'.\nSupported chains: {supported_str}", | ||
| args.chain, | ||
| ) | ||
| } else { | ||
| format!( | ||
| "chain '{}' is not supported by this CLI build.\n\ | ||
| Supported chains: {supported_str}", | ||
| args.chain, | ||
| ) | ||
| } | ||
| })?; | ||
| let mut registry = TransactionConverterRegistry::new(); | ||
| for plugin in &plugins { | ||
| plugin.register(&mut registry); | ||
| } | ||
|
|
||
| let chain_metadata = plugin.create_metadata(args.network.clone())?; | ||
| let plugin = plugins.iter().find(|p| p.chain() == chain); | ||
|
|
||
| let options = VisualSignOptions { | ||
| decode_transfers: true, | ||
| transaction_name: None, | ||
| metadata: chain_metadata, | ||
| developer_config: Some(DeveloperConfig { | ||
| allow_signed_transactions: true, | ||
| }), | ||
| let Some(plugin) = plugin else { | ||
| let supported: Vec<String> = plugins | ||
| .iter() | ||
| .map(|p| p.chain().as_str().to_lowercase()) | ||
| .collect(); | ||
| let supported_str = if supported.is_empty() { | ||
| "none".to_string() | ||
| } else { | ||
| supported.join(", ") | ||
| }; | ||
| if chain == Chain::Unspecified { | ||
| return Err(format!( | ||
| "unrecognized chain '{chain_str}'.\nSupported chains: {supported_str}" | ||
| )); | ||
| } | ||
| return Err(format!( | ||
| "chain '{chain_str}' is not supported by this CLI build.\nSupported chains: {supported_str}" | ||
| )); | ||
| }; | ||
|
|
||
| let raw_tx = match crate::tx_input::resolve_transaction_input(&args.transaction) { | ||
| Ok(tx) => tx, | ||
| Err(e) => { | ||
| eprintln!("Error: {e}"); | ||
| std::process::exit(1); | ||
| } | ||
| }; | ||
| let chain_metadata = plugin.create_metadata(network)?; | ||
|
|
||
| let options = VisualSignOptions { | ||
| decode_transfers: true, | ||
| transaction_name: None, | ||
| metadata: chain_metadata, | ||
| developer_config: Some(DeveloperConfig { | ||
| allow_signed_transactions: true, | ||
| }), | ||
| }; | ||
|
|
||
| Ok(Runtime { registry, options }) | ||
| } | ||
|
|
||
| fn execute_decode(args: &DecodeArgs) { | ||
| let plugin_args = args.plugin_args(); | ||
| let runtime = match prepare_runtime(&args.chain, args.network.clone(), &plugin_args) { | ||
| Ok(r) => r, | ||
| Err(e) => { | ||
| eprintln!("Error: {e}"); | ||
| std::process::exit(1); | ||
| } | ||
| }; | ||
|
|
||
| let raw_tx = match crate::tx_input::resolve_transaction_input(&args.transaction) { | ||
| Ok(tx) => tx, | ||
| Err(e) => { | ||
| eprintln!("Error: {e}"); | ||
| std::process::exit(1); | ||
| } | ||
| }; | ||
|
|
||
| if let Err(e) = parse_and_display( | ||
| &args.chain, | ||
| &raw_tx, | ||
| &runtime.registry, | ||
| runtime.options, | ||
| args.output, | ||
| args.condensed_only, | ||
| ) { | ||
| eprintln!("Error: {e}"); | ||
| std::process::exit(1); | ||
| } | ||
| } | ||
|
|
||
| parse_and_display( | ||
| &args.chain, | ||
| &raw_tx, | ||
| ®istry, | ||
| options, | ||
| args.output, | ||
| args.condensed_only, | ||
| ) | ||
| /// CLI entry point. | ||
| pub struct Cli; | ||
| impl Cli { | ||
| /// Parse arguments and run the transaction visualizer. | ||
| pub fn execute() -> Result<(), String> { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
After the refactor, both Either change the signature to Found by Claude on behalf of @pepe-anchor.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed. |
||
| let args = Args::parse(); | ||
| match &args.command { | ||
| Command::Decode(a) => execute_decode(a), | ||
| #[cfg(feature = "serve")] | ||
| Command::Serve(a) => crate::serve::execute_serve(a), | ||
| } | ||
| Ok(()) | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Confirm no external callers of the old flat
--chain/-tsyntaxThe old
parser_cli --chain ethereum -t <hex>interface is hard-broken. In-tree tests and fixtures were updated correctly.Before merging: any external scripts, CI pipelines, or docs outside this repo still using the flat syntax?
Found by Claude on behalf of @pepe-anchor.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Confirmed: searched anchorageoss/* for
parser_cli --chainandparser_cli -tinvocations outside this repo viagh code-searchand found none. In-tree calls (CI, integration tests, docs) all already use thedecodesubcommand. The break is intentional and pre-1.0; happy to ship a one-release transition alias (parser_cli --chain ... -t ...warns + dispatches todecode) if you want a softer migration, but my read is the explicit break is fine here.