From 5dac6d6e31e01c9aa29cff9b443a78a7db7e8381 Mon Sep 17 00:00:00 2001 From: fryorcraken Date: Wed, 7 Jan 2026 13:13:14 +1100 Subject: [PATCH] add `wallet account label` feature - can add a label to own account via `wallet` CLI - labels are displayed with `wallet account get` command - labels are displayed with `wallet account list` command - labels are persisted. --- wallet/src/chain_storage.rs | 6 ++- wallet/src/cli/account.rs | 71 ++++++++++++++++++++++++++--------- wallet/src/config.rs | 5 ++- wallet/src/helperfunctions.rs | 4 +- wallet/src/lib.rs | 9 ++++- 5 files changed, 72 insertions(+), 23 deletions(-) diff --git a/wallet/src/chain_storage.rs b/wallet/src/chain_storage.rs index 7d8ed50..b426376 100644 --- a/wallet/src/chain_storage.rs +++ b/wallet/src/chain_storage.rs @@ -16,12 +16,14 @@ use crate::config::{InitialAccountData, PersistentAccountData, WalletConfig}; pub struct WalletChainStore { pub user_data: NSSAUserData, pub wallet_config: WalletConfig, + pub labels: HashMap, } impl WalletChainStore { pub fn new( config: WalletConfig, persistent_accounts: Vec, + labels: HashMap, ) -> Result { if persistent_accounts.is_empty() { anyhow::bail!("Roots not found; please run setup beforehand"); @@ -85,6 +87,7 @@ impl WalletChainStore { private_tree, )?, wallet_config: config, + labels, }) } @@ -120,6 +123,7 @@ impl WalletChainStore { private_tree, )?, wallet_config: config, + labels: HashMap::new(), }) } @@ -291,6 +295,6 @@ mod tests { let config = create_sample_wallet_config(); let accs = create_sample_persistent_accounts(); - let _ = WalletChainStore::new(config.clone(), accs).unwrap(); + let _ = WalletChainStore::new(config.clone(), accs, HashMap::new()).unwrap(); } } diff --git a/wallet/src/cli/account.rs b/wallet/src/cli/account.rs index d10c8c5..136e49f 100644 --- a/wallet/src/cli/account.rs +++ b/wallet/src/cli/account.rs @@ -32,6 +32,15 @@ pub enum AccountSubcommand { /// List all accounts owned by the wallet #[command(visible_alias = "ls")] List {}, + /// Set a label for an account + Label { + /// Valid 32 byte base58 string with privacy prefix + #[arg(short, long)] + account_id: String, + /// The label to assign to the account + #[arg(short, long)] + label: String, + }, } /// Represents generic register CLI subcommand @@ -158,9 +167,13 @@ impl WalletSubcommand for AccountSubcommand { ) -> Result { match self { AccountSubcommand::Get { raw, account_id } => { - let (account_id, addr_kind) = parse_addr_with_privacy_prefix(&account_id)?; + let (account_id_str, addr_kind) = parse_addr_with_privacy_prefix(&account_id)?; - let account_id = account_id.parse()?; + let account_id: nssa::AccountId = account_id_str.parse()?; + + if let Some(label) = wallet_core.storage.labels.get(&account_id_str) { + println!("Label: {label}"); + } let account = match addr_kind { AccountPrivacyKind::Public => { @@ -254,35 +267,57 @@ impl WalletSubcommand for AccountSubcommand { } AccountSubcommand::List {} => { let user_data = &wallet_core.storage.user_data; + let labels = &wallet_core.storage.labels; + + let format_with_label = |prefix: &str, id: &nssa::AccountId| { + let id_str = id.to_string(); + if let Some(label) = labels.get(&id_str) { + format!("{prefix} [{label}]") + } else { + prefix.to_string() + } + }; + let accounts = user_data .default_pub_account_signing_keys .keys() - .map(|id| format!("Preconfigured Public/{id}")) + .map(|id| format_with_label(&format!("Preconfigured Public/{id}"), id)) .chain( user_data .default_user_private_accounts .keys() - .map(|id| format!("Preconfigured Private/{id}")), - ) - .chain( - user_data - .public_key_tree - .account_id_map - .iter() - .map(|(id, chain_index)| format!("{chain_index} Public/{id}")), - ) - .chain( - user_data - .private_key_tree - .account_id_map - .iter() - .map(|(id, chain_index)| format!("{chain_index} Private/{id}")), + .map(|id| format_with_label(&format!("Preconfigured Private/{id}"), id)), ) + .chain(user_data.public_key_tree.account_id_map.iter().map( + |(id, chain_index)| { + format_with_label(&format!("{chain_index} Public/{id}"), id) + }, + )) + .chain(user_data.private_key_tree.account_id_map.iter().map( + |(id, chain_index)| { + format_with_label(&format!("{chain_index} Private/{id}"), id) + }, + )) .format(",\n"); println!("{accounts}"); Ok(SubcommandReturnValue::Empty) } + AccountSubcommand::Label { account_id, label } => { + let (account_id_str, _) = parse_addr_with_privacy_prefix(&account_id)?; + + wallet_core + .storage + .labels + .insert(account_id_str.clone(), label.clone()); + + let path = wallet_core.store_persistent_data().await?; + + println!("Label '{label}' set for account {account_id_str}"); + println!("Stored persistent data at {path:#?}"); + + Ok(SubcommandReturnValue::Empty) + } } } } diff --git a/wallet/src/config.rs b/wallet/src/config.rs index c06ccc4..466a329 100644 --- a/wallet/src/config.rs +++ b/wallet/src/config.rs @@ -1,4 +1,4 @@ -use std::str::FromStr; +use std::{collections::HashMap, str::FromStr}; use key_protocol::key_management::{ KeyChain, @@ -103,6 +103,9 @@ pub enum PersistentAccountData { pub struct PersistentStorage { pub accounts: Vec, pub last_synced_block: u64, + /// Account labels keyed by account ID string (e.g., "2rnKprXqWGWJTkDZKsQbFXa4ctKRbapsdoTKQFnaVGG8") + #[serde(default)] + pub labels: HashMap, } impl InitialAccountData { diff --git a/wallet/src/helperfunctions.rs b/wallet/src/helperfunctions.rs index 5f1dcf7..ede279c 100644 --- a/wallet/src/helperfunctions.rs +++ b/wallet/src/helperfunctions.rs @@ -1,4 +1,4 @@ -use std::{path::PathBuf, str::FromStr}; +use std::{collections::HashMap, path::PathBuf, str::FromStr}; use anyhow::Result; use base64::{Engine, engine::general_purpose::STANDARD as BASE64}; @@ -134,6 +134,7 @@ pub async fn fetch_persistent_storage() -> Result { pub fn produce_data_for_storage( user_data: &NSSAUserData, last_synced_block: u64, + labels: HashMap, ) -> PersistentStorage { let mut vec_for_storage = vec![]; @@ -187,6 +188,7 @@ pub fn produce_data_for_storage( PersistentStorage { accounts: vec_for_storage, last_synced_block, + labels, } } diff --git a/wallet/src/lib.rs b/wallet/src/lib.rs index bad6435..daf11be 100644 --- a/wallet/src/lib.rs +++ b/wallet/src/lib.rs @@ -145,9 +145,10 @@ impl WalletCore { let PersistentStorage { accounts: persistent_accounts, last_synced_block, + labels, } = fetch_persistent_storage().await?; - let storage = WalletChainStore::new(config, persistent_accounts)?; + let storage = WalletChainStore::new(config, persistent_accounts, labels)?; Ok(Self { storage, @@ -186,7 +187,11 @@ impl WalletCore { let home = get_home()?; let storage_path = home.join("storage.json"); - let data = produce_data_for_storage(&self.storage.user_data, self.last_synced_block); + let data = produce_data_for_storage( + &self.storage.user_data, + self.last_synced_block, + self.storage.labels.clone(), + ); let storage = serde_json::to_vec_pretty(&data)?; let mut storage_file = tokio::fs::File::create(storage_path.as_path()).await?;