diff --git a/src/ui/tokens/update_token_config.rs b/src/ui/tokens/update_token_config.rs index 541164f2b..fb576ffa3 100644 --- a/src/ui/tokens/update_token_config.rs +++ b/src/ui/tokens/update_token_config.rs @@ -2,10 +2,16 @@ use crate::app::AppAction; use crate::backend_task::tokens::TokenTask; use crate::backend_task::BackendTask; use crate::context::AppContext; +use crate::model::qualified_identity::QualifiedIdentity; +use crate::model::wallet::Wallet; use crate::ui::components::left_panel::add_left_panel; use crate::ui::components::tokens_subscreen_chooser_panel::add_tokens_subscreen_chooser_panel; use crate::ui::components::top_panel::add_top_panel; -use crate::ui::{MessageType, ScreenLike}; +use crate::ui::components::wallet_unlock::ScreenWithWalletUnlock; +use crate::ui::identities::get_selected_wallet; +use crate::ui::identities::keys::add_key_screen::AddKeyScreen; +use crate::ui::identities::keys::key_info_screen::KeyInfoScreen; +use crate::ui::{MessageType, Screen, ScreenLike}; use chrono::{DateTime, Utc}; use dash_sdk::dpp::balances::credits::TokenAmount; use dash_sdk::dpp::data_contract::associated_token::token_configuration_convention::v0::TokenConfigurationConventionV0; @@ -17,11 +23,15 @@ use dash_sdk::dpp::data_contract::associated_token::token_perpetual_distribution use dash_sdk::dpp::data_contract::associated_token::token_perpetual_distribution::v0::TokenPerpetualDistributionV0; use dash_sdk::dpp::data_contract::associated_token::token_perpetual_distribution::TokenPerpetualDistribution; use dash_sdk::dpp::data_contract::change_control_rules::authorized_action_takers::AuthorizedActionTakers; +use dash_sdk::dpp::identity::accessors::IdentityGettersV0; +use dash_sdk::dpp::identity::identity_public_key::accessors::v0::IdentityPublicKeyGettersV0; +use dash_sdk::dpp::identity::{KeyType, Purpose, SecurityLevel}; use dash_sdk::dpp::platform_value::string_encoding::Encoding; use dash_sdk::platform::{Identifier, IdentityPublicKey}; use egui::{Color32, Ui}; use egui::{Context, RichText}; -use std::collections::BTreeMap; +use std::sync::RwLock; +use std::collections::{BTreeMap, HashSet}; use std::sync::Arc; use super::tokens_screen::IdentityTokenBalance; @@ -38,8 +48,14 @@ pub struct UpdateTokenConfigScreen { update_status: UpdateTokenConfigStatus, pub app_context: Arc, change_item: TokenConfigurationChangeItem, - signing_key: IdentityPublicKey, + signing_key: Option, + identity: QualifiedIdentity, public_note: Option, + + selected_wallet: Option>>, + wallet_password: String, + show_password: bool, + error_message: Option, // unused } impl UpdateTokenConfigScreen { @@ -47,16 +63,30 @@ impl UpdateTokenConfigScreen { identity_token_balance: IdentityTokenBalance, app_context: &Arc, ) -> Self { + // Find the local qualified identity that corresponds to `identity_token_balance.identity_id` let identity = app_context - .get_identity_by_id(&identity_token_balance.identity_id) - .expect("Error getting identity by ID") - .expect("Identity not found"); - let possible_key = identity - .available_authentication_keys_non_master() - .first() - .expect("No authentication keys found") - .identity_public_key - .clone(); + .load_local_qualified_identities() + .unwrap_or_default() + .into_iter() + .find(|id| id.identity.id() == identity_token_balance.identity_id) + .expect("No local qualified identity found matching the token's identity"); + + // Grab a default key if possible + let identity_clone = identity.identity.clone(); + let possible_key = identity_clone.get_first_public_key_matching( + Purpose::AUTHENTICATION, + HashSet::from([ + SecurityLevel::HIGH, + SecurityLevel::MEDIUM, + SecurityLevel::CRITICAL, + ]), + KeyType::all_key_types().into(), + false, + ); + + let mut error_message = None; + let selected_wallet = + get_selected_wallet(&identity, None, possible_key.clone(), &mut error_message); Self { identity_token_balance: identity_token_balance.clone(), @@ -64,15 +94,21 @@ impl UpdateTokenConfigScreen { update_status: UpdateTokenConfigStatus::NotUpdating, app_context: app_context.clone(), change_item: TokenConfigurationChangeItem::TokenConfigurationNoChange, - signing_key: possible_key, + signing_key: possible_key.cloned(), + identity, public_note: None, + + selected_wallet, + wallet_password: String::new(), + show_password: false, + error_message, } } fn render_token_config_updater(&mut self, ui: &mut egui::Ui) -> AppAction { let mut action = AppAction::None; - ui.heading("1. Select the item to update"); + ui.heading("2. Select the item to update"); ui.add_space(10.0); let item = &mut self.change_item; @@ -449,7 +485,7 @@ impl UpdateTokenConfigScreen { ui.separator(); ui.add_space(10.0); - ui.heading("2. Public note (optional)"); + ui.heading("3. Public note (optional)"); ui.add_space(10.0); // Render text input for the public note @@ -477,7 +513,7 @@ impl UpdateTokenConfigScreen { action = AppAction::BackendTask(BackendTask::TokenTask(TokenTask::UpdateTokenConfig { identity_token_balance: self.identity_token_balance.clone(), change_item: self.change_item.clone(), - signing_key: self.signing_key.clone(), + signing_key: self.signing_key.clone().expect("Signing key must be set"), public_note: self.public_note.clone(), })); } @@ -545,6 +581,36 @@ impl UpdateTokenConfigScreen { }); action } + + /// Renders a ComboBox or similar for selecting an authentication key + fn render_key_selection(&mut self, ui: &mut Ui) { + ui.horizontal(|ui| { + ui.label("Select Key:"); + egui::ComboBox::from_id_salt("token_update_key_selector") + .selected_text(match &self.signing_key { + Some(key) => format!("Key ID: {}", key.id()), + None => "Select a key".to_string(), + }) + .show_ui(ui, |ui| { + if self.app_context.developer_mode { + // Show all loaded public keys + for key in self.identity.identity.public_keys().values() { + let label = + format!("Key ID: {} (Purpose: {:?})", key.id(), key.purpose()); + ui.selectable_value(&mut self.signing_key, Some(key.clone()), label); + } + } else { + // Show only "available" auth keys + for key_wrapper in self.identity.available_authentication_keys() { + let key = &key_wrapper.identity_public_key; + let label = + format!("Key ID: {} (Purpose: {:?})", key.id(), key.purpose()); + ui.selectable_value(&mut self.signing_key, Some(key.clone()), label); + } + } + }); + }); + } } impl ScreenLike for UpdateTokenConfigScreen { @@ -607,31 +673,110 @@ impl ScreenLike for UpdateTokenConfigScreen { ui.heading("Update Token Configuration"); ui.add_space(10.0); - action |= self.render_token_config_updater(ui); + // Check if user has any auth keys + let has_keys = if self.app_context.developer_mode { + !self.identity.identity.public_keys().is_empty() + } else { + !self.identity.available_authentication_keys().is_empty() + }; - if let Some((msg, msg_type, _)) = &self.backend_message { + if !has_keys { + ui.colored_label( + Color32::DARK_RED, + format!( + "No authentication keys found for this {} identity.", + self.identity.identity_type, + ), + ); ui.add_space(10.0); - match msg_type { - MessageType::Success => { - ui.colored_label(Color32::DARK_GREEN, msg); - } - MessageType::Error => { - ui.colored_label(Color32::DARK_RED, msg); + + // Show "Add key" or "Check keys" option + let first_key = self.identity.identity.get_first_public_key_matching( + Purpose::AUTHENTICATION, + HashSet::from([ + SecurityLevel::HIGH, + SecurityLevel::MEDIUM, + SecurityLevel::CRITICAL, + ]), + KeyType::all_key_types().into(), + false, + ); + + if let Some(key) = first_key { + if ui.button("Check Keys").clicked() { + action |= AppAction::AddScreen(Screen::KeyInfoScreen(KeyInfoScreen::new( + self.identity.clone(), + key.clone(), + None, + &self.app_context, + ))); } - MessageType::Info => { - ui.label(msg); + ui.add_space(5.0); + } + + if ui.button("Add key").clicked() { + action |= AppAction::AddScreen(Screen::AddKeyScreen(AddKeyScreen::new( + self.identity.clone(), + &self.app_context, + ))); + } + } else { + // Possibly handle locked wallet scenario (similar to TransferTokens) + if self.selected_wallet.is_some() { + let (needed_unlock, just_unlocked) = self.render_wallet_unlock_if_needed(ui); + + if needed_unlock && !just_unlocked { + // Must unlock before we can proceed + return; } - }; - } + } + + // 1) Key selection + ui.heading("1. Select the key to sign the transaction"); + ui.add_space(10.0); + ui.horizontal(|ui| { + self.render_key_selection(ui); + ui.add_space(5.0); + let identity_id_string = + self.identity.identity.id().to_string(Encoding::Base58); + let identity_display = self + .identity + .alias + .as_deref() + .unwrap_or_else(|| &identity_id_string); + ui.label(format!("Identity: {}", identity_display)); + }); - if self.update_status != UpdateTokenConfigStatus::NotUpdating { ui.add_space(10.0); - match &self.update_status { - UpdateTokenConfigStatus::Updating(start_time) => { - let elapsed = Utc::now().signed_duration_since(*start_time); - ui.label(format!("Updating... ({} seconds)", elapsed.num_seconds())); + ui.separator(); + ui.add_space(10.0); + + action |= self.render_token_config_updater(ui); + + if let Some((msg, msg_type, _)) = &self.backend_message { + ui.add_space(10.0); + match msg_type { + MessageType::Success => { + ui.colored_label(Color32::DARK_GREEN, msg); + } + MessageType::Error => { + ui.colored_label(Color32::DARK_RED, msg); + } + MessageType::Info => { + ui.label(msg); + } + }; + } + + if self.update_status != UpdateTokenConfigStatus::NotUpdating { + ui.add_space(10.0); + match &self.update_status { + UpdateTokenConfigStatus::Updating(start_time) => { + let elapsed = Utc::now().signed_duration_since(*start_time); + ui.label(format!("Updating... ({} seconds)", elapsed.num_seconds())); + } + _ => {} } - _ => {} } } }); @@ -639,3 +784,33 @@ impl ScreenLike for UpdateTokenConfigScreen { action } } + +impl ScreenWithWalletUnlock for UpdateTokenConfigScreen { + fn selected_wallet_ref(&self) -> &Option>> { + &self.selected_wallet + } + + fn wallet_password_ref(&self) -> &String { + &self.wallet_password + } + + fn wallet_password_mut(&mut self) -> &mut String { + &mut self.wallet_password + } + + fn show_password(&self) -> bool { + self.show_password + } + + fn show_password_mut(&mut self) -> &mut bool { + &mut self.show_password + } + + fn set_error_message(&mut self, error_message: Option) { + self.error_message = error_message; + } + + fn error_message(&self) -> Option<&String> { + self.error_message.as_ref() + } +}