From 2a9a1bfb6c7dab1c088476cb7b140985409a1227 Mon Sep 17 00:00:00 2001 From: /alex/ Date: Fri, 28 Jul 2023 09:07:40 +0200 Subject: [PATCH] feat(cli): Add cli command to list wallet accounts (#950) * add cli command to list accounts * add list-accounts as wallet command * review suggestions * some fixes * update changelog * nit * also print account indexes * format as table * undo breaking change * nit --- cli/CHANGELOG.md | 6 +++ cli/src/account.rs | 91 ++++++++++++++++++++--------------- cli/src/account_completion.rs | 3 +- cli/src/command/wallet.rs | 16 ++++++ cli/src/helper.rs | 4 +- cli/src/main.rs | 9 ++-- cli/src/wallet.rs | 8 ++- sdk/src/wallet/core/mod.rs | 6 +-- 8 files changed, 90 insertions(+), 53 deletions(-) diff --git a/cli/CHANGELOG.md b/cli/CHANGELOG.md index bba6480056..38bd56cfd9 100644 --- a/cli/CHANGELOG.md +++ b/cli/CHANGELOG.md @@ -19,6 +19,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Security --> +## 1.1.0 - 2023-MM-DD + +### Added + +`WalletCommand::Accounts` variant to list all available accounts in a wallet; + ## 1.0.0 - 2023-07-27 First release of the `cli-wallet`. diff --git a/cli/src/account.rs b/cli/src/account.rs index 15a08f25ec..f5a63acfa1 100644 --- a/cli/src/account.rs +++ b/cli/src/account.rs @@ -4,7 +4,7 @@ use clap::Parser; use colored::Colorize; use dialoguer::Input; -use iota_sdk::wallet::Account; +use iota_sdk::wallet::{Account, Wallet}; use crate::{ account_completion::ACCOUNT_COMPLETION, @@ -25,10 +25,10 @@ use crate::{ }; // loop on the account prompt -pub async fn account_prompt(account: Account) -> Result<(), Error> { +pub async fn account_prompt(wallet: &Wallet, account: &Account) -> Result<(), Error> { let mut history = AccountHistory::default(); loop { - match account_prompt_internal(account.clone(), &mut history).await { + match account_prompt_internal(wallet, account, &mut history).await { Ok(true) => { return Ok(()); } @@ -41,7 +41,11 @@ pub async fn account_prompt(account: Account) -> Result<(), Error> { } // loop on the account prompt -pub async fn account_prompt_internal(account: Account, history: &mut AccountHistory) -> Result { +pub async fn account_prompt_internal( + wallet: &Wallet, + account: &Account, + history: &mut AccountHistory, +) -> Result { let alias = { let account = account.details().await; account.alias().clone() @@ -52,11 +56,20 @@ pub async fn account_prompt_internal(account: Account, history: &mut AccountHist .completion_with(&ACCOUNT_COMPLETION) .interact_text()?; match command.as_str() { - "h" => print_account_help(), - "clear" => { + "h" | "help" => print_account_help(), + "c" | "clear" => { // Clear console let _ = std::process::Command::new("clear").status(); } + "accounts" => { + // List all accounts + let accounts = wallet.get_accounts().await?; + println!("INDEX\tALIAS"); + for account in accounts { + let details = &*account.details().await; + println!("{}\t{}", details.index(), details.alias()); + } + } _ => { // Prepend `Account: ` so the parsing will be correct let command = format!("Account: {}", command.trim()); @@ -68,16 +81,16 @@ pub async fn account_prompt_internal(account: Account, history: &mut AccountHist } }; if let Err(err) = match account_cli.command { - AccountCommand::Addresses => addresses_command(&account).await, - AccountCommand::Balance { addresses } => balance_command(&account, addresses).await, + AccountCommand::Addresses => addresses_command(account).await, + AccountCommand::Balance { addresses } => balance_command(account, addresses).await, AccountCommand::BurnNativeToken { token_id, amount } => { - burn_native_token_command(&account, token_id, amount).await + burn_native_token_command(account, token_id, amount).await } - AccountCommand::BurnNft { nft_id } => burn_nft_command(&account, nft_id).await, - AccountCommand::Claim { output_id } => claim_command(&account, output_id).await, - AccountCommand::ClaimableOutputs => claimable_outputs_command(&account).await, - AccountCommand::Consolidate => consolidate_command(&account).await, - AccountCommand::CreateAliasOutput => create_alias_outputs_command(&account).await, + AccountCommand::BurnNft { nft_id } => burn_nft_command(account, nft_id).await, + AccountCommand::Claim { output_id } => claim_command(account, output_id).await, + AccountCommand::ClaimableOutputs => claimable_outputs_command(account).await, + AccountCommand::Consolidate => consolidate_command(account).await, + AccountCommand::CreateAliasOutput => create_alias_outputs_command(account).await, AccountCommand::CreateNativeToken { circulating_supply, maximum_supply, @@ -85,24 +98,24 @@ pub async fn account_prompt_internal(account: Account, history: &mut AccountHist foundry_metadata_file, } => { create_native_token_command( - &account, + account, circulating_supply, maximum_supply, bytes_from_hex_or_file(foundry_metadata_hex, foundry_metadata_file).await?, ) .await } - AccountCommand::DestroyAlias { alias_id } => destroy_alias_command(&account, alias_id).await, - AccountCommand::DestroyFoundry { foundry_id } => destroy_foundry_command(&account, foundry_id).await, + AccountCommand::DestroyAlias { alias_id } => destroy_alias_command(account, alias_id).await, + AccountCommand::DestroyFoundry { foundry_id } => destroy_foundry_command(account, foundry_id).await, AccountCommand::Exit => { return Ok(true); } - AccountCommand::Faucet { address, url } => faucet_command(&account, address, url).await, + AccountCommand::Faucet { address, url } => faucet_command(account, address, url).await, AccountCommand::MeltNativeToken { token_id, amount } => { - melt_native_token_command(&account, token_id, amount).await + melt_native_token_command(account, token_id, amount).await } AccountCommand::MintNativeToken { token_id, amount } => { - mint_native_token(&account, token_id, amount).await + mint_native_token(account, token_id, amount).await } AccountCommand::MintNft { address, @@ -115,7 +128,7 @@ pub async fn account_prompt_internal(account: Account, history: &mut AccountHist issuer, } => { mint_nft_command( - &account, + account, address, bytes_from_hex_or_file(immutable_metadata_hex, immutable_metadata_file).await?, bytes_from_hex_or_file(metadata_hex, metadata_file).await?, @@ -125,10 +138,10 @@ pub async fn account_prompt_internal(account: Account, history: &mut AccountHist ) .await } - AccountCommand::NewAddress => new_address_command(&account).await, - AccountCommand::NodeInfo => node_info_command(&account).await, - AccountCommand::Output { output_id } => output_command(&account, output_id).await, - AccountCommand::Outputs => outputs_command(&account).await, + AccountCommand::NewAddress => new_address_command(account).await, + AccountCommand::NodeInfo => node_info_command(account).await, + AccountCommand::Output { output_id } => output_command(account, output_id).await, + AccountCommand::Outputs => outputs_command(account).await, AccountCommand::Send { address, amount, @@ -142,7 +155,7 @@ pub async fn account_prompt_internal(account: Account, history: &mut AccountHist allow_micro_amount }; send_command( - &account, + account, address, amount, return_address, @@ -156,22 +169,22 @@ pub async fn account_prompt_internal(account: Account, history: &mut AccountHist token_id, amount, gift_storage_deposit, - } => send_native_token_command(&account, address, token_id, amount, gift_storage_deposit).await, - AccountCommand::SendNft { address, nft_id } => send_nft_command(&account, address, nft_id).await, - AccountCommand::Sync => sync_command(&account).await, - AccountCommand::Transaction { transaction_id } => transaction_command(&account, &transaction_id).await, - AccountCommand::Transactions { show_details } => transactions_command(&account, show_details).await, - AccountCommand::UnspentOutputs => unspent_outputs_command(&account).await, - AccountCommand::Vote { event_id, answers } => vote_command(&account, event_id, answers).await, - AccountCommand::StopParticipating { event_id } => stop_participating_command(&account, event_id).await, + } => send_native_token_command(account, address, token_id, amount, gift_storage_deposit).await, + AccountCommand::SendNft { address, nft_id } => send_nft_command(account, address, nft_id).await, + AccountCommand::Sync => sync_command(account).await, + AccountCommand::Transaction { transaction_id } => transaction_command(account, &transaction_id).await, + AccountCommand::Transactions { show_details } => transactions_command(account, show_details).await, + AccountCommand::UnspentOutputs => unspent_outputs_command(account).await, + AccountCommand::Vote { event_id, answers } => vote_command(account, event_id, answers).await, + AccountCommand::StopParticipating { event_id } => stop_participating_command(account, event_id).await, AccountCommand::ParticipationOverview { event_ids } => { let event_ids = (!event_ids.is_empty()).then_some(event_ids); - participation_overview_command(&account, event_ids).await + participation_overview_command(account, event_ids).await } - AccountCommand::VotingPower => voting_power_command(&account).await, - AccountCommand::IncreaseVotingPower { amount } => increase_voting_power_command(&account, amount).await, - AccountCommand::DecreaseVotingPower { amount } => decrease_voting_power_command(&account, amount).await, - AccountCommand::VotingOutput => voting_output_command(&account).await, + AccountCommand::VotingPower => voting_power_command(account).await, + AccountCommand::IncreaseVotingPower { amount } => increase_voting_power_command(account, amount).await, + AccountCommand::DecreaseVotingPower { amount } => decrease_voting_power_command(account, amount).await, + AccountCommand::VotingOutput => voting_output_command(account).await, } { println_log_error!("{err}"); } diff --git a/cli/src/account_completion.rs b/cli/src/account_completion.rs index 8c05db9e31..1bdfdd54d6 100644 --- a/cli/src/account_completion.rs +++ b/cli/src/account_completion.rs @@ -4,11 +4,12 @@ use dialoguer::Completion; pub(crate) struct AccountCompletion<'a> { - options: [&'a str; 37], + options: [&'a str; 38], } pub(crate) const ACCOUNT_COMPLETION: AccountCompletion = AccountCompletion { options: [ + "accounts", "addresses", "balance", "burn-native-token", diff --git a/cli/src/command/wallet.rs b/cli/src/command/wallet.rs index ff74bc9f76..0200637b7a 100644 --- a/cli/src/command/wallet.rs +++ b/cli/src/command/wallet.rs @@ -46,6 +46,8 @@ pub struct WalletCli { #[derive(Debug, Clone, Subcommand)] pub enum WalletCommand { + /// List all accounts. + Accounts, /// Create a stronghold backup file. Backup { /// Path of the created stronghold backup file. @@ -107,6 +109,20 @@ impl Default for InitParameters { } } +pub async fn accounts_command(storage_path: &Path, snapshot_path: &Path) -> Result<(), Error> { + let password = get_password("Stronghold password", false)?; + let wallet = unlock_wallet(storage_path, snapshot_path, password).await?; + let accounts = wallet.get_accounts().await?; + + println!("INDEX\tALIAS"); + for account in accounts { + let details = &*account.details().await; + println!("{}\t{}", details.index(), details.alias()); + } + + Ok(()) +} + pub async fn backup_command(storage_path: &Path, snapshot_path: &Path, backup_path: &Path) -> Result<(), Error> { let password = get_password("Stronghold password", !snapshot_path.exists())?; let wallet = unlock_wallet(storage_path, snapshot_path, password.clone()).await?; diff --git a/cli/src/helper.rs b/cli/src/helper.rs index f0915e31d9..05f84293f2 100644 --- a/cli/src/helper.rs +++ b/cli/src/helper.rs @@ -77,11 +77,11 @@ pub async fn pick_account(wallet: &Wallet) -> Result, Error> { 1 => Ok(Some(accounts.swap_remove(0))), _ => { // fetch all available account aliases to display to the user - let aliases = wallet.get_account_aliases().await?; + let account_aliases = wallet.get_account_aliases().await?; let index = Select::with_theme(&ColorfulTheme::default()) .with_prompt("Select an account:") - .items(&aliases) + .items(&account_aliases) .default(0) .interact_on(&Term::stderr())?; diff --git a/cli/src/main.rs b/cli/src/main.rs index d6b055e10d..0c4386a9a9 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -48,12 +48,9 @@ fn logger_init(cli: &WalletCli) -> Result<(), Error> { } async fn run(cli: WalletCli) -> Result<(), Error> { - let (wallet, account) = new_wallet(cli).await?; - - if let Some(wallet) = wallet { - if let Some(account) = account { - account::account_prompt(wallet.get_account(account).await?).await?; - } + if let (Some(wallet), Some(account)) = new_wallet(cli).await? { + let account = wallet.get_account(account).await?; + account::account_prompt(&wallet, &account).await?; } Ok(()) diff --git a/cli/src/wallet.rs b/cli/src/wallet.rs index 07fe35f6c4..8dc2662f0e 100644 --- a/cli/src/wallet.rs +++ b/cli/src/wallet.rs @@ -7,7 +7,7 @@ use iota_sdk::wallet::Wallet; use crate::{ command::wallet::{ - add_account, backup_command, change_password_command, init_command, + accounts_command, add_account, backup_command, change_password_command, init_command, migrate_stronghold_snapshot_v2_to_v3_command, mnemonic_command, new_account_command, node_info_command, restore_command, set_node_url_command, sync_command, unlock_wallet, InitParameters, WalletCli, WalletCommand, }, @@ -22,6 +22,10 @@ pub async fn new_wallet(cli: WalletCli) -> Result<(Option, Option { + accounts_command(storage_path, snapshot_path).await?; + return Ok((None, None)); + } WalletCommand::Init(init_parameters) => { let wallet = init_command(storage_path, snapshot_path, init_parameters).await?; (Some(wallet), None) @@ -108,7 +112,7 @@ async fn create_initial_account(wallet: Wallet) -> Result<(Option, Optio if get_decision("Create initial account?")? { let alias = get_account_alias("New account alias", &wallet).await?; let alias = add_account(&wallet, Some(alias)).await?; - println_log_info!("Created initial account. Type `help` to see all available commands."); + println_log_info!("Created initial account.\nType `help` to see all available account commands."); Ok((Some(wallet), Some(alias))) } else { Ok((Some(wallet), None)) diff --git a/sdk/src/wallet/core/mod.rs b/sdk/src/wallet/core/mod.rs index f007ac896a..170570d6c0 100644 --- a/sdk/src/wallet/core/mod.rs +++ b/sdk/src/wallet/core/mod.rs @@ -97,11 +97,11 @@ where /// Get all account aliases pub async fn get_account_aliases(&self) -> crate::wallet::Result> { let accounts = self.accounts.read().await; - let mut aliases = Vec::with_capacity(accounts.len()); + let mut account_aliases = Vec::with_capacity(accounts.len()); for handle in accounts.iter() { - aliases.push(handle.details().await.alias().clone()); + account_aliases.push(handle.details().await.alias().clone()); } - Ok(aliases) + Ok(account_aliases) } /// Removes the latest account (account with the largest account index).