extraction and parsing for balances, draft version

Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
This commit is contained in:
Uncle Stretch 2024-12-05 20:07:26 +03:00
parent 63e35a4120
commit dc20ba936a
Signed by: str3tch
GPG Key ID: 84F3190747EE79AA
9 changed files with 166 additions and 40 deletions

View File

@ -5,7 +5,7 @@ use subxt::utils::H256;
use subxt::config::substrate::DigestItem; use subxt::config::substrate::DigestItem;
use crate::{ use crate::{
types::{ActionLevel, EraInfo, CasperExtrinsicDetails}, types::{SystemAccount, ActionLevel, EraInfo, CasperExtrinsicDetails},
}; };
use subxt::utils::AccountId32; use subxt::utils::AccountId32;
@ -30,6 +30,9 @@ pub enum Action {
NewAccount(String), NewAccount(String),
NewAddressBookRecord(String, String), NewAddressBookRecord(String, String),
BalanceRequest([u8; 32], bool),
BalanceResponse([u8; 32], SystemAccount),
RenameAccount(String), RenameAccount(String),
RenameAddressBookRecord(String), RenameAddressBookRecord(String),
UpdateAccountName(String), UpdateAccountName(String),

View File

@ -107,6 +107,9 @@ impl App {
for component in self.components.iter_mut() { for component in self.components.iter_mut() {
component.register_action_handler(self.action_tx.clone())?; component.register_action_handler(self.action_tx.clone())?;
} }
for component in self.components.iter_mut() {
component.register_network_handler(self.network_tx.clone())?;
}
for component in self.components.iter_mut() { for component in self.components.iter_mut() {
component.register_config_handler(self.config.clone())?; component.register_config_handler(self.config.clone())?;
} }

View File

@ -5,6 +5,7 @@ use ratatui::{
Frame, Frame,
}; };
use tokio::sync::mpsc::UnboundedSender; use tokio::sync::mpsc::UnboundedSender;
use std::sync::mpsc::Sender;
use crate::{palette, action::Action, config::Config, tui::Event}; use crate::{palette, action::Action, config::Config, tui::Event};
@ -17,6 +18,11 @@ pub mod wallet;
pub mod empty; pub mod empty;
pub trait Component { pub trait Component {
fn register_network_handler(&mut self, tx: Sender<Action>) -> Result<()> {
let _ = tx;
Ok(())
}
fn register_action_handler(&mut self, tx: UnboundedSender<Action>) -> Result<()> { fn register_action_handler(&mut self, tx: UnboundedSender<Action>) -> Result<()> {
let _ = tx; let _ = tx;
Ok(()) Ok(())

View File

@ -23,24 +23,35 @@ use subxt::{
}, },
}; };
use tokio::sync::mpsc::UnboundedSender; use tokio::sync::mpsc::UnboundedSender;
use std::sync::mpsc::Sender;
use super::{PartialComponent, Component, CurrentTab}; use super::{PartialComponent, Component, CurrentTab};
use crate::casper::CasperConfig; use crate::casper::CasperConfig;
use crate::types::ActionLevel; use crate::types::{SystemAccount, ActionLevel};
use crate::{ use crate::{
action::Action, action::Action,
config::Config, config::Config,
palette::StylePalette, palette::StylePalette,
}; };
struct AccountInfo {
name: String,
address: String,
account_id: [u8; 32],
seed: String,
pair_signer: PairSigner<CasperConfig, Pair>,
}
pub struct Accounts { pub struct Accounts {
is_active: bool, is_active: bool,
wallet_keys_file: PathBuf,
action_tx: Option<UnboundedSender<Action>>, action_tx: Option<UnboundedSender<Action>>,
network_tx: Option<Sender<Action>>,
wallet_keys_file: PathBuf,
palette: StylePalette, palette: StylePalette,
scroll_state: ScrollbarState, scroll_state: ScrollbarState,
table_state: TableState, table_state: TableState,
wallet_keys: Vec<(String, String, String, PairSigner<CasperConfig, Pair>)>, wallet_keys: Vec<AccountInfo>,
balances: std::collections::HashMap<[u8; 32], SystemAccount>,
} }
impl Default for Accounts { impl Default for Accounts {
@ -55,16 +66,25 @@ impl Accounts {
is_active: false, is_active: false,
wallet_keys_file: Default::default(), wallet_keys_file: Default::default(),
action_tx: None, action_tx: None,
network_tx: None,
scroll_state: ScrollbarState::new(0), scroll_state: ScrollbarState::new(0),
table_state: TableState::new(), table_state: TableState::new(),
wallet_keys: Vec::new(), wallet_keys: Vec::new(),
balances: Default::default(),
palette: StylePalette::default(), palette: StylePalette::default(),
} }
} }
fn send_balance_request(&mut self, account_id: [u8; 32], remove: bool) {
if let Some(action_tx) = &self.network_tx {
let _ = action_tx.send(Action::BalanceRequest(account_id, remove));
}
}
fn create_new_account(&mut self, name: String) { fn create_new_account(&mut self, name: String) {
let (pair, seed) = Pair::generate(); let (pair, seed) = Pair::generate();
let secret_seed = hex::encode(seed); let secret_seed = hex::encode(seed);
let account_id = pair.public().0;
let pair_signer = PairSigner::<CasperConfig, Pair>::new(pair); let pair_signer = PairSigner::<CasperConfig, Pair>::new(pair);
let address = AccountId32::from(seed.clone()) let address = AccountId32::from(seed.clone())
.to_ss58check_with_version(Ss58AddressFormat::custom(1996)); .to_ss58check_with_version(Ss58AddressFormat::custom(1996));
@ -76,14 +96,21 @@ impl Accounts {
)); ));
} }
self.wallet_keys.push((name, address, secret_seed, pair_signer)); self.send_balance_request(account_id, false);
self.wallet_keys.push(AccountInfo {
name,
address,
account_id,
seed: secret_seed,
pair_signer,
});
self.last_row(); self.last_row();
self.save_to_file(); self.save_to_file();
} }
fn rename_account(&mut self, new_name: String) { fn rename_account(&mut self, new_name: String) {
if let Some(index) = self.table_state.selected() { if let Some(index) = self.table_state.selected() {
let old_name = self.wallet_keys[index].0.clone(); let old_name = self.wallet_keys[index].name.clone();
if let Some(action_tx) = &self.action_tx { if let Some(action_tx) = &self.action_tx {
let _ = action_tx.send(Action::WalletLog( let _ = action_tx.send(Action::WalletLog(
@ -92,7 +119,7 @@ impl Accounts {
)); ));
} }
self.wallet_keys[index].0 = new_name; self.wallet_keys[index].name = new_name;
self.save_to_file(); self.save_to_file();
} }
@ -102,7 +129,7 @@ impl Accounts {
if let Some(index) = self.table_state.selected() { if let Some(index) = self.table_state.selected() {
if let Some(action_tx) = &self.action_tx { if let Some(action_tx) = &self.action_tx {
let _ = action_tx.send(Action::RenameAccount( let _ = action_tx.send(Action::RenameAccount(
self.wallet_keys[index].0.clone() self.wallet_keys[index].name.clone()
)); ));
} }
} }
@ -134,13 +161,14 @@ impl Accounts {
if let Some(index) = self.table_state.selected() { if let Some(index) = self.table_state.selected() {
if self.wallet_keys.len() > 1 { if self.wallet_keys.len() > 1 {
let wallet = self.wallet_keys.remove(index); let wallet = self.wallet_keys.remove(index);
self.send_balance_request(wallet.account_id, true);
self.previous_row(); self.previous_row();
self.save_to_file(); self.save_to_file();
if let Some(action_tx) = &self.action_tx { if let Some(action_tx) = &self.action_tx {
let _ = action_tx.send(Action::WalletLog( let _ = action_tx.send(Action::WalletLog(
format!("wallet `{}` with public address {} removed", format!("wallet `{}` with public address {} removed",
&wallet.0, &wallet.1), &wallet.name, &wallet.address),
ActionLevel::Warn, ActionLevel::Warn,
)); ));
} }
@ -151,7 +179,7 @@ impl Accounts {
fn save_to_file(&mut self) { fn save_to_file(&mut self) {
let mut file = File::create(&self.wallet_keys_file).unwrap(); let mut file = File::create(&self.wallet_keys_file).unwrap();
for wallet in self.wallet_keys.iter() { for wallet in self.wallet_keys.iter() {
writeln!(file, "{}:0x{}", wallet.0, &wallet.2).unwrap(); writeln!(file, "{}:0x{}", wallet.name, &wallet.seed).unwrap();
} }
} }
@ -173,11 +201,19 @@ impl Accounts {
.expect("stored seed is valid length; qed"); .expect("stored seed is valid length; qed");
let pair = Pair::from_seed(&seed); let pair = Pair::from_seed(&seed);
let address = AccountId32::from(pair.public().0) let account_id = pair.public().0;
let address = AccountId32::from(account_id)
.to_ss58check_with_version(Ss58AddressFormat::custom(1996)); .to_ss58check_with_version(Ss58AddressFormat::custom(1996));
let pair_signer = PairSigner::<CasperConfig, Pair>::new(pair); let pair_signer = PairSigner::<CasperConfig, Pair>::new(pair);
self.wallet_keys.push((wallet_name.to_string(), address, wallet_key.to_string(), pair_signer)); self.send_balance_request(account_id, false);
self.wallet_keys.push(AccountInfo {
name: wallet_name.to_string(),
account_id,
address,
seed: wallet_key.to_string(),
pair_signer,
});
} }
if let Some(action_tx) = &self.action_tx { if let Some(action_tx) = &self.action_tx {
let _ = action_tx.send(Action::WalletLog( let _ = action_tx.send(Action::WalletLog(
@ -220,6 +256,7 @@ impl Accounts {
}; };
let secret_seed = hex::encode(seed); let secret_seed = hex::encode(seed);
let account_id = pair.public().0;
let address = AccountId32::from(pair.public().0) let address = AccountId32::from(pair.public().0)
.to_ss58check_with_version(Ss58AddressFormat::custom(1996)); .to_ss58check_with_version(Ss58AddressFormat::custom(1996));
let pair_signer = PairSigner::<CasperConfig, Pair>::new(pair); let pair_signer = PairSigner::<CasperConfig, Pair>::new(pair);
@ -227,7 +264,14 @@ impl Accounts {
let mut new_file = File::create(file_path)?; let mut new_file = File::create(file_path)?;
writeln!(new_file, "ghostie:0x{}", &secret_seed)?; writeln!(new_file, "ghostie:0x{}", &secret_seed)?;
self.wallet_keys.push(("ghostie".to_string(), address, secret_seed, pair_signer)); self.send_balance_request(account_id, false);
self.wallet_keys.push(AccountInfo {
name: "ghostie".to_string(),
address,
account_id,
seed: secret_seed,
pair_signer,
});
} }
}; };
self.table_state.select(Some(0)); self.table_state.select(Some(0));
@ -238,8 +282,10 @@ impl Accounts {
fn send_wallet_change(&mut self, index: usize) { fn send_wallet_change(&mut self, index: usize) {
if let Some(action_tx) = &self.action_tx { if let Some(action_tx) = &self.action_tx {
let (_, _, _, pair) = &self.wallet_keys[index]; let _ = action_tx.send(Action::UsedAccount(self.wallet_keys[index]
let _ = action_tx.send(Action::UsedAccount(pair.account_id().clone())); .pair_signer
.account_id()
.clone()));
} }
} }
@ -287,6 +333,11 @@ impl Accounts {
self.table_state.select(Some(i)); self.table_state.select(Some(i));
self.scroll_state = self.scroll_state.position(i); self.scroll_state = self.scroll_state.position(i);
} }
fn prepare_u128(&self, value: u128, after: usize, ticker: Option<&str>) -> String {
let value = value as f64 / 10f64.powi(18);
format!("{:.after$}{}", value, ticker.unwrap_or_default())
}
} }
impl PartialComponent for Accounts { impl PartialComponent for Accounts {
@ -299,7 +350,11 @@ impl PartialComponent for Accounts {
} }
impl Component for Accounts { impl Component for Accounts {
// TODO network_tx is needed here NOT action_tx fn register_network_handler(&mut self, tx: Sender<Action>) -> Result<()> {
self.network_tx = Some(tx);
Ok(())
}
fn register_action_handler(&mut self, tx: UnboundedSender<Action>) -> Result<()> { fn register_action_handler(&mut self, tx: UnboundedSender<Action>) -> Result<()> {
self.action_tx = Some(tx); self.action_tx = Some(tx);
Ok(()) Ok(())
@ -328,6 +383,9 @@ impl Component for Accounts {
match action { match action {
Action::NewAccount(name) => self.create_new_account(name), Action::NewAccount(name) => self.create_new_account(name),
Action::UpdateAccountName(new_name) => self.rename_account(new_name), Action::UpdateAccountName(new_name) => self.rename_account(new_name),
Action::BalanceResponse(account_id, balance) => {
let _ = self.balances.insert(account_id, balance);
},
_ => {} _ => {}
}; };
Ok(None) Ok(None)
@ -355,15 +413,21 @@ impl Component for Accounts {
let table = Table::new( let table = Table::new(
self.wallet_keys self.wallet_keys
.iter() .iter()
.map(|info| Row::new(vec![ .map(|info| {
Cell::from(Text::from(info.0.clone()).alignment(Alignment::Left)), let balance = self.balances
Cell::from(Text::from(info.1.clone()).alignment(Alignment::Center)), .get(&info.account_id)
Cell::from(Text::from("31 CSPR".to_string()).alignment(Alignment::Right)), .map(|b| b.free)
])), .unwrap_or_default();
Row::new(vec![
Cell::from(Text::from(info.name.clone()).alignment(Alignment::Left)),
Cell::from(Text::from(info.address.clone()).alignment(Alignment::Center)),
Cell::from(Text::from(self.prepare_u128(balance, 2, Some(" CSPR"))).alignment(Alignment::Right)),
])
}),
[ [
Constraint::Min(5), Constraint::Min(5),
Constraint::Max(51), Constraint::Max(51),
Constraint::Min(10), Constraint::Min(11),
], ],
) )
.highlight_style(self.palette.create_highlight_style()) .highlight_style(self.palette.create_highlight_style())

View File

@ -5,6 +5,9 @@ use ratatui::{
Frame, Frame,
}; };
use std::sync::mpsc::Sender;
use tokio::sync::mpsc::UnboundedSender;
mod balance; mod balance;
mod transfer; mod transfer;
mod address_book; mod address_book;
@ -17,7 +20,6 @@ mod add_address_book_record;
mod rename_address_book_record; mod rename_address_book_record;
use balance::Balance; use balance::Balance;
use tokio::sync::mpsc::UnboundedSender;
use transfer::Transfer; use transfer::Transfer;
use address_book::AddressBook; use address_book::AddressBook;
use add_account::AddAccount; use add_account::AddAccount;
@ -94,6 +96,13 @@ impl Wallet {
} }
impl Component for Wallet { impl Component for Wallet {
fn register_network_handler(&mut self, tx: Sender<Action>) -> Result<()> {
for component in self.components.iter_mut() {
component.register_network_handler(tx.clone())?;
}
Ok(())
}
fn register_action_handler(&mut self, tx: UnboundedSender<Action>) -> Result<()> { fn register_action_handler(&mut self, tx: UnboundedSender<Action>) -> Result<()> {
for component in self.components.iter_mut() { for component in self.components.iter_mut() {
component.register_action_handler(tx.clone())?; component.register_action_handler(tx.clone())?;

View File

@ -27,6 +27,7 @@ pub struct Network {
rpc_client: RpcClient, rpc_client: RpcClient,
best_hash: Option<H256>, best_hash: Option<H256>,
finalized_hash: Option<H256>, finalized_hash: Option<H256>,
accounts_to_watch: std::collections::HashSet<[u8; 32]>
} }
impl Network { impl Network {
@ -43,6 +44,7 @@ impl Network {
rpc_client, rpc_client,
best_hash: None, best_hash: None,
finalized_hash: None, finalized_hash: None,
accounts_to_watch: Default::default(),
} }
} }
@ -50,6 +52,9 @@ impl Network {
match io_event { match io_event {
Action::NewBestHash(hash) => { Action::NewBestHash(hash) => {
self.best_hash = Some(hash); self.best_hash = Some(hash);
for account_id in self.accounts_to_watch.iter() {
predefinded_calls::get_balance(&self.action_tx, &self.online_client_api, &account_id).await?;
}
Ok(()) Ok(())
}, },
Action::NewFinalizedHash(hash) => { Action::NewFinalizedHash(hash) => {
@ -68,7 +73,16 @@ impl Network {
Action::GetExistentialDeposit => predefinded_calls::get_existential_deposit(&self.action_tx, &self.online_client_api).await, Action::GetExistentialDeposit => predefinded_calls::get_existential_deposit(&self.action_tx, &self.online_client_api).await,
Action::GetTotalIssuance => predefinded_calls::get_total_issuance(&self.action_tx, &self.online_client_api).await, Action::GetTotalIssuance => predefinded_calls::get_total_issuance(&self.action_tx, &self.online_client_api).await,
//Action::UsedAccount(account_id) => predefinded_calls::get_balance(&self.action_tx, &self.online_client_api, account_id).await,
Action::BalanceRequest(account_id, remove) => {
if remove {
let _ = self.accounts_to_watch.remove(&account_id);
Ok(())
} else {
let _ = self.accounts_to_watch.insert(account_id);
predefinded_calls::get_balance(&self.action_tx, &self.online_client_api, &account_id).await
}
}
_ => Ok(()) _ => Ok(())
} }
} }

View File

@ -15,7 +15,7 @@ use crate::{
self, self,
runtime_types::sp_consensus_slots, runtime_types::sp_consensus_slots,
}, },
types::EraInfo, types::{SystemAccount, EraInfo},
CasperAccountId, CasperConfig CasperAccountId, CasperConfig
}; };
@ -148,18 +148,33 @@ pub async fn get_existential_deposit(
} }
//pub async fn get_balance( pub async fn get_balance(
// action_tx: &UnboundedSender<Action>, action_tx: &UnboundedSender<Action>,
// api: &OnlineClient<CasperConfig>, api: &OnlineClient<CasperConfig>,
// account_id: subxt::utils::AccountId32, account_id: &[u8; 32],
//) -> Result<()> { ) -> Result<()> {
// let storage_key = casper_network::storage().system().account(&account_id); let account_id_converted = subxt::utils::AccountId32::from(*account_id);
// let balance = api.storage() let storage_key = casper_network::storage().system().account(account_id_converted);
// .at_latest()
// .await? let maybe_balance = api
// .fetch(&storage_key) .storage()
// .await?; .at_latest()
// .await?
// action_tx.send(Action::SetTotalIssuance(total_issuance))?; .fetch(&storage_key)
// Ok(()) .await?;
//}
let balance = match maybe_balance {
Some(balance) => {
SystemAccount {
nonce: balance.nonce,
free: balance.data.free,
reserved: balance.data.reserved,
frozen: balance.data.frozen,
}
},
None => SystemAccount::default(),
};
action_tx.send(Action::BalanceResponse(*account_id, balance))?;
Ok(())
}

10
src/types/account.rs Normal file
View File

@ -0,0 +1,10 @@
use codec::Decode;
use serde::{Serialize, Deserialize};
#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize, Decode)]
pub struct SystemAccount {
pub nonce: u32,
pub free: u128,
pub reserved: u128,
pub frozen: u128,
}

View File

@ -1,7 +1,9 @@
mod era; mod era;
mod extrinsics; mod extrinsics;
mod log; mod log;
mod account;
pub use extrinsics::CasperExtrinsicDetails; pub use extrinsics::CasperExtrinsicDetails;
pub use era::EraInfo; pub use era::EraInfo;
pub use log::ActionLevel; pub use log::ActionLevel;
pub use account::SystemAccount;