From 53ab4c8ec94a0a6ec66d9605c93043d180185487 Mon Sep 17 00:00:00 2001 From: Mark Sinclair Date: Wed, 1 May 2024 15:24:29 +0100 Subject: [PATCH 1/8] Wallet delegations: tap errors in requests per delegation. Return an error with strings of all sub errors. --- nym-wallet/Cargo.lock | 4 +- nym-wallet/src-tauri/Cargo.toml | 1 + nym-wallet/src-tauri/src/error.rs | 3 + .../src/operations/mixnet/delegate.rs | 104 +++++++++++++++--- 4 files changed, 98 insertions(+), 14 deletions(-) diff --git a/nym-wallet/Cargo.lock b/nym-wallet/Cargo.lock index 9573b2af63..2418e313a1 100644 --- a/nym-wallet/Cargo.lock +++ b/nym-wallet/Cargo.lock @@ -3005,6 +3005,7 @@ dependencies = [ "schemars", "serde", "tendermint", + "time", ] [[package]] @@ -3442,7 +3443,7 @@ dependencies = [ [[package]] name = "nym_wallet" -version = "1.2.12" +version = "1.2.13" dependencies = [ "async-trait", "base64 0.13.1", @@ -3476,6 +3477,7 @@ dependencies = [ "serde_json", "serde_repr", "strum 0.23.0", + "tap", "tauri", "tauri-build", "tauri-codegen", diff --git a/nym-wallet/src-tauri/Cargo.toml b/nym-wallet/src-tauri/Cargo.toml index 6d2dc3185d..3c00c10253 100644 --- a/nym-wallet/src-tauri/Cargo.toml +++ b/nym-wallet/src-tauri/Cargo.toml @@ -37,6 +37,7 @@ serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" serde_repr = "0.1" strum = { version = "0.23", features = ["derive"] } +tap = "1" tauri = { version = "=1.2.3", features = ["clipboard-all", "shell-open", "updater", "window-maximize", "window-print"] } #tendermint-rpc = "0.23.0" time = { version = "0.3.17", features = ["local-offset"] } diff --git a/nym-wallet/src-tauri/src/error.rs b/nym-wallet/src-tauri/src/error.rs index 7f542d257c..b89ecd1fd5 100644 --- a/nym-wallet/src-tauri/src/error.rs +++ b/nym-wallet/src-tauri/src/error.rs @@ -155,6 +155,9 @@ pub enum BackendError { #[error("This command ({name}) has been removed. Please try to use {alternative} instead.")] RemovedCommand { name: String, alternative: String }, + + #[error("Unable to get list of delegations, errors: {0}")] + DelegationsListError(String), } impl Serialize for BackendError { diff --git a/nym-wallet/src-tauri/src/operations/mixnet/delegate.rs b/nym-wallet/src-tauri/src/operations/mixnet/delegate.rs index 1a47f7bee9..7b9e8655a5 100644 --- a/nym-wallet/src-tauri/src/operations/mixnet/delegate.rs +++ b/nym-wallet/src-tauri/src/operations/mixnet/delegate.rs @@ -4,6 +4,7 @@ use crate::error::BackendError; use crate::state::WalletState; use crate::vesting::delegate::vesting_undelegate_from_mixnode; +use nym_mixnet_contract_common::mixnode::StakeSaturationResponse; use nym_mixnet_contract_common::MixId; use nym_types::currency::DecCoin; use nym_types::delegation::{Delegation, DelegationWithEverything, DelegationsSummaryResponse}; @@ -18,6 +19,7 @@ use nym_validator_client::nyxd::contract_traits::{ MixnetQueryClient, MixnetSigningClient, NymContractsProvider, PagedMixnetQueryClient, }; use nym_validator_client::nyxd::Fee; +use tap::TapFallible; #[tauri::command] pub async fn get_pending_delegation_events( @@ -152,6 +154,8 @@ pub async fn get_all_mix_delegations( ) -> Result, BackendError> { log::info!(">>> Get all mixnode delegations"); + let mut error_strings: Vec = vec![]; + let guard = state.read().await; let client = guard.current_client()?; let reg = guard.registered_coins()?; @@ -165,10 +169,23 @@ pub async fn get_all_mix_delegations( .expect("vesting contract address is not available"); log::info!(" >>> Get delegations"); - let delegations = client.nyxd.get_all_delegator_delegations(&address).await?; + let delegations = client + .nyxd + .get_all_delegator_delegations(&address) + .await + .tap_err(|err| { + error_strings.push(format!("{}", err)); + log::error!(" <<< {}", err); + })?; log::info!(" <<< {} delegations", delegations.len()); - let pending_events_for_account = get_pending_delegation_events(state.clone()).await?; + let pending_events_for_account = + get_pending_delegation_events(state.clone()) + .await + .tap_err(|err| { + error_strings.push(format!("{}", err)); + log::error!(" <<< {}", err); + })?; log::info!( " <<< {} pending delegation events for account", @@ -178,7 +195,10 @@ pub async fn get_all_mix_delegations( let mut with_everything: Vec = Vec::with_capacity(delegations.len()); for delegation in delegations { - let d = Delegation::from_mixnet_contract(delegation, reg)?; + let d = Delegation::from_mixnet_contract(delegation, reg).tap_err(|err| { + log::error!(" <<< {}", err); + })?; + let uses_vesting_contract_tokens = d .proxy .as_ref() @@ -194,7 +214,11 @@ pub async fn get_all_mix_delegations( let mixnode = client .nyxd .get_mixnode_details(d.mix_id) - .await? + .await + .tap_err(|err| { + error_strings.push(format!("{}", err)); + log::error!(" <<< {}", err); + })? .mixnode_details; let accumulated_by_operator = mixnode @@ -202,14 +226,24 @@ pub async fn get_all_mix_delegations( .map(|m| { guard.display_coin_from_base_decimal(&base_mix_denom, m.rewarding_details.operator) }) - .transpose()?; + .transpose() + .tap_err(|err| { + error_strings.push(format!("{}", err)); + log::error!(" <<< {}", err); + }) + .unwrap_or_default(); let accumulated_by_delegates = mixnode .as_ref() .map(|m| { guard.display_coin_from_base_decimal(&base_mix_denom, m.rewarding_details.delegates) }) - .transpose()?; + .transpose() + .tap_err(|err| { + error_strings.push(format!("{}", err)); + log::error!(" <<< {}", err); + }) + .unwrap_or_default(); let cost_params = mixnode .as_ref() @@ -219,19 +253,39 @@ pub async fn get_all_mix_delegations( reg, ) }) - .transpose()?; + .transpose() + .tap_err(|err| { + error_strings.push(format!("{}", err)); + log::error!(" <<< {}", err); + }) + .unwrap_or_default(); log::trace!(" >>> Get accumulated rewards: address = {}", address); let pending_reward = client .nyxd .get_pending_delegator_reward(&address, d.mix_id, d.proxy.clone()) - .await?; + .await + .tap_err(|err| { + error_strings.push(format!("{}", err)); + log::error!(" <<< {}", err); + }) + .unwrap_or_default(); let accumulated_rewards = match &pending_reward.amount_earned { Some(reward) => { - let amount = guard.attempt_convert_to_display_dec_coin(reward.clone().into())?; - log::trace!(" <<< rewards = {:?}, amount = {}", pending_reward, amount); - Some(amount) + let amount = guard + .attempt_convert_to_display_dec_coin(reward.clone().into()) + .tap_err(|err| { + error_strings.push(format!("{}", err)); + log::error!(" <<< {}", err); + }) + .ok(); + log::trace!( + " <<< rewards = {:?}, amount = {:?}", + pending_reward, + amount + ); + amount } None => { log::trace!(" <<< no rewards waiting"); @@ -240,7 +294,19 @@ pub async fn get_all_mix_delegations( }; log::trace!(" >>> Get stake saturation: mix_id = {}", d.mix_id); - let stake_saturation = client.nyxd.get_mixnode_stake_saturation(d.mix_id).await?; + let stake_saturation = client + .nyxd + .get_mixnode_stake_saturation(d.mix_id) + .await + .tap_err(|err| { + error_strings.push(format!("{}", err)); + log::error!(" <<< {}", err); + }) + .unwrap_or(StakeSaturationResponse { + mix_id: d.mix_id, + uncapped_saturation: None, + current_saturation: None, + }); log::trace!(" <<< {:?}", stake_saturation); log::trace!( @@ -251,6 +317,10 @@ pub async fn get_all_mix_delegations( .nym_api .get_mixnode_avg_uptime(d.mix_id) .await + .tap_err(|err| { + error_strings.push(format!("{}", err)); + log::error!(" <<< {}", err); + }) .ok() .map(|r| r.avg_uptime); log::trace!(" <<< {:?}", avg_uptime_percent); @@ -262,7 +332,11 @@ pub async fn get_all_mix_delegations( let timestamp = client .nyxd .get_block_timestamp(Some(d.height as u32)) - .await?; + .await + .tap_err(|err| { + error_strings.push(format!("{}", err)); + log::error!(" <<< {}", err); + })?; let delegated_on_iso_datetime = timestamp.to_rfc3339(); log::trace!( " <<< timestamp = {:?}, delegated_on_iso_datetime = {:?}", @@ -305,6 +379,10 @@ pub async fn get_all_mix_delegations( } log::trace!("<<< {:?}", with_everything); + if !error_strings.is_empty() { + return Err(BackendError::DelegationsListError(error_strings.join("\n"))); + } + Ok(with_everything) } From ad86ec93153bb361e24e1a2552d0c065d89fc123 Mon Sep 17 00:00:00 2001 From: Mark Sinclair Date: Wed, 1 May 2024 15:59:45 +0100 Subject: [PATCH 2/8] Wallet delegations: add more error information --- .../src/operations/mixnet/delegate.rs | 78 +++++++++++++------ 1 file changed, 56 insertions(+), 22 deletions(-) diff --git a/nym-wallet/src-tauri/src/operations/mixnet/delegate.rs b/nym-wallet/src-tauri/src/operations/mixnet/delegate.rs index 7b9e8655a5..6991c058e8 100644 --- a/nym-wallet/src-tauri/src/operations/mixnet/delegate.rs +++ b/nym-wallet/src-tauri/src/operations/mixnet/delegate.rs @@ -175,7 +175,7 @@ pub async fn get_all_mix_delegations( .await .tap_err(|err| { error_strings.push(format!("{}", err)); - log::error!(" <<< {}", err); + log::error!(" <<< Failed to get delegations. Error: {}", err); })?; log::info!(" <<< {} delegations", delegations.len()); @@ -184,7 +184,7 @@ pub async fn get_all_mix_delegations( .await .tap_err(|err| { error_strings.push(format!("{}", err)); - log::error!(" <<< {}", err); + log::error!(" <<< Failed to get pending delegations. Error: {}", err); })?; log::info!( @@ -195,8 +195,12 @@ pub async fn get_all_mix_delegations( let mut with_everything: Vec = Vec::with_capacity(delegations.len()); for delegation in delegations { - let d = Delegation::from_mixnet_contract(delegation, reg).tap_err(|err| { - log::error!(" <<< {}", err); + let d = Delegation::from_mixnet_contract(delegation.clone(), reg).tap_err(|err| { + log::error!( + " <<< Failed to get delegation for mix id {} from contract. Error: {}", + delegation.mix_id, + err + ); })?; let uses_vesting_contract_tokens = d @@ -216,8 +220,12 @@ pub async fn get_all_mix_delegations( .get_mixnode_details(d.mix_id) .await .tap_err(|err| { - error_strings.push(format!("{}", err)); - log::error!(" <<< {}", err); + let str_err = format!( + "Failed to get mixnode details for mix_id = {}. Error: {}", + d.mix_id, err + ); + log::error!(" <<< {}", str_err); + error_strings.push(str_err); })? .mixnode_details; @@ -228,8 +236,12 @@ pub async fn get_all_mix_delegations( }) .transpose() .tap_err(|err| { - error_strings.push(format!("{}", err)); - log::error!(" <<< {}", err); + let str_err = format!( + "Failed to get operator rewards as a display coin for mix_id = {}. Error: {}", + d.mix_id, err + ); + log::error!(" <<< {}", str_err); + error_strings.push(str_err); }) .unwrap_or_default(); @@ -240,8 +252,12 @@ pub async fn get_all_mix_delegations( }) .transpose() .tap_err(|err| { - error_strings.push(format!("{}", err)); - log::error!(" <<< {}", err); + let str_err = format!( + "Failed to get delegator rewards as a display coin for mix_id = {}. Error: {}", + d.mix_id, err + ); + log::error!(" <<< {}", str_err); + error_strings.push(str_err); }) .unwrap_or_default(); @@ -255,8 +271,12 @@ pub async fn get_all_mix_delegations( }) .transpose() .tap_err(|err| { - error_strings.push(format!("{}", err)); - log::error!(" <<< {}", err); + let str_err = format!( + "Failed to mixnode cost params for mix_id = {}. Error: {}", + d.mix_id, err + ); + log::error!(" <<< {}", str_err); + error_strings.push(str_err); }) .unwrap_or_default(); @@ -266,8 +286,12 @@ pub async fn get_all_mix_delegations( .get_pending_delegator_reward(&address, d.mix_id, d.proxy.clone()) .await .tap_err(|err| { - error_strings.push(format!("{}", err)); - log::error!(" <<< {}", err); + let str_err = format!( + "Failed to get accumulated rewards for mix_id = {}. Error: {}", + d.mix_id, err + ); + log::error!(" <<< {}", str_err); + error_strings.push(str_err); }) .unwrap_or_default(); @@ -276,8 +300,9 @@ pub async fn get_all_mix_delegations( let amount = guard .attempt_convert_to_display_dec_coin(reward.clone().into()) .tap_err(|err| { - error_strings.push(format!("{}", err)); - log::error!(" <<< {}", err); + let str_err = format!("Failed to get convert reward to a display coin for mix_id = {}. Error: {}", d.mix_id, err); + log::error!(" <<< {}", str_err); + error_strings.push(str_err); }) .ok(); log::trace!( @@ -299,8 +324,12 @@ pub async fn get_all_mix_delegations( .get_mixnode_stake_saturation(d.mix_id) .await .tap_err(|err| { - error_strings.push(format!("{}", err)); - log::error!(" <<< {}", err); + let str_err = format!( + "Failed to get stake saturation for mix_id = {}. Error: {}", + d.mix_id, err + ); + log::error!(" <<< {}", str_err); + error_strings.push(str_err); }) .unwrap_or(StakeSaturationResponse { mix_id: d.mix_id, @@ -318,8 +347,12 @@ pub async fn get_all_mix_delegations( .get_mixnode_avg_uptime(d.mix_id) .await .tap_err(|err| { - error_strings.push(format!("{}", err)); - log::error!(" <<< {}", err); + let str_err = format!( + "Failed to get average uptime percentage for mix_id = {}. Error: {}", + d.mix_id, err + ); + log::error!(" <<< {}", str_err); + error_strings.push(str_err); }) .ok() .map(|r| r.avg_uptime); @@ -334,8 +367,9 @@ pub async fn get_all_mix_delegations( .get_block_timestamp(Some(d.height as u32)) .await .tap_err(|err| { - error_strings.push(format!("{}", err)); - log::error!(" <<< {}", err); + let str_err = format!("Failed to get block timestamp for height = {} for delegation to mix_id = {}. Error: {}", d.height, d.mix_id, err); + log::error!(" <<< {}", str_err); + error_strings.push(str_err); })?; let delegated_on_iso_datetime = timestamp.to_rfc3339(); log::trace!( From 08e580ec8bbedbdc056395d09c9d33f689054f0b Mon Sep 17 00:00:00 2001 From: Mark Sinclair Date: Wed, 1 May 2024 17:59:46 +0100 Subject: [PATCH 3/8] Wallet delegations list - add an error to each row, and display as a tooltip if present --- common/types/src/delegation.rs | 4 ++- nym-wallet/src-tauri/src/error.rs | 3 --- .../src/operations/mixnet/delegate.rs | 22 ++++++++-------- .../components/Delegation/DelegationItem.tsx | 25 +++++++++++++------ .../Delegation/DelegationList.stories.tsx | 12 +++++++++ nym-wallet/src/context/mocks/delegations.tsx | 2 ++ .../types/rust/DelegationWithEverything.ts | 3 ++- 7 files changed, 48 insertions(+), 23 deletions(-) diff --git a/common/types/src/delegation.rs b/common/types/src/delegation.rs index 550f17a261..e98c56fd40 100644 --- a/common/types/src/delegation.rs +++ b/common/types/src/delegation.rs @@ -50,7 +50,7 @@ pub struct DelegationWithEverything { pub accumulated_by_delegates: Option, pub accumulated_by_operator: Option, pub block_height: u64, - pub delegated_on_iso_datetime: String, + pub delegated_on_iso_datetime: Option, pub cost_params: Option, pub avg_uptime_percent: Option, @@ -60,6 +60,8 @@ pub struct DelegationWithEverything { pub uses_vesting_contract_tokens: bool, pub unclaimed_rewards: Option, + pub errors: Option, + // DEPRECATED, IF POSSIBLE TRY TO DISCONTINUE USE OF IT! pub pending_events: Vec, pub mixnode_is_unbonding: Option, diff --git a/nym-wallet/src-tauri/src/error.rs b/nym-wallet/src-tauri/src/error.rs index b89ecd1fd5..7f542d257c 100644 --- a/nym-wallet/src-tauri/src/error.rs +++ b/nym-wallet/src-tauri/src/error.rs @@ -155,9 +155,6 @@ pub enum BackendError { #[error("This command ({name}) has been removed. Please try to use {alternative} instead.")] RemovedCommand { name: String, alternative: String }, - - #[error("Unable to get list of delegations, errors: {0}")] - DelegationsListError(String), } impl Serialize for BackendError { diff --git a/nym-wallet/src-tauri/src/operations/mixnet/delegate.rs b/nym-wallet/src-tauri/src/operations/mixnet/delegate.rs index 6991c058e8..6d4db9d476 100644 --- a/nym-wallet/src-tauri/src/operations/mixnet/delegate.rs +++ b/nym-wallet/src-tauri/src/operations/mixnet/delegate.rs @@ -154,8 +154,6 @@ pub async fn get_all_mix_delegations( ) -> Result, BackendError> { log::info!(">>> Get all mixnode delegations"); - let mut error_strings: Vec = vec![]; - let guard = state.read().await; let client = guard.current_client()?; let reg = guard.registered_coins()?; @@ -174,7 +172,6 @@ pub async fn get_all_mix_delegations( .get_all_delegator_delegations(&address) .await .tap_err(|err| { - error_strings.push(format!("{}", err)); log::error!(" <<< Failed to get delegations. Error: {}", err); })?; log::info!(" <<< {} delegations", delegations.len()); @@ -183,7 +180,6 @@ pub async fn get_all_mix_delegations( get_pending_delegation_events(state.clone()) .await .tap_err(|err| { - error_strings.push(format!("{}", err)); log::error!(" <<< Failed to get pending delegations. Error: {}", err); })?; @@ -195,6 +191,8 @@ pub async fn get_all_mix_delegations( let mut with_everything: Vec = Vec::with_capacity(delegations.len()); for delegation in delegations { + let mut error_strings: Vec = vec![]; + let d = Delegation::from_mixnet_contract(delegation.clone(), reg).tap_err(|err| { log::error!( " <<< Failed to get delegation for mix id {} from contract. Error: {}", @@ -370,8 +368,8 @@ pub async fn get_all_mix_delegations( let str_err = format!("Failed to get block timestamp for height = {} for delegation to mix_id = {}. Error: {}", d.height, d.mix_id, err); log::error!(" <<< {}", str_err); error_strings.push(str_err); - })?; - let delegated_on_iso_datetime = timestamp.to_rfc3339(); + }).ok(); + let delegated_on_iso_datetime = timestamp.map(|ts| ts.to_rfc3339()); log::trace!( " <<< timestamp = {:?}, delegated_on_iso_datetime = {:?}", timestamp, @@ -391,6 +389,9 @@ pub async fn get_all_mix_delegations( mixnode_is_unbonding ); + error_strings.push("Oh no!".to_string()); + error_strings.push("Oh no, Again!".to_string()); + with_everything.push(DelegationWithEverything { owner: d.owner, mix_id: d.mix_id, @@ -409,14 +410,15 @@ pub async fn get_all_mix_delegations( unclaimed_rewards: accumulated_rewards, pending_events, mixnode_is_unbonding, + errors: if error_strings.is_empty() { + None + } else { + Some(error_strings.join("\n")) + }, }) } log::trace!("<<< {:?}", with_everything); - if !error_strings.is_empty() { - return Err(BackendError::DelegationsListError(error_strings.join("\n"))); - } - Ok(with_everything) } diff --git a/nym-wallet/src/components/Delegation/DelegationItem.tsx b/nym-wallet/src/components/Delegation/DelegationItem.tsx index c405c18d83..895124c3c6 100644 --- a/nym-wallet/src/components/Delegation/DelegationItem.tsx +++ b/nym-wallet/src/components/Delegation/DelegationItem.tsx @@ -45,13 +45,22 @@ export const DelegationItem = ({ {nodeIsUnbonded ? ( '-' ) : ( - + <> + {item.errors && ( + {item.errors}}> + + ⚠️ + + + )} + + )} @@ -70,7 +79,7 @@ export const DelegationItem = ({ {getStakeSaturation(item)} - {format(new Date(item.delegated_on_iso_datetime), 'dd/MM/yyyy')} + {item.delegated_on_iso_datetime && format(new Date(item.delegated_on_iso_datetime), 'dd/MM/yyyy')} diff --git a/nym-wallet/src/components/Delegation/DelegationList.stories.tsx b/nym-wallet/src/components/Delegation/DelegationList.stories.tsx index 9683d268ca..4b24a3dcfe 100644 --- a/nym-wallet/src/components/Delegation/DelegationList.stories.tsx +++ b/nym-wallet/src/components/Delegation/DelegationList.stories.tsx @@ -34,6 +34,7 @@ export const items: DelegationWithEverything[] = [ uses_vesting_contract_tokens: false, pending_events: [], mixnode_is_unbonding: true, + errors: null, }, { mix_id: 2, @@ -57,6 +58,7 @@ export const items: DelegationWithEverything[] = [ uses_vesting_contract_tokens: true, pending_events: [], mixnode_is_unbonding: true, + errors: null, }, { mix_id: 3, @@ -80,6 +82,7 @@ export const items: DelegationWithEverything[] = [ uses_vesting_contract_tokens: true, pending_events: [], mixnode_is_unbonding: true, + errors: null, }, { mix_id: 4, @@ -103,6 +106,7 @@ export const items: DelegationWithEverything[] = [ uses_vesting_contract_tokens: true, pending_events: [], mixnode_is_unbonding: true, + errors: null, }, { mix_id: 5, @@ -126,6 +130,7 @@ export const items: DelegationWithEverything[] = [ uses_vesting_contract_tokens: true, pending_events: [], mixnode_is_unbonding: true, + errors: null, }, { mix_id: 6, @@ -149,6 +154,7 @@ export const items: DelegationWithEverything[] = [ uses_vesting_contract_tokens: true, pending_events: [], mixnode_is_unbonding: true, + errors: null, }, { mix_id: 7, @@ -172,6 +178,7 @@ export const items: DelegationWithEverything[] = [ uses_vesting_contract_tokens: false, pending_events: [], mixnode_is_unbonding: true, + errors: null, }, { mix_id: 8, @@ -195,6 +202,7 @@ export const items: DelegationWithEverything[] = [ uses_vesting_contract_tokens: true, pending_events: [], mixnode_is_unbonding: true, + errors: null, }, { mix_id: 9, @@ -218,6 +226,7 @@ export const items: DelegationWithEverything[] = [ uses_vesting_contract_tokens: true, pending_events: [], mixnode_is_unbonding: true, + errors: null, }, { mix_id: 10, @@ -241,6 +250,7 @@ export const items: DelegationWithEverything[] = [ uses_vesting_contract_tokens: true, pending_events: [], mixnode_is_unbonding: true, + errors: null, }, { mix_id: 11, @@ -264,6 +274,7 @@ export const items: DelegationWithEverything[] = [ uses_vesting_contract_tokens: true, pending_events: [], mixnode_is_unbonding: true, + errors: null, }, { mix_id: 12, @@ -287,6 +298,7 @@ export const items: DelegationWithEverything[] = [ uses_vesting_contract_tokens: true, pending_events: [], mixnode_is_unbonding: true, + errors: null, }, ]; diff --git a/nym-wallet/src/context/mocks/delegations.tsx b/nym-wallet/src/context/mocks/delegations.tsx index 2a9609b022..23cb1929a7 100644 --- a/nym-wallet/src/context/mocks/delegations.tsx +++ b/nym-wallet/src/context/mocks/delegations.tsx @@ -37,6 +37,7 @@ let mockDelegations: DelegationWithEverything[] = [ uses_vesting_contract_tokens: false, pending_events: [], mixnode_is_unbonding: false, + errors: null, }, { mix_id: 5678, @@ -60,6 +61,7 @@ let mockDelegations: DelegationWithEverything[] = [ uses_vesting_contract_tokens: true, pending_events: [], mixnode_is_unbonding: false, + errors: null, }, ]; diff --git a/ts-packages/types/src/types/rust/DelegationWithEverything.ts b/ts-packages/types/src/types/rust/DelegationWithEverything.ts index ecf949c5ae..7071f291dd 100644 --- a/ts-packages/types/src/types/rust/DelegationWithEverything.ts +++ b/ts-packages/types/src/types/rust/DelegationWithEverything.ts @@ -11,7 +11,7 @@ export interface DelegationWithEverything { accumulated_by_delegates: DecCoin | null; accumulated_by_operator: DecCoin | null; block_height: bigint; - delegated_on_iso_datetime: string; + delegated_on_iso_datetime: string | null; cost_params: MixNodeCostParams | null; avg_uptime_percent: number | null; stake_saturation: string | null; @@ -19,4 +19,5 @@ export interface DelegationWithEverything { unclaimed_rewards: DecCoin | null; pending_events: Array; mixnode_is_unbonding: boolean | null; + errors: string | null; } From 650865e59afd193dad9604989d0e6b66c0d3e8d6 Mon Sep 17 00:00:00 2001 From: Mark Sinclair Date: Wed, 1 May 2024 18:02:34 +0100 Subject: [PATCH 4/8] Wallet delegations: slow refresh from 1 min to 5 mins --- nym-wallet/src/pages/delegation/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nym-wallet/src/pages/delegation/index.tsx b/nym-wallet/src/pages/delegation/index.tsx index 64a24b41db..3e985a3526 100644 --- a/nym-wallet/src/pages/delegation/index.tsx +++ b/nym-wallet/src/pages/delegation/index.tsx @@ -101,7 +101,7 @@ export const Delegation: FC<{ isStorybook?: boolean }> = ({ isStorybook }) => { // Refresh the rewards and delegations periodically when page is mounted useEffect(() => { - const timer = setInterval(refreshWithIntervalUpdate, 1 * 60 * 1000); // every 1 minute + const timer = setInterval(refreshWithIntervalUpdate, 5 * 60 * 1000); // every 5 minutes return () => clearInterval(timer); }, []); From 8fbad9cad8fb97e8a362d155e71753b9a6fa90ea Mon Sep 17 00:00:00 2001 From: Mark Sinclair Date: Wed, 1 May 2024 19:33:10 +0100 Subject: [PATCH 5/8] Remove test errors --- nym-wallet/src-tauri/src/operations/mixnet/delegate.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/nym-wallet/src-tauri/src/operations/mixnet/delegate.rs b/nym-wallet/src-tauri/src/operations/mixnet/delegate.rs index 6d4db9d476..7a705f6d03 100644 --- a/nym-wallet/src-tauri/src/operations/mixnet/delegate.rs +++ b/nym-wallet/src-tauri/src/operations/mixnet/delegate.rs @@ -389,9 +389,6 @@ pub async fn get_all_mix_delegations( mixnode_is_unbonding ); - error_strings.push("Oh no!".to_string()); - error_strings.push("Oh no, Again!".to_string()); - with_everything.push(DelegationWithEverything { owner: d.owner, mix_id: d.mix_id, From 6060ce5fb8cc29ac1983295365bfac19c4a1beb8 Mon Sep 17 00:00:00 2001 From: fmtabbara Date: Wed, 1 May 2024 23:38:06 +0100 Subject: [PATCH 6/8] fix error tooltip overflow --- .../src/components/Delegation/DelegationItem.tsx | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/nym-wallet/src/components/Delegation/DelegationItem.tsx b/nym-wallet/src/components/Delegation/DelegationItem.tsx index 895124c3c6..0eef5ff2ee 100644 --- a/nym-wallet/src/components/Delegation/DelegationItem.tsx +++ b/nym-wallet/src/components/Delegation/DelegationItem.tsx @@ -1,8 +1,8 @@ import React from 'react'; -import { Chip, IconButton, TableCell, TableRow, Tooltip, Typography } from '@mui/material'; +import { Box, Chip, IconButton, TableCell, TableRow, Tooltip, Typography } from '@mui/material'; import { Link } from '@nymproject/react/link/Link'; import { decimalToPercentage, DelegationWithEverything } from '@nymproject/types'; -import { LockOutlined } from '@mui/icons-material'; +import { LockOutlined, WarningAmberOutlined } from '@mui/icons-material'; import { isDelegation } from 'src/context/delegations'; import { toPercentIntegerString } from 'src/utils'; import { format } from 'date-fns'; @@ -45,12 +45,10 @@ export const DelegationItem = ({ {nodeIsUnbonded ? ( '-' ) : ( - <> + {item.errors && ( - {item.errors}}> - - ⚠️ - + {item.errors}}> + )} - + )} From 3ba83795d4b116af4534710cd25b5bd731bd0864 Mon Sep 17 00:00:00 2001 From: fmtabbara Date: Thu, 2 May 2024 11:22:34 +0100 Subject: [PATCH 7/8] add error dialog --- .../src/components/Delegation/DelegationItem.tsx | 14 +++++++++++--- .../src/components/Delegation/DelegationList.tsx | 11 ++++++++++- nym-wallet/src/context/delegations.tsx | 16 +++++++++++++++- nym-wallet/src/context/mocks/delegations.tsx | 3 +++ 4 files changed, 39 insertions(+), 5 deletions(-) diff --git a/nym-wallet/src/components/Delegation/DelegationItem.tsx b/nym-wallet/src/components/Delegation/DelegationItem.tsx index 0eef5ff2ee..96db0c3854 100644 --- a/nym-wallet/src/components/Delegation/DelegationItem.tsx +++ b/nym-wallet/src/components/Delegation/DelegationItem.tsx @@ -3,7 +3,7 @@ import { Box, Chip, IconButton, TableCell, TableRow, Tooltip, Typography } from import { Link } from '@nymproject/react/link/Link'; import { decimalToPercentage, DelegationWithEverything } from '@nymproject/types'; import { LockOutlined, WarningAmberOutlined } from '@mui/icons-material'; -import { isDelegation } from 'src/context/delegations'; +import { isDelegation, useDelegationContext } from 'src/context/delegations'; import { toPercentIntegerString } from 'src/utils'; import { format } from 'date-fns'; import { Undelegate } from 'src/svg-icons'; @@ -29,6 +29,8 @@ export const DelegationItem = ({ nodeIsUnbonded: boolean; onItemActionClick?: (item: DelegationWithEverything, action: DelegationListItemActions) => void; }) => { + const { setDelegationItemErrors } = useDelegationContext(); + const operatingCost = isDelegation(item) && item.cost_params?.interval_operating_cost; const tooltipText = () => { @@ -47,8 +49,14 @@ export const DelegationItem = ({ ) : ( {item.errors && ( - {item.errors}}> - + + setDelegationItemErrors({ nodeId: item.node_identity, errors: item.errors! })} + > + + )} ('asc'); const [orderBy, setOrderBy] = React.useState('delegated_on_iso_datetime'); + const { delegationItemErrors, setDelegationItemErrors } = useDelegationContext(); + const handleRequestSort = (_: React.MouseEvent, property: any) => { const isAsc = orderBy === property && order === 'asc'; setOrder(isAsc ? 'desc' : 'asc'); @@ -105,6 +108,12 @@ export const DelegationList: FCWithChildren<{ return ( {isLoading && } + setDelegationItemErrors(undefined)} + /> diff --git a/nym-wallet/src/context/delegations.tsx b/nym-wallet/src/context/delegations.tsx index 5f235e0703..d77e52467e 100644 --- a/nym-wallet/src/context/delegations.tsx +++ b/nym-wallet/src/context/delegations.tsx @@ -20,6 +20,7 @@ import { decCoinToDisplay } from 'src/utils'; import { Console } from 'src/utils/console'; export type TDelegationContext = { + delegationItemErrors?: { nodeId: string; errors: string }; isLoading: boolean; delegations?: TDelegations; pendingDelegations?: WrappedDelegationEvent[]; @@ -34,6 +35,7 @@ export type TDelegationContext = { ) => Promise; undelegate: (mix_id: number, fee?: Fee) => Promise; undelegateVesting: (mix_id: number) => Promise; + setDelegationItemErrors: (data: { nodeId: string; errors: string } | undefined) => void; }; export type TDelegationTransaction = { @@ -61,6 +63,7 @@ export const DelegationContext = createContext({ undelegateVesting: () => { throw new Error('Not implemented'); }, + setDelegationItemErrors: () => undefined, }); export const DelegationContextProvider: FC<{ @@ -69,6 +72,7 @@ export const DelegationContextProvider: FC<{ // eslint-disable-next-line @typescript-eslint/no-unused-vars }> = ({ network, children }) => { const [isLoading, setIsLoading] = useState(true); + const [delegationItemErrors, setDelegationItemErrors] = useState<{ nodeId: string; errors: string }>(); const [delegations, setDelegations] = useState(); const [totalDelegations, setTotalDelegations] = useState(); const [totalRewards, setTotalRewards] = useState(); @@ -130,6 +134,7 @@ export const DelegationContextProvider: FC<{ const memoizedValue = useMemo( () => ({ + delegationItemErrors, isLoading, delegations, pendingDelegations, @@ -137,11 +142,20 @@ export const DelegationContextProvider: FC<{ totalRewards, totalDelegationsAndRewards, refresh, + setDelegationItemErrors, addDelegation, undelegate: undelegateFromMixnode, undelegateVesting: vestingUndelegateFromMixnode, }), - [isLoading, delegations, pendingDelegations, totalDelegations, totalRewards, totalDelegationsAndRewards], + [ + isLoading, + delegations, + delegationItemErrors, + pendingDelegations, + totalDelegations, + totalRewards, + totalDelegationsAndRewards, + ], ); return {children}; diff --git a/nym-wallet/src/context/mocks/delegations.tsx b/nym-wallet/src/context/mocks/delegations.tsx index 23cb1929a7..0c984d7369 100644 --- a/nym-wallet/src/context/mocks/delegations.tsx +++ b/nym-wallet/src/context/mocks/delegations.tsx @@ -71,6 +71,7 @@ export const MockDelegationContextProvider: FCWithChildren = ({ children }) => { const [error, setError] = useState(); const [delegations, setDelegations] = useState(); const [totalDelegations, setTotalDelegations] = useState(); + const [delegationItemErrors, setDelegationItemErrors] = useState<{ nodeId: string; errors: string }>(); const triggerStateUpdate = () => setTrigger(new Date()); @@ -232,6 +233,8 @@ export const MockDelegationContextProvider: FCWithChildren = ({ children }) => { const memoizedValue = useMemo( () => ({ + delegationItemErrors, + setDelegationItemErrors, isLoading, error, delegations, From 6e23322ac46f33fa43871a7777436aa55c77a307 Mon Sep 17 00:00:00 2001 From: Mark Sinclair <14054343+mmsinclair@users.noreply.github.com> Date: Thu, 2 May 2024 14:57:00 +0100 Subject: [PATCH 8/8] Update nym-wallet/src/components/Delegation/DelegationList.tsx --- nym-wallet/src/components/Delegation/DelegationList.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nym-wallet/src/components/Delegation/DelegationList.tsx b/nym-wallet/src/components/Delegation/DelegationList.tsx index af736df195..ebda436025 100644 --- a/nym-wallet/src/components/Delegation/DelegationList.tsx +++ b/nym-wallet/src/components/Delegation/DelegationList.tsx @@ -111,7 +111,7 @@ export const DelegationList: FCWithChildren<{ setDelegationItemErrors(undefined)} />