Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
faa495f
feat: add button to top up address on wallet screen
lklimek Jul 22, 2025
675943d
chore: identity top up using funding widget
lklimek Jul 22, 2025
03dc7a7
chore: asset locks
lklimek Jul 22, 2025
0d651e8
refactor: use FundingWidget in identity create
lklimek Jul 23, 2025
fb018a3
feat: top up new address button
lklimek Jul 23, 2025
d488c1f
feat: identity create done
lklimek Jul 23, 2025
0bd971f
chore: top up
lklimek Jul 23, 2025
2db8037
chore: remove unused steps
lklimek Jul 23, 2025
6a13346
feat: show/hide validation
lklimek Jul 23, 2025
8eee4a6
chore: wallet screen top up autoclose
lklimek Jul 23, 2025
f81d268
fix: top up of existing address fails when there is already a utxo
lklimek Jul 23, 2025
2302750
chore: top up automatically
lklimek Jul 23, 2025
b6b6058
chore: copilot review
lklimek Jul 24, 2025
d408a25
chore: copilot feedback
lklimek Jul 24, 2025
0cc6bbd
chore: clippy
lklimek Jul 24, 2025
d34ca91
feat: amount input, first building version
lklimek Jul 24, 2025
dc665be
test(amount): fixed tests
lklimek Jul 24, 2025
0478847
chore: I think final
lklimek Jul 24, 2025
589cfb5
chore: my_tokens display correct amount
lklimek Jul 24, 2025
1585a93
chore: transfer tokens update
lklimek Jul 24, 2025
317add9
chore: hide unit on rewards estimate column
lklimek Jul 28, 2025
ed4f2af
chore: two new helper methods
lklimek Jul 28, 2025
f9cd9a9
chore: I think finals
lklimek Jul 28, 2025
3c36bc0
cargo fmt
lklimek Jul 28, 2025
212e3fa
feat: component trait
lklimek Jul 28, 2025
1b07901
impl Component for AmountInput
lklimek Jul 28, 2025
f2009cb
chore: updated component trait
lklimek Jul 28, 2025
8adb4b9
chore: update for egui enabled state mgmt
lklimek Jul 28, 2025
80cf70e
doc: component design pattern doc
lklimek Jul 28, 2025
4289d21
chore: component design pattern continued
lklimek Jul 28, 2025
a00ace6
chore: impl Component for FundingWidget
lklimek Jul 28, 2025
7a14ddb
Merge branch 'refactor/amount-input' into refactor/top-up-plus-amount
lklimek Jul 28, 2025
c9915ee
chore: amount improvements
lklimek Jul 28, 2025
1022318
chore: implement amount on top up
lklimek Jul 28, 2025
70422e6
chore: amount improvements
lklimek Jul 28, 2025
ed47168
chore: amount improvements
lklimek Jul 28, 2025
4bdf017
chore: copilot review
lklimek Jul 28, 2025
5bc61f3
chore: amount improvements
lklimek Jul 28, 2025
e597182
Merge branch 'refactor/amount-input' into feat/wallet-address-top-up
lklimek Jul 28, 2025
51cd7a2
backport: amount component from
lklimek Jul 29, 2025
3636d41
chore: fix imports
lklimek Jul 29, 2025
e89ad0b
chore: refactor
lklimek Jul 29, 2025
c074249
chore: futher refactor
lklimek Jul 29, 2025
8710d44
chore: further refactor based on feedback
lklimek Jul 29, 2025
73c46b3
doc: simplified component design pattern description
lklimek Jul 29, 2025
463e82c
chore: peer review
lklimek Jul 30, 2025
d02c637
doc: update docs
lklimek Jul 30, 2025
a172f03
chore: amount input
lklimek Jul 30, 2025
5aae125
Merge commit 'a172f031067aed35f07a25df29b239745d9c1144' into feat/wal…
lklimek Aug 4, 2025
710ecd8
Merge remote-tracking branch 'origin/v1.0-dev' into feat/wallet-addre…
lklimek Aug 4, 2025
054542a
chore: fixes after merge
lklimek Aug 4, 2025
d6c82c8
chore: remove unused impl Widget
lklimek Aug 4, 2025
863c979
fix: unblock UI on error
lklimek Aug 4, 2025
8876772
chore: various fixes
lklimek Aug 4, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions src/backend_task/identity/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ mod withdraw_from_identity;
use super::BackendTaskSuccessResult;
use crate::app::TaskResult;
use crate::context::AppContext;
use crate::model::amount::Amount;
use crate::model::qualified_identity::encrypted_key_storage::{KeyStorage, WalletDerivationPath};
use crate::model::qualified_identity::qualified_identity_public_key::QualifiedIdentityPublicKey;
use crate::model::qualified_identity::{IdentityType, PrivateKeyTarget, QualifiedIdentity};
Expand All @@ -21,7 +22,6 @@ use dash_sdk::dashcore_rpc::dashcore::bip32::DerivationPath;
use dash_sdk::dashcore_rpc::dashcore::key::Secp256k1;
use dash_sdk::dashcore_rpc::dashcore::{Address, PrivateKey, TxOut};
use dash_sdk::dpp::ProtocolError;
use dash_sdk::dpp::balances::credits::Duffs;
use dash_sdk::dpp::dashcore::hashes::Hash;
use dash_sdk::dpp::dashcore::{OutPoint, Transaction};
use dash_sdk::dpp::fee::Credits;
Expand Down Expand Up @@ -197,14 +197,15 @@ pub type TopUpIndex = u32;
pub enum RegisterIdentityFundingMethod {
UseAssetLock(Address, Box<AssetLockProof>, Box<Transaction>),
FundWithUtxo(OutPoint, TxOut, Address, IdentityIndex),
FundWithWallet(Duffs, IdentityIndex),
FundWithWallet(Amount, IdentityIndex),
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum TopUpIdentityFundingMethod {
UseAssetLock(Address, Box<AssetLockProof>, Box<Transaction>),
// QR code mathod
FundWithUtxo(OutPoint, TxOut, Address, IdentityIndex, TopUpIndex),
FundWithWallet(Duffs, IdentityIndex, TopUpIndex),
FundWithWallet(Amount, IdentityIndex, TopUpIndex),
}

#[derive(Debug, Clone)]
Expand Down
4 changes: 2 additions & 2 deletions src/backend_task/identity/register_identity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ impl AppContext {
wallet_id = wallet.seed_hash();
match wallet.registration_asset_lock_transaction(
sdk.network,
amount,
amount.dash_to_duffs().expect("amount should be in DASH"),
true,
identity_index,
Some(self),
Expand All @@ -193,7 +193,7 @@ impl AppContext {
.map_err(|e| e.to_string())?;
wallet.registration_asset_lock_transaction(
sdk.network,
amount,
amount.dash_to_duffs().expect("amount should be in DASH"),
true,
identity_index,
Some(self),
Expand Down
9 changes: 5 additions & 4 deletions src/backend_task/identity/top_up_identity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use crate::app::TaskResult;
use crate::backend_task::BackendTaskSuccessResult;
use crate::backend_task::identity::{IdentityTopUpInfo, TopUpIdentityFundingMethod};
use crate::context::AppContext;
use crate::model::amount::Amount;
use dash_sdk::Error;
use dash_sdk::dashcore_rpc::RpcApi;
use dash_sdk::dpp::ProtocolError;
Expand Down Expand Up @@ -92,7 +93,7 @@ impl AppContext {
let mut wallet = wallet.write().unwrap();
match wallet.top_up_asset_lock_transaction(
sdk.network,
amount,
amount.dash_to_duffs().expect("amount should be in DASH"),
true,
identity_index,
top_up_index,
Expand All @@ -112,7 +113,7 @@ impl AppContext {
.map_err(|e| e.to_string())?;
wallet.top_up_asset_lock_transaction(
sdk.network,
amount,
amount.dash_to_duffs().expect("amount should be in DASH"),
true,
identity_index,
top_up_index,
Expand Down Expand Up @@ -242,7 +243,7 @@ impl AppContext {
asset_lock_proof,
asset_lock_proof_private_key,
tx_id,
Some((tx_out.value, top_up_index)),
Some((Amount::dash_from_duffs(tx_out.value), top_up_index)),
)
}
};
Expand Down Expand Up @@ -326,7 +327,7 @@ impl AppContext {
.insert_top_up(
qualified_identity.identity.id().as_bytes(),
top_up_index,
amount,
amount.dash_to_duffs().expect("amount should be in DASH"),
)
.map_err(|e| e.to_string())?;
}
Expand Down
2 changes: 1 addition & 1 deletion src/backend_task/system_task/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ impl AppContext {
theme_mode: ThemeMode,
) -> Result<BackendTaskSuccessResult, String> {
let _guard = self.invalidate_settings_cache();

self.db
.update_theme_preference(theme_mode)
.map_err(|e| e.to_string())?;
Expand Down
3 changes: 2 additions & 1 deletion src/database/top_ups.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::database::Database;
use dash_sdk::dpp::balances::credits::Duffs;
use rusqlite::{OptionalExtension, params};

impl Database {
Expand Down Expand Up @@ -34,7 +35,7 @@ impl Database {
&self,
identity_id: &[u8],
top_up_index: u32,
amount: u64,
amount: Duffs,
) -> rusqlite::Result<()> {
self.execute(
"INSERT INTO top_up (identity_id, top_up_index, amount) VALUES (?, ?, ?)",
Expand Down
20 changes: 14 additions & 6 deletions src/model/wallet/asset_lock_transaction.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use crate::context::AppContext;
use crate::model::wallet::Wallet;
use dash_sdk::dashcore_rpc::dashcore::key::Secp256k1;
use dash_sdk::dpp::balances::credits::Duffs;
use dash_sdk::dpp::dashcore::psbt::serialize::Serialize;
use dash_sdk::dpp::dashcore::secp256k1::Message;
use dash_sdk::dpp::dashcore::sighash::SighashCache;
Expand All @@ -16,7 +17,7 @@ impl Wallet {
pub fn registration_asset_lock_transaction(
&mut self,
network: Network,
amount: u64,
amount: Duffs,
allow_take_fee_from_amount: bool,
identity_index: u32,
register_addresses: Option<&AppContext>,
Expand Down Expand Up @@ -47,7 +48,7 @@ impl Wallet {
pub fn top_up_asset_lock_transaction(
&mut self,
network: Network,
amount: u64,
amount_duffs: Duffs,
allow_take_fee_from_amount: bool,
identity_index: u32,
top_up_index: u32,
Expand All @@ -69,7 +70,7 @@ impl Wallet {
)?;
self.asset_lock_transaction_from_private_key(
network,
amount,
amount_duffs,
allow_take_fee_from_amount,
private_key,
register_addresses,
Expand All @@ -80,7 +81,7 @@ impl Wallet {
fn asset_lock_transaction_from_private_key(
&mut self,
network: Network,
amount: u64,
amount_duffs: Duffs,
allow_take_fee_from_amount: bool,
private_key: PrivateKey,
register_addresses: Option<&AppContext>,
Expand All @@ -99,8 +100,15 @@ impl Wallet {
let one_time_key_hash = asset_lock_public_key.pubkey_hash();
let fee = 3_000;

tracing::debug!(wallet=?self.alias,
"Creating asset lock transaction with amount: {}, fee: {}, allow_take_fee_from_amount: {}",
amount_duffs,
fee,
allow_take_fee_from_amount
);

let (utxos, change_option) = self
.take_unspent_utxos_for(amount, fee, allow_take_fee_from_amount)
.take_unspent_utxos_for(amount_duffs, fee, allow_take_fee_from_amount)
.ok_or("take_unspent_utxos_for() returned None".to_string())?;

let actual_amount = if change_option.is_none() && allow_take_fee_from_amount {
Expand All @@ -109,7 +117,7 @@ impl Wallet {
let total_input_value: u64 = utxos.iter().map(|(_, (tx_out, _))| tx_out.value).sum();
total_input_value - fee
} else {
amount
amount_duffs
};

let payload_output = TxOut {
Expand Down
1 change: 1 addition & 0 deletions src/model/wallet/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,7 @@ impl Wallet {
!self.unused_asset_locks.is_empty()
}

/// Returns the maximum balance of the wallet by summing up all UTXOs.
pub fn max_balance(&self) -> u64 {
self.utxos
.values()
Expand Down
58 changes: 45 additions & 13 deletions src/ui/components/amount_input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ impl AmountInput {
show_max_button: false,
desired_width: None,
show_validation_errors: true, // Default to showing validation errors
changed: false,
changed: true, // Start as changed to force initial validation
}
}

Expand All @@ -135,13 +135,36 @@ impl AmountInput {
self.decimal_places
}

/// Update decimal places used to render values.
///
/// Value displayed in the input is not changed, but the actual [Amount]
/// will be multiplied or divided by 10^(difference of decimal places).
///
/// ## Example
///
/// The input contains `12.34` and decimal places is set to 3.
/// It will be interpreted as `12.340` when parsed (credits value `12_340`).
///
///
/// If you change the decimal places from 3 to 5:
///
/// * The input will still display `12.34` (unchanged)
/// * The next time the input is parsed, it will generate `12.34000`
/// (credits value `1_234_000`).
pub fn set_decimal_places(&mut self, decimal_places: u8) -> &mut Self {
self.decimal_places = decimal_places;
self.changed = true;

self
}

/// Gets the unit name this input is configured for.
pub fn unit_name(&self) -> Option<&str> {
self.unit_name.as_deref()
}

/// Sets the label for the input field.
pub fn label<T: Into<WidgetText>>(mut self, label: T) -> Self {
pub fn with_label<T: Into<WidgetText>>(mut self, label: T) -> Self {
self.label = Some(label.into());
self
}
Expand All @@ -154,7 +177,7 @@ impl AmountInput {
}

/// Sets the hint text for the input field.
pub fn hint_text<T: Into<WidgetText>>(mut self, hint_text: T) -> Self {
pub fn with_hint_text<T: Into<WidgetText>>(mut self, hint_text: T) -> Self {
self.hint_text = Some(hint_text.into());
self
}
Expand All @@ -167,7 +190,7 @@ impl AmountInput {

/// Sets the maximum amount allowed. If provided, a "Max" button will be shown
/// when `show_max_button` is true.
pub fn max_amount(mut self, max_amount: Option<Credits>) -> Self {
pub fn with_max_amount(mut self, max_amount: Option<Credits>) -> Self {
self.max_amount = max_amount;
self
}
Expand All @@ -181,7 +204,7 @@ impl AmountInput {

/// Sets the minimum amount allowed. Defaults to 1 (must be greater than zero).
/// Set to Some(0) to allow zero amounts, or None to disable minimum validation.
pub fn min_amount(mut self, min_amount: Option<Credits>) -> Self {
pub fn with_min_amount(mut self, min_amount: Option<Credits>) -> Self {
self.min_amount = min_amount;
self
}
Expand All @@ -193,7 +216,7 @@ impl AmountInput {
}

/// Whether to show a "Max" button that sets the amount to the maximum.
pub fn max_button(mut self, show: bool) -> Self {
pub fn with_max_button(mut self, show: bool) -> Self {
self.show_max_button = show;
self
}
Expand All @@ -205,7 +228,7 @@ impl AmountInput {
}

/// Sets the desired width of the input field.
pub fn desired_width(mut self, width: f32) -> Self {
pub fn with_desired_width(mut self, width: f32) -> Self {
self.desired_width = Some(width);
self
}
Expand All @@ -217,7 +240,7 @@ impl AmountInput {
}

/// Controls whether validation errors are displayed as a label within the component.
pub fn show_validation_errors(mut self, show: bool) -> Self {
pub fn with_validation_errors_display(mut self, show: bool) -> Self {
self.show_validation_errors = show;
self
}
Expand Down Expand Up @@ -343,6 +366,15 @@ impl Component for AmountInput {
fn show(&mut self, ui: &mut Ui) -> InnerResponse<Self::Response> {
AmountInput::show_internal(self, ui)
}

fn current_value(&self) -> Option<Self::DomainType> {
// Validate the current amount string and return the parsed amount
match self.validate_amount() {
Ok(Some(amount)) => Some(amount),
Ok(None) => None, // Empty input
Err(_) => None, // Invalid input returns None
}
}
}

#[cfg(test)]
Expand Down Expand Up @@ -382,15 +414,15 @@ mod tests {
assert_eq!(input.min_amount, Some(1));

// Custom minimum
let input = AmountInput::new(Amount::new(0, 8)).min_amount(Some(1000));
let input = AmountInput::new(Amount::new(0, 8)).with_min_amount(Some(1000));
assert_eq!(input.min_amount, Some(1000));

// Allow zero
let input = AmountInput::new(Amount::new(0, 8)).min_amount(Some(0));
let input = AmountInput::new(Amount::new(0, 8)).with_min_amount(Some(0));
assert_eq!(input.min_amount, Some(0));

// No minimum
let input = AmountInput::new(Amount::new(0, 8)).min_amount(None);
let input = AmountInput::new(Amount::new(0, 8)).with_min_amount(None);
assert_eq!(input.min_amount, None);
}

Expand Down Expand Up @@ -478,8 +510,8 @@ mod tests {
fn test_min_max_validation() {
let amount = Amount::new(0, 2);
let mut input = AmountInput::new(amount)
.min_amount(Some(100)) // Minimum 1.00
.max_amount(Some(10000)); // Maximum 100.00
.with_min_amount(Some(100)) // Minimum 1.00
.with_max_amount(Some(10000)); // Maximum 100.00

// Test amount below minimum
input.amount_str = "0.50".to_string(); // 50 (below min of 100)
Expand Down
8 changes: 8 additions & 0 deletions src/ui/components/component_trait.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,4 +92,12 @@ pub trait Component {
/// An [`InnerResponse`] containing the component's response data in [`InnerResponse::inner`] field.
/// [`InnerResponse::inner`] should implement [`ComponentResponse`] trait.
fn show(&mut self, ui: &mut Ui) -> InnerResponse<Self::Response>;

/// Returns the current value of the component.
///
/// Note that only valid values should be returned here.
/// If the component value is invalid, this should return `None`.
///
/// See [`ComponentResponse::current_value`] for more details.
fn current_value(&self) -> Option<Self::DomainType>;
}
Loading
Loading