fix: account subcommand updated

This commit is contained in:
Oleksandr Pravdyvyi 2025-10-24 15:26:30 +03:00
parent 0384efc38f
commit 66ee0c5449
No known key found for this signature in database
GPG Key ID: 9F8955C63C443871
2 changed files with 202 additions and 77 deletions

View File

@ -1,21 +1,81 @@
use std::str::FromStr;
use anyhow::Result;
use base58::ToBase58;
use clap::Subcommand;
use common::transaction::NSSATransaction;
use nssa::Address;
use nssa::{Address, program::Program};
use serde::Serialize;
use crate::{
SubcommandReturnValue, WalletCore, cli::WalletSubcommand, helperfunctions::HumanReadableAccount,
SubcommandReturnValue, WalletCore,
cli::WalletSubcommand,
helperfunctions::{AddressPrivacyKind, HumanReadableAccount, parse_addr_with_privacy_prefix},
};
const TOKEN_DEFINITION_TYPE: u8 = 0;
const TOKEN_DEFINITION_DATA_SIZE: usize = 23;
const TOKEN_HOLDING_TYPE: u8 = 1;
const TOKEN_HOLDING_DATA_SIZE: usize = 49;
struct TokenDefinition {
#[allow(unused)]
account_type: u8,
name: [u8; 6],
total_supply: u128,
}
struct TokenHolding {
#[allow(unused)]
account_type: u8,
definition_id: Address,
balance: u128,
}
impl TokenDefinition {
fn parse(data: &[u8]) -> Option<Self> {
if data.len() != TOKEN_DEFINITION_DATA_SIZE || data[0] != TOKEN_DEFINITION_TYPE {
None
} else {
let account_type = data[0];
let name = data[1..7].try_into().unwrap();
let total_supply = u128::from_le_bytes(data[7..].try_into().unwrap());
Some(Self {
account_type,
name,
total_supply,
})
}
}
}
impl TokenHolding {
fn parse(data: &[u8]) -> Option<Self> {
if data.len() != TOKEN_HOLDING_DATA_SIZE || data[0] != TOKEN_HOLDING_TYPE {
None
} else {
let account_type = data[0];
let definition_id = Address::new(data[1..33].try_into().unwrap());
let balance = u128::from_le_bytes(data[33..].try_into().unwrap());
Some(Self {
definition_id,
balance,
account_type,
})
}
}
}
///Represents generic chain CLI subcommand
#[derive(Subcommand, Debug, Clone)]
pub enum AccountSubcommand {
///Get
#[command(subcommand)]
Get(GetSubcommand),
Get {
#[arg(long)]
raw: bool,
#[arg(short, long)]
addr: String,
},
///Fetch
#[command(subcommand)]
Fetch(FetchSubcommand),
@ -24,31 +84,6 @@ pub enum AccountSubcommand {
New(NewSubcommand),
}
///Represents generic getter CLI subcommand
#[derive(Subcommand, Debug, Clone)]
pub enum GetSubcommand {
///Get account `addr` balance
PublicAccountBalance {
#[arg(short, long)]
addr: String,
},
///Get account `addr` nonce
PublicAccountNonce {
#[arg(short, long)]
addr: String,
},
///Get account at address `addr`
PublicAccount {
#[arg(short, long)]
addr: String,
},
///Get private account with `addr` from storage
PrivateAccount {
#[arg(short, long)]
addr: String,
},
}
///Represents generic getter CLI subcommand
#[derive(Subcommand, Debug, Clone)]
pub enum FetchSubcommand {
@ -80,49 +115,6 @@ pub enum NewSubcommand {
Private {},
}
impl WalletSubcommand for GetSubcommand {
async fn handle_subcommand(
self,
wallet_core: &mut WalletCore,
) -> Result<SubcommandReturnValue> {
match self {
GetSubcommand::PublicAccountBalance { addr } => {
let addr = Address::from_str(&addr)?;
let balance = wallet_core.get_account_balance(addr).await?;
println!("Accounts {addr} balance is {balance}");
Ok(SubcommandReturnValue::Empty)
}
GetSubcommand::PublicAccountNonce { addr } => {
let addr = Address::from_str(&addr)?;
let nonce = wallet_core.get_accounts_nonces(vec![addr]).await?[0];
println!("Accounts {addr} nonce is {nonce}");
Ok(SubcommandReturnValue::Empty)
}
GetSubcommand::PublicAccount { addr } => {
let addr: Address = addr.parse()?;
let account = wallet_core.get_account_public(addr).await?;
let account_hr: HumanReadableAccount = account.clone().into();
println!("{}", serde_json::to_string(&account_hr).unwrap());
Ok(SubcommandReturnValue::Account(account))
}
GetSubcommand::PrivateAccount { addr } => {
let addr: Address = addr.parse()?;
if let Some(account) = wallet_core.get_account_private(&addr) {
println!("{}", serde_json::to_string(&account).unwrap());
} else {
println!("Private account not found.");
}
Ok(SubcommandReturnValue::Empty)
}
}
}
}
impl WalletSubcommand for FetchSubcommand {
async fn handle_subcommand(
self,
@ -237,14 +229,107 @@ impl WalletSubcommand for NewSubcommand {
}
}
#[derive(Debug, Serialize)]
pub struct AuthenticatedTransferAccountView {
pub balance: u128,
}
impl From<nssa::Account> for AuthenticatedTransferAccountView {
fn from(value: nssa::Account) -> Self {
Self {
balance: value.balance,
}
}
}
#[derive(Debug, Serialize)]
pub struct TokedDefinitionAccountView {
pub account_type: String,
pub name: String,
pub total_supply: u128,
}
impl From<TokenDefinition> for TokedDefinitionAccountView {
fn from(value: TokenDefinition) -> Self {
Self {
account_type: "Token definition".to_string(),
name: hex::encode(value.name),
total_supply: value.total_supply,
}
}
}
#[derive(Debug, Serialize)]
pub struct TokedHoldingAccountView {
pub account_type: String,
pub definition_id: String,
pub balance: u128,
}
impl From<TokenHolding> for TokedHoldingAccountView {
fn from(value: TokenHolding) -> Self {
Self {
account_type: "Token holding".to_string(),
definition_id: value.definition_id.to_string(),
balance: value.balance,
}
}
}
impl WalletSubcommand for AccountSubcommand {
async fn handle_subcommand(
self,
wallet_core: &mut WalletCore,
) -> Result<SubcommandReturnValue> {
match self {
AccountSubcommand::Get(get_subcommand) => {
get_subcommand.handle_subcommand(wallet_core).await
AccountSubcommand::Get { raw, addr } => {
let (addr, addr_kind) = parse_addr_with_privacy_prefix(&addr)?;
let account = match addr_kind {
AddressPrivacyKind::Public => wallet_core.get_account_public(addr).await?,
AddressPrivacyKind::Private => wallet_core
.get_account_private(&addr)
.ok_or(anyhow::anyhow!("Private account not found in storage"))?,
};
if raw {
let account_hr: HumanReadableAccount = account.clone().into();
println!("{}", serde_json::to_string(&account_hr).unwrap());
return Ok(SubcommandReturnValue::Empty);
}
let auth_tr_prog_id = Program::authenticated_transfer_program().id();
let token_prog_id = Program::token().id();
let acc_view = match &account.program_owner {
_ if &account.program_owner == &auth_tr_prog_id => {
let acc_view: AuthenticatedTransferAccountView = account.into();
serde_json::to_string(&acc_view)?
}
_ if &account.program_owner == &token_prog_id => {
if let Some(token_def) = TokenDefinition::parse(&account.data) {
let acc_view: TokedDefinitionAccountView = token_def.into();
serde_json::to_string(&acc_view)?
} else if let Some(token_hold) = TokenHolding::parse(&account.data) {
let acc_view: TokedHoldingAccountView = token_hold.into();
serde_json::to_string(&acc_view)?
} else {
anyhow::bail!("Invalid data for account {addr:#?} with token program");
}
}
_ => {
let account_hr: HumanReadableAccount = account.clone().into();
serde_json::to_string(&account_hr).unwrap()
}
};
println!("{}", acc_view);
Ok(SubcommandReturnValue::Empty)
}
AccountSubcommand::Fetch(fetch_subcommand) => {
fetch_subcommand.handle_subcommand(wallet_core).await

View File

@ -6,7 +6,7 @@ use tokio::io::AsyncReadExt;
use anyhow::Result;
use key_protocol::key_protocol_core::NSSAUserData;
use nssa::Account;
use nssa::{Account, Address};
use serde::Serialize;
use crate::{
@ -86,6 +86,30 @@ pub(crate) fn produce_random_nonces(size: usize) -> Vec<Nonce> {
result.into_iter().map(Nonce::from_le_bytes).collect()
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AddressPrivacyKind {
Public,
Private,
}
pub(crate) fn parse_addr_with_privacy_prefix(
addr_base58: &str,
) -> Result<(Address, AddressPrivacyKind)> {
if addr_base58.starts_with("Public/") {
Ok((
addr_base58.strip_prefix("Public/").unwrap().parse()?,
AddressPrivacyKind::Public,
))
} else if addr_base58.starts_with("Private/") {
Ok((
addr_base58.strip_prefix("Private/").unwrap().parse()?,
AddressPrivacyKind::Private,
))
} else {
anyhow::bail!("Unsupported privacy kind, available variants is Public/ and Private/");
}
}
/// Human-readable representation of an account.
#[derive(Serialize)]
pub(crate) struct HumanReadableAccount {
@ -126,4 +150,20 @@ mod tests {
std::env::remove_var(HOME_DIR_ENV_VAR);
}
}
#[test]
fn test_addr_parse_with_privacy() {
let addr_base58 = "Public/BLgCRDXYdQPMMWVHYRFGQZbgeHx9frkipa8GtpG2Syqy";
let (_, addr_kind) = parse_addr_with_privacy_prefix(addr_base58).unwrap();
assert_eq!(addr_kind, AddressPrivacyKind::Public);
let addr_base58 = "Private/BLgCRDXYdQPMMWVHYRFGQZbgeHx9frkipa8GtpG2Syqy";
let (_, addr_kind) = parse_addr_with_privacy_prefix(addr_base58).unwrap();
assert_eq!(addr_kind, AddressPrivacyKind::Private);
let addr_base58 = "asdsada/BLgCRDXYdQPMMWVHYRFGQZbgeHx9frkipa8GtpG2Syqy";
assert!(parse_addr_with_privacy_prefix(addr_base58).is_err());
}
}