diff --git a/CHANGELOG.md b/CHANGELOG.md index 1d079256f..4bb807b4c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,7 +26,8 @@ * [BREAKING] Added genesis commitment header to `TonicRpcClient` requests (#1045). * Added authentication arguments support to `TransactionRequest` ([#1121](https://github.com/0xMiden/miden-client/pull/1121)). * Added bindings for retrieving storage `AccountDelta` in the web client ([#1098](https://github.com/0xMiden/miden-client/pull/1098)). - +* Added `multicall` support for the CLI ([#1141](https://github.com/0xMiden/miden-client/pull/1141)) + ## 0.10.1 (2025-07-26) * Avoid passing unneeded nodes to `PartialMmr::from_parts` (#1081). diff --git a/bin/miden-cli/src/commands/account.rs b/bin/miden-cli/src/commands/account.rs index 8285c4425..ecffff190 100644 --- a/bin/miden-cli/src/commands/account.rs +++ b/bin/miden-cli/src/commands/account.rs @@ -9,7 +9,7 @@ use miden_objects::PrettyPrint; use crate::config::CliConfig; use crate::errors::CliError; use crate::utils::{load_config_file, load_faucet_details_map, parse_account_id, update_config}; -use crate::{CLIENT_BINARY_NAME, create_dynamic_table}; +use crate::{client_binary_name, create_dynamic_table}; // ACCOUNT COMMAND // ================================================================================================ @@ -319,7 +319,10 @@ pub(crate) fn maybe_set_default_account( let account_id = account_id.to_bech32(current_config.rpc.endpoint.0.to_network_id()?); println!("Setting account {account_id} as the default account ID."); - println!("You can unset it with `{CLIENT_BINARY_NAME} account --default none`."); + println!( + "You can unset it with `{} account --default none`.", + client_binary_name().display() + ); current_config.default_account_id = Some(account_id); Ok(()) diff --git a/bin/miden-cli/src/commands/new_account.rs b/bin/miden-cli/src/commands/new_account.rs index f56eaf303..35e0278f7 100644 --- a/bin/miden-cli/src/commands/new_account.rs +++ b/bin/miden-cli/src/commands/new_account.rs @@ -25,7 +25,7 @@ use tracing::debug; use crate::commands::account::maybe_set_default_account; use crate::errors::CliError; use crate::utils::load_config_file; -use crate::{CLIENT_BINARY_NAME, CliKeyStore}; +use crate::{CliKeyStore, client_binary_name}; // CLI TYPES // ================================================================================================ @@ -130,7 +130,8 @@ impl NewWalletCmd { println!("Successfully created new wallet."); println!( - "To view account details execute {CLIENT_BINARY_NAME} account -s {account_address}", + "To view account details execute {} account -s {account_address}", + client_binary_name().display() ); maybe_set_default_account(&mut current_config, new_account.id())?; @@ -193,7 +194,8 @@ impl NewAccountCmd { println!("Successfully created new account."); println!( - "To view account details execute {CLIENT_BINARY_NAME} account -s {account_address}" + "To view account details execute {} account -s {account_address}", + client_binary_name().display() ); Ok(()) diff --git a/bin/miden-cli/src/errors.rs b/bin/miden-cli/src/errors.rs index 869100b19..a4dfed756 100644 --- a/bin/miden-cli/src/errors.rs +++ b/bin/miden-cli/src/errors.rs @@ -7,8 +7,7 @@ use miden_objects::{AccountError, AccountIdError, AssetError, NetworkIdError}; use miette::Diagnostic; use thiserror::Error; -use crate::CLIENT_BINARY_NAME; - +use crate::client_binary_name; type SourceError = Box; #[derive(Debug, Diagnostic, Error)] @@ -32,7 +31,9 @@ pub enum CliError { #[diagnostic( code(cli::config_error), help( - "Check if the configuration file exists and is well-formed. If it does not exist, run `{CLIENT_BINARY_NAME} init` command to create it." + "Check if the configuration file exists and is well-formed. If it does not exist, run `{} init` command to create it.", + client_binary_name().display() + ) )] Config(#[source] SourceError, String), diff --git a/bin/miden-cli/src/lib.rs b/bin/miden-cli/src/lib.rs index 7d25ff973..f18429079 100644 --- a/bin/miden-cli/src/lib.rs +++ b/bin/miden-cli/src/lib.rs @@ -1,7 +1,8 @@ use std::env; +use std::ffi::OsString; use std::sync::Arc; -use clap::Parser; +use clap::{Parser, Subcommand}; use comfy_table::{Attribute, Cell, ContentArrangement, Table, presets}; use errors::CliError; use miden_client::account::AccountHeader; @@ -38,7 +39,24 @@ mod utils; const CLIENT_CONFIG_FILE_NAME: &str = "miden-client.toml"; /// Client binary name. -pub const CLIENT_BINARY_NAME: &str = "miden-client"; +/// +/// If, for whatever reason, we fail to obtain the client's executable name, +/// then we simply display the standard "miden-client". +pub fn client_binary_name() -> OsString { + std::env::current_exe() + .inspect_err(|e| { + eprintln!( + "WARNING: Couldn't obtain the path of the current executable because of {e}.\ + Defaulting to miden-client." + ); + }) + .and_then(|executable_path| { + executable_path.file_name().map(std::ffi::OsStr::to_os_string).ok_or( + std::io::Error::other("Couldn't obtain the file name of the current executable"), + ) + }) + .unwrap_or(OsString::from("miden-client")) +} /// Number of blocks that must elapse after a transaction’s reference block before it is marked /// stale and discarded. @@ -52,14 +70,55 @@ const TX_GRACEFUL_BLOCK_DELTA: u32 = 20; version, rename_all = "kebab-case" )] -pub struct Cli { +#[command(multicall(true))] +pub struct MidenClientCli { #[command(subcommand)] - action: Command, + behavior: Behavior, +} + +impl From for Cli { + fn from(value: MidenClientCli) -> Self { + match value.behavior { + Behavior::MidenClient { cli } => cli, + Behavior::External(args) => Cli::parse_from(args).set_external(), + } + } +} + +#[derive(Debug, Subcommand)] +#[command(rename_all = "kebab-case")] +enum Behavior { + /// The Miden Client CLI. + MidenClient { + #[command(flatten)] + cli: Cli, + }, + /// Used when the Miden Client CLI is called under a different name, like + /// when it is called from [Midenup](https://github.com/0xMiden/midenup). + /// Vec holds the "raw" arguments passed to the command line, + /// analogous to `argv`. + #[command(external_subcommand)] + External(Vec), +} + +#[derive(Parser, Debug)] +#[command(name = "miden-client")] +pub struct Cli { /// Activates the executor's debug mode, which enables debug output for scripts /// that were compiled and executed with this mode. #[arg(short, long, default_value_t = false)] debug: bool, + + #[command(subcommand)] + action: Command, + + /// Indicates whether the client's CLI is being called directly, or + /// externally under an alias (like in the case of + /// [Midenup](https://github.com/0xMiden/midenup). + #[arg(skip)] + #[allow(unused)] + external: bool, } /// CLI actions. @@ -150,6 +209,11 @@ impl Cli { Command::ConsumeNotes(consume_notes) => consume_notes.execute(client).await, } } + + fn set_external(mut self) -> Self { + self.external = true; + self + } } pub fn create_dynamic_table(headers: &[&str]) -> Table { diff --git a/bin/miden-cli/src/main.rs b/bin/miden-cli/src/main.rs index c7b7fda67..27ab8440c 100644 --- a/bin/miden-cli/src/main.rs +++ b/bin/miden-cli/src/main.rs @@ -1,14 +1,17 @@ -use miden_client_cli::Cli; +use clap::FromArgMatches; +use miden_client_cli::{Cli, MidenClientCli}; extern crate std; #[tokio::main] async fn main() -> miette::Result<()> { - use clap::Parser; - tracing_subscriber::fmt::init(); + // read command-line args - let cli = Cli::parse(); + let input = ::command(); + let matches = input.get_matches(); + let parsed = MidenClientCli::from_arg_matches(&matches).unwrap_or_else(|err| err.exit()); + let cli: Cli = parsed.into(); // execute cli action Ok(cli.execute().await?)