Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
243 changes: 209 additions & 34 deletions src/ui/tokens/update_token_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -38,41 +48,67 @@ pub struct UpdateTokenConfigScreen {
update_status: UpdateTokenConfigStatus,
pub app_context: Arc<AppContext>,
change_item: TokenConfigurationChangeItem,
signing_key: IdentityPublicKey,
signing_key: Option<IdentityPublicKey>,
identity: QualifiedIdentity,
public_note: Option<String>,

selected_wallet: Option<Arc<RwLock<Wallet>>>,
wallet_password: String,
show_password: bool,
error_message: Option<String>, // unused
}

impl UpdateTokenConfigScreen {
pub fn new(
identity_token_balance: IdentityTokenBalance,
app_context: &Arc<AppContext>,
) -> 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(),
backend_message: None,
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;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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(),
}));
}
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -607,35 +673,144 @@ 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()));
}
_ => {}
}
_ => {}
}
}
});

action
}
}

impl ScreenWithWalletUnlock for UpdateTokenConfigScreen {
fn selected_wallet_ref(&self) -> &Option<Arc<RwLock<Wallet>>> {
&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<String>) {
self.error_message = error_message;
}

fn error_message(&self) -> Option<&String> {
self.error_message.as_ref()
}
}