From 895f7f79ba4574aaa3f47cb794a4b53731a39a0e Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Sat, 25 Jul 2026 00:48:37 +0200 Subject: [PATCH] Account manager --- src/engine/engine.rs | 65 ++++++++++++++++++++++++++++---- src/engine/store/accounts.rs | 72 ++++++++++++++++++++++++++++++++++-- 2 files changed, 125 insertions(+), 12 deletions(-) diff --git a/src/engine/engine.rs b/src/engine/engine.rs index d029200..138a3a4 100644 --- a/src/engine/engine.rs +++ b/src/engine/engine.rs @@ -2,21 +2,27 @@ use std::sync::Arc; use tokio::{sync::Mutex, task::JoinHandle}; -use crate::{store::config::Config, terminal::TerminalServer}; +use crate::{ + store::{accounts::AccountList, config::Config}, + terminal::TerminalServer, +}; #[derive(Debug, Clone)] pub struct Engine { pub terminal_server: Arc, pub config: Arc>, + pub accounts: Arc>, } impl Engine { pub async fn new() -> tokio::io::Result> { let config = Arc::new(Mutex::new(Config::new().await?)); + let accounts = Arc::new(Mutex::new(AccountList::new().await?)); Ok(Arc::new_cyclic(|engine| Self { terminal_server: TerminalServer::new(engine.clone()), config, + accounts, })) } @@ -78,15 +84,15 @@ impl Engine { } } + pub async fn invalid_command_usage(&self, name: &str) -> tokio::io::Result<()> { + self.terminal_server.error(name, "Invalid usage").await + } + pub async fn execute_command(&self, command: &str, args: Vec<&str>) -> tokio::io::Result<()> { match command { - "config" => { - const MESSAGE: &str = "Invalid command arguments, usage: config "; - + "config" | "cfg" => { if args.len() != 1 { - self.terminal_server.error("config", MESSAGE).await?; - - return Ok(()); + return self.invalid_command_usage("config").await; } match args[0] { @@ -95,7 +101,50 @@ impl Engine { } _ => { - self.terminal_server.error("config", MESSAGE).await?; + self.terminal_server + .error("config", "Invalid usage") + .await?; + } + } + } + + "account" | "acc" => { + if args.len() == 0 { + return self.invalid_command_usage("account man").await; + } + + match args[0] { + "reload" => { + *self.accounts.lock().await = AccountList::new().await?; + } + + "list" | "ls" => { + self.terminal_server + .info("account man", "ACCOUNT LIST") + .await?; + + let accounts = self.accounts.lock().await; + + for (name, acc) in &accounts.accounts { + self.terminal_server + .info( + "account man", + &if name == &accounts.active { + format!( + "{} (active) -> {}", + name, + acc.get_truncated_address() + ) + } else { + format!("{} -> {}", name, acc.get_truncated_address()) + }, + ) + .await?; + } + } + + _ => { + return self.invalid_command_usage("account man").await; } } } diff --git a/src/engine/store/accounts.rs b/src/engine/store/accounts.rs index f0ef40a..9da4af6 100644 --- a/src/engine/store/accounts.rs +++ b/src/engine/store/accounts.rs @@ -1,15 +1,56 @@ -use std::collections::HashMap; +use std::{collections::HashMap, str::FromStr}; -use hypersdk::U160; +use hypersdk::{U160, hypercore::PrivateKeySigner}; + +#[derive(Debug)] +pub struct PrivateKey(pub PrivateKeySigner, pub String); + +impl FromStr for PrivateKey { + type Err = ::Err; + + fn from_str(s: &str) -> Result { + Ok(Self(s.parse()?, s.to_string())) + } +} + +impl std::fmt::Display for PrivateKey { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.1.fmt(f) + } +} + +impl<'de> serde::Deserialize<'de> for PrivateKey { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let s = String::deserialize(deserializer)?; + s.parse().map_err(serde::de::Error::custom) + } +} + +impl serde::Serialize for PrivateKey { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(&self.to_string()) + } +} #[derive(Debug, serde::Serialize, serde::Deserialize)] pub struct Account { pub address: U160, - pub private_key: String, + pub private_key: PrivateKey, } #[derive(Debug, Default, serde::Serialize, serde::Deserialize)] -pub struct AccountList(pub HashMap); +pub struct AccountList { + pub active: String, + + #[serde(flatten)] + pub accounts: HashMap, +} impl AccountList { pub async fn new() -> tokio::io::Result { @@ -39,3 +80,26 @@ impl AccountList { .map_err(|v| tokio::io::Error::new(std::io::ErrorKind::Other, v.to_string())) } } + +impl Account { + pub fn get_truncated_address(&self) -> String { + let s = format!("{:x}", self.address); + + if s.len() <= 6 { + return format!("INVALID"); + } + + let first: String = s.chars().take(4).collect(); + + let last: String = s + .chars() + .rev() + .take(4) + .collect::>() + .into_iter() + .rev() + .collect(); + + format!("{}..{}", first, last) + } +}