mirror of
https://github.com/logos-blockchain/logos-execution-zone.git
synced 2026-04-24 02:13:49 +00:00
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.
This commit is contained in:
parent
bbef426f86
commit
636a3daedc
@ -11,17 +11,19 @@ use key_protocol::{
|
|||||||
use log::debug;
|
use log::debug;
|
||||||
use nssa::program::Program;
|
use nssa::program::Program;
|
||||||
|
|
||||||
use crate::config::{InitialAccountData, PersistentAccountData, WalletConfig};
|
use crate::config::{InitialAccountData, Label, PersistentAccountData, WalletConfig};
|
||||||
|
|
||||||
pub struct WalletChainStore {
|
pub struct WalletChainStore {
|
||||||
pub user_data: NSSAUserData,
|
pub user_data: NSSAUserData,
|
||||||
pub wallet_config: WalletConfig,
|
pub wallet_config: WalletConfig,
|
||||||
|
pub labels: HashMap<String, Label>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WalletChainStore {
|
impl WalletChainStore {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
config: WalletConfig,
|
config: WalletConfig,
|
||||||
persistent_accounts: Vec<PersistentAccountData>,
|
persistent_accounts: Vec<PersistentAccountData>,
|
||||||
|
labels: HashMap<String, Label>,
|
||||||
) -> Result<Self> {
|
) -> Result<Self> {
|
||||||
if persistent_accounts.is_empty() {
|
if persistent_accounts.is_empty() {
|
||||||
anyhow::bail!("Roots not found; please run setup beforehand");
|
anyhow::bail!("Roots not found; please run setup beforehand");
|
||||||
@ -85,6 +87,7 @@ impl WalletChainStore {
|
|||||||
private_tree,
|
private_tree,
|
||||||
)?,
|
)?,
|
||||||
wallet_config: config,
|
wallet_config: config,
|
||||||
|
labels,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -120,6 +123,7 @@ impl WalletChainStore {
|
|||||||
private_tree,
|
private_tree,
|
||||||
)?,
|
)?,
|
||||||
wallet_config: config,
|
wallet_config: config,
|
||||||
|
labels: HashMap::new(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -291,6 +295,6 @@ mod tests {
|
|||||||
let config = create_sample_wallet_config();
|
let config = create_sample_wallet_config();
|
||||||
let accs = create_sample_persistent_accounts();
|
let accs = create_sample_persistent_accounts();
|
||||||
|
|
||||||
let _ = WalletChainStore::new(config.clone(), accs).unwrap();
|
let _ = WalletChainStore::new(config.clone(), accs, HashMap::new()).unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,6 +9,7 @@ use serde::Serialize;
|
|||||||
use crate::{
|
use crate::{
|
||||||
TokenDefinition, TokenHolding, WalletCore,
|
TokenDefinition, TokenHolding, WalletCore,
|
||||||
cli::{SubcommandReturnValue, WalletSubcommand},
|
cli::{SubcommandReturnValue, WalletSubcommand},
|
||||||
|
config::Label,
|
||||||
helperfunctions::{AccountPrivacyKind, HumanReadableAccount, parse_addr_with_privacy_prefix},
|
helperfunctions::{AccountPrivacyKind, HumanReadableAccount, parse_addr_with_privacy_prefix},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -39,6 +40,15 @@ pub enum AccountSubcommand {
|
|||||||
#[arg(short, long)]
|
#[arg(short, long)]
|
||||||
long: bool,
|
long: bool,
|
||||||
},
|
},
|
||||||
|
/// 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
|
/// Represents generic register CLI subcommand
|
||||||
@ -218,9 +228,13 @@ impl WalletSubcommand for AccountSubcommand {
|
|||||||
keys,
|
keys,
|
||||||
account_id,
|
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 {
|
let account = match addr_kind {
|
||||||
AccountPrivacyKind::Public => {
|
AccountPrivacyKind::Public => {
|
||||||
@ -316,32 +330,35 @@ impl WalletSubcommand for AccountSubcommand {
|
|||||||
}
|
}
|
||||||
AccountSubcommand::List { long } => {
|
AccountSubcommand::List { long } => {
|
||||||
let user_data = &wallet_core.storage.user_data;
|
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()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
if !long {
|
if !long {
|
||||||
let accounts = user_data
|
let accounts = user_data
|
||||||
.default_pub_account_signing_keys
|
.default_pub_account_signing_keys
|
||||||
.keys()
|
.keys()
|
||||||
.map(|id| format!("Preconfigured Public/{id}"))
|
.map(|id| format_with_label(&format!("Preconfigured Public/{id}"), id))
|
||||||
.chain(
|
.chain(user_data.default_user_private_accounts.keys().map(|id| {
|
||||||
user_data
|
format_with_label(&format!("Preconfigured Private/{id}"), id)
|
||||||
.default_user_private_accounts
|
}))
|
||||||
.keys()
|
.chain(user_data.public_key_tree.account_id_map.iter().map(
|
||||||
.map(|id| format!("Preconfigured Private/{id}")),
|
|(id, chain_index)| {
|
||||||
)
|
format_with_label(&format!("{chain_index} Public/{id}"), id)
|
||||||
.chain(
|
},
|
||||||
user_data
|
))
|
||||||
.public_key_tree
|
.chain(user_data.private_key_tree.account_id_map.iter().map(
|
||||||
.account_id_map
|
|(id, chain_index)| {
|
||||||
.iter()
|
format_with_label(&format!("{chain_index} Private/{id}"), id)
|
||||||
.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}")),
|
|
||||||
)
|
|
||||||
.format("\n");
|
.format("\n");
|
||||||
|
|
||||||
println!("{accounts}");
|
println!("{accounts}");
|
||||||
@ -351,7 +368,10 @@ impl WalletSubcommand for AccountSubcommand {
|
|||||||
// Detailed listing with --long flag
|
// Detailed listing with --long flag
|
||||||
// Preconfigured public accounts
|
// Preconfigured public accounts
|
||||||
for id in user_data.default_pub_account_signing_keys.keys() {
|
for id in user_data.default_pub_account_signing_keys.keys() {
|
||||||
println!("Preconfigured Public/{id}");
|
println!(
|
||||||
|
"{}",
|
||||||
|
format_with_label(&format!("Preconfigured Public/{id}"), id)
|
||||||
|
);
|
||||||
match wallet_core.get_account_public(*id).await {
|
match wallet_core.get_account_public(*id).await {
|
||||||
Ok(account) if account != Account::default() => {
|
Ok(account) if account != Account::default() => {
|
||||||
let (description, json_view) = format_account_details(&account);
|
let (description, json_view) = format_account_details(&account);
|
||||||
@ -365,7 +385,10 @@ impl WalletSubcommand for AccountSubcommand {
|
|||||||
|
|
||||||
// Preconfigured private accounts
|
// Preconfigured private accounts
|
||||||
for id in user_data.default_user_private_accounts.keys() {
|
for id in user_data.default_user_private_accounts.keys() {
|
||||||
println!("Preconfigured Private/{id}");
|
println!(
|
||||||
|
"{}",
|
||||||
|
format_with_label(&format!("Preconfigured Private/{id}"), id)
|
||||||
|
);
|
||||||
match wallet_core.get_account_private(id) {
|
match wallet_core.get_account_private(id) {
|
||||||
Some(account) if account != Account::default() => {
|
Some(account) if account != Account::default() => {
|
||||||
let (description, json_view) = format_account_details(&account);
|
let (description, json_view) = format_account_details(&account);
|
||||||
@ -379,7 +402,10 @@ impl WalletSubcommand for AccountSubcommand {
|
|||||||
|
|
||||||
// Public key tree accounts
|
// Public key tree accounts
|
||||||
for (id, chain_index) in user_data.public_key_tree.account_id_map.iter() {
|
for (id, chain_index) in user_data.public_key_tree.account_id_map.iter() {
|
||||||
println!("{chain_index} Public/{id}");
|
println!(
|
||||||
|
"{}",
|
||||||
|
format_with_label(&format!("{chain_index} Public/{id}"), id)
|
||||||
|
);
|
||||||
match wallet_core.get_account_public(*id).await {
|
match wallet_core.get_account_public(*id).await {
|
||||||
Ok(account) if account != Account::default() => {
|
Ok(account) if account != Account::default() => {
|
||||||
let (description, json_view) = format_account_details(&account);
|
let (description, json_view) = format_account_details(&account);
|
||||||
@ -393,7 +419,10 @@ impl WalletSubcommand for AccountSubcommand {
|
|||||||
|
|
||||||
// Private key tree accounts
|
// Private key tree accounts
|
||||||
for (id, chain_index) in user_data.private_key_tree.account_id_map.iter() {
|
for (id, chain_index) in user_data.private_key_tree.account_id_map.iter() {
|
||||||
println!("{chain_index} Private/{id}");
|
println!(
|
||||||
|
"{}",
|
||||||
|
format_with_label(&format!("{chain_index} Private/{id}"), id)
|
||||||
|
);
|
||||||
match wallet_core.get_account_private(id) {
|
match wallet_core.get_account_private(id) {
|
||||||
Some(account) if account != Account::default() => {
|
Some(account) if account != Account::default() => {
|
||||||
let (description, json_view) = format_account_details(&account);
|
let (description, json_view) = format_account_details(&account);
|
||||||
@ -405,6 +434,23 @@ impl WalletSubcommand for AccountSubcommand {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Ok(SubcommandReturnValue::Empty)
|
||||||
|
}
|
||||||
|
AccountSubcommand::Label { account_id, label } => {
|
||||||
|
let (account_id_str, _) = parse_addr_with_privacy_prefix(&account_id)?;
|
||||||
|
|
||||||
|
let old_label = wallet_core
|
||||||
|
.storage
|
||||||
|
.labels
|
||||||
|
.insert(account_id_str.clone(), Label::new(label.clone()));
|
||||||
|
|
||||||
|
wallet_core.store_persistent_data().await?;
|
||||||
|
|
||||||
|
if let Some(old) = old_label {
|
||||||
|
eprintln!("Warning: overriding existing label '{old}'");
|
||||||
|
}
|
||||||
|
println!("Label '{label}' set for account {account_id_str}");
|
||||||
|
|
||||||
Ok(SubcommandReturnValue::Empty)
|
Ok(SubcommandReturnValue::Empty)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
use std::{
|
use std::{
|
||||||
|
collections::HashMap,
|
||||||
io::{BufReader, Write as _},
|
io::{BufReader, Write as _},
|
||||||
path::Path,
|
path::Path,
|
||||||
str::FromStr,
|
str::FromStr,
|
||||||
@ -105,10 +106,30 @@ pub enum PersistentAccountData {
|
|||||||
Preconfigured(InitialAccountData),
|
Preconfigured(InitialAccountData),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A human-readable label for an account.
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||||
|
pub struct Label(String);
|
||||||
|
|
||||||
|
impl Label {
|
||||||
|
pub fn new(label: String) -> Self {
|
||||||
|
Self(label)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for Label {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{}", self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct PersistentStorage {
|
pub struct PersistentStorage {
|
||||||
pub accounts: Vec<PersistentAccountData>,
|
pub accounts: Vec<PersistentAccountData>,
|
||||||
pub last_synced_block: u64,
|
pub last_synced_block: u64,
|
||||||
|
/// Account labels keyed by account ID string (e.g.,
|
||||||
|
/// "2rnKprXqWGWJTkDZKsQbFXa4ctKRbapsdoTKQFnaVGG8")
|
||||||
|
#[serde(default)]
|
||||||
|
pub labels: HashMap<String, Label>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PersistentStorage {
|
impl PersistentStorage {
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
use std::{path::PathBuf, str::FromStr};
|
use std::{collections::HashMap, path::PathBuf, str::FromStr};
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use base64::{Engine, engine::general_purpose::STANDARD as BASE64};
|
use base64::{Engine, engine::general_purpose::STANDARD as BASE64};
|
||||||
@ -11,7 +11,7 @@ use serde::Serialize;
|
|||||||
use crate::{
|
use crate::{
|
||||||
HOME_DIR_ENV_VAR,
|
HOME_DIR_ENV_VAR,
|
||||||
config::{
|
config::{
|
||||||
InitialAccountData, InitialAccountDataPrivate, InitialAccountDataPublic,
|
InitialAccountData, InitialAccountDataPrivate, InitialAccountDataPublic, Label,
|
||||||
PersistentAccountDataPrivate, PersistentAccountDataPublic, PersistentStorage,
|
PersistentAccountDataPrivate, PersistentAccountDataPublic, PersistentStorage,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@ -57,6 +57,7 @@ pub fn fetch_persistent_storage_path() -> Result<PathBuf> {
|
|||||||
pub fn produce_data_for_storage(
|
pub fn produce_data_for_storage(
|
||||||
user_data: &NSSAUserData,
|
user_data: &NSSAUserData,
|
||||||
last_synced_block: u64,
|
last_synced_block: u64,
|
||||||
|
labels: HashMap<String, Label>,
|
||||||
) -> PersistentStorage {
|
) -> PersistentStorage {
|
||||||
let mut vec_for_storage = vec![];
|
let mut vec_for_storage = vec![];
|
||||||
|
|
||||||
@ -110,6 +111,7 @@ pub fn produce_data_for_storage(
|
|||||||
PersistentStorage {
|
PersistentStorage {
|
||||||
accounts: vec_for_storage,
|
accounts: vec_for_storage,
|
||||||
last_synced_block,
|
last_synced_block,
|
||||||
|
labels,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -148,6 +148,7 @@ impl WalletCore {
|
|||||||
let PersistentStorage {
|
let PersistentStorage {
|
||||||
accounts: persistent_accounts,
|
accounts: persistent_accounts,
|
||||||
last_synced_block,
|
last_synced_block,
|
||||||
|
labels,
|
||||||
} = PersistentStorage::from_path(&storage_path)
|
} = PersistentStorage::from_path(&storage_path)
|
||||||
.with_context(|| format!("Failed to read persistent storage at {storage_path:#?}"))?;
|
.with_context(|| format!("Failed to read persistent storage at {storage_path:#?}"))?;
|
||||||
|
|
||||||
@ -155,7 +156,7 @@ impl WalletCore {
|
|||||||
config_path,
|
config_path,
|
||||||
storage_path,
|
storage_path,
|
||||||
config_overrides,
|
config_overrides,
|
||||||
|config| WalletChainStore::new(config, persistent_accounts),
|
|config| WalletChainStore::new(config, persistent_accounts, labels),
|
||||||
last_synced_block,
|
last_synced_block,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -228,7 +229,11 @@ impl WalletCore {
|
|||||||
|
|
||||||
/// Store persistent data at home
|
/// Store persistent data at home
|
||||||
pub async fn store_persistent_data(&self) -> Result<()> {
|
pub async fn store_persistent_data(&self) -> Result<()> {
|
||||||
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 storage = serde_json::to_vec_pretty(&data)?;
|
||||||
|
|
||||||
let mut storage_file = tokio::fs::File::create(&self.storage_path).await?;
|
let mut storage_file = tokio::fs::File::create(&self.storage_path).await?;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user