diff --git a/Cargo.toml b/Cargo.toml index 2368979..5968efc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,7 @@ name = "ghost-eye" authors = ["str3tch "] description = "Application for interacting with Casper/Ghost nodes that are exposing RPC only to the localhost" -version = "0.3.52" +version = "0.3.53" edition = "2021" homepage = "https://git.ghostchain.io/ghostchain" repository = "https://git.ghostchain.io/ghostchain/ghost-eye" diff --git a/src/action.rs b/src/action.rs index 5300c44..2b3cc0f 100644 --- a/src/action.rs +++ b/src/action.rs @@ -6,7 +6,7 @@ use subxt::config::substrate::DigestItem; use crate::types::{ ActionLevel, ActionTarget, CasperExtrinsicDetails, EraInfo, EraRewardPoints, - Nominator, PeerInformation, SessionKeyInfo, UnlockChunk, SystemAccount, + Nominator, Nominations, PeerInformation, SessionKeyInfo, UnlockChunk, SystemAccount, RewardDestination, }; @@ -63,6 +63,7 @@ pub enum Action { UnbondFrom([u8; 32], u128), RebondFrom([u8; 32], u128), WithdrawUnbondedFrom([u8; 32], u32), + NominateTargets([u8; 32], Vec<[u8; 32]>), EventLog(String, ActionLevel, ActionTarget), NewBestBlock(u32), @@ -100,6 +101,7 @@ pub enum Action { GetNominatorsNumber, GetInflation, GetNominatorsByValidator([u8; 32], bool), + GetNominatorsByAccount([u8; 32], bool), GetValidatorAllRewards([u8; 32], bool), GetValidatorLedger([u8; 32], bool), GetIsStashBonded([u8; 32], bool), @@ -133,6 +135,7 @@ pub enum Action { SetListenAddresses(Vec), SetLocalIdentity(String), SetNominatorsByValidator(Vec, [u8; 32]), + SetNominatorsByAccount(Nominations, [u8; 32]), SetValidatorEraReward(u32, u128), SetValidatorEraClaimed(u32, bool), SetValidatorEraSlash(u32, u128), diff --git a/src/components/validator/nominators.rs b/src/components/validator/nominators.rs index eac336c..2ddfc3c 100644 --- a/src/components/validator/nominators.rs +++ b/src/components/validator/nominators.rs @@ -54,9 +54,8 @@ impl NominatorsByValidator { fn update_nominators(&mut self, nominators: Vec) { if self.nominators.len() > nominators.len() { - if let Some(_) = self.table_state.selected() { - self.last_row(); - } + self.table_state.select(None); + self.scroll_state = self.scroll_state.position(0); } self.nominators = nominators; self.scroll_state = self.scroll_state.content_length(self.nominators.len()); @@ -178,7 +177,7 @@ impl Component for NominatorsByValidator { .iter() .map(|info| { Row::new(vec![ - Cell::from(Text::from(info.who.clone()).alignment(Alignment::Left)), + Cell::from(Text::from(info.address.clone()).alignment(Alignment::Left)), Cell::from(Text::from(self.prepare_u128(info.value)).alignment(Alignment::Right)), ]) }), diff --git a/src/components/wallet/accounts.rs b/src/components/wallet/accounts.rs index e5555d9..c732c41 100644 --- a/src/components/wallet/accounts.rs +++ b/src/components/wallet/accounts.rs @@ -94,6 +94,11 @@ impl Accounts { account_id, used_seed.clone())); } + if let Some(network_tx) = &self.network_tx { + let _ = network_tx.send(Action::GetNominatorsByAccount( + account_id, + false)); + } self.set_sender_nonce(index); } @@ -311,10 +316,8 @@ impl Accounts { }); } }; - self.table_state.select(Some(0)); self.scroll_state = self.scroll_state.content_length(self.wallet_keys.len()); - self.set_balance_active(0); - self.set_used_account(0); + self.first_row(); Ok(()) } diff --git a/src/components/wallet/current_validators.rs b/src/components/wallet/current_validators.rs index fa917ed..be9abc3 100644 --- a/src/components/wallet/current_validators.rs +++ b/src/components/wallet/current_validators.rs @@ -2,13 +2,15 @@ use std::fs::File; use std::path::PathBuf; use std::io::{Write, BufRead, BufReader}; +use subxt::ext::sp_core::crypto::{AccountId32, Ss58AddressFormat, Ss58Codec}; + use color_eyre::Result; use crossterm::event::{KeyCode, KeyEvent}; use ratatui::layout::{Constraint, Margin}; use ratatui::style::{Stylize, Modifier}; use ratatui::widgets::Clear; use ratatui::{ - text::Text, + text::{Line, Text}, layout::{Alignment, Rect}, widgets::{ Block, Cell, Row, Table, TableState, Scrollbar, Padding, @@ -17,9 +19,10 @@ use ratatui::{ Frame }; use tokio::sync::mpsc::UnboundedSender; +use std::sync::mpsc::Sender; use super::{PartialComponent, Component, CurrentTab}; -use crate::types::EraRewardPoints; +use crate::types::{ActionLevel, Nominations, ActionTarget, EraRewardPoints}; use crate::{ action::Action, config::Config, @@ -28,12 +31,14 @@ use crate::{ pub struct CurrentValidators { is_active: bool, + network_tx: Option>, action_tx: Option>, known_validators_file: PathBuf, palette: StylePalette, scroll_state: ScrollbarState, table_state: TableState, individual: Vec, + not_active_nominations: Vec, known_validators: std::collections::HashMap<[u8; 32], String>, checked_validators: std::collections::HashSet<[u8; 32]>, total_points: u32, @@ -41,6 +46,8 @@ pub struct CurrentValidators { my_stash_id: Option<[u8; 32]>, account_id: [u8; 32], account_secret_seed: [u8; 32], + my_nominations: std::collections::HashMap<[u8; 32], Nominations>, + filtered_vector: Vec, } impl Default for CurrentValidators { @@ -55,6 +62,7 @@ impl CurrentValidators { pub fn new() -> Self { Self { is_active: false, + network_tx: None, action_tx: None, known_validators_file: Default::default(), scroll_state: ScrollbarState::new(0), @@ -68,6 +76,9 @@ impl CurrentValidators { account_id: [0u8; 32], account_secret_seed: [0u8; 32], palette: StylePalette::default(), + my_nominations: Default::default(), + not_active_nominations: Default::default(), + filtered_vector: Default::default(), } } @@ -79,7 +90,7 @@ impl CurrentValidators { } fn move_selected(&mut self, index: usize) { - if self.individual.len() > 0 { + if self.filtered_vector.len() > 0 { self.table_state.select(Some(index)); self.scroll_state = self.scroll_state.position(index); self.update_choosen_details(index); @@ -96,9 +107,27 @@ impl CurrentValidators { self.account_secret_seed = secret_seed; } + fn store_nominators(&mut self, nominations: Nominations, account_id: [u8; 32]) { + if self.account_id == account_id { + self.not_active_nominations.clear(); + for account in nominations.targets.iter() { + if !self.individual.iter().any(|r| r.account_id == *account) { + self.not_active_nominations.push(EraRewardPoints { + account_id: *account, + address: AccountId32::from(*account) + .to_ss58check_with_version(Ss58AddressFormat::custom(1996)), + points: 0, + disabled: true, + }); + } + } + } + self.my_nominations.insert(account_id, nominations); + } + fn update_choosen_details(&self, index: usize) { if let Some(action_tx) = &self.action_tx { - let (selected_account_id, selected_points) = self.individual + let (selected_account_id, selected_points) = self.filtered_vector .get(index) .map(|data| (data.account_id, data.points)) .unwrap_or_default(); @@ -114,9 +143,41 @@ impl CurrentValidators { self.checked_validators.clear(); } + fn choose_current_nominated(&mut self) { + self.clear_choosen(); + self.checked_validators.extend(self.my_nominations + .get(&self.account_id) + .map(|nom| nom.targets.clone()) + .unwrap_or_default()); + } + + fn choose_all_validators(&mut self) { + self.clear_choosen(); + self.checked_validators.extend(self.individual + .iter().map(|ind| ind.account_id)); + } + + fn swap_choosen_filter(&mut self) { + let is_individual = self.filtered_vector.len() == self.individual.len(); + self.filtered_vector = self.individual + .iter() + .filter_map(|data| { + let is_good = !is_individual || self.checked_validators.contains(&data.account_id); + is_good.then(|| data.clone()) + }) + .collect::>(); + self.scroll_state = self.scroll_state.content_length(self.filtered_vector.len()); + if self.filtered_vector.len() == 0 { + self.table_state.select(None); + self.scroll_state = self.scroll_state.position(0); + } else { + self.first_row(); + } + } + fn flip_validator_check(&mut self) { if let Some(index) = self.table_state.selected() { - if let Some(indiv) = self.individual.get(index) { + if let Some(indiv) = self.filtered_vector.get(index) { let current_account_id = indiv.account_id; if self.checked_validators.contains(¤t_account_id) { self.checked_validators.remove(¤t_account_id); @@ -129,7 +190,7 @@ impl CurrentValidators { fn save_validator_name(&mut self, new_name: String) { if let Some(index) = self.table_state.selected() { - let account_id = self.individual[index].account_id; + let account_id = self.filtered_vector[index].account_id; let _ = self.known_validators.insert(account_id, new_name); let mut file = File::create(&self.known_validators_file) @@ -172,7 +233,7 @@ impl CurrentValidators { } fn first_row(&mut self) { - if self.individual.len() > 0 { + if self.filtered_vector.len() > 0 { self.move_selected(0); } } @@ -180,7 +241,7 @@ impl CurrentValidators { fn next_row(&mut self) { let i = match self.table_state.selected() { Some(i) => { - if i >= self.individual.len() - 1 { + if i >= self.filtered_vector.len() - 1 { i } else { i + 1 @@ -192,8 +253,8 @@ impl CurrentValidators { } fn last_row(&mut self) { - if self.individual.len() > 0 { - let last = self.individual.len() - 1; + if self.filtered_vector.len() > 0 { + let last = self.filtered_vector.len() - 1; self.move_selected(last); } } @@ -218,21 +279,67 @@ impl CurrentValidators { total_points: u32, individual: &Vec, ) { + let previous_length = self.individual.len(); self.individual = individual.to_vec(); self.total_points = total_points; self.era_index = era_index; - if let Some(account_id) = self.my_stash_id { - if self.individual.len() > 1 { + if individual.len() > 0 { + if let Some(account_id) = self.my_stash_id { if let Some(index) = self.individual .iter() .position(|item| item.account_id == account_id) { self.individual.swap(0, index); } } + if previous_length == 0 { + self.filtered_vector = self.individual.clone(); + self.scroll_state = self.scroll_state.content_length(self.individual.len()); + self.first_row(); + } } + } - self.scroll_state = self.scroll_state.content_length(self.individual.len()); + fn nominate_choosen(&mut self) { + if self.my_stash_id.map(|acc| acc == self.account_id).unwrap_or_default() { + if let Some(action_tx) = &self.action_tx { + let _ = action_tx.send(Action::EventLog( + "nomination from stash account will stop node validation, use another account for nomination".to_string(), + ActionLevel::Error, + ActionTarget::WalletLog)); + } + } else { + if let Some(network_tx) = &self.network_tx { + let nominate_targets: Vec<[u8; 32]> = self.checked_validators + .clone() + .into_iter() + .collect::>(); + let _ = network_tx.send(Action::NominateTargets( + self.account_secret_seed, nominate_targets)); + } + } + self.close_popup(); + } + + fn prepare_nomination_line(&self) -> String { + let empty_nominations = Nominations::default(); + let nominations = self.my_nominations + .get(&self.account_id) + .unwrap_or(&empty_nominations); + + if nominations.targets.len() == 0 { + "No nominations found".to_string() + } else { + let status = if nominations.suppressed { + "Suppressed" + } else { + "Active" + }; + format!("Submitted at era #{} | {} ", + nominations.submitted_in, + status, + ) + } } } @@ -251,6 +358,11 @@ impl Component for CurrentValidators { Ok(()) } + fn register_network_handler(&mut self, tx: Sender) -> Result<()> { + self.network_tx = Some(tx); + Ok(()) + } + fn register_config_handler(&mut self, config: Config) -> Result<()> { if let Some(style) = config.styles.get(&crate::app::Mode::Wallet) { self.palette.with_normal_style(style.get("normal_style").copied()); @@ -273,6 +385,7 @@ impl Component for CurrentValidators { match action { Action::UpdateKnownValidator(validator_name) => self.save_validator_name(validator_name), Action::UsedAccount(account_id, secret_seed) => self.update_used_account(account_id, secret_seed), + Action::SetNominatorsByAccount(nominations, account_id) => self.store_nominators(nominations, account_id), Action::SetStashAccount(account_id) => self.my_stash_id = Some(account_id), Action::SetCurrentValidatorEraRewards(era_index, total_points, individual) => self.update_era_rewards(era_index, total_points, &individual), @@ -289,7 +402,13 @@ impl Component for CurrentValidators { KeyCode::Char('g') => self.first_row(), KeyCode::Char('G') => self.last_row(), KeyCode::Char('R') => self.update_known_validator_record(), - KeyCode::Char('C') => self.clear_choosen(), + + KeyCode::Char('c') => self.clear_choosen(), + KeyCode::Char('m') => self.choose_current_nominated(), + KeyCode::Char('a') => self.choose_all_validators(), + KeyCode::Char('f') => self.swap_choosen_filter(), + + KeyCode::Char('N') => self.nominate_choosen(), KeyCode::Enter => self.flip_validator_check(), KeyCode::Esc => self.close_popup(), _ => {}, @@ -302,61 +421,66 @@ impl Component for CurrentValidators { if self.is_active { let (border_style, border_type) = self.palette.create_border_style(self.is_active); let [place, _] = super::nominator_layout(area); + + let top_title = format!("Validators {} | Total points: {}", self.individual.len(), self.total_points); + let bottom_title = self.prepare_nomination_line(); + let table = Table::new( - self.individual - .iter() - .enumerate() - .map(|(index, info)| { - let mut address_text = Text::from(info.address.clone()).alignment(Alignment::Center); - let mut points_text = Text::from(info.points.to_string()).alignment(Alignment::Right); - let is_choosen_text = if self.checked_validators.contains(&info.account_id) { - ">" - } else { - "" - }; + self.filtered_vector + .iter() + .chain(&self.not_active_nominations) + .enumerate() + .map(|(index, info)| { + let is_validator_choosen = self.checked_validators.contains(&info.account_id); + let is_current_nomination = self.my_nominations + .get(&self.account_id) + .map(|x| x.targets.contains(&info.account_id)) + .unwrap_or_default(); - if info.disabled { - address_text = address_text.add_modifier(Modifier::CROSSED_OUT); - points_text = points_text.add_modifier(Modifier::CROSSED_OUT); - } + let mut address_text = Text::from(info.address.clone()).alignment(Alignment::Center); + let mut points_text = Text::from(info.points.to_string()).alignment(Alignment::Right); - let mut current_validator_is_my_stash = false; - if index == 0 { - if let Some(account_id) = self.my_stash_id { - current_validator_is_my_stash = account_id == info.account_id; + let (row_style, is_choosen_text) = if is_validator_choosen { + address_text = address_text.add_modifier(Modifier::ITALIC); + points_text = points_text.add_modifier(Modifier::ITALIC); + (self.palette.create_highlight_style(), ">") + } else if is_current_nomination { + (Default::default(), "*") + } else { + (Default::default(), "") + }; + + if info.disabled { + address_text = address_text.add_modifier(Modifier::CROSSED_OUT); + points_text = points_text.add_modifier(Modifier::CROSSED_OUT); } - }; - if current_validator_is_my_stash { + let default_name = if index == 0 && self.my_stash_id + .map(|account_id| account_id == info.account_id) + .unwrap_or_default() == true { + "My stash" + } else { + "Ghostie" + }; + let name = self.known_validators .get(&info.account_id) .cloned() - .unwrap_or("My stash".to_string()); + .unwrap_or(default_name.to_string()); + Row::new(vec![ Cell::from(Text::from(is_choosen_text).alignment(Alignment::Left)), Cell::from(Text::from(name).alignment(Alignment::Left)), Cell::from(address_text), Cell::from(points_text), - ]).style(self.palette.create_highlight_style()) - } else { - let name = self.known_validators - .get(&info.account_id) - .cloned() - .unwrap_or("Ghostie".to_string()); - Row::new(vec![ - Cell::from(Text::from(is_choosen_text).alignment(Alignment::Left)), - Cell::from(Text::from(name).alignment(Alignment::Left)), - Cell::from(address_text), - Cell::from(points_text), - ]) - } - }), - [ - Constraint::Length(1), - Constraint::Length(12), - Constraint::Min(0), - Constraint::Length(6), - ], + ]).style(row_style) + }), + [ + Constraint::Length(1), + Constraint::Length(12), + Constraint::Min(0), + Constraint::Length(6), + ], ) .style(self.palette.create_basic_style(false)) .highlight_style(self.palette.create_basic_style(true)) @@ -365,9 +489,9 @@ impl Component for CurrentValidators { .border_style(border_style) .border_type(border_type) .padding(Padding::right(2)) - .title_alignment(Alignment::Right) .title_style(self.palette.create_title_style(false)) - .title(format!("Validators {} | Total points: {}", self.individual.len(), self.total_points))); + .title_bottom(Line::from(bottom_title).left_aligned()) + .title_top(Line::from(top_title).right_aligned())); let scrollbar = Scrollbar::default() .orientation(ScrollbarOrientation::VerticalRight) diff --git a/src/network/mod.rs b/src/network/mod.rs index e2a8814..21a4e31 100644 --- a/src/network/mod.rs +++ b/src/network/mod.rs @@ -1,10 +1,7 @@ use tokio::sync::mpsc::UnboundedSender; use color_eyre::Result; use subxt::{ - backend::rpc::RpcClient, - tx::{TxProgress, TxStatus}, - utils::H256, - OnlineClient, + backend::rpc::RpcClient, tx::{TxProgress, TxStatus}, utils::H256, OnlineClient }; mod legacy_rpc_calls; @@ -132,7 +129,9 @@ impl Network { predefined_calls::get_account_payee(&self.action_tx, &self.online_client_api, &validator_details_to_watch).await?; predefined_calls::get_validators_ledger(&self.action_tx, &self.online_client_api, &validator_details_to_watch).await?; predefined_calls::get_is_stash_bonded(&self.action_tx, &self.online_client_api, &validator_details_to_watch).await?; + predefined_calls::get_nominators_by_account(&self.action_tx, &self.online_client_api, &validator_details_to_watch).await?; } + for account_id in self.accounts_to_watch.iter() { predefined_calls::get_balance(&self.action_tx, &self.online_client_api, &account_id).await?; } @@ -242,6 +241,10 @@ impl Network { self.store_stash_or_validator_if_possible(account_id, is_stash); predefined_calls::get_nominators_by_validator(&self.action_tx, &self.online_client_api, &account_id).await }, + Action::GetNominatorsByAccount(account_id, is_stash) => { + self.store_stash_or_validator_if_possible(account_id, is_stash); + predefined_calls::get_nominators_by_account(&self.action_tx, &self.online_client_api, &account_id).await + }, Action::GetValidatorAllRewards(account_id, is_stash) => { self.store_stash_or_validator_if_possible(account_id, is_stash); predefined_calls::get_validator_staking_results(&self.action_tx, &self.online_client_api, &account_id).await @@ -474,6 +477,24 @@ impl Network { } Ok(()) } + Action::NominateTargets(sender, nomination_targets) => { + let sender_str = hex::encode(sender); + let maybe_nonce = self.senders.get_mut(&sender_str); + if let Ok(tx_progress) = predefined_txs::nominate( + &self.action_tx, + &self.online_client_api, + &sender, + &nomination_targets, + maybe_nonce, + ).await { + self.transactions_to_watch.push(TxToWatch { + tx_progress, + sender: sender_str, + target: ActionTarget::WalletLog, + }); + } + Ok(()) + } _ => Ok(()) } } diff --git a/src/network/predefined_calls.rs b/src/network/predefined_calls.rs index 64bdf12..a581c5c 100644 --- a/src/network/predefined_calls.rs +++ b/src/network/predefined_calls.rs @@ -14,7 +14,7 @@ use subxt::{ use crate::{ action::Action, casper_network::runtime_types::{pallet_staking::RewardDestination, sp_consensus_slots}, - types::{EraInfo, EraRewardPoints, Nominator, SessionKeyInfo, SystemAccount, UnlockChunk}, + types::{EraInfo, EraRewardPoints, Nominator, Nominations, SessionKeyInfo, SystemAccount, UnlockChunk}, CasperAccountId, CasperConfig }; @@ -447,6 +447,27 @@ pub async fn get_validators_ledger( Ok(()) } + +pub async fn get_nominators_by_account( + action_tx: &UnboundedSender, + api: &OnlineClient, + account_id: &[u8; 32], +) -> Result<()> { + let nominators = super::raw_calls::staking::nominators(api, None, account_id) + .await? + .map(|n| Nominations { + targets: n.targets + .0 + .into_iter() + .map(|account_id_32| account_id_32.0) + .collect::>(), + submitted_in: n.submitted_in, + suppressed: n.suppressed, + }) + .unwrap_or_default(); + action_tx.send(Action::SetNominatorsByAccount(nominators, *account_id))?; + Ok(()) +} pub async fn get_nominators_by_validator( action_tx: &UnboundedSender, @@ -458,21 +479,32 @@ pub async fn get_nominators_by_validator( .map(|era_info| era_info.index) .unwrap_or_default(); - let maybe_eras_stakers = super::raw_calls::staking::eras_stakers(api, None, active_era_index, account_id) + let maybe_eras_stakers_overview = super::raw_calls::staking::eras_stakers_overview(api, None, active_era_index, account_id) .await?; - let nominators = match maybe_eras_stakers { - Some(eras_stakers) => eras_stakers - .others - .iter() - .map(|info| { - Nominator { - who: AccountId32::from(info.who.0) - .to_ss58check_with_version(Ss58AddressFormat::custom(1996)), - value: info.value, - } - }) - .collect::>(), + let nominators = match maybe_eras_stakers_overview { + Some(overview) => { + let mut others = Vec::with_capacity(overview.nominator_count as usize); + for page in 0..overview.page_count { + let page_index = page as u32; + let nominators = super::raw_calls::staking::eras_stakers_paged(api, None, active_era_index, page_index, account_id) + .await?; + others.append(&mut nominators + .map(|n| n.others + .iter() + .map(|info| Nominator { + account_id: info.who.0, + address: AccountId32::from(info.who.0) + .to_ss58check_with_version(Ss58AddressFormat::custom(1996)), + value: info.value, + }) + .collect::>() + ) + .unwrap_or_default() + ); + } + others + }, None => Vec::new(), }; diff --git a/src/network/predefined_txs.rs b/src/network/predefined_txs.rs index 1a84224..c519d90 100644 --- a/src/network/predefined_txs.rs +++ b/src/network/predefined_txs.rs @@ -261,6 +261,29 @@ pub async fn set_payee( ).await } +pub async fn nominate( + action_tx: &UnboundedSender, + api: &OnlineClient, + sender: &[u8; 32], + nomination_targets: &Vec<[u8; 32]>, + maybe_nonce: Option<&mut u32>, +) -> Result>> { + let targets = nomination_targets + .iter() + .map(|acc| subxt::utils::MultiAddress::Id(subxt::utils::AccountId32::from(*acc))) + .collect::>(); + let nominate_tx = casper_network::tx().staking().nominate(targets); + inner_sign_and_submit_then_watch( + action_tx, + api, + sender, + maybe_nonce, + Box::new(nominate_tx), + "nominate", + ActionTarget::WalletLog, + ).await +} + async fn inner_sign_and_submit_then_watch( action_tx: &UnboundedSender, api: &OnlineClient, diff --git a/src/network/raw_calls/staking.rs b/src/network/raw_calls/staking.rs index 95b3fe0..8789799 100644 --- a/src/network/raw_calls/staking.rs +++ b/src/network/raw_calls/staking.rs @@ -9,10 +9,11 @@ use crate::{ self, runtime_types::{ pallet_staking::{ - slashing::SlashingSpans, ActiveEraInfo, EraRewardPoints, RewardDestination, StakingLedger, ValidatorPrefs + slashing::SlashingSpans, ActiveEraInfo, EraRewardPoints, + RewardDestination, StakingLedger, ValidatorPrefs, Nominations, }, sp_arithmetic::per_things::Perbill, - sp_staking::{Exposure, PagedExposureMetadata}, + sp_staking::{ExposurePage, PagedExposureMetadata}, }, }, CasperConfig, @@ -54,6 +55,17 @@ pub async fn counter_for_nominators( Ok(maybe_counter_for_nominators) } +pub async fn nominators( + online_client: &OnlineClient, + at_hash: Option<&H256>, + account: &[u8; 32], +) -> Result> { + let account_id = super::convert_array_to_account_id(account); + let storage_key = casper_network::storage().staking().nominators(account_id); + let maybe_nominators = super::do_storage_call(online_client, &storage_key, at_hash).await.unwrap(); + Ok(maybe_nominators) +} + pub async fn eras_total_stake( online_client: &OnlineClient, at_hash: Option<&H256>, @@ -119,16 +131,17 @@ pub async fn ledger( Ok(maybe_ledger) } -pub async fn eras_stakers( +pub async fn eras_stakers_paged( online_client: &OnlineClient, at_hash: Option<&H256>, era_index: u32, + page_index: u32, account: &[u8; 32], -) -> Result>> { +) -> Result>> { let account_id = super::convert_array_to_account_id(account); - let storage_key = casper_network::storage().staking().eras_stakers(era_index, account_id); - let maybe_eras_stakers = super::do_storage_call(online_client, &storage_key, at_hash).await?; - Ok(maybe_eras_stakers) + let storage_key = casper_network::storage().staking().eras_stakers_paged(era_index, account_id, page_index); + let maybe_eras_stakers_paged = super::do_storage_call(online_client, &storage_key, at_hash).await?; + Ok(maybe_eras_stakers_paged) } pub async fn bonded( diff --git a/src/types/mod.rs b/src/types/mod.rs index 3f7737c..d27c49c 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -15,5 +15,6 @@ pub use account::SystemAccount; pub use peer::PeerInformation; pub use session::SessionKeyInfo; pub use nominator::Nominator; +pub use nominator::Nominations; pub use staking::UnlockChunk; pub use staking::RewardDestination; diff --git a/src/types/nominator.rs b/src/types/nominator.rs index 91fb9e5..83fadcd 100644 --- a/src/types/nominator.rs +++ b/src/types/nominator.rs @@ -3,6 +3,14 @@ use serde::{Serialize, Deserialize}; #[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize, Decode)] pub struct Nominator { - pub who: String, + pub account_id: [u8; 32], + pub address: String, pub value: u128, } + +#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize, Decode)] +pub struct Nominations { + pub targets: Vec<[u8; 32]>, + pub submitted_in: u32, + pub suppressed: bool, +}