From 586a5bfd46279654659fa630cbba3b789065c4f9 Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Mon, 25 Nov 2024 19:07:31 +0300 Subject: [PATCH 1/5] a lot of work for top up --- src/backend_task/identity/mod.rs | 25 +- .../identity/register_identity.rs | 316 +++++---- src/backend_task/identity/top_up_identity.rs | 296 +++++++++ src/backend_task/mod.rs | 1 + .../by_using_unused_asset_lock.rs | 8 +- .../by_using_unused_balance.rs | 8 +- .../by_wallet_qr_code.rs | 24 +- .../identities/add_new_identity_screen/mod.rs | 86 +-- src/ui/identities/funding_common.rs | 44 ++ src/ui/identities/mod.rs | 2 + .../by_using_unused_asset_lock.rs | 114 ++++ .../by_using_unused_balance.rs | 74 +++ .../by_wallet_qr_code.rs | 184 ++++++ .../identities/top_up_identity_screen/mod.rs | 624 ++++++++++++++++++ .../top_up_identity_screen/success_screen.rs | 44 ++ 15 files changed, 1600 insertions(+), 250 deletions(-) create mode 100644 src/backend_task/identity/top_up_identity.rs create mode 100644 src/ui/identities/funding_common.rs create mode 100644 src/ui/identities/top_up_identity_screen/by_using_unused_asset_lock.rs create mode 100644 src/ui/identities/top_up_identity_screen/by_using_unused_balance.rs create mode 100644 src/ui/identities/top_up_identity_screen/by_wallet_qr_code.rs create mode 100644 src/ui/identities/top_up_identity_screen/mod.rs create mode 100644 src/ui/identities/top_up_identity_screen/success_screen.rs diff --git a/src/backend_task/identity/mod.rs b/src/backend_task/identity/mod.rs index d41e846ed..a0282aa68 100644 --- a/src/backend_task/identity/mod.rs +++ b/src/backend_task/identity/mod.rs @@ -4,6 +4,7 @@ mod load_identity_from_wallet; mod refresh_identity; mod register_dpns_name; mod register_identity; +mod top_up_identity; mod transfer; mod withdraw_from_identity; @@ -192,7 +193,7 @@ impl IdentityKeys { pub type IdentityIndex = u32; #[derive(Debug, Clone, PartialEq, Eq)] -pub enum IdentityRegistrationMethod { +pub enum IdentityFundingMethod { UseAssetLock(Address, AssetLockProof, Transaction), FundWithUtxo(OutPoint, TxOut, Address, IdentityIndex), FundWithWallet(Duffs, IdentityIndex), @@ -204,17 +205,31 @@ pub struct IdentityRegistrationInfo { pub keys: IdentityKeys, pub wallet: Arc>, pub wallet_identity_index: u32, - pub identity_registration_method: IdentityRegistrationMethod, + pub identity_funding_method: IdentityFundingMethod, } impl PartialEq for IdentityRegistrationInfo { fn eq(&self, other: &Self) -> bool { self.alias_input == other.alias_input - && self.identity_registration_method == other.identity_registration_method + && self.identity_funding_method == other.identity_funding_method && self.keys == other.keys } } +#[derive(Debug, Clone)] +pub struct IdentityTopUpInfo { + pub qualified_identity: QualifiedIdentity, + pub wallet: Arc>, + pub identity_funding_method: IdentityFundingMethod, +} + +impl PartialEq for IdentityTopUpInfo { + fn eq(&self, other: &Self) -> bool { + self.qualified_identity == other.qualified_identity + && self.identity_funding_method == other.identity_funding_method + } +} + #[derive(Debug, Clone, PartialEq)] pub struct RegisterDpnsNameInput { pub qualified_identity: QualifiedIdentity, @@ -226,6 +241,7 @@ pub(crate) enum IdentityTask { LoadIdentity(IdentityInputToLoad), SearchIdentityFromWallet(WalletArcRef, IdentityIndex), RegisterIdentity(IdentityRegistrationInfo), + TopUpIdentity(IdentityTopUpInfo), AddKeyToIdentity(QualifiedIdentity, QualifiedIdentityPublicKey, [u8; 32]), WithdrawFromIdentity(QualifiedIdentity, Option
, Credits, Option), Transfer(QualifiedIdentity, Identifier, Credits, Option), @@ -440,6 +456,9 @@ impl AppContext { self.load_user_identity_from_wallet(sdk, wallet, identity_index) .await } + IdentityTask::TopUpIdentity(top_up_info) => { + self.top_up_identity(top_up_info, sender).await + } } } } diff --git a/src/backend_task/identity/register_identity.rs b/src/backend_task/identity/register_identity.rs index c460b833d..f8b8203a0 100644 --- a/src/backend_task/identity/register_identity.rs +++ b/src/backend_task/identity/register_identity.rs @@ -1,5 +1,5 @@ use crate::app::TaskResult; -use crate::backend_task::identity::{IdentityRegistrationInfo, IdentityRegistrationMethod}; +use crate::backend_task::identity::{IdentityFundingMethod, IdentityRegistrationInfo}; use crate::backend_task::BackendTaskSuccessResult; use crate::context::AppContext; use crate::model::qualified_identity::{IdentityType, QualifiedIdentity}; @@ -115,7 +115,7 @@ impl AppContext { keys, wallet, wallet_identity_index, - identity_registration_method, + identity_funding_method, } = input; let sdk = self.sdk.clone(); @@ -126,186 +126,176 @@ impl AppContext { let wallet_id; - let (asset_lock_proof, asset_lock_proof_private_key, tx_id) = - match identity_registration_method { - IdentityRegistrationMethod::UseAssetLock( - address, - asset_lock_proof, - transaction, - ) => { - let tx_id = transaction.txid(); - - // eprintln!("UseAssetLock: transaction id for {:#?} is {}", transaction, tx_id); - let wallet = wallet.read().unwrap(); - wallet_id = wallet.seed_hash(); - let private_key = wallet - .private_key_for_address(&address, self.network)? - .ok_or("Asset Lock not valid for wallet")?; - let asset_lock_proof = - if let AssetLockProof::Instant(instant_asset_lock_proof) = - asset_lock_proof.as_ref() - { - // we need to make sure the instant send asset lock is recent - let raw_transaction_info = self - .core_client - .get_raw_transaction_info(&tx_id, None) - .map_err(|e| e.to_string())?; - - if raw_transaction_info.chainlock - && raw_transaction_info.height.is_some() - && raw_transaction_info.confirmations.is_some() - && raw_transaction_info.confirmations.unwrap() > 8 - { - // we should use a chain lock instead - AssetLockProof::Chain(ChainAssetLockProof { - core_chain_locked_height: metadata.core_chain_locked_height, - out_point: OutPoint::new(tx_id, 0), - }) - } else { - AssetLockProof::Instant(instant_asset_lock_proof.clone()) - } - } else { - asset_lock_proof - }; - (asset_lock_proof, private_key, tx_id) - } - IdentityRegistrationMethod::FundWithWallet(amount, identity_index) => { - // Scope the write lock to avoid holding it across an await. - let (asset_lock_transaction, asset_lock_proof_private_key, _, used_utxos) = { - let mut wallet = wallet.write().unwrap(); - wallet_id = wallet.seed_hash(); - match wallet.asset_lock_transaction( - sdk.network, - amount, - true, - identity_index, - Some(self), - ) { - Ok(transaction) => transaction, - Err(_) => { - wallet - .reload_utxos(&self.core_client, self.network, Some(self)) - .map_err(|e| e.to_string())?; - wallet.asset_lock_transaction( - sdk.network, - amount, - true, - identity_index, - Some(self), - )? - } - } - }; - - let tx_id = asset_lock_transaction.txid(); - // todo: maybe one day we will want to use platform again, but for right now we use - // the local core as it is more stable - // let asset_lock_proof = self - // .broadcast_and_retrieve_asset_lock(&asset_lock_transaction, &change_address) - // .await - // .map_err(|e| e.to_string())?; - - { - let mut proofs = self.transactions_waiting_for_finality.lock().unwrap(); - proofs.insert(tx_id, None); - } - - self.core_client - .send_raw_transaction(&asset_lock_transaction) + let (asset_lock_proof, asset_lock_proof_private_key, tx_id) = match identity_funding_method + { + IdentityFundingMethod::UseAssetLock(address, asset_lock_proof, transaction) => { + let tx_id = transaction.txid(); + + // eprintln!("UseAssetLock: transaction id for {:#?} is {}", transaction, tx_id); + let wallet = wallet.read().unwrap(); + wallet_id = wallet.seed_hash(); + let private_key = wallet + .private_key_for_address(&address, self.network)? + .ok_or("Asset Lock not valid for wallet")?; + let asset_lock_proof = if let AssetLockProof::Instant(instant_asset_lock_proof) = + asset_lock_proof.as_ref() + { + // we need to make sure the instant send asset lock is recent + let raw_transaction_info = self + .core_client + .get_raw_transaction_info(&tx_id, None) .map_err(|e| e.to_string())?; + if raw_transaction_info.chainlock + && raw_transaction_info.height.is_some() + && raw_transaction_info.confirmations.is_some() + && raw_transaction_info.confirmations.unwrap() > 8 { - let mut wallet = wallet.write().unwrap(); - wallet.utxos.retain(|_, utxo_map| { - utxo_map.retain(|outpoint, _| !used_utxos.contains_key(outpoint)); - !utxo_map.is_empty() // Keep addresses that still have UTXOs - }); - for utxo in used_utxos.keys() { - self.db - .drop_utxo(utxo, &self.network.to_string()) + // we should use a chain lock instead + AssetLockProof::Chain(ChainAssetLockProof { + core_chain_locked_height: metadata.core_chain_locked_height, + out_point: OutPoint::new(tx_id, 0), + }) + } else { + AssetLockProof::Instant(instant_asset_lock_proof.clone()) + } + } else { + asset_lock_proof + }; + (asset_lock_proof, private_key, tx_id) + } + IdentityFundingMethod::FundWithWallet(amount, identity_index) => { + // Scope the write lock to avoid holding it across an await. + let (asset_lock_transaction, asset_lock_proof_private_key, _, used_utxos) = { + let mut wallet = wallet.write().unwrap(); + wallet_id = wallet.seed_hash(); + match wallet.asset_lock_transaction( + sdk.network, + amount, + true, + identity_index, + Some(self), + ) { + Ok(transaction) => transaction, + Err(_) => { + wallet + .reload_utxos(&self.core_client, self.network, Some(self)) .map_err(|e| e.to_string())?; + wallet.asset_lock_transaction( + sdk.network, + amount, + true, + identity_index, + Some(self), + )? } } + }; + + let tx_id = asset_lock_transaction.txid(); + // todo: maybe one day we will want to use platform again, but for right now we use + // the local core as it is more stable + // let asset_lock_proof = self + // .broadcast_and_retrieve_asset_lock(&asset_lock_transaction, &change_address) + // .await + // .map_err(|e| e.to_string())?; + + { + let mut proofs = self.transactions_waiting_for_finality.lock().unwrap(); + proofs.insert(tx_id, None); + } - let asset_lock_proof; - - loop { - { - let proofs = self.transactions_waiting_for_finality.lock().unwrap(); - if let Some(Some(proof)) = proofs.get(&tx_id) { - asset_lock_proof = proof.clone(); - break; - } - } - tokio::time::sleep(Duration::from_millis(200)).await; + self.core_client + .send_raw_transaction(&asset_lock_transaction) + .map_err(|e| e.to_string())?; + + { + let mut wallet = wallet.write().unwrap(); + wallet.utxos.retain(|_, utxo_map| { + utxo_map.retain(|outpoint, _| !used_utxos.contains_key(outpoint)); + !utxo_map.is_empty() // Keep addresses that still have UTXOs + }); + for utxo in used_utxos.keys() { + self.db + .drop_utxo(utxo, &self.network.to_string()) + .map_err(|e| e.to_string())?; } - - (asset_lock_proof, asset_lock_proof_private_key, tx_id) } - IdentityRegistrationMethod::FundWithUtxo( - utxo, - tx_out, - input_address, - identity_index, - ) => { - // Scope the write lock to avoid holding it across an await. - let (asset_lock_transaction, asset_lock_proof_private_key) = { - let mut wallet = wallet.write().unwrap(); - wallet_id = wallet.seed_hash(); - wallet.asset_lock_transaction_for_utxo( - sdk.network, - utxo, - tx_out.clone(), - input_address.clone(), - identity_index, - Some(self), - )? - }; - - let tx_id = asset_lock_transaction.txid(); - // todo: maybe one day we will want to use platform again, but for right now we use - // the local core as it is more stable - // let asset_lock_proof = self - // .broadcast_and_retrieve_asset_lock(&asset_lock_transaction, &change_address) - // .await - // .map_err(|e| e.to_string())?; + let asset_lock_proof; + + loop { { - let mut proofs = self.transactions_waiting_for_finality.lock().unwrap(); - proofs.insert(tx_id, None); + let proofs = self.transactions_waiting_for_finality.lock().unwrap(); + if let Some(Some(proof)) = proofs.get(&tx_id) { + asset_lock_proof = proof.clone(); + break; + } } + tokio::time::sleep(Duration::from_millis(200)).await; + } - self.core_client - .send_raw_transaction(&asset_lock_transaction) - .map_err(|e| e.to_string())?; + (asset_lock_proof, asset_lock_proof_private_key, tx_id) + } + IdentityFundingMethod::FundWithUtxo(utxo, tx_out, input_address, identity_index) => { + // Scope the write lock to avoid holding it across an await. + let (asset_lock_transaction, asset_lock_proof_private_key) = { + let mut wallet = wallet.write().unwrap(); + wallet_id = wallet.seed_hash(); + wallet.asset_lock_transaction_for_utxo( + sdk.network, + utxo, + tx_out.clone(), + input_address.clone(), + identity_index, + Some(self), + )? + }; + + let tx_id = asset_lock_transaction.txid(); + // todo: maybe one day we will want to use platform again, but for right now we use + // the local core as it is more stable + // let asset_lock_proof = self + // .broadcast_and_retrieve_asset_lock(&asset_lock_transaction, &change_address) + // .await + // .map_err(|e| e.to_string())?; + + { + let mut proofs = self.transactions_waiting_for_finality.lock().unwrap(); + proofs.insert(tx_id, None); + } - { - let mut wallet = wallet.write().unwrap(); - wallet.utxos.retain(|_, utxo_map| { - utxo_map.retain(|outpoint, _| outpoint != &utxo); - !utxo_map.is_empty() - }); - self.db - .drop_utxo(&utxo, &self.network.to_string()) - .map_err(|e| e.to_string())?; - } + self.core_client + .send_raw_transaction(&asset_lock_transaction) + .map_err(|e| e.to_string())?; + + { + let mut wallet = wallet.write().unwrap(); + wallet.utxos.retain(|_, utxo_map| { + utxo_map.retain(|outpoint, _| outpoint != &utxo); + !utxo_map.is_empty() + }); + self.db + .drop_utxo(&utxo, &self.network.to_string()) + .map_err(|e| e.to_string())?; + } - let asset_lock_proof; + let asset_lock_proof; - loop { - { - let proofs = self.transactions_waiting_for_finality.lock().unwrap(); - if let Some(Some(proof)) = proofs.get(&tx_id) { - asset_lock_proof = proof.clone(); - break; - } + loop { + { + let proofs = self.transactions_waiting_for_finality.lock().unwrap(); + if let Some(Some(proof)) = proofs.get(&tx_id) { + asset_lock_proof = proof.clone(); + break; } - tokio::time::sleep(Duration::from_millis(200)).await; } - - (asset_lock_proof, asset_lock_proof_private_key, tx_id) + tokio::time::sleep(Duration::from_millis(200)).await; } - }; + + (asset_lock_proof, asset_lock_proof_private_key, tx_id) + } + }; let identity_id = asset_lock_proof .create_identifier() diff --git a/src/backend_task/identity/top_up_identity.rs b/src/backend_task/identity/top_up_identity.rs new file mode 100644 index 000000000..9116ebf8b --- /dev/null +++ b/src/backend_task/identity/top_up_identity.rs @@ -0,0 +1,296 @@ +use crate::app::TaskResult; +use crate::backend_task::identity::{ + IdentityFundingMethod, IdentityRegistrationInfo, IdentityTopUpInfo, +}; +use crate::backend_task::BackendTaskSuccessResult; +use crate::context::AppContext; +use crate::model::qualified_identity::{IdentityType, QualifiedIdentity}; +use dash_sdk::dashcore_rpc::RpcApi; +use dash_sdk::dpp::block::extended_epoch_info::ExtendedEpochInfo; +use dash_sdk::dpp::dashcore::hashes::Hash; +use dash_sdk::dpp::dashcore::OutPoint; +use dash_sdk::dpp::identity::accessors::{IdentityGettersV0, IdentitySettersV0}; +use dash_sdk::dpp::identity::state_transition::asset_lock_proof::chain::ChainAssetLockProof; +use dash_sdk::dpp::native_bls::NativeBlsModule; +use dash_sdk::dpp::prelude::AssetLockProof; +use dash_sdk::dpp::state_transition::identity_create_transition::methods::IdentityCreateTransitionMethodsV0; +use dash_sdk::dpp::state_transition::identity_create_transition::IdentityCreateTransition; +use dash_sdk::dpp::state_transition::identity_topup_transition::methods::IdentityTopUpTransitionMethodsV0; +use dash_sdk::dpp::state_transition::identity_topup_transition::IdentityTopUpTransition; +use dash_sdk::dpp::version::PlatformVersion; +use dash_sdk::dpp::ProtocolError; +use dash_sdk::platform::transition::put_identity::PutIdentity; +use dash_sdk::platform::transition::top_up_identity::TopUpIdentity; +use dash_sdk::platform::{Fetch, Identity}; +use dash_sdk::Error; +use std::collections::BTreeMap; +use std::time::Duration; +use tokio::sync::mpsc; + +impl AppContext { + pub(super) async fn top_up_identity( + &self, + input: IdentityTopUpInfo, + sender: mpsc::Sender, + ) -> Result { + let IdentityTopUpInfo { + mut qualified_identity, + wallet, + identity_funding_method, + } = input; + + let sdk = self.sdk.clone(); + + let (_, metadata) = ExtendedEpochInfo::fetch_with_metadata(&sdk, 0, None) + .await + .map_err(|e| e.to_string())?; + + let wallet_id; + + let (asset_lock_proof, asset_lock_proof_private_key, tx_id) = match identity_funding_method + { + IdentityFundingMethod::UseAssetLock(address, asset_lock_proof, transaction) => { + let tx_id = transaction.txid(); + + // eprintln!("UseAssetLock: transaction id for {:#?} is {}", transaction, tx_id); + let wallet = wallet.read().unwrap(); + wallet_id = wallet.seed_hash(); + let private_key = wallet + .private_key_for_address(&address, self.network)? + .ok_or("Asset Lock not valid for wallet")?; + let asset_lock_proof = if let AssetLockProof::Instant(instant_asset_lock_proof) = + asset_lock_proof.as_ref() + { + // we need to make sure the instant send asset lock is recent + let raw_transaction_info = self + .core_client + .get_raw_transaction_info(&tx_id, None) + .map_err(|e| e.to_string())?; + + if raw_transaction_info.chainlock + && raw_transaction_info.height.is_some() + && raw_transaction_info.confirmations.is_some() + && raw_transaction_info.confirmations.unwrap() > 8 + { + // we should use a chain lock instead + AssetLockProof::Chain(ChainAssetLockProof { + core_chain_locked_height: metadata.core_chain_locked_height, + out_point: OutPoint::new(tx_id, 0), + }) + } else { + AssetLockProof::Instant(instant_asset_lock_proof.clone()) + } + } else { + asset_lock_proof + }; + (asset_lock_proof, private_key, tx_id) + } + IdentityFundingMethod::FundWithWallet(amount, identity_index) => { + // Scope the write lock to avoid holding it across an await. + let (asset_lock_transaction, asset_lock_proof_private_key, _, used_utxos) = { + let mut wallet = wallet.write().unwrap(); + wallet_id = wallet.seed_hash(); + match wallet.asset_lock_transaction( + sdk.network, + amount, + true, + identity_index, + Some(self), + ) { + Ok(transaction) => transaction, + Err(_) => { + wallet + .reload_utxos(&self.core_client, self.network, Some(self)) + .map_err(|e| e.to_string())?; + wallet.asset_lock_transaction( + sdk.network, + amount, + true, + identity_index, + Some(self), + )? + } + } + }; + + let tx_id = asset_lock_transaction.txid(); + // todo: maybe one day we will want to use platform again, but for right now we use + // the local core as it is more stable + // let asset_lock_proof = self + // .broadcast_and_retrieve_asset_lock(&asset_lock_transaction, &change_address) + // .await + // .map_err(|e| e.to_string())?; + + { + let mut proofs = self.transactions_waiting_for_finality.lock().unwrap(); + proofs.insert(tx_id, None); + } + + self.core_client + .send_raw_transaction(&asset_lock_transaction) + .map_err(|e| e.to_string())?; + + { + let mut wallet = wallet.write().unwrap(); + wallet.utxos.retain(|_, utxo_map| { + utxo_map.retain(|outpoint, _| !used_utxos.contains_key(outpoint)); + !utxo_map.is_empty() // Keep addresses that still have UTXOs + }); + for utxo in used_utxos.keys() { + self.db + .drop_utxo(utxo, &self.network.to_string()) + .map_err(|e| e.to_string())?; + } + } + + let asset_lock_proof; + + loop { + { + let proofs = self.transactions_waiting_for_finality.lock().unwrap(); + if let Some(Some(proof)) = proofs.get(&tx_id) { + asset_lock_proof = proof.clone(); + break; + } + } + tokio::time::sleep(Duration::from_millis(200)).await; + } + + (asset_lock_proof, asset_lock_proof_private_key, tx_id) + } + IdentityFundingMethod::FundWithUtxo(utxo, tx_out, input_address, identity_index) => { + // Scope the write lock to avoid holding it across an await. + let (asset_lock_transaction, asset_lock_proof_private_key) = { + let mut wallet = wallet.write().unwrap(); + wallet_id = wallet.seed_hash(); + wallet.asset_lock_transaction_for_utxo( + sdk.network, + utxo, + tx_out.clone(), + input_address.clone(), + identity_index, + Some(self), + )? + }; + + let tx_id = asset_lock_transaction.txid(); + // todo: maybe one day we will want to use platform again, but for right now we use + // the local core as it is more stable + // let asset_lock_proof = self + // .broadcast_and_retrieve_asset_lock(&asset_lock_transaction, &change_address) + // .await + // .map_err(|e| e.to_string())?; + + { + let mut proofs = self.transactions_waiting_for_finality.lock().unwrap(); + proofs.insert(tx_id, None); + } + + self.core_client + .send_raw_transaction(&asset_lock_transaction) + .map_err(|e| e.to_string())?; + + { + let mut wallet = wallet.write().unwrap(); + wallet.utxos.retain(|_, utxo_map| { + utxo_map.retain(|outpoint, _| outpoint != &utxo); + !utxo_map.is_empty() + }); + self.db + .drop_utxo(&utxo, &self.network.to_string()) + .map_err(|e| e.to_string())?; + } + + let asset_lock_proof; + + loop { + { + let proofs = self.transactions_waiting_for_finality.lock().unwrap(); + if let Some(Some(proof)) = proofs.get(&tx_id) { + asset_lock_proof = proof.clone(); + break; + } + } + tokio::time::sleep(Duration::from_millis(200)).await; + } + + (asset_lock_proof, asset_lock_proof_private_key, tx_id) + } + }; + + self.db + .set_asset_lock_identity_id_before_confirmation_by_network( + tx_id.as_byte_array(), + qualified_identity.identity.id().as_bytes(), + ) + .map_err(|e| e.to_string())?; + + let updated_identity_balance = match qualified_identity + .identity + .top_up_identity( + &sdk, + asset_lock_proof.clone(), + &asset_lock_proof_private_key, + None, + ) + .await + { + Ok(updated_identity) => updated_identity, + Err(e) => { + if matches!(e, Error::Protocol(ProtocolError::UnknownVersionError(_))) { + qualified_identity + .identity + .top_up_identity( + &sdk, + asset_lock_proof.clone(), + &asset_lock_proof_private_key, + None, + ) + .await + .map_err(|e| { + let identity_create_transition = + IdentityTopUpTransition::try_from_identity( + &qualified_identity.identity, + asset_lock_proof, + asset_lock_proof_private_key.inner.as_ref(), + 0, + PlatformVersion::latest(), + None, + ) + .expect("expected to make transition"); + format!( + "error: {}, transaction is {:?}", + e.to_string(), + identity_create_transition + ) + })? + } else { + return Err(e.to_string()); + } + } + }; + + qualified_identity + .identity + .set_balance(updated_identity_balance); + + self.update_local_qualified_identity(&qualified_identity) + .map_err(|e| e.to_string())?; + + self.db + .set_asset_lock_identity_id( + tx_id.as_byte_array(), + qualified_identity.identity.id().as_bytes(), + ) + .map_err(|e| e.to_string())?; + + sender + .send(TaskResult::Success(BackendTaskSuccessResult::None)) + .await + .map_err(|e| e.to_string())?; + + Ok(BackendTaskSuccessResult::RegisteredIdentity( + qualified_identity, + )) + } +} diff --git a/src/backend_task/mod.rs b/src/backend_task/mod.rs index e4e4fca23..33dccc4c9 100644 --- a/src/backend_task/mod.rs +++ b/src/backend_task/mod.rs @@ -37,6 +37,7 @@ pub(crate) enum BackendTaskSuccessResult { Documents(Documents), CoreItem(CoreItem), RegisteredIdentity(QualifiedIdentity), + ToppedUpIdentity(QualifiedIdentity), SuccessfulVotes(Vec), WithdrawalStatus(WithdrawStatusPartialData), } diff --git a/src/ui/identities/add_new_identity_screen/by_using_unused_asset_lock.rs b/src/ui/identities/add_new_identity_screen/by_using_unused_asset_lock.rs index a5bca56d4..98521cc27 100644 --- a/src/ui/identities/add_new_identity_screen/by_using_unused_asset_lock.rs +++ b/src/ui/identities/add_new_identity_screen/by_using_unused_asset_lock.rs @@ -1,6 +1,6 @@ use crate::app::AppAction; use crate::ui::identities::add_new_identity_screen::{ - AddNewIdentityScreen, AddNewIdentityWalletFundedScreenStep, FundingMethod, + AddNewIdentityScreen, FundingMethod, WalletFundedScreenStep, }; use egui::Ui; @@ -63,7 +63,7 @@ impl AddNewIdentityScreen { // Update the step to ready to create identity let mut step = self.step.write().unwrap(); - *step = AddNewIdentityWalletFundedScreenStep::ReadyToCreate; + *step = WalletFundedScreenStep::ReadyToCreate; } }); @@ -98,10 +98,10 @@ impl AddNewIdentityScreen { } match step { - AddNewIdentityWalletFundedScreenStep::WaitingForPlatformAcceptance => { + WalletFundedScreenStep::WaitingForPlatformAcceptance => { ui.heading("Waiting for Platform acknowledgement"); } - AddNewIdentityWalletFundedScreenStep::Success => { + WalletFundedScreenStep::Success => { ui.heading("...Success..."); } _ => {} diff --git a/src/ui/identities/add_new_identity_screen/by_using_unused_balance.rs b/src/ui/identities/add_new_identity_screen/by_using_unused_balance.rs index 3d67aea6e..803acc182 100644 --- a/src/ui/identities/add_new_identity_screen/by_using_unused_balance.rs +++ b/src/ui/identities/add_new_identity_screen/by_using_unused_balance.rs @@ -1,6 +1,6 @@ use crate::app::AppAction; use crate::ui::identities::add_new_identity_screen::{ - AddNewIdentityScreen, AddNewIdentityWalletFundedScreenStep, FundingMethod, + AddNewIdentityScreen, FundingMethod, WalletFundedScreenStep, }; use egui::Ui; @@ -54,13 +54,13 @@ impl AddNewIdentityScreen { } match step { - AddNewIdentityWalletFundedScreenStep::WaitingForAssetLock => { + WalletFundedScreenStep::WaitingForAssetLock => { ui.heading("Waiting for Core Chain to produce proof of transfer of funds."); } - AddNewIdentityWalletFundedScreenStep::WaitingForPlatformAcceptance => { + WalletFundedScreenStep::WaitingForPlatformAcceptance => { ui.heading("Waiting for Platform acknowledgement"); } - AddNewIdentityWalletFundedScreenStep::Success => { + WalletFundedScreenStep::Success => { ui.heading("...Success..."); } _ => {} diff --git a/src/ui/identities/add_new_identity_screen/by_wallet_qr_code.rs b/src/ui/identities/add_new_identity_screen/by_wallet_qr_code.rs index e3f8433e7..b5a82f8e9 100644 --- a/src/ui/identities/add_new_identity_screen/by_wallet_qr_code.rs +++ b/src/ui/identities/add_new_identity_screen/by_wallet_qr_code.rs @@ -1,12 +1,12 @@ use crate::app::AppAction; use crate::backend_task::identity::{ - IdentityRegistrationInfo, IdentityRegistrationMethod, IdentityTask, + IdentityFundingMethod, IdentityRegistrationInfo, IdentityTask, }; use crate::backend_task::BackendTask; use crate::ui::identities::add_new_identity_screen::{ - copy_to_clipboard, generate_qr_code_image, AddNewIdentityScreen, - AddNewIdentityWalletFundedScreenStep, + AddNewIdentityScreen, WalletFundedScreenStep, }; +use crate::ui::identities::funding_common::{copy_to_clipboard, generate_qr_code_image}; use dash_sdk::dashcore_rpc::RpcApi; use eframe::epaint::TextureHandle; use egui::Ui; @@ -144,11 +144,11 @@ impl AddNewIdentityScreen { ui.add_space(20.0); match step { - AddNewIdentityWalletFundedScreenStep::ChooseFundingMethod => {} - AddNewIdentityWalletFundedScreenStep::WaitingOnFunds => { + WalletFundedScreenStep::ChooseFundingMethod => {} + WalletFundedScreenStep::WaitingOnFunds => { ui.heading("=> Waiting for funds. <="); } - AddNewIdentityWalletFundedScreenStep::FundsReceived => { + WalletFundedScreenStep::FundsReceived => { let Some(selected_wallet) = &self.selected_wallet else { return action; }; @@ -158,7 +158,7 @@ impl AddNewIdentityScreen { keys: self.identity_keys.clone(), wallet: Arc::clone(selected_wallet), // Clone the Arc reference wallet_identity_index: self.identity_id_number, - identity_registration_method: IdentityRegistrationMethod::FundWithUtxo( + identity_funding_method: IdentityFundingMethod::FundWithUtxo( utxo, tx_out, address, @@ -167,7 +167,7 @@ impl AddNewIdentityScreen { }; let mut step = self.step.write().unwrap(); - *step = AddNewIdentityWalletFundedScreenStep::WaitingForAssetLock; + *step = WalletFundedScreenStep::WaitingForAssetLock; // Create the backend task to register the identity action |= AppAction::BackendTask(BackendTask::IdentityTask( @@ -175,14 +175,14 @@ impl AddNewIdentityScreen { )) } } - AddNewIdentityWalletFundedScreenStep::ReadyToCreate => {} - AddNewIdentityWalletFundedScreenStep::WaitingForAssetLock => { + WalletFundedScreenStep::ReadyToCreate => {} + WalletFundedScreenStep::WaitingForAssetLock => { ui.heading("=> Waiting for Core Chain to produce proof of transfer of funds. <="); } - AddNewIdentityWalletFundedScreenStep::WaitingForPlatformAcceptance => { + WalletFundedScreenStep::WaitingForPlatformAcceptance => { ui.heading("=> Waiting for Platform acknowledgement. <="); } - AddNewIdentityWalletFundedScreenStep::Success => { + WalletFundedScreenStep::Success => { ui.heading("...Success..."); } } diff --git a/src/ui/identities/add_new_identity_screen/mod.rs b/src/ui/identities/add_new_identity_screen/mod.rs index e99943dc4..5f5d451b0 100644 --- a/src/ui/identities/add_new_identity_screen/mod.rs +++ b/src/ui/identities/add_new_identity_screen/mod.rs @@ -6,13 +6,14 @@ mod success_screen; use crate::app::AppAction; use crate::backend_task::core::CoreItem; use crate::backend_task::identity::{ - IdentityKeys, IdentityRegistrationInfo, IdentityRegistrationMethod, IdentityTask, + IdentityFundingMethod, IdentityKeys, IdentityRegistrationInfo, IdentityTask, }; use crate::backend_task::{BackendTask, BackendTaskSuccessResult}; use crate::context::AppContext; use crate::model::wallet::Wallet; use crate::ui::components::top_panel::add_top_panel; use crate::ui::components::wallet_unlock::ScreenWithWalletUnlock; +use crate::ui::identities::funding_common::WalletFundedScreenStep; use crate::ui::{MessageType, ScreenLike}; use arboard::Clipboard; use dash_sdk::dashcore_rpc::dashcore::transaction::special_transaction::TransactionPayload; @@ -55,20 +56,9 @@ impl fmt::Display for FundingMethod { } } -#[derive(Eq, PartialEq, Ord, PartialOrd, Copy, Clone)] -pub enum AddNewIdentityWalletFundedScreenStep { - ChooseFundingMethod, - WaitingOnFunds, - FundsReceived, - ReadyToCreate, - WaitingForAssetLock, - WaitingForPlatformAcceptance, - Success, -} - pub struct AddNewIdentityScreen { identity_id_number: u32, - step: Arc>, + step: Arc>, funding_asset_lock: Option<(Transaction, AssetLockProof, Address)>, selected_wallet: Option>>, core_has_funding_address: Option, @@ -91,35 +81,6 @@ pub struct AddNewIdentityScreen { successful_qualified_identity_id: Option, } -// Function to generate a QR code image from the address -fn generate_qr_code_image(pay_uri: &str) -> Result { - // Generate the QR code - let code = QrCode::new(pay_uri.as_bytes())?; - - // Render the QR code into an image buffer - let image = code.render::>().build(); - - // Convert the image buffer to ColorImage - let size = [image.width() as usize, image.height() as usize]; - let pixels = image.into_raw(); - let pixels: Vec = pixels - .into_iter() - .map(|p| { - let color = 255 - p; // Invert colors for better visibility - Color32::from_rgba_unmultiplied(color, color, color, 255) - }) - .collect(); - - Ok(ColorImage { size, pixels }) -} - -pub fn copy_to_clipboard(text: &str) -> Result<(), String> { - let mut clipboard = Clipboard::new().map_err(|e| e.to_string())?; - clipboard - .set_text(text.to_string()) - .map_err(|e| e.to_string()) -} - impl AddNewIdentityScreen { pub fn new(app_context: &Arc) -> Self { let mut selected_wallet = None; @@ -189,9 +150,7 @@ impl AddNewIdentityScreen { Self { identity_id_number, - step: Arc::new(RwLock::new( - AddNewIdentityWalletFundedScreenStep::ChooseFundingMethod, - )), + step: Arc::new(RwLock::new(WalletFundedScreenStep::ChooseFundingMethod)), funding_asset_lock: None, selected_wallet, core_has_funding_address: None, @@ -514,7 +473,7 @@ impl AddNewIdentityScreen { { self.update_identity_key(); let mut step = self.step.write().unwrap(); // Write lock on step - *step = AddNewIdentityWalletFundedScreenStep::ReadyToCreate; + *step = WalletFundedScreenStep::ReadyToCreate; } } if has_balance { @@ -532,7 +491,7 @@ impl AddNewIdentityScreen { self.funding_amount = format!("{:.4}", max_amount as f64 * 1e-8); } let mut step = self.step.write().unwrap(); // Write lock on step - *step = AddNewIdentityWalletFundedScreenStep::ReadyToCreate; + *step = WalletFundedScreenStep::ReadyToCreate; } } if ui @@ -544,7 +503,7 @@ impl AddNewIdentityScreen { .changed() { let mut step = self.step.write().unwrap(); // Write lock on step - *step = AddNewIdentityWalletFundedScreenStep::WaitingOnFunds; + *step = WalletFundedScreenStep::WaitingOnFunds; } // Uncomment this if AttachedCoreWallet is available in the future @@ -687,7 +646,7 @@ impl AddNewIdentityScreen { keys: self.identity_keys.clone(), wallet: Arc::clone(selected_wallet), // Clone the Arc reference wallet_identity_index: self.identity_id_number, - identity_registration_method: IdentityRegistrationMethod::UseAssetLock( + identity_funding_method: IdentityFundingMethod::UseAssetLock( address, funding_asset_lock, tx, @@ -695,7 +654,7 @@ impl AddNewIdentityScreen { }; let mut step = self.step.write().unwrap(); - *step = AddNewIdentityWalletFundedScreenStep::WaitingForPlatformAcceptance; + *step = WalletFundedScreenStep::WaitingForPlatformAcceptance; AppAction::BackendTask(BackendTask::IdentityTask( IdentityTask::RegisterIdentity(identity_input), @@ -718,14 +677,14 @@ impl AddNewIdentityScreen { keys: self.identity_keys.clone(), wallet: Arc::clone(selected_wallet), // Clone the Arc reference wallet_identity_index: self.identity_id_number, - identity_registration_method: IdentityRegistrationMethod::FundWithWallet( + identity_funding_method: IdentityFundingMethod::FundWithWallet( amount, self.identity_id_number, ), }; let mut step = self.step.write().unwrap(); - *step = AddNewIdentityWalletFundedScreenStep::WaitingForAssetLock; + *step = WalletFundedScreenStep::WaitingForAssetLock; // Create the backend task to register the identity AppAction::BackendTask(BackendTask::IdentityTask(IdentityTask::RegisterIdentity( @@ -902,8 +861,8 @@ impl ScreenLike for AddNewIdentityScreen { fn display_task_result(&mut self, backend_task_success_result: BackendTaskSuccessResult) { let mut step = self.step.write().unwrap(); match *step { - AddNewIdentityWalletFundedScreenStep::ChooseFundingMethod => {} - AddNewIdentityWalletFundedScreenStep::WaitingOnFunds => { + WalletFundedScreenStep::ChooseFundingMethod => {} + WalletFundedScreenStep::WaitingOnFunds => { if let Some(funding_address) = self.funding_address.as_ref() { if let BackendTaskSuccessResult::CoreItem( CoreItem::ReceivedAvailableUTXOTransaction(_, outpoints_with_addresses), @@ -911,16 +870,16 @@ impl ScreenLike for AddNewIdentityScreen { { for (outpoint, tx_out, address) in outpoints_with_addresses { if funding_address == &address { - *step = AddNewIdentityWalletFundedScreenStep::FundsReceived; + *step = WalletFundedScreenStep::FundsReceived; self.funding_utxo = Some((outpoint, tx_out, address)) } } } } } - AddNewIdentityWalletFundedScreenStep::FundsReceived => {} - AddNewIdentityWalletFundedScreenStep::ReadyToCreate => {} - AddNewIdentityWalletFundedScreenStep::WaitingForAssetLock => { + WalletFundedScreenStep::FundsReceived => {} + WalletFundedScreenStep::ReadyToCreate => {} + WalletFundedScreenStep::WaitingForAssetLock => { if let BackendTaskSuccessResult::CoreItem( CoreItem::ReceivedAvailableUTXOTransaction(tx, _), ) = backend_task_success_result @@ -947,21 +906,20 @@ impl ScreenLike for AddNewIdentityScreen { }) .is_some() { - *step = - AddNewIdentityWalletFundedScreenStep::WaitingForPlatformAcceptance; + *step = WalletFundedScreenStep::WaitingForPlatformAcceptance; } } } } - AddNewIdentityWalletFundedScreenStep::WaitingForPlatformAcceptance => { + WalletFundedScreenStep::WaitingForPlatformAcceptance => { if let BackendTaskSuccessResult::RegisteredIdentity(qualified_identity) = backend_task_success_result { self.successful_qualified_identity_id = Some(qualified_identity.identity.id()); - *step = AddNewIdentityWalletFundedScreenStep::Success; + *step = WalletFundedScreenStep::Success; } } - AddNewIdentityWalletFundedScreenStep::Success => {} + WalletFundedScreenStep::Success => {} } } fn ui(&mut self, ctx: &Context) -> AppAction { @@ -978,7 +936,7 @@ impl ScreenLike for AddNewIdentityScreen { egui::CentralPanel::default().show(ctx, |ui| { ScrollArea::vertical().show(ui, |ui| { let step = {self.step.read().unwrap().clone()}; - if step == AddNewIdentityWalletFundedScreenStep::Success { + if step == WalletFundedScreenStep::Success { action |= self.show_success(ui); return; } diff --git a/src/ui/identities/funding_common.rs b/src/ui/identities/funding_common.rs new file mode 100644 index 000000000..6cce51edb --- /dev/null +++ b/src/ui/identities/funding_common.rs @@ -0,0 +1,44 @@ +use arboard::Clipboard; +use eframe::epaint::{Color32, ColorImage}; +use image::Luma; +use qrcode::QrCode; + +#[derive(Eq, PartialEq, Ord, PartialOrd, Copy, Clone)] +pub enum WalletFundedScreenStep { + ChooseFundingMethod, + WaitingOnFunds, + FundsReceived, + ReadyToCreate, + WaitingForAssetLock, + WaitingForPlatformAcceptance, + Success, +} + +// Function to generate a QR code image from the address +pub fn generate_qr_code_image(pay_uri: &str) -> Result { + // Generate the QR code + let code = QrCode::new(pay_uri.as_bytes())?; + + // Render the QR code into an image buffer + let image = code.render::>().build(); + + // Convert the image buffer to ColorImage + let size = [image.width() as usize, image.height() as usize]; + let pixels = image.into_raw(); + let pixels: Vec = pixels + .into_iter() + .map(|p| { + let color = 255 - p; // Invert colors for better visibility + Color32::from_rgba_unmultiplied(color, color, color, 255) + }) + .collect(); + + Ok(ColorImage { size, pixels }) +} + +pub fn copy_to_clipboard(text: &str) -> Result<(), String> { + let mut clipboard = Clipboard::new().map_err(|e| e.to_string())?; + clipboard + .set_text(text.to_string()) + .map_err(|e| e.to_string()) +} diff --git a/src/ui/identities/mod.rs b/src/ui/identities/mod.rs index 4a35447c2..dabe88c8c 100644 --- a/src/ui/identities/mod.rs +++ b/src/ui/identities/mod.rs @@ -1,6 +1,8 @@ pub mod add_existing_identity_screen; pub mod add_new_identity_screen; +mod funding_common; pub mod identities_screen; pub mod keys; pub mod register_dpns_name_screen; +pub mod top_up_identity_screen; pub mod withdraw_from_identity_screen; diff --git a/src/ui/identities/top_up_identity_screen/by_using_unused_asset_lock.rs b/src/ui/identities/top_up_identity_screen/by_using_unused_asset_lock.rs new file mode 100644 index 000000000..95b0d8733 --- /dev/null +++ b/src/ui/identities/top_up_identity_screen/by_using_unused_asset_lock.rs @@ -0,0 +1,114 @@ +use crate::app::AppAction; +use crate::ui::identities::add_new_identity_screen::FundingMethod; +use crate::ui::identities::top_up_identity_screen::{TopUpIdentityScreen, WalletFundedScreenStep}; +use egui::Ui; + +impl TopUpIdentityScreen { + fn render_choose_funding_asset_lock(&mut self, ui: &mut egui::Ui) { + // Ensure a wallet is selected + let Some(selected_wallet) = self.selected_wallet.clone() else { + ui.label("No wallet selected."); + return; + }; + + // Read the wallet to access unused asset locks + let wallet = selected_wallet.read().unwrap(); + + if wallet.unused_asset_locks.is_empty() { + ui.label("No unused asset locks available."); + return; + } + + ui.heading("Select an unused asset lock:"); + + // Track the index of the currently selected asset lock (if any) + let selected_index = self.funding_asset_lock.as_ref().and_then(|(_, proof, _)| { + wallet + .unused_asset_locks + .iter() + .position(|(_, _, _, _, p)| p.as_ref() == Some(proof)) + }); + + // Display the asset locks in a scrollable area + egui::ScrollArea::vertical().show(ui, |ui| { + for (index, (tx, address, amount, islock, proof)) in + wallet.unused_asset_locks.iter().enumerate() + { + ui.horizontal(|ui| { + let tx_id = tx.txid().to_string(); + let lock_amount = *amount as f64 * 1e-8; // Convert to DASH + let is_locked = if islock.is_some() { "Yes" } else { "No" }; + + // Display asset lock information with "Selected" if this one is selected + let selected_text = if Some(index) == selected_index { + " (Selected)" + } else { + "" + }; + + ui.label(format!( + "TxID: {}, Address: {}, Amount: {:.8} DASH, InstantLock: {}{}", + tx_id, address, lock_amount, is_locked, selected_text + )); + + // Button to select this asset lock + if ui.button("Select").clicked() { + // Update the selected asset lock + self.funding_asset_lock = Some(( + tx.clone(), + proof.clone().expect("Asset lock proof is required"), + address.clone(), + )); + + // Update the step to ready to create identity + let mut step = self.step.write().unwrap(); + *step = WalletFundedScreenStep::ReadyToCreate; + } + }); + + ui.add_space(5.0); // Add space between each entry + } + }); + } + + pub fn render_ui_by_using_unused_asset_lock( + &mut self, + ui: &mut Ui, + step_number: u32, + ) -> AppAction { + let mut action = AppAction::None; + + // Extract the step from the RwLock to minimize borrow scope + let step = self.step.read().unwrap().clone(); + + ui.heading( + format!( + "{}. Choose the unused asset lock that you would like to use.", + step_number + ) + .as_str(), + ); + ui.add_space(10.0); + self.render_choose_funding_asset_lock(ui); + + if ui.button("Create Identity").clicked() { + self.error_message = None; + action |= self.register_identity_clicked(FundingMethod::UseUnusedAssetLock); + } + + match step { + WalletFundedScreenStep::WaitingForPlatformAcceptance => { + ui.heading("Waiting for Platform acknowledgement"); + } + WalletFundedScreenStep::Success => { + ui.heading("...Success..."); + } + _ => {} + } + + if let Some(error_message) = self.error_message.as_ref() { + ui.heading(error_message); + } + action + } +} diff --git a/src/ui/identities/top_up_identity_screen/by_using_unused_balance.rs b/src/ui/identities/top_up_identity_screen/by_using_unused_balance.rs new file mode 100644 index 000000000..ee061871c --- /dev/null +++ b/src/ui/identities/top_up_identity_screen/by_using_unused_balance.rs @@ -0,0 +1,74 @@ +use crate::app::AppAction; +use crate::ui::identities::add_new_identity_screen::FundingMethod; +use crate::ui::identities::top_up_identity_screen::{TopUpIdentityScreen, WalletFundedScreenStep}; +use egui::Ui; + +impl TopUpIdentityScreen { + fn show_wallet_balance(&self, ui: &mut egui::Ui) { + if let Some(selected_wallet) = &self.selected_wallet { + let wallet = selected_wallet.read().unwrap(); // Read lock on the wallet + + let total_balance: u64 = wallet.max_balance(); // Sum up all the balances + + let dash_balance = total_balance as f64 * 1e-8; // Convert to DASH units + + ui.horizontal(|ui| { + ui.label(format!("Wallet Balance: {:.8} DASH", dash_balance)); + }); + } else { + ui.label("No wallet selected"); + } + } + + pub fn render_ui_by_using_unused_balance( + &mut self, + ui: &mut Ui, + mut step_number: u32, + ) -> AppAction { + let mut action = AppAction::None; + + self.show_wallet_balance(ui); + + ui.add_space(10.0); + + ui.heading(format!( + "{}. How much of your wallet balance would you like to transfer?", + step_number + )); + + step_number += 1; + + self.top_up_funding_amount_input(ui); + + // Extract the step from the RwLock to minimize borrow scope + let step = self.step.read().unwrap().clone(); + + let Ok(_) = self.funding_amount.parse::() else { + return action; + }; + + if ui.button("Create Identity").clicked() { + self.error_message = None; + action = self.top_up_identity_clicked(FundingMethod::UseWalletBalance); + } + + match step { + WalletFundedScreenStep::WaitingForAssetLock => { + ui.heading("Waiting for Core Chain to produce proof of transfer of funds."); + } + WalletFundedScreenStep::WaitingForPlatformAcceptance => { + ui.heading("Waiting for Platform acknowledgement"); + } + WalletFundedScreenStep::Success => { + ui.heading("...Success..."); + } + _ => {} + } + + if let Some(error_message) = self.error_message.as_ref() { + ui.heading(error_message); + } + + action + } +} diff --git a/src/ui/identities/top_up_identity_screen/by_wallet_qr_code.rs b/src/ui/identities/top_up_identity_screen/by_wallet_qr_code.rs new file mode 100644 index 000000000..69a53d3bc --- /dev/null +++ b/src/ui/identities/top_up_identity_screen/by_wallet_qr_code.rs @@ -0,0 +1,184 @@ +use crate::app::AppAction; +use crate::backend_task::identity::{IdentityFundingMethod, IdentityTask, IdentityTopUpInfo}; +use crate::backend_task::BackendTask; +use crate::ui::identities::funding_common::{copy_to_clipboard, generate_qr_code_image}; +use crate::ui::identities::top_up_identity_screen::{TopUpIdentityScreen, WalletFundedScreenStep}; +use dash_sdk::dashcore_rpc::RpcApi; +use eframe::epaint::TextureHandle; +use egui::Ui; +use std::sync::Arc; + +impl TopUpIdentityScreen { + fn render_qr_code(&mut self, ui: &mut egui::Ui, amount: f64) -> Result<(), String> { + let (address, should_check_balance) = { + // Scope the write lock to ensure it's dropped before calling `start_balance_check`. + + if let Some(wallet_guard) = self.selected_wallet.as_ref() { + // Get the receive address + if self.funding_address.is_none() { + let mut wallet = wallet_guard.write().unwrap(); + let receive_address = wallet.receive_address( + self.app_context.network, + false, + Some(&self.app_context), + )?; + + if let Some(has_address) = self.core_has_funding_address { + if !has_address { + self.app_context + .core_client + .import_address( + &receive_address, + Some("Managed by Dash Evo Tool"), + Some(false), + ) + .map_err(|e| e.to_string())?; + } + self.funding_address = Some(receive_address); + } else { + let info = self + .app_context + .core_client + .get_address_info(&receive_address) + .map_err(|e| e.to_string())?; + + if !(info.is_watchonly || info.is_mine) { + self.app_context + .core_client + .import_address( + &receive_address, + Some("Managed by Dash Evo Tool"), + Some(false), + ) + .map_err(|e| e.to_string())?; + } + self.funding_address = Some(receive_address); + self.core_has_funding_address = Some(true); + } + + // Extract the address to return it outside this scope + (self.funding_address.as_ref().unwrap().clone(), true) + } else { + (self.funding_address.as_ref().unwrap().clone(), false) + } + } else { + return Err("No wallet selected".to_string()); + } + }; + + let pay_uri = format!("{}?amount={:.4}", address.to_qr_uri(), amount); + + // Generate the QR code image + if let Ok(qr_image) = generate_qr_code_image(&pay_uri) { + let texture: TextureHandle = + ui.ctx() + .load_texture("qr_code", qr_image, egui::TextureOptions::LINEAR); + ui.image(&texture); + } else { + ui.label("Failed to generate QR code."); + } + + ui.add_space(10.0); + + ui.horizontal(|ui| { + ui.label(&pay_uri); + ui.add_space(8.0); + + if ui.button("Copy").clicked() { + if let Err(e) = copy_to_clipboard(pay_uri.as_str()) { + self.copied_to_clipboard = Some(Some(e)); + } else { + self.copied_to_clipboard = Some(None); + } + } + + if let Some(error) = self.copied_to_clipboard.as_ref() { + if let Some(error) = error { + ui.label(format!("Failed to copy to clipboard: {}", error)); + } else { + ui.label("Address copied to clipboard."); + } + } + }); + + Ok(()) + } + + pub fn render_ui_by_wallet_qr_code(&mut self, ui: &mut Ui, step_number: u32) -> AppAction { + let mut action = AppAction::None; + + // Extract the step from the RwLock to minimize borrow scope + let step = self.step.read().unwrap().clone(); + + let Ok(amount_dash) = self.funding_amount.parse::() else { + return action; + }; + + ui.add_space(10.0); + + ui.heading( + format!( + "{}. Select how much you would like to transfer?", + step_number + ) + .as_str(), + ); + + ui.add_space(8.0); + + self.top_up_funding_amount_input(ui); + + if let Err(e) = self.render_qr_code(ui, amount_dash) { + eprintln!("Error: {:?}", e); + } + + ui.add_space(20.0); + + match step { + WalletFundedScreenStep::ChooseFundingMethod => {} + WalletFundedScreenStep::WaitingOnFunds => { + ui.heading("=> Waiting for funds. <="); + } + WalletFundedScreenStep::FundsReceived => { + let Some(selected_wallet) = &self.selected_wallet else { + return action; + }; + if let Some((utxo, tx_out, address)) = self.funding_utxo.clone() { + let identity_input = IdentityTopUpInfo { + qualified_identity: QualifiedIdentity {}, + wallet: Arc::clone(selected_wallet), // Clone the Arc reference + identity_funding_method: IdentityFundingMethod::FundWithUtxo( + utxo, + tx_out, + address, + self.identity_id_number, + ), + }; + + let mut step = self.step.write().unwrap(); + *step = WalletFundedScreenStep::WaitingForAssetLock; + + // Create the backend task to register the identity + action |= AppAction::BackendTask(BackendTask::IdentityTask( + IdentityTask::TopUpIdentity(identity_input), + )) + } + } + WalletFundedScreenStep::ReadyToCreate => {} + WalletFundedScreenStep::WaitingForAssetLock => { + ui.heading("=> Waiting for Core Chain to produce proof of transfer of funds. <="); + } + WalletFundedScreenStep::WaitingForPlatformAcceptance => { + ui.heading("=> Waiting for Platform acknowledgement. <="); + } + WalletFundedScreenStep::Success => { + ui.heading("...Success..."); + } + } + + if let Some(error_message) = self.error_message.as_ref() { + ui.heading(error_message); + } + action + } +} diff --git a/src/ui/identities/top_up_identity_screen/mod.rs b/src/ui/identities/top_up_identity_screen/mod.rs new file mode 100644 index 000000000..fe71528da --- /dev/null +++ b/src/ui/identities/top_up_identity_screen/mod.rs @@ -0,0 +1,624 @@ +mod by_using_unused_asset_lock; +mod by_using_unused_balance; +mod by_wallet_qr_code; +mod success_screen; + +use crate::app::AppAction; +use crate::backend_task::core::CoreItem; +use crate::backend_task::identity::{ + IdentityFundingMethod, IdentityKeys, IdentityTask, IdentityTopUpInfo, +}; +use crate::backend_task::{BackendTask, BackendTaskSuccessResult}; +use crate::context::AppContext; +use crate::model::qualified_identity::QualifiedIdentity; +use crate::model::wallet::Wallet; +use crate::ui::components::top_panel::add_top_panel; +use crate::ui::components::wallet_unlock::ScreenWithWalletUnlock; +use crate::ui::identities::add_new_identity_screen::FundingMethod; +use crate::ui::identities::funding_common::WalletFundedScreenStep; +use crate::ui::{MessageType, ScreenLike}; +use dash_sdk::dashcore_rpc::dashcore::transaction::special_transaction::TransactionPayload; +use dash_sdk::dashcore_rpc::dashcore::Address; +use dash_sdk::dpp::balances::credits::Duffs; +use dash_sdk::dpp::dashcore::{OutPoint, Transaction, TxOut}; +use dash_sdk::dpp::identity::accessors::IdentityGettersV0; +use dash_sdk::dpp::identity::{KeyType, Purpose, SecurityLevel}; +use dash_sdk::dpp::platform_value::string_encoding::Encoding; +use dash_sdk::dpp::prelude::AssetLockProof; +use eframe::egui::Context; +use egui::{ComboBox, ScrollArea, Ui}; +use std::cmp::PartialEq; +use std::sync::atomic::Ordering; +use std::sync::{Arc, RwLock}; + +pub struct TopUpIdentityScreen { + identity: QualifiedIdentity, + step: Arc>, + funding_asset_lock: Option<(Transaction, AssetLockProof, Address)>, + selected_wallet: Option>>, + core_has_funding_address: Option, + funding_address: Option
, + funding_address_balance: Arc>>, + funding_method: Arc>, + funding_amount: String, + funding_amount_exact: Option, + funding_utxo: Option<(OutPoint, TxOut, Address)>, + alias_input: String, + copied_to_clipboard: Option>, + error_message: Option, + show_password: bool, + wallet_password: String, + show_pop_up_info: Option, + in_key_selection_advanced_mode: bool, + pub app_context: Arc, +} + +impl TopUpIdentityScreen { + pub fn new(qualified_identity: QualifiedIdentity, app_context: &Arc) -> Self { + let mut selected_wallet = None; + let mut identity_keys = None; + if app_context.has_wallet.load(Ordering::Relaxed) { + let wallets = &app_context.wallets.read().unwrap(); + if let Some(wallet) = wallets.first() { + // Automatically select the only available wallet + selected_wallet = Some(wallet.clone()); + let mut wallet = wallet.write().unwrap(); + + if wallet.is_open() { + identity_keys = Some(IdentityKeys { + master_private_key: Some( + wallet + .identity_authentication_ecdsa_private_key( + app_context.network, + 0, + 0, + Some(&app_context), + ) + .expect("expected to have decrypted wallet"), + ), + master_private_key_type: KeyType::ECDSA_HASH160, + keys_input: vec![ + ( + wallet + .identity_authentication_ecdsa_private_key( + app_context.network, + 0, + 1, + Some(&app_context), + ) + .expect("expected to have decrypted wallet"), + KeyType::ECDSA_HASH160, + Purpose::AUTHENTICATION, + SecurityLevel::HIGH, + ), + ( + wallet + .identity_authentication_ecdsa_private_key( + app_context.network, + 0, + 2, + Some(&app_context), + ) + .expect("expected to have decrypted wallet"), + KeyType::ECDSA_HASH160, + Purpose::TRANSFER, + SecurityLevel::CRITICAL, + ), + ], + }); + } + } + } + + Self { + identity: qualified_identity, + step: Arc::new(RwLock::new(WalletFundedScreenStep::ChooseFundingMethod)), + funding_asset_lock: None, + selected_wallet, + core_has_funding_address: None, + funding_address: None, + funding_address_balance: Arc::new(RwLock::new(None)), + funding_method: Arc::new(RwLock::new(FundingMethod::NoSelection)), + funding_amount: "0.5".to_string(), + funding_amount_exact: None, + funding_utxo: None, + alias_input: String::new(), + copied_to_clipboard: None, + error_message: None, + show_password: false, + wallet_password: "".to_string(), + show_pop_up_info: None, + in_key_selection_advanced_mode: false, + app_context: app_context.clone(), + } + } + + fn render_identity_index_input(&mut self, ui: &mut egui::Ui) { + let mut index_changed = false; // Track if the index has changed + + let identity_id = self.identity.alias.clone().unwrap_or_else(|| { + let id_str = self.identity.id().to_string(Encoding::Base58); + id_str.chars().take(5).collect() + }); + ui.label(format!("Topping up Identity: {}", identity_id)); + + // If the index has changed, update the identity key + if index_changed { + self.update_identity_key(); + } + } + + fn render_wallet_selection(&mut self, ui: &mut Ui) -> bool { + if self.app_context.has_wallet.load(Ordering::Relaxed) { + let wallets = &self.app_context.wallets.read().unwrap(); + if wallets.len() > 1 { + // Retrieve the alias of the currently selected wallet, if any + let selected_wallet_alias = self + .selected_wallet + .as_ref() + .and_then(|wallet| wallet.read().ok()?.alias.clone()) + .unwrap_or_else(|| "Select".to_string()); + + ui.heading( + "1. Choose the wallet to use in which this identities keys will come from.", + ); + + ui.add_space(10.0); + + // Display the ComboBox for wallet selection + ComboBox::from_label("Select Wallet") + .selected_text(selected_wallet_alias) + .show_ui(ui, |ui| { + for wallet in wallets.iter() { + let wallet_alias = wallet + .read() + .ok() + .and_then(|w| w.alias.clone()) + .unwrap_or_else(|| "Unnamed Wallet".to_string()); + + let is_selected = self + .selected_wallet + .as_ref() + .map_or(false, |selected| Arc::ptr_eq(selected, wallet)); + + if ui.selectable_label(is_selected, wallet_alias).clicked() { + { + let wallet = wallet.read().unwrap(); + self.identity_id_number = + wallet.identities.keys().copied().max().unwrap_or_default(); + } + // Update the selected wallet + self.selected_wallet = Some(wallet.clone()); + } + } + }); + ui.add_space(10.0); + true + } else if let Some(wallet) = wallets.first() { + if self.selected_wallet.is_none() { + // Automatically select the only available wallet + self.selected_wallet = Some(wallet.clone()); + } + false + } else { + false + } + } else { + false + } + } + + fn render_funding_method(&mut self, ui: &mut egui::Ui) { + let Some(selected_wallet) = self.selected_wallet.clone() else { + return; + }; + let funding_method_arc = self.funding_method.clone(); + let mut funding_method = funding_method_arc.write().unwrap(); // Write lock on funding_method + + ComboBox::from_label("Funding Method") + .selected_text(format!("{}", *funding_method)) + .show_ui(ui, |ui| { + ui.selectable_value( + &mut *funding_method, + FundingMethod::NoSelection, + "Please select funding method", + ); + + let (has_unused_asset_lock, has_balance) = { + let wallet = selected_wallet.read().unwrap(); + (wallet.has_unused_asset_lock(), wallet.has_balance()) + }; + + if has_unused_asset_lock { + if ui + .selectable_value( + &mut *funding_method, + FundingMethod::UseUnusedAssetLock, + "Use Unused Evo Funding Locks (recommended)", + ) + .changed() + { + self.update_identity_key(); + let mut step = self.step.write().unwrap(); // Write lock on step + *step = WalletFundedScreenStep::ReadyToCreate; + } + } + if has_balance { + if ui + .selectable_value( + &mut *funding_method, + FundingMethod::UseWalletBalance, + "Use Wallet Balance", + ) + .changed() + { + if let Some(wallet) = &self.selected_wallet { + let wallet = wallet.read().unwrap(); // Read lock on the wallet + let max_amount = wallet.max_balance(); + self.funding_amount = format!("{:.4}", max_amount as f64 * 1e-8); + } + let mut step = self.step.write().unwrap(); // Write lock on step + *step = WalletFundedScreenStep::ReadyToCreate; + } + } + if ui + .selectable_value( + &mut *funding_method, + FundingMethod::AddressWithQRCode, + "Address with QR Code", + ) + .changed() + { + let mut step = self.step.write().unwrap(); // Write lock on step + *step = WalletFundedScreenStep::WaitingOnFunds; + } + + // Uncomment this if AttachedCoreWallet is available in the future + // ui.selectable_value( + // &mut *funding_method, + // FundingMethod::AttachedCoreWallet, + // "Attached Core Wallet", + // ); + }); + } + fn top_up_identity_clicked(&mut self, funding_method: FundingMethod) -> AppAction { + let Some(selected_wallet) = &self.selected_wallet else { + return AppAction::None; + }; + match funding_method { + FundingMethod::UseUnusedAssetLock => { + if let Some((tx, funding_asset_lock, address)) = self.funding_asset_lock.clone() { + let identity_input = IdentityTopUpInfo { + wallet: Arc::clone(selected_wallet), // Clone the Arc reference + identity_funding_method: IdentityFundingMethod::UseAssetLock( + address, + funding_asset_lock, + tx, + ), + }; + + let mut step = self.step.write().unwrap(); + *step = WalletFundedScreenStep::WaitingForPlatformAcceptance; + + AppAction::BackendTask(BackendTask::IdentityTask(IdentityTask::TopUpIdentity( + identity_input, + ))) + } else { + AppAction::None + } + } + FundingMethod::UseWalletBalance => { + // Parse the funding amount or fall back to the default value + let amount = self.funding_amount_exact.unwrap_or_else(|| { + (self.funding_amount.parse::().unwrap_or_else(|_| 0.0) * 1e8) as u64 + }); + + if amount == 0 { + return AppAction::None; + } + let identity_input = IdentityTopUpInfo { + wallet: Arc::clone(selected_wallet), // Clone the Arc reference + identity_funding_method: IdentityFundingMethod::FundWithWallet( + amount, + self.identity_id_number, + ), + }; + + let mut step = self.step.write().unwrap(); + *step = WalletFundedScreenStep::WaitingForAssetLock; + + // Create the backend task to top_up the identity + AppAction::BackendTask(BackendTask::IdentityTask(IdentityTask::RegisterIdentity( + identity_input, + ))) + } + _ => AppAction::None, + } + } + + fn top_up_funding_amount_input(&mut self, ui: &mut egui::Ui) { + let funding_method = self.funding_method.read().unwrap(); // Read lock on funding_method + + ui.horizontal(|ui| { + ui.label("Funding Amount (DASH):"); + + // Render the text input field for the funding amount + let amount_input = ui + .add( + egui::TextEdit::singleline(&mut self.funding_amount) + .hint_text("Enter amount (e.g., 0.1234)") + .desired_width(100.0), + ) + .lost_focus(); + + let enter_pressed = ui.input(|i| i.key_pressed(egui::Key::Enter)); + + if amount_input && enter_pressed { + // Optional: Validate the input when Enter is pressed + if self.funding_amount.parse::().is_err() { + ui.label("Invalid amount. Please enter a valid number."); + } + } + + // Check if the funding method is `UseWalletBalance` + if *funding_method == FundingMethod::UseWalletBalance { + // Safely access the selected wallet + if let Some(wallet) = &self.selected_wallet { + let wallet = wallet.read().unwrap(); // Read lock on the wallet + if ui.button("Max").clicked() { + let max_amount = wallet.max_balance(); + self.funding_amount = format!("{:.4}", max_amount as f64 * 1e-8); + self.funding_amount_exact = Some(max_amount); + } + } + } + }); + } +} + +impl ScreenWithWalletUnlock for TopUpIdentityScreen { + 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() + } +} + +impl ScreenLike for TopUpIdentityScreen { + fn display_message(&mut self, message: &str, message_type: MessageType) { + if message_type == MessageType::Error { + self.error_message = Some(format!("Error top_uping identity: {}", message)); + } else { + self.error_message = Some(message.to_string()); + } + } + fn display_task_result(&mut self, backend_task_success_result: BackendTaskSuccessResult) { + let mut step = self.step.write().unwrap(); + match *step { + WalletFundedScreenStep::ChooseFundingMethod => {} + WalletFundedScreenStep::WaitingOnFunds => { + if let Some(funding_address) = self.funding_address.as_ref() { + if let BackendTaskSuccessResult::CoreItem( + CoreItem::ReceivedAvailableUTXOTransaction(_, outpoints_with_addresses), + ) = backend_task_success_result + { + for (outpoint, tx_out, address) in outpoints_with_addresses { + if funding_address == &address { + *step = WalletFundedScreenStep::FundsReceived; + self.funding_utxo = Some((outpoint, tx_out, address)) + } + } + } + } + } + WalletFundedScreenStep::FundsReceived => {} + WalletFundedScreenStep::ReadyToCreate => {} + WalletFundedScreenStep::WaitingForAssetLock => { + if let BackendTaskSuccessResult::CoreItem( + CoreItem::ReceivedAvailableUTXOTransaction(tx, _), + ) = backend_task_success_result + { + if let Some(TransactionPayload::AssetLockPayloadType(asset_lock_payload)) = + tx.special_transaction_payload + { + if asset_lock_payload + .credit_outputs + .iter() + .find(|tx_out| { + let Ok(address) = Address::from_script( + &tx_out.script_pubkey, + self.app_context.network, + ) else { + return false; + }; + if let Some(wallet) = &self.selected_wallet { + let wallet = wallet.read().unwrap(); + wallet.known_addresses.contains_key(&address) + } else { + false + } + }) + .is_some() + { + *step = WalletFundedScreenStep::WaitingForPlatformAcceptance; + } + } + } + } + WalletFundedScreenStep::WaitingForPlatformAcceptance => { + if let BackendTaskSuccessResult::ToppedUpIdentity(qualified_identity) = + backend_task_success_result + { + *step = WalletFundedScreenStep::Success; + } + } + WalletFundedScreenStep::Success => {} + } + } + fn ui(&mut self, ctx: &Context) -> AppAction { + let mut action = add_top_panel( + ctx, + &self.app_context, + vec![ + ("Identities", AppAction::GoToMainScreen), + ("Top Up Identity", AppAction::None), + ], + vec![], + ); + + egui::CentralPanel::default().show(ctx, |ui| { + ScrollArea::vertical().show(ui, |ui| { + let step = {self.step.read().unwrap().clone()}; + if step == WalletFundedScreenStep::Success { + action |= self.show_success(ui); + return; + } + ui.add_space(10.0); + ui.heading("Follow these steps to top up your identity!"); + ui.add_space(15.0); + + let mut step_number = 1; + + if self.render_wallet_selection(ui) { + // We had more than 1 wallet + step_number += 1; + } + + if self.selected_wallet.is_none() { + return; + }; + + // Display the heading with an info icon that shows a tooltip on hover + ui.horizontal(|ui| { + let wallet_guard = self.selected_wallet.as_ref().unwrap(); + let wallet = wallet_guard.read().unwrap(); + if wallet.identities.is_empty() { + ui.heading(format!( + "{}. Choose an identity index. Leave this 0 if this is your first identity for this wallet.", + step_number + )); + } else { + ui.heading(format!( + "{}. Choose an identity index. Leaving this {} is recommended.", + step_number, + wallet.identities.keys().cloned().max().map(|max| max + 1).unwrap_or_default() + )); + } + + + // Create a label with click sense and tooltip + let info_icon = egui::Label::new("ℹ").sense(egui::Sense::click()); + let response = ui.add(info_icon) + .on_hover_text("The identity index is an internal reference within the wallet. The wallet’s seed phrase can always be used to recover any identity, including this one, by using the same index."); + + // Check if the label was clicked + if response.clicked() { + self.show_pop_up_info = Some("The identity index is an internal reference within the wallet. The wallet’s seed phrase can always be used to recover any identity, including this one, by using the same index.".to_string()); + } + }); + + step_number += 1; + + ui.add_space(8.0); + + self.render_identity_index_input(ui); + + ui.add_space(10.0); + + // Display the heading with an info icon that shows a tooltip on hover + ui.horizontal(|ui| { + ui.heading(format!( + "{}. Choose what keys you want to add to this new identity.", + step_number + )); + + // Create a label with click sense and tooltip + let info_icon = egui::Label::new("ℹ").sense(egui::Sense::click()); + let response = ui.add(info_icon) + .on_hover_text("Keys allow an identity to perform actions on the Blockchain. They are contained in your wallet and allow you to prove that the action you are making is really coming from yourself."); + + // Check if the label was clicked + if response.clicked() { + self.show_pop_up_info = Some("Keys allow an identity to perform actions on the Blockchain. They are contained in your wallet and allow you to prove that the action you are making is really coming from yourself.".to_string()); + } + }); + + step_number += 1; + + ui.add_space(8.0); + + self.render_key_selection(ui); + + ui.add_space(10.0); + + ui.heading( + format!("{}. Choose your funding method.", step_number).as_str() + ); + step_number += 1; + + ui.add_space(10.0); + self.render_funding_method(ui); + + // Extract the funding method from the RwLock to minimize borrow scope + let funding_method = self.funding_method.read().unwrap().clone(); + + if funding_method == FundingMethod::NoSelection { + return; + } + + match funding_method { + FundingMethod::NoSelection => return, + FundingMethod::UseUnusedAssetLock => { + action |= self.render_ui_by_using_unused_asset_lock(ui, step_number); + }, + FundingMethod::UseWalletBalance => { + action |= self.render_ui_by_using_unused_balance(ui, step_number); + }, + FundingMethod::AddressWithQRCode => { + action |= self.render_ui_by_wallet_qr_code(ui, step_number) + }, + FundingMethod::AttachedCoreWallet => return, + } + }); + }); + + // Show the popup window if `show_popup` is true + if let Some(show_pop_up_info_text) = self.show_pop_up_info.clone() { + egui::Window::new("Identity Index Information") + .collapsible(false) // Prevent collapsing + .resizable(false) // Prevent resizing + .show(ctx, |ui| { + ui.label(show_pop_up_info_text); + + // Add a close button to dismiss the popup + if ui.button("Close").clicked() { + self.show_pop_up_info = None + } + }); + } + + action + } +} diff --git a/src/ui/identities/top_up_identity_screen/success_screen.rs b/src/ui/identities/top_up_identity_screen/success_screen.rs new file mode 100644 index 000000000..3550cdf24 --- /dev/null +++ b/src/ui/identities/top_up_identity_screen/success_screen.rs @@ -0,0 +1,44 @@ +use crate::app::AppAction; +use crate::ui::identities::add_new_identity_screen::TopUpIdentityScreen; +use crate::ui::identities::register_dpns_name_screen::RegisterDpnsNameScreen; +use crate::ui::identities::top_up_identity_screen::TopUpIdentityScreen; +use crate::ui::{RootScreenType, Screen}; +use egui::Ui; + +impl TopUpIdentityScreen { + pub fn show_success(&self, ui: &mut Ui) -> AppAction { + let mut action = AppAction::None; + + // Center the content vertically and horizontally + ui.vertical_centered(|ui| { + ui.add_space(50.0); + + ui.heading("🎉"); + ui.heading("Success!"); + + ui.add_space(20.0); + + // Display the "Back to Identities" button + if ui.button("Back to Identities").clicked() { + // Handle navigation back to the identities screen + action = AppAction::PopScreenAndRefresh; + } + + // Display the "Register Name" button + if ui.button("Register Name").clicked() { + let mut screen = RegisterDpnsNameScreen::new(&self.app_context); + if let Some(identity_id) = self.successful_qualified_identity_id { + screen.select_identity(identity_id); + screen.show_identity_selector = false; + } + // Handle the registration of a new name + action = AppAction::PopThenAddScreenToMainScreen( + RootScreenType::RootScreenDPNSOwnedNames, + Screen::RegisterDpnsNameScreen(screen), + ); + } + }); + + action + } +} From e26e1225888bd7d4da03a72c413ac36ea4bd22f9 Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Mon, 25 Nov 2024 19:15:09 +0300 Subject: [PATCH 2/5] a lot of work for top up --- src/ui/identities/identities_screen.rs | 18 +++++++++-------- .../identities/top_up_identity_screen/mod.rs | 2 +- src/ui/mod.rs | 20 ++++++++++++++++++- 3 files changed, 30 insertions(+), 10 deletions(-) diff --git a/src/ui/identities/identities_screen.rs b/src/ui/identities/identities_screen.rs index 046cb760e..428e37b4c 100644 --- a/src/ui/identities/identities_screen.rs +++ b/src/ui/identities/identities_screen.rs @@ -300,8 +300,7 @@ impl IdentitiesScreen { .column(Column::initial(100.0).resizable(true)) // Balance .column(Column::initial(80.0).resizable(true)) // Type .column(Column::initial(80.0).resizable(true)) // Keys - .column(Column::initial(80.0).resizable(true)) // Withdraw - .column(Column::initial(80.0).resizable(true)) // Transfer + .column(Column::initial(100.0).resizable(true)) // Balance .column(Column::initial(80.0).resizable(true)) // Actions .header(30.0, |mut header| { header.col(|ui| { @@ -323,10 +322,7 @@ impl IdentitiesScreen { ui.heading("Keys"); }); header.col(|ui| { - ui.heading("Withdraw"); - }); - header.col(|ui| { - ui.heading("Transfer"); + ui.heading("Balance"); }); header.col(|ui| { ui.heading("Actions"); @@ -446,8 +442,14 @@ impl IdentitiesScreen { )), ); } - }); - row.col(|ui| { + if ui.button("Top up").clicked() { + action = AppAction::AddScreen( + Screen::WithdrawalScreen(WithdrawalScreen::new( + qualified_identity.clone(), + &self.app_context, + )), + ); + } if ui.button("Transfer").clicked() { action = AppAction::AddScreen(Screen::TransferScreen( TransferScreen::new( diff --git a/src/ui/identities/top_up_identity_screen/mod.rs b/src/ui/identities/top_up_identity_screen/mod.rs index fe71528da..df662c2e3 100644 --- a/src/ui/identities/top_up_identity_screen/mod.rs +++ b/src/ui/identities/top_up_identity_screen/mod.rs @@ -32,7 +32,7 @@ use std::sync::atomic::Ordering; use std::sync::{Arc, RwLock}; pub struct TopUpIdentityScreen { - identity: QualifiedIdentity, + pub identity: QualifiedIdentity, step: Arc>, funding_asset_lock: Option<(Transaction, AssetLockProof, Address)>, selected_wallet: Option>>, diff --git a/src/ui/mod.rs b/src/ui/mod.rs index 556879dda..9a3513a62 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -10,6 +10,7 @@ use crate::ui::dpns_contested_names_screen::DPNSContestedNamesScreen; use crate::ui::identities::keys::add_key_screen::AddKeyScreen; use crate::ui::identities::keys::key_info_screen::KeyInfoScreen; use crate::ui::identities::keys::keys_screen::KeysScreen; +use crate::ui::identities::top_up_identity_screen::TopUpIdentityScreen; use crate::ui::identities::withdraw_from_identity_screen::WithdrawalScreen; use crate::ui::network_chooser_screen::NetworkChooserScreen; use crate::ui::tool_screens::proof_log_screen::ProofLogScreen; @@ -20,7 +21,7 @@ use crate::ui::withdrawal_statuses_screen::WithdrawsStatusScreen; use dash_sdk::dpp::identity::Identity; use dash_sdk::dpp::prelude::IdentityPublicKey; use dpns_contested_names_screen::DPNSSubscreen; -use egui::Context; +use egui::{Context, Widget}; use identities::add_existing_identity_screen::AddExistingIdentityScreen; use identities::add_new_identity_screen::AddNewIdentityScreen; use identities::identities_screen::IdentitiesScreen; @@ -136,6 +137,7 @@ pub enum ScreenType { NetworkChooser, RegisterDpnsName, ProofLog, + TopUpIdentity(QualifiedIdentity), } impl ScreenType { @@ -154,6 +156,9 @@ impl ScreenType { ScreenType::AddNewIdentity => { Screen::AddNewIdentityScreen(AddNewIdentityScreen::new(app_context)) } + ScreenType::TopUpIdentity(identity) => { + Screen::TopUpIdentityScreen(TopUpIdentityScreen::new(identity.clone(), app_context)) + } ScreenType::AddExistingIdentity => { Screen::AddExistingIdentityScreen(AddExistingIdentityScreen::new(app_context)) } @@ -218,6 +223,7 @@ pub enum Screen { KeysScreen(KeysScreen), RegisterDpnsNameScreen(RegisterDpnsNameScreen), WithdrawalScreen(WithdrawalScreen), + TopUpIdentityScreen(TopUpIdentityScreen), TransferScreen(TransferScreen), AddKeyScreen(AddKeyScreen), ProofLogScreen(ProofLogScreen), @@ -244,6 +250,7 @@ impl Screen { Screen::RegisterDpnsNameScreen(screen) => screen.app_context = app_context, Screen::AddNewWalletScreen(screen) => screen.app_context = app_context, Screen::TransferScreen(screen) => screen.app_context = app_context, + Screen::TopUpIdentityScreen(screen) => screen.app_context = app_context, Screen::WalletsBalancesScreen(screen) => screen.app_context = app_context, Screen::WithdrawsStatusScreen(screen) => screen.app_context = app_context, Screen::ImportWalletScreen(screen) => screen.app_context = app_context, @@ -318,6 +325,9 @@ impl Screen { Screen::AddKeyScreen(screen) => ScreenType::AddKeyScreen(screen.identity.clone()), Screen::DocumentQueryScreen(_) => ScreenType::DocumentQueryScreen, Screen::AddNewIdentityScreen(_) => ScreenType::AddExistingIdentity, + Screen::TopUpIdentityScreen(screen) => { + ScreenType::TopUpIdentity(screen.identity.clone()) + } Screen::RegisterDpnsNameScreen(_) => ScreenType::RegisterDpnsName, Screen::AddNewWalletScreen(_) => ScreenType::AddNewWallet, Screen::TransferScreen(screen) => ScreenType::TransferScreen(screen.identity.clone()), @@ -338,6 +348,7 @@ impl ScreenLike for Screen { Screen::AddNewWalletScreen(screen) => screen.refresh(), Screen::ImportWalletScreen(screen) => screen.refresh(), Screen::AddNewIdentityScreen(screen) => screen.refresh(), + Screen::TopUpIdentityScreen(screen) => screen.refresh(), Screen::AddExistingIdentityScreen(screen) => screen.refresh(), Screen::KeyInfoScreen(screen) => screen.refresh(), Screen::KeysScreen(screen) => screen.refresh(), @@ -361,6 +372,7 @@ impl ScreenLike for Screen { Screen::AddNewWalletScreen(screen) => screen.refresh_on_arrival(), Screen::ImportWalletScreen(screen) => screen.refresh_on_arrival(), Screen::AddNewIdentityScreen(screen) => screen.refresh_on_arrival(), + Screen::TopUpIdentityScreen(screen) => screen.refresh_on_arrival(), Screen::AddExistingIdentityScreen(screen) => screen.refresh_on_arrival(), Screen::KeyInfoScreen(screen) => screen.refresh_on_arrival(), Screen::KeysScreen(screen) => screen.refresh_on_arrival(), @@ -384,6 +396,7 @@ impl ScreenLike for Screen { Screen::AddNewWalletScreen(screen) => screen.ui(ctx), Screen::ImportWalletScreen(screen) => screen.ui(ctx), Screen::AddNewIdentityScreen(screen) => screen.ui(ctx), + Screen::TopUpIdentityScreen(screen) => screen.ui(ctx), Screen::AddExistingIdentityScreen(screen) => screen.ui(ctx), Screen::KeyInfoScreen(screen) => screen.ui(ctx), Screen::KeysScreen(screen) => screen.ui(ctx), @@ -409,6 +422,7 @@ impl ScreenLike for Screen { Screen::AddNewWalletScreen(screen) => screen.display_message(message, message_type), Screen::ImportWalletScreen(screen) => screen.display_message(message, message_type), Screen::AddNewIdentityScreen(screen) => screen.display_message(message, message_type), + Screen::TopUpIdentityScreen(screen) => screen.display_message(message, message_type), Screen::AddExistingIdentityScreen(screen) => { screen.display_message(message, message_type) } @@ -448,6 +462,9 @@ impl ScreenLike for Screen { Screen::AddNewIdentityScreen(screen) => { screen.display_task_result(backend_task_success_result.clone()) } + Screen::TopUpIdentityScreen(screen) => { + screen.display_task_result(backend_task_success_result.clone()) + } Screen::AddExistingIdentityScreen(screen) => { screen.display_task_result(backend_task_success_result.clone()) } @@ -495,6 +512,7 @@ impl ScreenLike for Screen { Screen::AddNewWalletScreen(screen) => screen.pop_on_success(), Screen::ImportWalletScreen(screen) => screen.pop_on_success(), Screen::AddNewIdentityScreen(screen) => screen.pop_on_success(), + Screen::TopUpIdentityScreen(screen) => screen.pop_on_success(), Screen::AddExistingIdentityScreen(screen) => screen.pop_on_success(), Screen::KeyInfoScreen(screen) => screen.pop_on_success(), Screen::KeysScreen(screen) => screen.pop_on_success(), From a10481b14fbcb265aaa428e555f9b104bfdaade6 Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Tue, 26 Nov 2024 15:23:52 +0300 Subject: [PATCH 3/5] more work on topping up indentities --- Cargo.toml | 2 +- src/app.rs | 2 +- src/backend_task/core/start_dash_qt.rs | 2 - src/backend_task/identity/load_identity.rs | 1 + .../identity/load_identity_from_wallet.rs | 1 + src/backend_task/identity/mod.rs | 16 ++- .../identity/register_identity.rs | 20 ++- src/backend_task/identity/top_up_identity.rs | 29 ++-- src/backend_task/mod.rs | 2 +- src/database/wallet.rs | 3 +- src/model/proof_log_item.rs | 1 - src/model/qualified_identity/mod.rs | 5 + src/model/wallet/asset_lock_transaction.rs | 107 ++++++++++++++- src/model/wallet/mod.rs | 26 ++++ .../by_wallet_qr_code.rs | 4 +- .../identities/add_new_identity_screen/mod.rs | 6 +- .../by_using_unused_asset_lock.rs | 4 +- .../by_using_unused_balance.rs | 2 +- .../by_wallet_qr_code.rs | 12 +- .../identities/top_up_identity_screen/mod.rs | 125 ++++-------------- .../top_up_identity_screen/success_screen.rs | 19 +-- src/ui/tool_screens/proof_log_screen.rs | 2 +- .../transition_visualizer_screen.rs | 1 - 23 files changed, 226 insertions(+), 166 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 7a517a8cc..be738e079 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,7 +24,7 @@ strum = { version = "0.26.1", features = ["derive"] } bs58 = "0.5.0" base64 = "0.22.1" copypasta = "0.10.1" -dash-sdk = { git = "https://github.com/dashpay/platform", rev = "c49af53698bad1070fcfc344ccaf6b3cc404e516" } +dash-sdk = { git = "https://github.com/dashpay/platform", rev = "f78f152403d789cdd091f3e88165671e975a6d63" } thiserror = "1" serde = "1.0.197" serde_json = "1.0.120" diff --git a/src/app.rs b/src/app.rs index d952724a5..e3b2e5d7a 100644 --- a/src/app.rs +++ b/src/app.rs @@ -457,7 +457,7 @@ impl App for AppState { let core_item = CoreItem::ReceivedAvailableUTXOTransaction(tx.clone(), utxos); self.visible_screen_mut() - .display_task_result(core_item.into()); + .display_task_result(BackendTaskSuccessResult::CoreItem(core_item)); } Err(e) => { eprintln!("Failed to store asset lock: {}", e); diff --git a/src/backend_task/core/start_dash_qt.rs b/src/backend_task/core/start_dash_qt.rs index 49d626269..2cf92c880 100644 --- a/src/backend_task/core/start_dash_qt.rs +++ b/src/backend_task/core/start_dash_qt.rs @@ -2,8 +2,6 @@ use crate::context::AppContext; use dash_sdk::dpp::dashcore::Network; use std::path::PathBuf; use std::process::{Command, Stdio}; -use std::sync::Arc; -use std::time::{Duration, SystemTime, UNIX_EPOCH}; use std::{env, io}; impl AppContext { diff --git a/src/backend_task/identity/load_identity.rs b/src/backend_task/identity/load_identity.rs index 3c68d9b27..7896d151d 100644 --- a/src/backend_task/identity/load_identity.rs +++ b/src/backend_task/identity/load_identity.rs @@ -295,6 +295,7 @@ impl AppContext { .iter() .map(|wallet| (wallet.read().unwrap().seed_hash(), wallet.clone())) .collect(), + wallet_index: None, //todo }; // Insert qualified identity into the database diff --git a/src/backend_task/identity/load_identity_from_wallet.rs b/src/backend_task/identity/load_identity_from_wallet.rs index 7160cdc5e..42ae7ed14 100644 --- a/src/backend_task/identity/load_identity_from_wallet.rs +++ b/src/backend_task/identity/load_identity_from_wallet.rs @@ -137,6 +137,7 @@ impl AppContext { wallet_arc_ref.wallet.read().unwrap().seed_hash(), wallet_arc_ref.wallet.clone(), )]), + wallet_index: Some(identity_index), }; // Insert qualified identity into the database diff --git a/src/backend_task/identity/mod.rs b/src/backend_task/identity/mod.rs index a0282aa68..8b6f6c370 100644 --- a/src/backend_task/identity/mod.rs +++ b/src/backend_task/identity/mod.rs @@ -20,7 +20,7 @@ use dash_sdk::dashcore_rpc::dashcore::key::Secp256k1; use dash_sdk::dashcore_rpc::dashcore::{Address, PrivateKey, TxOut}; use dash_sdk::dpp::balances::credits::Duffs; use dash_sdk::dpp::dashcore::hashes::Hash; -use dash_sdk::dpp::dashcore::{OutPoint, PublicKey, Transaction}; +use dash_sdk::dpp::dashcore::{OutPoint, Transaction}; use dash_sdk::dpp::fee::Credits; use dash_sdk::dpp::identity::accessors::IdentityGettersV0; use dash_sdk::dpp::identity::identity_public_key::accessors::v0::IdentityPublicKeyGettersV0; @@ -192,20 +192,28 @@ impl IdentityKeys { } pub type IdentityIndex = u32; +pub type TopUpIndex = u32; #[derive(Debug, Clone, PartialEq, Eq)] -pub enum IdentityFundingMethod { +pub enum RegisterIdentityFundingMethod { UseAssetLock(Address, AssetLockProof, Transaction), FundWithUtxo(OutPoint, TxOut, Address, IdentityIndex), FundWithWallet(Duffs, IdentityIndex), } +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum TopUpIdentityFundingMethod { + UseAssetLock(Address, AssetLockProof, Transaction), + FundWithUtxo(OutPoint, TxOut, Address, IdentityIndex, TopUpIndex), + FundWithWallet(Duffs, IdentityIndex, TopUpIndex), +} + #[derive(Debug, Clone)] pub struct IdentityRegistrationInfo { pub alias_input: String, pub keys: IdentityKeys, pub wallet: Arc>, pub wallet_identity_index: u32, - pub identity_funding_method: IdentityFundingMethod, + pub identity_funding_method: RegisterIdentityFundingMethod, } impl PartialEq for IdentityRegistrationInfo { @@ -220,7 +228,7 @@ impl PartialEq for IdentityRegistrationInfo { pub struct IdentityTopUpInfo { pub qualified_identity: QualifiedIdentity, pub wallet: Arc>, - pub identity_funding_method: IdentityFundingMethod, + pub identity_funding_method: TopUpIdentityFundingMethod, } impl PartialEq for IdentityTopUpInfo { diff --git a/src/backend_task/identity/register_identity.rs b/src/backend_task/identity/register_identity.rs index f8b8203a0..fe32397cb 100644 --- a/src/backend_task/identity/register_identity.rs +++ b/src/backend_task/identity/register_identity.rs @@ -1,5 +1,5 @@ use crate::app::TaskResult; -use crate::backend_task::identity::{IdentityFundingMethod, IdentityRegistrationInfo}; +use crate::backend_task::identity::{IdentityRegistrationInfo, RegisterIdentityFundingMethod}; use crate::backend_task::BackendTaskSuccessResult; use crate::context::AppContext; use crate::model::qualified_identity::{IdentityType, QualifiedIdentity}; @@ -128,7 +128,7 @@ impl AppContext { let (asset_lock_proof, asset_lock_proof_private_key, tx_id) = match identity_funding_method { - IdentityFundingMethod::UseAssetLock(address, asset_lock_proof, transaction) => { + RegisterIdentityFundingMethod::UseAssetLock(address, asset_lock_proof, transaction) => { let tx_id = transaction.txid(); // eprintln!("UseAssetLock: transaction id for {:#?} is {}", transaction, tx_id); @@ -164,12 +164,12 @@ impl AppContext { }; (asset_lock_proof, private_key, tx_id) } - IdentityFundingMethod::FundWithWallet(amount, identity_index) => { + RegisterIdentityFundingMethod::FundWithWallet(amount, identity_index) => { // Scope the write lock to avoid holding it across an await. let (asset_lock_transaction, asset_lock_proof_private_key, _, used_utxos) = { let mut wallet = wallet.write().unwrap(); wallet_id = wallet.seed_hash(); - match wallet.asset_lock_transaction( + match wallet.registration_asset_lock_transaction( sdk.network, amount, true, @@ -181,7 +181,7 @@ impl AppContext { wallet .reload_utxos(&self.core_client, self.network, Some(self)) .map_err(|e| e.to_string())?; - wallet.asset_lock_transaction( + wallet.registration_asset_lock_transaction( sdk.network, amount, true, @@ -237,12 +237,17 @@ impl AppContext { (asset_lock_proof, asset_lock_proof_private_key, tx_id) } - IdentityFundingMethod::FundWithUtxo(utxo, tx_out, input_address, identity_index) => { + RegisterIdentityFundingMethod::FundWithUtxo( + utxo, + tx_out, + input_address, + identity_index, + ) => { // Scope the write lock to avoid holding it across an await. let (asset_lock_transaction, asset_lock_proof_private_key) = { let mut wallet = wallet.write().unwrap(); wallet_id = wallet.seed_hash(); - wallet.asset_lock_transaction_for_utxo( + wallet.registration_asset_lock_transaction_for_utxo( sdk.network, utxo, tx_out.clone(), @@ -326,6 +331,7 @@ impl AppContext { wallet.read().unwrap().seed_hash(), wallet.clone(), )]), + wallet_index: Some(wallet_identity_index), }; if !alias_input.is_empty() { diff --git a/src/backend_task/identity/top_up_identity.rs b/src/backend_task/identity/top_up_identity.rs index 9116ebf8b..2bb306c34 100644 --- a/src/backend_task/identity/top_up_identity.rs +++ b/src/backend_task/identity/top_up_identity.rs @@ -1,29 +1,23 @@ use crate::app::TaskResult; use crate::backend_task::identity::{ - IdentityFundingMethod, IdentityRegistrationInfo, IdentityTopUpInfo, + IdentityTopUpInfo, RegisterIdentityFundingMethod, TopUpIdentityFundingMethod, }; use crate::backend_task::BackendTaskSuccessResult; use crate::context::AppContext; -use crate::model::qualified_identity::{IdentityType, QualifiedIdentity}; use dash_sdk::dashcore_rpc::RpcApi; use dash_sdk::dpp::block::extended_epoch_info::ExtendedEpochInfo; use dash_sdk::dpp::dashcore::hashes::Hash; use dash_sdk::dpp::dashcore::OutPoint; use dash_sdk::dpp::identity::accessors::{IdentityGettersV0, IdentitySettersV0}; use dash_sdk::dpp::identity::state_transition::asset_lock_proof::chain::ChainAssetLockProof; -use dash_sdk::dpp::native_bls::NativeBlsModule; use dash_sdk::dpp::prelude::AssetLockProof; -use dash_sdk::dpp::state_transition::identity_create_transition::methods::IdentityCreateTransitionMethodsV0; -use dash_sdk::dpp::state_transition::identity_create_transition::IdentityCreateTransition; use dash_sdk::dpp::state_transition::identity_topup_transition::methods::IdentityTopUpTransitionMethodsV0; use dash_sdk::dpp::state_transition::identity_topup_transition::IdentityTopUpTransition; use dash_sdk::dpp::version::PlatformVersion; use dash_sdk::dpp::ProtocolError; -use dash_sdk::platform::transition::put_identity::PutIdentity; use dash_sdk::platform::transition::top_up_identity::TopUpIdentity; -use dash_sdk::platform::{Fetch, Identity}; +use dash_sdk::platform::Fetch; use dash_sdk::Error; -use std::collections::BTreeMap; use std::time::Duration; use tokio::sync::mpsc; @@ -49,7 +43,7 @@ impl AppContext { let (asset_lock_proof, asset_lock_proof_private_key, tx_id) = match identity_funding_method { - IdentityFundingMethod::UseAssetLock(address, asset_lock_proof, transaction) => { + TopUpIdentityFundingMethod::UseAssetLock(address, asset_lock_proof, transaction) => { let tx_id = transaction.txid(); // eprintln!("UseAssetLock: transaction id for {:#?} is {}", transaction, tx_id); @@ -85,12 +79,12 @@ impl AppContext { }; (asset_lock_proof, private_key, tx_id) } - IdentityFundingMethod::FundWithWallet(amount, identity_index) => { + TopUpIdentityFundingMethod::FundWithWallet(amount, identity_index, top_up_index) => { // Scope the write lock to avoid holding it across an await. let (asset_lock_transaction, asset_lock_proof_private_key, _, used_utxos) = { let mut wallet = wallet.write().unwrap(); wallet_id = wallet.seed_hash(); - match wallet.asset_lock_transaction( + match wallet.registration_asset_lock_transaction( sdk.network, amount, true, @@ -102,7 +96,7 @@ impl AppContext { wallet .reload_utxos(&self.core_client, self.network, Some(self)) .map_err(|e| e.to_string())?; - wallet.asset_lock_transaction( + wallet.registration_asset_lock_transaction( sdk.network, amount, true, @@ -158,17 +152,24 @@ impl AppContext { (asset_lock_proof, asset_lock_proof_private_key, tx_id) } - IdentityFundingMethod::FundWithUtxo(utxo, tx_out, input_address, identity_index) => { + TopUpIdentityFundingMethod::FundWithUtxo( + utxo, + tx_out, + input_address, + identity_index, + top_up_index, + ) => { // Scope the write lock to avoid holding it across an await. let (asset_lock_transaction, asset_lock_proof_private_key) = { let mut wallet = wallet.write().unwrap(); wallet_id = wallet.seed_hash(); - wallet.asset_lock_transaction_for_utxo( + wallet.top_up_asset_lock_transaction_for_utxo( sdk.network, utxo, tx_out.clone(), input_address.clone(), identity_index, + top_up_index, Some(self), )? }; diff --git a/src/backend_task/mod.rs b/src/backend_task/mod.rs index 33dccc4c9..2c1bdbc0d 100644 --- a/src/backend_task/mod.rs +++ b/src/backend_task/mod.rs @@ -30,7 +30,7 @@ pub(crate) enum BackendTask { WithdrawalTask(WithdrawalsTask), } -#[derive(Debug, Clone, PartialEq, From)] +#[derive(Debug, Clone, PartialEq)] pub(crate) enum BackendTaskSuccessResult { None, Message(String), diff --git a/src/database/wallet.rs b/src/database/wallet.rs index 1c971742a..88316d58c 100644 --- a/src/database/wallet.rs +++ b/src/database/wallet.rs @@ -438,7 +438,8 @@ impl Database { let (identity_data, wallet_seed_hash_array, wallet_index) = row?; if let Some(wallet) = wallets_map.get_mut(&wallet_seed_hash_array) { - let identity: QualifiedIdentity = QualifiedIdentity::from_bytes(&identity_data); + let mut identity: QualifiedIdentity = QualifiedIdentity::from_bytes(&identity_data); + identity.wallet_index = Some(wallet_index); // Insert the identity into the wallet's identities HashMap with wallet_index as the key wallet.identities.insert(wallet_index, identity.identity); diff --git a/src/model/proof_log_item.rs b/src/model/proof_log_item.rs index 8a9cdc694..4517d4107 100644 --- a/src/model/proof_log_item.rs +++ b/src/model/proof_log_item.rs @@ -34,7 +34,6 @@ pub enum RequestType { GetCurrentQuorumsInfo = 32, } -use dash_sdk::drive::query::PathQuery; use std::convert::TryFrom; impl From for u8 { diff --git a/src/model/qualified_identity/mod.rs b/src/model/qualified_identity/mod.rs index 79a2c7ac4..477c843eb 100644 --- a/src/model/qualified_identity/mod.rs +++ b/src/model/qualified_identity/mod.rs @@ -96,6 +96,8 @@ pub struct QualifiedIdentity { pub private_keys: KeyStorage, pub dpns_names: Vec, pub associated_wallets: BTreeMap>>, + /// The index used to register the identity + pub wallet_index: Option, } impl PartialEq for QualifiedIdentity { @@ -105,6 +107,7 @@ impl PartialEq for QualifiedIdentity { && self.associated_operator_identity == other.associated_operator_identity && self.associated_owner_key_id == other.associated_owner_key_id && self.identity_type == other.identity_type + && self.wallet_index == other.wallet_index && self.alias == other.alias && self.private_keys == other.private_keys && self.dpns_names == other.dpns_names @@ -146,6 +149,7 @@ impl Decode for QualifiedIdentity { private_keys: KeyStorage::decode(decoder)?, dpns_names: Vec::::decode(decoder)?, associated_wallets: BTreeMap::new(), // Initialize with an empty vector + wallet_index: None, }) } } @@ -353,6 +357,7 @@ impl From for QualifiedIdentity { private_keys: Default::default(), dpns_names: vec![], associated_wallets: BTreeMap::new(), + wallet_index: None, } } } diff --git a/src/model/wallet/asset_lock_transaction.rs b/src/model/wallet/asset_lock_transaction.rs index 769e8b86c..2e8e03684 100644 --- a/src/model/wallet/asset_lock_transaction.rs +++ b/src/model/wallet/asset_lock_transaction.rs @@ -12,7 +12,7 @@ use dash_sdk::dpp::dashcore::{ use std::collections::BTreeMap; impl Wallet { - pub fn asset_lock_transaction( + pub fn registration_asset_lock_transaction( &mut self, network: Network, amount: u64, @@ -28,12 +28,69 @@ impl Wallet { ), String, > { - let secp = Secp256k1::new(); let private_key = self.identity_registration_ecdsa_private_key( network, identity_index, register_addresses, )?; + self.asset_lock_transaction_from_private_key( + network, + amount, + allow_take_fee_from_amount, + private_key, + register_addresses, + ) + } + + pub fn top_up_asset_lock_transaction( + &mut self, + network: Network, + amount: u64, + allow_take_fee_from_amount: bool, + identity_index: u32, + top_up_index: u32, + register_addresses: Option<&AppContext>, + ) -> Result< + ( + Transaction, + PrivateKey, + Option
, + BTreeMap, + ), + String, + > { + let private_key = self.identity_top_up_ecdsa_private_key( + network, + identity_index, + top_up_index, + register_addresses, + )?; + self.asset_lock_transaction_from_private_key( + network, + amount, + allow_take_fee_from_amount, + private_key, + register_addresses, + ) + } + + fn asset_lock_transaction_from_private_key( + &mut self, + network: Network, + amount: u64, + allow_take_fee_from_amount: bool, + private_key: PrivateKey, + register_addresses: Option<&AppContext>, + ) -> Result< + ( + Transaction, + PrivateKey, + Option
, + BTreeMap, + ), + String, + > { + let secp = Secp256k1::new(); let asset_lock_public_key = private_key.public_key(&secp); let one_time_key_hash = asset_lock_public_key.pubkey_hash(); @@ -168,7 +225,7 @@ impl Wallet { Ok((tx, private_key, change_address, utxos)) } - pub fn asset_lock_transaction_for_utxo( + pub fn registration_asset_lock_transaction_for_utxo( &mut self, network: Network, utxo: OutPoint, @@ -177,12 +234,54 @@ impl Wallet { identity_index: u32, register_addresses: Option<&AppContext>, ) -> Result<(Transaction, PrivateKey), String> { - let secp = Secp256k1::new(); let private_key = self.identity_registration_ecdsa_private_key( network, identity_index, register_addresses, )?; + self.asset_lock_transaction_for_utxo_from_private_key( + network, + utxo, + previous_tx_output, + input_address, + private_key, + ) + } + + pub fn top_up_asset_lock_transaction_for_utxo( + &mut self, + network: Network, + utxo: OutPoint, + previous_tx_output: TxOut, + input_address: Address, + identity_index: u32, + top_up_index: u32, + register_addresses: Option<&AppContext>, + ) -> Result<(Transaction, PrivateKey), String> { + let private_key = self.identity_top_up_ecdsa_private_key( + network, + identity_index, + top_up_index, + register_addresses, + )?; + self.asset_lock_transaction_for_utxo_from_private_key( + network, + utxo, + previous_tx_output, + input_address, + private_key, + ) + } + + pub fn asset_lock_transaction_for_utxo_from_private_key( + &mut self, + network: Network, + utxo: OutPoint, + previous_tx_output: TxOut, + input_address: Address, + private_key: PrivateKey, + ) -> Result<(Transaction, PrivateKey), String> { + let secp = Secp256k1::new(); let asset_lock_public_key = private_key.public_key(&secp); let one_time_key_hash = asset_lock_public_key.pubkey_hash(); diff --git a/src/model/wallet/mod.rs b/src/model/wallet/mod.rs index d642115db..95adc9893 100644 --- a/src/model/wallet/mod.rs +++ b/src/model/wallet/mod.rs @@ -614,6 +614,32 @@ impl Wallet { Ok(()) } + pub fn identity_top_up_ecdsa_private_key( + &mut self, + network: Network, + identity_index: u32, + top_up_index: u32, + register_addresses: Option<&AppContext>, + ) -> Result { + let derivation_path = + DerivationPath::identity_top_up_path(network, identity_index, top_up_index); + let extended_private_key = derivation_path + .derive_priv_ecdsa_for_master_seed(self.seed_bytes()?, network) + .expect("derivation should not be able to fail"); + let private_key = extended_private_key.to_priv(); + + if let Some(app_context) = register_addresses { + self.register_address_from_private_key( + &private_key, + &derivation_path, + DerivationPathType::CREDIT_FUNDING, + DerivationPathReference::BlockchainIdentityCreditRegistrationFunding, + app_context, + )?; + } + Ok(private_key) + } + pub fn identity_registration_ecdsa_private_key( &mut self, network: Network, diff --git a/src/ui/identities/add_new_identity_screen/by_wallet_qr_code.rs b/src/ui/identities/add_new_identity_screen/by_wallet_qr_code.rs index b5a82f8e9..990d02689 100644 --- a/src/ui/identities/add_new_identity_screen/by_wallet_qr_code.rs +++ b/src/ui/identities/add_new_identity_screen/by_wallet_qr_code.rs @@ -1,6 +1,6 @@ use crate::app::AppAction; use crate::backend_task::identity::{ - IdentityFundingMethod, IdentityRegistrationInfo, IdentityTask, + IdentityRegistrationInfo, IdentityTask, RegisterIdentityFundingMethod, }; use crate::backend_task::BackendTask; use crate::ui::identities::add_new_identity_screen::{ @@ -158,7 +158,7 @@ impl AddNewIdentityScreen { keys: self.identity_keys.clone(), wallet: Arc::clone(selected_wallet), // Clone the Arc reference wallet_identity_index: self.identity_id_number, - identity_funding_method: IdentityFundingMethod::FundWithUtxo( + identity_funding_method: RegisterIdentityFundingMethod::FundWithUtxo( utxo, tx_out, address, diff --git a/src/ui/identities/add_new_identity_screen/mod.rs b/src/ui/identities/add_new_identity_screen/mod.rs index 5f5d451b0..0a0d3d451 100644 --- a/src/ui/identities/add_new_identity_screen/mod.rs +++ b/src/ui/identities/add_new_identity_screen/mod.rs @@ -6,7 +6,7 @@ mod success_screen; use crate::app::AppAction; use crate::backend_task::core::CoreItem; use crate::backend_task::identity::{ - IdentityFundingMethod, IdentityKeys, IdentityRegistrationInfo, IdentityTask, + IdentityKeys, IdentityRegistrationInfo, IdentityTask, RegisterIdentityFundingMethod, }; use crate::backend_task::{BackendTask, BackendTaskSuccessResult}; use crate::context::AppContext; @@ -646,7 +646,7 @@ impl AddNewIdentityScreen { keys: self.identity_keys.clone(), wallet: Arc::clone(selected_wallet), // Clone the Arc reference wallet_identity_index: self.identity_id_number, - identity_funding_method: IdentityFundingMethod::UseAssetLock( + identity_funding_method: RegisterIdentityFundingMethod::UseAssetLock( address, funding_asset_lock, tx, @@ -677,7 +677,7 @@ impl AddNewIdentityScreen { keys: self.identity_keys.clone(), wallet: Arc::clone(selected_wallet), // Clone the Arc reference wallet_identity_index: self.identity_id_number, - identity_funding_method: IdentityFundingMethod::FundWithWallet( + identity_funding_method: RegisterIdentityFundingMethod::FundWithWallet( amount, self.identity_id_number, ), diff --git a/src/ui/identities/top_up_identity_screen/by_using_unused_asset_lock.rs b/src/ui/identities/top_up_identity_screen/by_using_unused_asset_lock.rs index 95b0d8733..edd41cfe6 100644 --- a/src/ui/identities/top_up_identity_screen/by_using_unused_asset_lock.rs +++ b/src/ui/identities/top_up_identity_screen/by_using_unused_asset_lock.rs @@ -6,7 +6,7 @@ use egui::Ui; impl TopUpIdentityScreen { fn render_choose_funding_asset_lock(&mut self, ui: &mut egui::Ui) { // Ensure a wallet is selected - let Some(selected_wallet) = self.selected_wallet.clone() else { + let Some(selected_wallet) = self.wallet.clone() else { ui.label("No wallet selected."); return; }; @@ -93,7 +93,7 @@ impl TopUpIdentityScreen { if ui.button("Create Identity").clicked() { self.error_message = None; - action |= self.register_identity_clicked(FundingMethod::UseUnusedAssetLock); + action |= self.top_up_identity_clicked(FundingMethod::UseUnusedAssetLock); } match step { diff --git a/src/ui/identities/top_up_identity_screen/by_using_unused_balance.rs b/src/ui/identities/top_up_identity_screen/by_using_unused_balance.rs index ee061871c..b35e2d763 100644 --- a/src/ui/identities/top_up_identity_screen/by_using_unused_balance.rs +++ b/src/ui/identities/top_up_identity_screen/by_using_unused_balance.rs @@ -5,7 +5,7 @@ use egui::Ui; impl TopUpIdentityScreen { fn show_wallet_balance(&self, ui: &mut egui::Ui) { - if let Some(selected_wallet) = &self.selected_wallet { + if let Some(selected_wallet) = &self.wallet { let wallet = selected_wallet.read().unwrap(); // Read lock on the wallet let total_balance: u64 = wallet.max_balance(); // Sum up all the balances diff --git a/src/ui/identities/top_up_identity_screen/by_wallet_qr_code.rs b/src/ui/identities/top_up_identity_screen/by_wallet_qr_code.rs index 69a53d3bc..a5beb1067 100644 --- a/src/ui/identities/top_up_identity_screen/by_wallet_qr_code.rs +++ b/src/ui/identities/top_up_identity_screen/by_wallet_qr_code.rs @@ -1,5 +1,5 @@ use crate::app::AppAction; -use crate::backend_task::identity::{IdentityFundingMethod, IdentityTask, IdentityTopUpInfo}; +use crate::backend_task::identity::{IdentityTask, IdentityTopUpInfo, TopUpIdentityFundingMethod}; use crate::backend_task::BackendTask; use crate::ui::identities::funding_common::{copy_to_clipboard, generate_qr_code_image}; use crate::ui::identities::top_up_identity_screen::{TopUpIdentityScreen, WalletFundedScreenStep}; @@ -13,7 +13,7 @@ impl TopUpIdentityScreen { let (address, should_check_balance) = { // Scope the write lock to ensure it's dropped before calling `start_balance_check`. - if let Some(wallet_guard) = self.selected_wallet.as_ref() { + if let Some(wallet_guard) = self.wallet.as_ref() { // Get the receive address if self.funding_address.is_none() { let mut wallet = wallet_guard.write().unwrap(); @@ -140,18 +140,18 @@ impl TopUpIdentityScreen { ui.heading("=> Waiting for funds. <="); } WalletFundedScreenStep::FundsReceived => { - let Some(selected_wallet) = &self.selected_wallet else { + let Some(selected_wallet) = &self.wallet else { return action; }; if let Some((utxo, tx_out, address)) = self.funding_utxo.clone() { let identity_input = IdentityTopUpInfo { - qualified_identity: QualifiedIdentity {}, + qualified_identity: self.identity.clone(), wallet: Arc::clone(selected_wallet), // Clone the Arc reference - identity_funding_method: IdentityFundingMethod::FundWithUtxo( + identity_funding_method: TopUpIdentityFundingMethod::FundWithUtxo( utxo, tx_out, address, - self.identity_id_number, + self.identity.wallet_index.unwrap_or(u32::MAX), ), }; diff --git a/src/ui/identities/top_up_identity_screen/mod.rs b/src/ui/identities/top_up_identity_screen/mod.rs index df662c2e3..105cc801f 100644 --- a/src/ui/identities/top_up_identity_screen/mod.rs +++ b/src/ui/identities/top_up_identity_screen/mod.rs @@ -6,7 +6,8 @@ mod success_screen; use crate::app::AppAction; use crate::backend_task::core::CoreItem; use crate::backend_task::identity::{ - IdentityFundingMethod, IdentityKeys, IdentityTask, IdentityTopUpInfo, + IdentityKeys, IdentityTask, IdentityTopUpInfo, RegisterIdentityFundingMethod, + TopUpIdentityFundingMethod, }; use crate::backend_task::{BackendTask, BackendTaskSuccessResult}; use crate::context::AppContext; @@ -35,7 +36,7 @@ pub struct TopUpIdentityScreen { pub identity: QualifiedIdentity, step: Arc>, funding_asset_lock: Option<(Transaction, AssetLockProof, Address)>, - selected_wallet: Option>>, + wallet: Option>>, core_has_funding_address: Option, funding_address: Option
, funding_address_balance: Arc>>, @@ -55,66 +56,16 @@ pub struct TopUpIdentityScreen { impl TopUpIdentityScreen { pub fn new(qualified_identity: QualifiedIdentity, app_context: &Arc) -> Self { - let mut selected_wallet = None; - let mut identity_keys = None; - if app_context.has_wallet.load(Ordering::Relaxed) { - let wallets = &app_context.wallets.read().unwrap(); - if let Some(wallet) = wallets.first() { - // Automatically select the only available wallet - selected_wallet = Some(wallet.clone()); - let mut wallet = wallet.write().unwrap(); - - if wallet.is_open() { - identity_keys = Some(IdentityKeys { - master_private_key: Some( - wallet - .identity_authentication_ecdsa_private_key( - app_context.network, - 0, - 0, - Some(&app_context), - ) - .expect("expected to have decrypted wallet"), - ), - master_private_key_type: KeyType::ECDSA_HASH160, - keys_input: vec![ - ( - wallet - .identity_authentication_ecdsa_private_key( - app_context.network, - 0, - 1, - Some(&app_context), - ) - .expect("expected to have decrypted wallet"), - KeyType::ECDSA_HASH160, - Purpose::AUTHENTICATION, - SecurityLevel::HIGH, - ), - ( - wallet - .identity_authentication_ecdsa_private_key( - app_context.network, - 0, - 2, - Some(&app_context), - ) - .expect("expected to have decrypted wallet"), - KeyType::ECDSA_HASH160, - Purpose::TRANSFER, - SecurityLevel::CRITICAL, - ), - ], - }); - } - } - } + let selected_wallet = qualified_identity + .associated_wallets + .first_key_value() + .map(|(_, wallet)| wallet.clone()); Self { identity: qualified_identity, step: Arc::new(RwLock::new(WalletFundedScreenStep::ChooseFundingMethod)), funding_asset_lock: None, - selected_wallet, + wallet: selected_wallet, core_has_funding_address: None, funding_address: None, funding_address_balance: Arc::new(RwLock::new(None)), @@ -133,28 +84,13 @@ impl TopUpIdentityScreen { } } - fn render_identity_index_input(&mut self, ui: &mut egui::Ui) { - let mut index_changed = false; // Track if the index has changed - - let identity_id = self.identity.alias.clone().unwrap_or_else(|| { - let id_str = self.identity.id().to_string(Encoding::Base58); - id_str.chars().take(5).collect() - }); - ui.label(format!("Topping up Identity: {}", identity_id)); - - // If the index has changed, update the identity key - if index_changed { - self.update_identity_key(); - } - } - fn render_wallet_selection(&mut self, ui: &mut Ui) -> bool { if self.app_context.has_wallet.load(Ordering::Relaxed) { - let wallets = &self.app_context.wallets.read().unwrap(); + let wallets = &self.identity.associated_wallets; if wallets.len() > 1 { // Retrieve the alias of the currently selected wallet, if any let selected_wallet_alias = self - .selected_wallet + .wallet .as_ref() .and_then(|wallet| wallet.read().ok()?.alias.clone()) .unwrap_or_else(|| "Select".to_string()); @@ -169,7 +105,7 @@ impl TopUpIdentityScreen { ComboBox::from_label("Select Wallet") .selected_text(selected_wallet_alias) .show_ui(ui, |ui| { - for wallet in wallets.iter() { + for wallet in wallets.values() { let wallet_alias = wallet .read() .ok() @@ -177,27 +113,22 @@ impl TopUpIdentityScreen { .unwrap_or_else(|| "Unnamed Wallet".to_string()); let is_selected = self - .selected_wallet + .wallet .as_ref() .map_or(false, |selected| Arc::ptr_eq(selected, wallet)); if ui.selectable_label(is_selected, wallet_alias).clicked() { - { - let wallet = wallet.read().unwrap(); - self.identity_id_number = - wallet.identities.keys().copied().max().unwrap_or_default(); - } // Update the selected wallet - self.selected_wallet = Some(wallet.clone()); + self.wallet = Some(wallet.clone()); } } }); ui.add_space(10.0); true - } else if let Some(wallet) = wallets.first() { - if self.selected_wallet.is_none() { + } else if let Some(wallet) = wallets.values().next() { + if self.wallet.is_none() { // Automatically select the only available wallet - self.selected_wallet = Some(wallet.clone()); + self.wallet = Some(wallet.clone()); } false } else { @@ -209,7 +140,7 @@ impl TopUpIdentityScreen { } fn render_funding_method(&mut self, ui: &mut egui::Ui) { - let Some(selected_wallet) = self.selected_wallet.clone() else { + let Some(selected_wallet) = self.wallet.clone() else { return; }; let funding_method_arc = self.funding_method.clone(); @@ -252,7 +183,7 @@ impl TopUpIdentityScreen { ) .changed() { - if let Some(wallet) = &self.selected_wallet { + if let Some(wallet) = &self.wallet { let wallet = wallet.read().unwrap(); // Read lock on the wallet let max_amount = wallet.max_balance(); self.funding_amount = format!("{:.4}", max_amount as f64 * 1e-8); @@ -282,15 +213,16 @@ impl TopUpIdentityScreen { }); } fn top_up_identity_clicked(&mut self, funding_method: FundingMethod) -> AppAction { - let Some(selected_wallet) = &self.selected_wallet else { + let Some(selected_wallet) = &self.wallet else { return AppAction::None; }; match funding_method { FundingMethod::UseUnusedAssetLock => { if let Some((tx, funding_asset_lock, address)) = self.funding_asset_lock.clone() { let identity_input = IdentityTopUpInfo { + qualified_identity: self.identity.clone(), wallet: Arc::clone(selected_wallet), // Clone the Arc reference - identity_funding_method: IdentityFundingMethod::UseAssetLock( + identity_funding_method: TopUpIdentityFundingMethod::UseAssetLock( address, funding_asset_lock, tx, @@ -317,8 +249,9 @@ impl TopUpIdentityScreen { return AppAction::None; } let identity_input = IdentityTopUpInfo { + qualified_identity: self.identity.clone(), wallet: Arc::clone(selected_wallet), // Clone the Arc reference - identity_funding_method: IdentityFundingMethod::FundWithWallet( + identity_funding_method: TopUpIdentityFundingMethod::FundWithWallet( amount, self.identity_id_number, ), @@ -328,7 +261,7 @@ impl TopUpIdentityScreen { *step = WalletFundedScreenStep::WaitingForAssetLock; // Create the backend task to top_up the identity - AppAction::BackendTask(BackendTask::IdentityTask(IdentityTask::RegisterIdentity( + AppAction::BackendTask(BackendTask::IdentityTask(IdentityTask::TopUpIdentity( identity_input, ))) } @@ -363,7 +296,7 @@ impl TopUpIdentityScreen { // Check if the funding method is `UseWalletBalance` if *funding_method == FundingMethod::UseWalletBalance { // Safely access the selected wallet - if let Some(wallet) = &self.selected_wallet { + if let Some(wallet) = &self.wallet { let wallet = wallet.read().unwrap(); // Read lock on the wallet if ui.button("Max").clicked() { let max_amount = wallet.max_balance(); @@ -378,7 +311,7 @@ impl TopUpIdentityScreen { impl ScreenWithWalletUnlock for TopUpIdentityScreen { fn selected_wallet_ref(&self) -> &Option>> { - &self.selected_wallet + &self.wallet } fn wallet_password_ref(&self) -> &String { @@ -453,7 +386,7 @@ impl ScreenLike for TopUpIdentityScreen { ) else { return false; }; - if let Some(wallet) = &self.selected_wallet { + if let Some(wallet) = &self.wallet { let wallet = wallet.read().unwrap(); wallet.known_addresses.contains_key(&address) } else { @@ -506,13 +439,13 @@ impl ScreenLike for TopUpIdentityScreen { step_number += 1; } - if self.selected_wallet.is_none() { + if self.wallet.is_none() { return; }; // Display the heading with an info icon that shows a tooltip on hover ui.horizontal(|ui| { - let wallet_guard = self.selected_wallet.as_ref().unwrap(); + let wallet_guard = self.wallet.as_ref().unwrap(); let wallet = wallet_guard.read().unwrap(); if wallet.identities.is_empty() { ui.heading(format!( diff --git a/src/ui/identities/top_up_identity_screen/success_screen.rs b/src/ui/identities/top_up_identity_screen/success_screen.rs index 3550cdf24..5a7bfe2e5 100644 --- a/src/ui/identities/top_up_identity_screen/success_screen.rs +++ b/src/ui/identities/top_up_identity_screen/success_screen.rs @@ -1,8 +1,5 @@ use crate::app::AppAction; -use crate::ui::identities::add_new_identity_screen::TopUpIdentityScreen; -use crate::ui::identities::register_dpns_name_screen::RegisterDpnsNameScreen; use crate::ui::identities::top_up_identity_screen::TopUpIdentityScreen; -use crate::ui::{RootScreenType, Screen}; use egui::Ui; impl TopUpIdentityScreen { @@ -14,7 +11,7 @@ impl TopUpIdentityScreen { ui.add_space(50.0); ui.heading("🎉"); - ui.heading("Success!"); + ui.heading("Successfully topped up!"); ui.add_space(20.0); @@ -23,20 +20,6 @@ impl TopUpIdentityScreen { // Handle navigation back to the identities screen action = AppAction::PopScreenAndRefresh; } - - // Display the "Register Name" button - if ui.button("Register Name").clicked() { - let mut screen = RegisterDpnsNameScreen::new(&self.app_context); - if let Some(identity_id) = self.successful_qualified_identity_id { - screen.select_identity(identity_id); - screen.show_identity_selector = false; - } - // Handle the registration of a new name - action = AppAction::PopThenAddScreenToMainScreen( - RootScreenType::RootScreenDPNSOwnedNames, - Screen::RegisterDpnsNameScreen(screen), - ); - } }); action diff --git a/src/ui/tool_screens/proof_log_screen.rs b/src/ui/tool_screens/proof_log_screen.rs index 749acb5f7..20447809c 100644 --- a/src/ui/tool_screens/proof_log_screen.rs +++ b/src/ui/tool_screens/proof_log_screen.rs @@ -7,7 +7,7 @@ use crate::ui::components::top_panel::add_top_panel; use crate::ui::{MessageType, RootScreenType, ScreenLike}; use dash_sdk::drive::grovedb::operations::proof::GroveDBProof; use dash_sdk::drive::query::PathQuery; -use eframe::egui::{self, Context, Grid, ScrollArea, TextEdit, Ui}; +use eframe::egui::{self, Context, Grid, ScrollArea, Ui}; use egui::text::LayoutJob; use egui::{Color32, FontId, Frame, Stroke, TextFormat, TextStyle, Vec2}; use regex::Regex; diff --git a/src/ui/tool_screens/transition_visualizer_screen.rs b/src/ui/tool_screens/transition_visualizer_screen.rs index ca8525578..a0e07e6a3 100644 --- a/src/ui/tool_screens/transition_visualizer_screen.rs +++ b/src/ui/tool_screens/transition_visualizer_screen.rs @@ -1,6 +1,5 @@ use crate::app::AppAction; use crate::context::AppContext; -use crate::ui::components::dpns_subscreen_chooser_panel::add_dpns_subscreen_chooser_panel; use crate::ui::components::left_panel::add_left_panel; use crate::ui::components::tools_subscreen_chooser_panel::add_tools_subscreen_chooser_panel; use crate::ui::components::top_panel::add_top_panel; From bfbff588d38594315f2d93e555346f47ac93f81a Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Tue, 26 Nov 2024 17:37:46 +0300 Subject: [PATCH 4/5] identity top up final touches --- src/app.rs | 3 + src/backend_task/identity/load_identity.rs | 5 +- .../identity/load_identity_from_wallet.rs | 2 +- src/backend_task/identity/top_up_identity.rs | 18 +-- src/backend_task/mod.rs | 1 - src/database/identities.rs | 2 +- src/ui/identities/identities_screen.rs | 16 +- .../by_using_unused_balance.rs | 2 +- .../by_wallet_qr_code.rs | 3 +- .../identities/top_up_identity_screen/mod.rs | 145 +++++++++--------- 10 files changed, 95 insertions(+), 102 deletions(-) diff --git a/src/app.rs b/src/app.rs index e3b2e5d7a..30d1970dc 100644 --- a/src/app.rs +++ b/src/app.rs @@ -423,6 +423,9 @@ impl App for AppState { BackendTaskSuccessResult::RegisteredIdentity(_) => { self.visible_screen_mut().display_task_result(message); } + BackendTaskSuccessResult::ToppedUpIdentity(_) => { + self.visible_screen_mut().display_task_result(message); + } }, TaskResult::Error(message) => { self.visible_screen_mut() diff --git a/src/backend_task/identity/load_identity.rs b/src/backend_task/identity/load_identity.rs index 7896d151d..dc80ea5da 100644 --- a/src/backend_task/identity/load_identity.rs +++ b/src/backend_task/identity/load_identity.rs @@ -7,7 +7,6 @@ use crate::model::qualified_identity::PrivateKeyTarget::{ self, PrivateKeyOnMainIdentity, PrivateKeyOnVoterIdentity, }; use crate::model::qualified_identity::{DPNSNameInfo, IdentityType, QualifiedIdentity}; -use crate::model::wallet::WalletSeedHash; use dash_sdk::dashcore_rpc::dashcore::key::Secp256k1; use dash_sdk::dashcore_rpc::dashcore::PrivateKey; use dash_sdk::dpp::dashcore::hashes::Hash; @@ -15,14 +14,14 @@ use dash_sdk::dpp::document::DocumentV0Getters; use dash_sdk::dpp::identifier::MasternodeIdentifiers; use dash_sdk::dpp::identity::accessors::IdentityGettersV0; use dash_sdk::dpp::identity::identity_public_key::accessors::v0::IdentityPublicKeyGettersV0; -use dash_sdk::dpp::identity::{KeyType, SecurityLevel}; +use dash_sdk::dpp::identity::SecurityLevel; use dash_sdk::dpp::platform_value::string_encoding::Encoding; use dash_sdk::dpp::platform_value::Value; use dash_sdk::drive::query::{WhereClause, WhereOperator}; use dash_sdk::platform::{Document, DocumentQuery, Fetch, FetchMany, Identifier, Identity}; use dash_sdk::Sdk; use egui::ahash::HashMap; -use std::collections::{BTreeMap, HashSet}; +use std::collections::BTreeMap; impl AppContext { pub(super) async fn load_identity( diff --git a/src/backend_task/identity/load_identity_from_wallet.rs b/src/backend_task/identity/load_identity_from_wallet.rs index 42ae7ed14..7c1c283f4 100644 --- a/src/backend_task/identity/load_identity_from_wallet.rs +++ b/src/backend_task/identity/load_identity_from_wallet.rs @@ -7,7 +7,7 @@ use crate::model::qualified_identity::qualified_identity_public_key::QualifiedId use crate::model::qualified_identity::{ DPNSNameInfo, IdentityType, PrivateKeyTarget, QualifiedIdentity, }; -use crate::model::wallet::{Wallet, WalletArcRef}; +use crate::model::wallet::WalletArcRef; use dash_sdk::dpp::dashcore::bip32::{DerivationPath, KeyDerivationType}; use dash_sdk::dpp::dashcore::hashes::Hash; use dash_sdk::dpp::document::DocumentV0Getters; diff --git a/src/backend_task/identity/top_up_identity.rs b/src/backend_task/identity/top_up_identity.rs index 2bb306c34..e2c85924d 100644 --- a/src/backend_task/identity/top_up_identity.rs +++ b/src/backend_task/identity/top_up_identity.rs @@ -1,7 +1,5 @@ use crate::app::TaskResult; -use crate::backend_task::identity::{ - IdentityTopUpInfo, RegisterIdentityFundingMethod, TopUpIdentityFundingMethod, -}; +use crate::backend_task::identity::{IdentityTopUpInfo, TopUpIdentityFundingMethod}; use crate::backend_task::BackendTaskSuccessResult; use crate::context::AppContext; use dash_sdk::dashcore_rpc::RpcApi; @@ -39,8 +37,6 @@ impl AppContext { .await .map_err(|e| e.to_string())?; - let wallet_id; - let (asset_lock_proof, asset_lock_proof_private_key, tx_id) = match identity_funding_method { TopUpIdentityFundingMethod::UseAssetLock(address, asset_lock_proof, transaction) => { @@ -48,7 +44,6 @@ impl AppContext { // eprintln!("UseAssetLock: transaction id for {:#?} is {}", transaction, tx_id); let wallet = wallet.read().unwrap(); - wallet_id = wallet.seed_hash(); let private_key = wallet .private_key_for_address(&address, self.network)? .ok_or("Asset Lock not valid for wallet")?; @@ -83,7 +78,6 @@ impl AppContext { // Scope the write lock to avoid holding it across an await. let (asset_lock_transaction, asset_lock_proof_private_key, _, used_utxos) = { let mut wallet = wallet.write().unwrap(); - wallet_id = wallet.seed_hash(); match wallet.registration_asset_lock_transaction( sdk.network, amount, @@ -162,7 +156,6 @@ impl AppContext { // Scope the write lock to avoid holding it across an await. let (asset_lock_transaction, asset_lock_proof_private_key) = { let mut wallet = wallet.write().unwrap(); - wallet_id = wallet.seed_hash(); wallet.top_up_asset_lock_transaction_for_utxo( sdk.network, utxo, @@ -278,6 +271,13 @@ impl AppContext { self.update_local_qualified_identity(&qualified_identity) .map_err(|e| e.to_string())?; + { + let mut wallet = wallet.write().unwrap(); + wallet + .unused_asset_locks + .retain(|(tx, _, _, _, _)| tx.txid() != tx_id); + } + self.db .set_asset_lock_identity_id( tx_id.as_byte_array(), @@ -290,7 +290,7 @@ impl AppContext { .await .map_err(|e| e.to_string())?; - Ok(BackendTaskSuccessResult::RegisteredIdentity( + Ok(BackendTaskSuccessResult::ToppedUpIdentity( qualified_identity, )) } diff --git a/src/backend_task/mod.rs b/src/backend_task/mod.rs index 2c1bdbc0d..c9b9a2ae8 100644 --- a/src/backend_task/mod.rs +++ b/src/backend_task/mod.rs @@ -9,7 +9,6 @@ use crate::context::AppContext; use crate::model::qualified_identity::QualifiedIdentity; use dash_sdk::dpp::voting::votes::Vote; use dash_sdk::query_types::Documents; -use derive_more::From; use std::sync::Arc; use tokio::sync::mpsc; diff --git a/src/database/identities.rs b/src/database/identities.rs index 8c83ae79e..471668e1d 100644 --- a/src/database/identities.rs +++ b/src/database/identities.rs @@ -5,7 +5,7 @@ use crate::model::wallet::Wallet; use dash_sdk::dpp::identity::accessors::IdentityGettersV0; use dash_sdk::platform::Identifier; use rusqlite::params; -use std::sync::{Arc, RwLock, RwLockReadGuard}; +use std::sync::{Arc, RwLock}; impl Database { /// Updates the alias of a specified identity. diff --git a/src/ui/identities/identities_screen.rs b/src/ui/identities/identities_screen.rs index 428e37b4c..65982a3b3 100644 --- a/src/ui/identities/identities_screen.rs +++ b/src/ui/identities/identities_screen.rs @@ -1,3 +1,4 @@ +use super::withdraw_from_identity_screen::WithdrawalScreen; use crate::app::{AppAction, DesiredAppAction}; use crate::backend_task::identity::IdentityTask; use crate::backend_task::BackendTask; @@ -14,6 +15,7 @@ use crate::ui::components::left_panel::add_left_panel; use crate::ui::components::top_panel::add_top_panel; use crate::ui::identities::keys::add_key_screen::AddKeyScreen; use crate::ui::identities::keys::key_info_screen::KeyInfoScreen; +use crate::ui::identities::top_up_identity_screen::TopUpIdentityScreen; use crate::ui::transfers::TransferScreen; use crate::ui::{RootScreenType, Screen, ScreenLike, ScreenType}; use dash_sdk::dpp::identity::accessors::IdentityGettersV0; @@ -31,8 +33,6 @@ use std::collections::HashMap; use std::sync::atomic::Ordering; use std::sync::{Arc, Mutex}; -use super::withdraw_from_identity_screen::WithdrawalScreen; - pub struct IdentitiesScreen { pub identities: Arc>>, pub app_context: Arc, @@ -297,10 +297,9 @@ impl IdentitiesScreen { .column(Column::initial(80.0).resizable(true)) // Name .column(Column::initial(330.0).resizable(true)) // Identity ID .column(Column::initial(60.0).resizable(true)) // In Wallet - .column(Column::initial(100.0).resizable(true)) // Balance .column(Column::initial(80.0).resizable(true)) // Type .column(Column::initial(80.0).resizable(true)) // Keys - .column(Column::initial(100.0).resizable(true)) // Balance + .column(Column::initial(140.0).resizable(true)) // Balance .column(Column::initial(80.0).resizable(true)) // Actions .header(30.0, |mut header| { header.col(|ui| { @@ -312,9 +311,6 @@ impl IdentitiesScreen { header.col(|ui| { ui.heading("In Wallet"); }); - header.col(|ui| { - ui.heading("Balance"); - }); header.col(|ui| { ui.heading("Type"); }); @@ -346,9 +342,6 @@ impl IdentitiesScreen { row.col(|ui| { self.show_in_wallet(ui, qualified_identity); }); - row.col(|ui| { - Self::show_balance(ui, qualified_identity); - }); row.col(|ui| { ui.label(format!("{}", qualified_identity.identity_type)); }); @@ -434,6 +427,7 @@ impl IdentitiesScreen { }}); }); row.col(|ui| { + Self::show_balance(ui, qualified_identity); if ui.button("Withdraw").clicked() { action = AppAction::AddScreen( Screen::WithdrawalScreen(WithdrawalScreen::new( @@ -444,7 +438,7 @@ impl IdentitiesScreen { } if ui.button("Top up").clicked() { action = AppAction::AddScreen( - Screen::WithdrawalScreen(WithdrawalScreen::new( + Screen::TopUpIdentityScreen(TopUpIdentityScreen::new( qualified_identity.clone(), &self.app_context, )), diff --git a/src/ui/identities/top_up_identity_screen/by_using_unused_balance.rs b/src/ui/identities/top_up_identity_screen/by_using_unused_balance.rs index b35e2d763..0ad85ff48 100644 --- a/src/ui/identities/top_up_identity_screen/by_using_unused_balance.rs +++ b/src/ui/identities/top_up_identity_screen/by_using_unused_balance.rs @@ -47,7 +47,7 @@ impl TopUpIdentityScreen { return action; }; - if ui.button("Create Identity").clicked() { + if ui.button("Top Up Identity").clicked() { self.error_message = None; action = self.top_up_identity_clicked(FundingMethod::UseWalletBalance); } diff --git a/src/ui/identities/top_up_identity_screen/by_wallet_qr_code.rs b/src/ui/identities/top_up_identity_screen/by_wallet_qr_code.rs index a5beb1067..5fb6ae4ea 100644 --- a/src/ui/identities/top_up_identity_screen/by_wallet_qr_code.rs +++ b/src/ui/identities/top_up_identity_screen/by_wallet_qr_code.rs @@ -151,7 +151,8 @@ impl TopUpIdentityScreen { utxo, tx_out, address, - self.identity.wallet_index.unwrap_or(u32::MAX), + self.identity.wallet_index.unwrap_or(u32::MAX >> 1), + self.top_up_index_number, ), }; diff --git a/src/ui/identities/top_up_identity_screen/mod.rs b/src/ui/identities/top_up_identity_screen/mod.rs index 105cc801f..125774ea0 100644 --- a/src/ui/identities/top_up_identity_screen/mod.rs +++ b/src/ui/identities/top_up_identity_screen/mod.rs @@ -5,10 +5,7 @@ mod success_screen; use crate::app::AppAction; use crate::backend_task::core::CoreItem; -use crate::backend_task::identity::{ - IdentityKeys, IdentityTask, IdentityTopUpInfo, RegisterIdentityFundingMethod, - TopUpIdentityFundingMethod, -}; +use crate::backend_task::identity::{IdentityTask, IdentityTopUpInfo, TopUpIdentityFundingMethod}; use crate::backend_task::{BackendTask, BackendTaskSuccessResult}; use crate::context::AppContext; use crate::model::qualified_identity::QualifiedIdentity; @@ -24,9 +21,9 @@ use dash_sdk::dpp::balances::credits::Duffs; use dash_sdk::dpp::dashcore::{OutPoint, Transaction, TxOut}; use dash_sdk::dpp::identity::accessors::IdentityGettersV0; use dash_sdk::dpp::identity::{KeyType, Purpose, SecurityLevel}; -use dash_sdk::dpp::platform_value::string_encoding::Encoding; use dash_sdk::dpp::prelude::AssetLockProof; use eframe::egui::Context; +use eframe::epaint::ahash::HashSet; use egui::{ComboBox, ScrollArea, Ui}; use std::cmp::PartialEq; use std::sync::atomic::Ordering; @@ -38,6 +35,7 @@ pub struct TopUpIdentityScreen { funding_asset_lock: Option<(Transaction, AssetLockProof, Address)>, wallet: Option>>, core_has_funding_address: Option, + top_up_index_number: u32, funding_address: Option
, funding_address_balance: Arc>>, funding_method: Arc>, @@ -67,6 +65,7 @@ impl TopUpIdentityScreen { funding_asset_lock: None, wallet: selected_wallet, core_has_funding_address: None, + top_up_index_number: 0, funding_address: None, funding_address_balance: Arc::new(RwLock::new(None)), funding_method: Arc::new(RwLock::new(FundingMethod::NoSelection)), @@ -139,6 +138,61 @@ impl TopUpIdentityScreen { } } + fn render_top_up_index_input(&mut self, ui: &mut egui::Ui) { + ui.horizontal(|ui| { + ui.label("Top up Index:"); + + // Check if we have access to the selected wallet + if let Some(wallet_guard) = self.wallet.as_ref() { + let wallet = wallet_guard.read().unwrap(); + let used_indices: HashSet = wallet.identities.keys().cloned().collect(); + + // Modify the selected text to include "(used)" if the current index is used + let selected_text = { + let is_used = used_indices.contains(&self.top_up_index_number); + if is_used { + format!("{} (used)", self.top_up_index_number) + } else { + format!("{}", self.top_up_index_number) + } + }; + + // Render a ComboBox to select the identity index + ComboBox::from_id_salt("identity_index") + .selected_text(selected_text) + .show_ui(ui, |ui| { + // Provide up to 30 entries for selection (0 to 29) + for i in 0..30 { + let is_used = used_indices.contains(&i); + let label = if is_used { + format!("{} (used)", i) + } else { + format!("{}", i) + }; + + let is_selected = self.top_up_index_number == i; + + // Enable the option if it's not used or if it's the currently selected index + let enabled = !is_used || is_selected; + + // Use `add_enabled` to disable used indices + let response = ui.add_enabled( + enabled, + egui::SelectableLabel::new(is_selected, label), + ); + + // Only allow selection if the index is not used + if response.clicked() && !is_used { + self.top_up_index_number = i; + } + } + }); + } else { + ui.label("No wallet selected"); + } + }); + } + fn render_funding_method(&mut self, ui: &mut egui::Ui) { let Some(selected_wallet) = self.wallet.clone() else { return; @@ -169,7 +223,6 @@ impl TopUpIdentityScreen { ) .changed() { - self.update_identity_key(); let mut step = self.step.write().unwrap(); // Write lock on step *step = WalletFundedScreenStep::ReadyToCreate; } @@ -253,7 +306,8 @@ impl TopUpIdentityScreen { wallet: Arc::clone(selected_wallet), // Clone the Arc reference identity_funding_method: TopUpIdentityFundingMethod::FundWithWallet( amount, - self.identity_id_number, + self.identity.wallet_index.unwrap_or(u32::MAX >> 1), + self.top_up_index_number, ), }; @@ -342,7 +396,7 @@ impl ScreenWithWalletUnlock for TopUpIdentityScreen { impl ScreenLike for TopUpIdentityScreen { fn display_message(&mut self, message: &str, message_type: MessageType) { if message_type == MessageType::Error { - self.error_message = Some(format!("Error top_uping identity: {}", message)); + self.error_message = Some(format!("Error topping up identity: {}", message)); } else { self.error_message = Some(message.to_string()); } @@ -423,7 +477,7 @@ impl ScreenLike for TopUpIdentityScreen { egui::CentralPanel::default().show(ctx, |ui| { ScrollArea::vertical().show(ui, |ui| { - let step = {self.step.read().unwrap().clone()}; + let step = { self.step.read().unwrap().clone() }; if step == WalletFundedScreenStep::Success { action |= self.show_success(ui); return; @@ -443,72 +497,15 @@ impl ScreenLike for TopUpIdentityScreen { return; }; - // Display the heading with an info icon that shows a tooltip on hover - ui.horizontal(|ui| { - let wallet_guard = self.wallet.as_ref().unwrap(); - let wallet = wallet_guard.read().unwrap(); - if wallet.identities.is_empty() { - ui.heading(format!( - "{}. Choose an identity index. Leave this 0 if this is your first identity for this wallet.", - step_number - )); - } else { - ui.heading(format!( - "{}. Choose an identity index. Leaving this {} is recommended.", - step_number, - wallet.identities.keys().cloned().max().map(|max| max + 1).unwrap_or_default() - )); - } - - - // Create a label with click sense and tooltip - let info_icon = egui::Label::new("ℹ").sense(egui::Sense::click()); - let response = ui.add(info_icon) - .on_hover_text("The identity index is an internal reference within the wallet. The wallet’s seed phrase can always be used to recover any identity, including this one, by using the same index."); - - // Check if the label was clicked - if response.clicked() { - self.show_pop_up_info = Some("The identity index is an internal reference within the wallet. The wallet’s seed phrase can always be used to recover any identity, including this one, by using the same index.".to_string()); - } - }); - - step_number += 1; - - ui.add_space(8.0); - - self.render_identity_index_input(ui); - - ui.add_space(10.0); - - // Display the heading with an info icon that shows a tooltip on hover - ui.horizontal(|ui| { - ui.heading(format!( - "{}. Choose what keys you want to add to this new identity.", - step_number - )); - - // Create a label with click sense and tooltip - let info_icon = egui::Label::new("ℹ").sense(egui::Sense::click()); - let response = ui.add(info_icon) - .on_hover_text("Keys allow an identity to perform actions on the Blockchain. They are contained in your wallet and allow you to prove that the action you are making is really coming from yourself."); - - // Check if the label was clicked - if response.clicked() { - self.show_pop_up_info = Some("Keys allow an identity to perform actions on the Blockchain. They are contained in your wallet and allow you to prove that the action you are making is really coming from yourself.".to_string()); - } - }); - - step_number += 1; - - ui.add_space(8.0); + let (needed_unlock, just_unlocked) = self.render_wallet_unlock_if_needed(ui); - self.render_key_selection(ui); + if needed_unlock && !just_unlocked { + return; + } ui.add_space(10.0); - ui.heading( - format!("{}. Choose your funding method.", step_number).as_str() - ); + ui.heading(format!("{}. Choose your funding method.", step_number).as_str()); step_number += 1; ui.add_space(10.0); @@ -525,13 +522,13 @@ impl ScreenLike for TopUpIdentityScreen { FundingMethod::NoSelection => return, FundingMethod::UseUnusedAssetLock => { action |= self.render_ui_by_using_unused_asset_lock(ui, step_number); - }, + } FundingMethod::UseWalletBalance => { action |= self.render_ui_by_using_unused_balance(ui, step_number); - }, + } FundingMethod::AddressWithQRCode => { action |= self.render_ui_by_wallet_qr_code(ui, step_number) - }, + } FundingMethod::AttachedCoreWallet => return, } }); From 05f6e0f911f85ce8a2ac284e858a83f7a1d17ae8 Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Wed, 27 Nov 2024 15:50:30 +0300 Subject: [PATCH 5/5] top ups working --- src/backend_task/identity/load_identity.rs | 1 + .../identity/load_identity_from_wallet.rs | 1 + .../identity/register_identity.rs | 1 + src/backend_task/identity/top_up_identity.rs | 331 ++++++++++-------- src/database/identities.rs | 31 +- src/database/initialization.rs | 7 +- src/database/mod.rs | 1 + src/database/top_ups.rs | 44 +++ src/model/qualified_identity/mod.rs | 3 + .../by_wallet_qr_code.rs | 13 +- .../identities/top_up_identity_screen/mod.rs | 65 +--- 11 files changed, 286 insertions(+), 212 deletions(-) create mode 100644 src/database/top_ups.rs diff --git a/src/backend_task/identity/load_identity.rs b/src/backend_task/identity/load_identity.rs index dc80ea5da..60ab3cec6 100644 --- a/src/backend_task/identity/load_identity.rs +++ b/src/backend_task/identity/load_identity.rs @@ -295,6 +295,7 @@ impl AppContext { .map(|wallet| (wallet.read().unwrap().seed_hash(), wallet.clone())) .collect(), wallet_index: None, //todo + top_ups: Default::default(), }; // Insert qualified identity into the database diff --git a/src/backend_task/identity/load_identity_from_wallet.rs b/src/backend_task/identity/load_identity_from_wallet.rs index 7c1c283f4..97982e243 100644 --- a/src/backend_task/identity/load_identity_from_wallet.rs +++ b/src/backend_task/identity/load_identity_from_wallet.rs @@ -138,6 +138,7 @@ impl AppContext { wallet_arc_ref.wallet.clone(), )]), wallet_index: Some(identity_index), + top_ups: Default::default(), }; // Insert qualified identity into the database diff --git a/src/backend_task/identity/register_identity.rs b/src/backend_task/identity/register_identity.rs index fe32397cb..c44cc1c62 100644 --- a/src/backend_task/identity/register_identity.rs +++ b/src/backend_task/identity/register_identity.rs @@ -332,6 +332,7 @@ impl AppContext { wallet.clone(), )]), wallet_index: Some(wallet_identity_index), + top_ups: Default::default(), }; if !alias_input.is_empty() { diff --git a/src/backend_task/identity/top_up_identity.rs b/src/backend_task/identity/top_up_identity.rs index e2c85924d..8b483b4f9 100644 --- a/src/backend_task/identity/top_up_identity.rs +++ b/src/backend_task/identity/top_up_identity.rs @@ -37,180 +37,201 @@ impl AppContext { .await .map_err(|e| e.to_string())?; - let (asset_lock_proof, asset_lock_proof_private_key, tx_id) = match identity_funding_method - { - TopUpIdentityFundingMethod::UseAssetLock(address, asset_lock_proof, transaction) => { - let tx_id = transaction.txid(); - - // eprintln!("UseAssetLock: transaction id for {:#?} is {}", transaction, tx_id); - let wallet = wallet.read().unwrap(); - let private_key = wallet - .private_key_for_address(&address, self.network)? - .ok_or("Asset Lock not valid for wallet")?; - let asset_lock_proof = if let AssetLockProof::Instant(instant_asset_lock_proof) = - asset_lock_proof.as_ref() - { - // we need to make sure the instant send asset lock is recent - let raw_transaction_info = self - .core_client - .get_raw_transaction_info(&tx_id, None) - .map_err(|e| e.to_string())?; + let (asset_lock_proof, asset_lock_proof_private_key, tx_id, top_up_index) = + match identity_funding_method { + TopUpIdentityFundingMethod::UseAssetLock( + address, + asset_lock_proof, + transaction, + ) => { + let tx_id = transaction.txid(); - if raw_transaction_info.chainlock - && raw_transaction_info.height.is_some() - && raw_transaction_info.confirmations.is_some() - && raw_transaction_info.confirmations.unwrap() > 8 - { - // we should use a chain lock instead - AssetLockProof::Chain(ChainAssetLockProof { - core_chain_locked_height: metadata.core_chain_locked_height, - out_point: OutPoint::new(tx_id, 0), - }) - } else { - AssetLockProof::Instant(instant_asset_lock_proof.clone()) - } - } else { - asset_lock_proof - }; - (asset_lock_proof, private_key, tx_id) - } - TopUpIdentityFundingMethod::FundWithWallet(amount, identity_index, top_up_index) => { - // Scope the write lock to avoid holding it across an await. - let (asset_lock_transaction, asset_lock_proof_private_key, _, used_utxos) = { - let mut wallet = wallet.write().unwrap(); - match wallet.registration_asset_lock_transaction( - sdk.network, - amount, - true, - identity_index, - Some(self), - ) { - Ok(transaction) => transaction, - Err(_) => { - wallet - .reload_utxos(&self.core_client, self.network, Some(self)) + // eprintln!("UseAssetLock: transaction id for {:#?} is {}", transaction, tx_id); + let wallet = wallet.read().unwrap(); + let private_key = wallet + .private_key_for_address(&address, self.network)? + .ok_or("Asset Lock not valid for wallet")?; + let asset_lock_proof = + if let AssetLockProof::Instant(instant_asset_lock_proof) = + asset_lock_proof.as_ref() + { + // we need to make sure the instant send asset lock is recent + let raw_transaction_info = self + .core_client + .get_raw_transaction_info(&tx_id, None) .map_err(|e| e.to_string())?; - wallet.registration_asset_lock_transaction( - sdk.network, - amount, - true, - identity_index, - Some(self), - )? - } - } - }; - - let tx_id = asset_lock_transaction.txid(); - // todo: maybe one day we will want to use platform again, but for right now we use - // the local core as it is more stable - // let asset_lock_proof = self - // .broadcast_and_retrieve_asset_lock(&asset_lock_transaction, &change_address) - // .await - // .map_err(|e| e.to_string())?; - { - let mut proofs = self.transactions_waiting_for_finality.lock().unwrap(); - proofs.insert(tx_id, None); + if raw_transaction_info.chainlock + && raw_transaction_info.height.is_some() + && raw_transaction_info.confirmations.is_some() + && raw_transaction_info.confirmations.unwrap() > 8 + { + // we should use a chain lock instead + AssetLockProof::Chain(ChainAssetLockProof { + core_chain_locked_height: metadata.core_chain_locked_height, + out_point: OutPoint::new(tx_id, 0), + }) + } else { + AssetLockProof::Instant(instant_asset_lock_proof.clone()) + } + } else { + asset_lock_proof + }; + (asset_lock_proof, private_key, tx_id, None) } + TopUpIdentityFundingMethod::FundWithWallet( + amount, + identity_index, + top_up_index, + ) => { + // Scope the write lock to avoid holding it across an await. + let (asset_lock_transaction, asset_lock_proof_private_key, _, used_utxos) = { + let mut wallet = wallet.write().unwrap(); + match wallet.top_up_asset_lock_transaction( + sdk.network, + amount, + true, + identity_index, + top_up_index, + Some(self), + ) { + Ok(transaction) => transaction, + Err(_) => { + wallet + .reload_utxos(&self.core_client, self.network, Some(self)) + .map_err(|e| e.to_string())?; + wallet.top_up_asset_lock_transaction( + sdk.network, + amount, + true, + identity_index, + top_up_index, + Some(self), + )? + } + } + }; - self.core_client - .send_raw_transaction(&asset_lock_transaction) - .map_err(|e| e.to_string())?; + let tx_id = asset_lock_transaction.txid(); + // todo: maybe one day we will want to use platform again, but for right now we use + // the local core as it is more stable + // let asset_lock_proof = self + // .broadcast_and_retrieve_asset_lock(&asset_lock_transaction, &change_address) + // .await + // .map_err(|e| e.to_string())?; - { - let mut wallet = wallet.write().unwrap(); - wallet.utxos.retain(|_, utxo_map| { - utxo_map.retain(|outpoint, _| !used_utxos.contains_key(outpoint)); - !utxo_map.is_empty() // Keep addresses that still have UTXOs - }); - for utxo in used_utxos.keys() { - self.db - .drop_utxo(utxo, &self.network.to_string()) - .map_err(|e| e.to_string())?; + { + let mut proofs = self.transactions_waiting_for_finality.lock().unwrap(); + proofs.insert(tx_id, None); } - } - let asset_lock_proof; + self.core_client + .send_raw_transaction(&asset_lock_transaction) + .map_err(|e| e.to_string())?; - loop { { - let proofs = self.transactions_waiting_for_finality.lock().unwrap(); - if let Some(Some(proof)) = proofs.get(&tx_id) { - asset_lock_proof = proof.clone(); - break; + let mut wallet = wallet.write().unwrap(); + wallet.utxos.retain(|_, utxo_map| { + utxo_map.retain(|outpoint, _| !used_utxos.contains_key(outpoint)); + !utxo_map.is_empty() // Keep addresses that still have UTXOs + }); + for utxo in used_utxos.keys() { + self.db + .drop_utxo(utxo, &self.network.to_string()) + .map_err(|e| e.to_string())?; } } - tokio::time::sleep(Duration::from_millis(200)).await; - } - (asset_lock_proof, asset_lock_proof_private_key, tx_id) - } - TopUpIdentityFundingMethod::FundWithUtxo( - utxo, - tx_out, - input_address, - identity_index, - top_up_index, - ) => { - // Scope the write lock to avoid holding it across an await. - let (asset_lock_transaction, asset_lock_proof_private_key) = { - let mut wallet = wallet.write().unwrap(); - wallet.top_up_asset_lock_transaction_for_utxo( - sdk.network, - utxo, - tx_out.clone(), - input_address.clone(), - identity_index, - top_up_index, - Some(self), - )? - }; + let asset_lock_proof; - let tx_id = asset_lock_transaction.txid(); - // todo: maybe one day we will want to use platform again, but for right now we use - // the local core as it is more stable - // let asset_lock_proof = self - // .broadcast_and_retrieve_asset_lock(&asset_lock_transaction, &change_address) - // .await - // .map_err(|e| e.to_string())?; + loop { + { + let proofs = self.transactions_waiting_for_finality.lock().unwrap(); + if let Some(Some(proof)) = proofs.get(&tx_id) { + asset_lock_proof = proof.clone(); + break; + } + } + tokio::time::sleep(Duration::from_millis(200)).await; + } - { - let mut proofs = self.transactions_waiting_for_finality.lock().unwrap(); - proofs.insert(tx_id, None); + ( + asset_lock_proof, + asset_lock_proof_private_key, + tx_id, + Some((amount, top_up_index)), + ) } + TopUpIdentityFundingMethod::FundWithUtxo( + utxo, + tx_out, + input_address, + identity_index, + top_up_index, + ) => { + // Scope the write lock to avoid holding it across an await. + let (asset_lock_transaction, asset_lock_proof_private_key) = { + let mut wallet = wallet.write().unwrap(); + wallet.top_up_asset_lock_transaction_for_utxo( + sdk.network, + utxo, + tx_out.clone(), + input_address.clone(), + identity_index, + top_up_index, + Some(self), + )? + }; - self.core_client - .send_raw_transaction(&asset_lock_transaction) - .map_err(|e| e.to_string())?; + let tx_id = asset_lock_transaction.txid(); + // todo: maybe one day we will want to use platform again, but for right now we use + // the local core as it is more stable + // let asset_lock_proof = self + // .broadcast_and_retrieve_asset_lock(&asset_lock_transaction, &change_address) + // .await + // .map_err(|e| e.to_string())?; - { - let mut wallet = wallet.write().unwrap(); - wallet.utxos.retain(|_, utxo_map| { - utxo_map.retain(|outpoint, _| outpoint != &utxo); - !utxo_map.is_empty() - }); - self.db - .drop_utxo(&utxo, &self.network.to_string()) - .map_err(|e| e.to_string())?; - } + { + let mut proofs = self.transactions_waiting_for_finality.lock().unwrap(); + proofs.insert(tx_id, None); + } - let asset_lock_proof; + self.core_client + .send_raw_transaction(&asset_lock_transaction) + .map_err(|e| e.to_string())?; - loop { { - let proofs = self.transactions_waiting_for_finality.lock().unwrap(); - if let Some(Some(proof)) = proofs.get(&tx_id) { - asset_lock_proof = proof.clone(); - break; + let mut wallet = wallet.write().unwrap(); + wallet.utxos.retain(|_, utxo_map| { + utxo_map.retain(|outpoint, _| outpoint != &utxo); + !utxo_map.is_empty() + }); + self.db + .drop_utxo(&utxo, &self.network.to_string()) + .map_err(|e| e.to_string())?; + } + + let asset_lock_proof; + + loop { + { + let proofs = self.transactions_waiting_for_finality.lock().unwrap(); + if let Some(Some(proof)) = proofs.get(&tx_id) { + asset_lock_proof = proof.clone(); + break; + } } + tokio::time::sleep(Duration::from_millis(200)).await; } - tokio::time::sleep(Duration::from_millis(200)).await; - } - (asset_lock_proof, asset_lock_proof_private_key, tx_id) - } - }; + ( + asset_lock_proof, + asset_lock_proof_private_key, + tx_id, + Some((tx_out.value, top_up_index)), + ) + } + }; self.db .set_asset_lock_identity_id_before_confirmation_by_network( @@ -285,6 +306,16 @@ impl AppContext { ) .map_err(|e| e.to_string())?; + if let Some((amount, top_up_index)) = top_up_index { + self.db + .insert_top_up( + qualified_identity.identity.id().as_bytes(), + top_up_index, + amount, + ) + .map_err(|e| e.to_string())?; + } + sender .send(TaskResult::Success(BackendTaskSuccessResult::None)) .await diff --git a/src/database/identities.rs b/src/database/identities.rs index 471668e1d..01be1220b 100644 --- a/src/database/identities.rs +++ b/src/database/identities.rs @@ -5,6 +5,7 @@ use crate::model::wallet::Wallet; use dash_sdk::dpp::identity::accessors::IdentityGettersV0; use dash_sdk::platform::Identifier; use rusqlite::params; +use std::collections::BTreeMap; use std::sync::{Arc, RwLock}; impl Database { @@ -167,20 +168,48 @@ impl Database { let network = app_context.network_string(); let conn = self.conn.lock().unwrap(); + + // Prepare the main statement to select identities, including wallet_index let mut stmt = conn.prepare( - "SELECT data, alias FROM identity WHERE is_local = 1 AND network = ? AND data IS NOT NULL", + "SELECT data, alias, wallet_index FROM identity WHERE is_local = 1 AND network = ? AND data IS NOT NULL", )?; + + // Prepare the statement to select top-ups (will be used multiple times) + let mut top_up_stmt = + conn.prepare("SELECT top_up_index, amount FROM top_up WHERE identity_id = ?")?; + + // Iterate over each identity let identity_iter = stmt.query_map(params![network], |row| { let data: Vec = row.get(0)?; let alias: Option = row.get(1)?; + let wallet_index: Option = row.get(2)?; + let mut identity: QualifiedIdentity = QualifiedIdentity::from_bytes(&data); identity.alias = alias; + identity.wallet_index = wallet_index; + // Associate wallets identity.associated_wallets = wallets .iter() .map(|wallet| (wallet.read().unwrap().seed_hash(), wallet.clone())) .collect(); + // Retrieve the identity_id as bytes + let identity_id = identity.identity.id().to_buffer(); + + // Query the top_up table for this identity_id + let mut top_ups = BTreeMap::new(); + let mut rows = top_up_stmt.query(params![identity_id])?; + + while let Some(top_up_row) = rows.next()? { + let top_up_index: u32 = top_up_row.get(0)?; + let amount: u32 = top_up_row.get(1)?; + top_ups.insert(top_up_index, amount); + } + + // Assign the top_ups to the identity + identity.top_ups = top_ups; + Ok(identity) })?; diff --git a/src/database/initialization.rs b/src/database/initialization.rs index 2a828c88d..5aec0d490 100644 --- a/src/database/initialization.rs +++ b/src/database/initialization.rs @@ -4,7 +4,7 @@ use rusqlite::{params, Connection}; use std::fs; use std::path::Path; -pub const DEFAULT_DB_VERSION: u16 = 3; +pub const DEFAULT_DB_VERSION: u16 = 4; pub const DEFAULT_NETWORK: &str = "dash"; @@ -34,6 +34,9 @@ impl Database { fn apply_version_changes(&self, version: u16) -> rusqlite::Result<()> { match version { + 4 => { + self.initialize_top_up_table()?; + } 3 => { self.add_custom_dash_qt_columns()?; } @@ -345,6 +348,8 @@ impl Database { self.initialize_proof_log_table()?; + self.initialize_top_up_table()?; + Ok(()) } diff --git a/src/database/mod.rs b/src/database/mod.rs index 83d06cf13..942dfe471 100644 --- a/src/database/mod.rs +++ b/src/database/mod.rs @@ -5,6 +5,7 @@ mod identities; mod initialization; mod proof_log; mod settings; +mod top_ups; mod utxo; mod wallet; diff --git a/src/database/top_ups.rs b/src/database/top_ups.rs new file mode 100644 index 000000000..d91fa11a2 --- /dev/null +++ b/src/database/top_ups.rs @@ -0,0 +1,44 @@ +use crate::database::Database; +use rusqlite::{params, OptionalExtension}; + +impl Database { + pub fn initialize_top_up_table(&self) -> rusqlite::Result<()> { + // Create the top_up table + self.execute( + "CREATE TABLE IF NOT EXISTS top_up ( + identity_id BLOB NOT NULL, + top_up_index INTEGER NOT NULL, + amount INTEGER NOT NULL, + PRIMARY KEY (identity_id, top_up_index), + FOREIGN KEY (identity_id) REFERENCES identity(id) ON DELETE CASCADE + )", + [], + )?; + Ok(()) + } + + pub fn get_next_top_up_index(&self, identity_id: &[u8]) -> rusqlite::Result { + let conn = self.conn.lock().unwrap(); + let max_index: Option = conn + .query_row( + "SELECT MAX(top_up_index) FROM top_up WHERE identity_id = ?", + params![identity_id], + |row| row.get(0), + ) + .optional()?; + Ok(max_index.unwrap_or(0) + 1) + } + + pub fn insert_top_up( + &self, + identity_id: &[u8], + top_up_index: u32, + amount: u64, + ) -> rusqlite::Result<()> { + self.execute( + "INSERT INTO top_up (identity_id, top_up_index, amount) VALUES (?, ?, ?)", + params![identity_id, top_up_index, amount], + )?; + Ok(()) + } +} diff --git a/src/model/qualified_identity/mod.rs b/src/model/qualified_identity/mod.rs index 477c843eb..99a8cd5b6 100644 --- a/src/model/qualified_identity/mod.rs +++ b/src/model/qualified_identity/mod.rs @@ -98,6 +98,7 @@ pub struct QualifiedIdentity { pub associated_wallets: BTreeMap>>, /// The index used to register the identity pub wallet_index: Option, + pub top_ups: BTreeMap, } impl PartialEq for QualifiedIdentity { @@ -150,6 +151,7 @@ impl Decode for QualifiedIdentity { dpns_names: Vec::::decode(decoder)?, associated_wallets: BTreeMap::new(), // Initialize with an empty vector wallet_index: None, + top_ups: Default::default(), }) } } @@ -358,6 +360,7 @@ impl From for QualifiedIdentity { dpns_names: vec![], associated_wallets: BTreeMap::new(), wallet_index: None, + top_ups: Default::default(), } } } diff --git a/src/ui/identities/top_up_identity_screen/by_wallet_qr_code.rs b/src/ui/identities/top_up_identity_screen/by_wallet_qr_code.rs index 5fb6ae4ea..dc8d81879 100644 --- a/src/ui/identities/top_up_identity_screen/by_wallet_qr_code.rs +++ b/src/ui/identities/top_up_identity_screen/by_wallet_qr_code.rs @@ -144,6 +144,15 @@ impl TopUpIdentityScreen { return action; }; if let Some((utxo, tx_out, address)) = self.funding_utxo.clone() { + let wallet_index = self.identity.wallet_index.unwrap_or(u32::MAX >> 1); + let top_up_index = self + .identity + .top_ups + .keys() + .max() + .cloned() + .map(|i| i + 1) + .unwrap_or_default(); let identity_input = IdentityTopUpInfo { qualified_identity: self.identity.clone(), wallet: Arc::clone(selected_wallet), // Clone the Arc reference @@ -151,8 +160,8 @@ impl TopUpIdentityScreen { utxo, tx_out, address, - self.identity.wallet_index.unwrap_or(u32::MAX >> 1), - self.top_up_index_number, + wallet_index, + top_up_index, ), }; diff --git a/src/ui/identities/top_up_identity_screen/mod.rs b/src/ui/identities/top_up_identity_screen/mod.rs index 125774ea0..423225d23 100644 --- a/src/ui/identities/top_up_identity_screen/mod.rs +++ b/src/ui/identities/top_up_identity_screen/mod.rs @@ -35,7 +35,6 @@ pub struct TopUpIdentityScreen { funding_asset_lock: Option<(Transaction, AssetLockProof, Address)>, wallet: Option>>, core_has_funding_address: Option, - top_up_index_number: u32, funding_address: Option
, funding_address_balance: Arc>>, funding_method: Arc>, @@ -65,7 +64,6 @@ impl TopUpIdentityScreen { funding_asset_lock: None, wallet: selected_wallet, core_has_funding_address: None, - top_up_index_number: 0, funding_address: None, funding_address_balance: Arc::new(RwLock::new(None)), funding_method: Arc::new(RwLock::new(FundingMethod::NoSelection)), @@ -138,61 +136,6 @@ impl TopUpIdentityScreen { } } - fn render_top_up_index_input(&mut self, ui: &mut egui::Ui) { - ui.horizontal(|ui| { - ui.label("Top up Index:"); - - // Check if we have access to the selected wallet - if let Some(wallet_guard) = self.wallet.as_ref() { - let wallet = wallet_guard.read().unwrap(); - let used_indices: HashSet = wallet.identities.keys().cloned().collect(); - - // Modify the selected text to include "(used)" if the current index is used - let selected_text = { - let is_used = used_indices.contains(&self.top_up_index_number); - if is_used { - format!("{} (used)", self.top_up_index_number) - } else { - format!("{}", self.top_up_index_number) - } - }; - - // Render a ComboBox to select the identity index - ComboBox::from_id_salt("identity_index") - .selected_text(selected_text) - .show_ui(ui, |ui| { - // Provide up to 30 entries for selection (0 to 29) - for i in 0..30 { - let is_used = used_indices.contains(&i); - let label = if is_used { - format!("{} (used)", i) - } else { - format!("{}", i) - }; - - let is_selected = self.top_up_index_number == i; - - // Enable the option if it's not used or if it's the currently selected index - let enabled = !is_used || is_selected; - - // Use `add_enabled` to disable used indices - let response = ui.add_enabled( - enabled, - egui::SelectableLabel::new(is_selected, label), - ); - - // Only allow selection if the index is not used - if response.clicked() && !is_used { - self.top_up_index_number = i; - } - } - }); - } else { - ui.label("No wallet selected"); - } - }); - } - fn render_funding_method(&mut self, ui: &mut egui::Ui) { let Some(selected_wallet) = self.wallet.clone() else { return; @@ -307,7 +250,13 @@ impl TopUpIdentityScreen { identity_funding_method: TopUpIdentityFundingMethod::FundWithWallet( amount, self.identity.wallet_index.unwrap_or(u32::MAX >> 1), - self.top_up_index_number, + self.identity + .top_ups + .keys() + .max() + .cloned() + .map(|i| i + 1) + .unwrap_or_default(), ), };