From de6d2cb59f470d636400b6dfbf749728c07b7068 Mon Sep 17 00:00:00 2001 From: Anton Yemelyanov Date: Thu, 12 Sep 2024 19:54:31 +0300 Subject: [PATCH] WIP --- core/resources/i18n/i18n.json | 2 + core/src/core.rs | 4 +- core/src/egui/selection_panels.rs | 59 ++-- core/src/imports.rs | 2 +- core/src/modules/account_manager/estimator.rs | 283 +++++++++++------- core/src/modules/account_manager/mod.rs | 58 +++- core/src/modules/account_manager/processor.rs | 122 ++++++-- core/src/modules/testing.rs | 23 +- core/src/runtime/services/feerate_monitor.rs | 7 +- core/src/utils/average.rs | 69 +++-- core/src/utils/format.rs | 1 - 11 files changed, 420 insertions(+), 210 deletions(-) diff --git a/core/resources/i18n/i18n.json b/core/resources/i18n/i18n.json index 47538bb..f7cc321 100644 --- a/core/resources/i18n/i18n.json +++ b/core/resources/i18n/i18n.json @@ -3363,6 +3363,7 @@ "Presets": "Presets", "Price": "Price", "Priority": "Priority", + "Priority Fee Estimator": "Priority Fee Estimator", "Private Key Mnemonic": "Private Key Mnemonic", "Processed Bodies": "Processed Bodies", "Processed Dependencies": "Processed Dependencies", @@ -3469,6 +3470,7 @@ "Total Tx": "Total Tx", "Total Tx/s": "Total Tx/s", "Track in the background": "Track in the background", + "Transaction Fee Estimator": "Transaction Fee Estimator", "Transaction Fees": "Transaction Fees", "Transactions": "Transactions", "Transactions:": "Transactions:", diff --git a/core/src/core.rs b/core/src/core.rs index 166474d..a947d97 100644 --- a/core/src/core.rs +++ b/core/src/core.rs @@ -60,7 +60,7 @@ pub struct Core { notifications: Notifications, pub storage: Storage, // pub feerate : Option>, - pub feerate : Option, + pub feerate: Option, } impl Core { @@ -222,7 +222,7 @@ impl Core { network_pressure: NetworkPressure::default(), notifications: Notifications::default(), storage, - feerate : None, + feerate: None, // daemon_storage_root: Mutex::new(daemon_storage_root), }; diff --git a/core/src/egui/selection_panels.rs b/core/src/egui/selection_panels.rs index ca45399..665a4e6 100644 --- a/core/src/egui/selection_panels.rs +++ b/core/src/egui/selection_panels.rs @@ -37,7 +37,8 @@ impl UILayoutExt for Ui { let mut child_rect = self.available_rect_before_wrap(); child_rect.min.x += indent; - let mut child_ui = self.child_ui_with_id_source(child_rect, *self.layout(), id_source, None); + let mut child_ui = + self.child_ui_with_id_source(child_rect, *self.layout(), id_source, None); let ret = add_contents(&mut child_ui); // let left_vline = self.visuals().indent_has_left_vline; @@ -73,7 +74,7 @@ impl UILayoutExt for Ui { } type UiBuilderFn = Box; -type FooterUiBuilderFn = Box; +// type FooterUiBuilderFn = Box; pub struct SelectionPanel { pub title: WidgetText, @@ -170,11 +171,11 @@ impl SelectionPanel { } pub struct SelectionPanels { - pub title: WidgetText, + // pub title: WidgetText, pub panel_min_width: f32, pub panel_max_width: f32, pub panels: Vec>, - pub build_footer: FooterUiBuilderFn, + // pub build_footer: FooterUiBuilderFn, pub panel_min_height: f32, pub vertical: bool, pub sep_ratio: f32, @@ -184,14 +185,14 @@ impl SelectionPanels { pub fn new( panel_min_width: f32, panel_max_width: f32, - title: impl Into, - build_footer: impl FnOnce(&mut Ui, &mut Value) + 'static, + // title: impl Into, + // build_footer: impl FnOnce(&mut Ui, &mut Value) + 'static, ) -> Self { Self { - title: title.into(), + // title: title.into(), panel_min_width, panel_max_width, - build_footer: Box::new(build_footer), + // build_footer: Box::new(build_footer), panels: vec![], panel_min_height: 0., vertical: false, @@ -266,22 +267,22 @@ impl SelectionPanels { let add_contents = |ui: &mut Ui| { let mut responce = ui.label(" "); - //ui.visuals_mut().override_text_color = Some(Color32::WHITE); - { - let available_width = ui.available_width() - indent; - let title = - self.title - .into_galley(ui, Some(TextWrapMode::Wrap), available_width, TextStyle::Heading); - let text_indent = (available_width - title.size().x) / 2.0; - let rect = ui.cursor().translate(Vec2::new(text_indent, 10.0)); - ui.allocate_exact_size( - title.size() + Vec2::new(text_indent, 10.0), - Sense::focusable_noninteractive(), - ); - // TODO @28 - ui.painter().galley(rect.min, title, visuals.text_color()); - // title.paint_with_fallback_color(ui.painter(), rect.min, text_color); - } + // { + // let available_width = ui.available_width() - indent; + // let title = self.title.into_galley( + // ui, + // Some(TextWrapMode::Wrap), + // available_width, + // TextStyle::Heading, + // ); + // let text_indent = (available_width - title.size().x) / 2.0; + // let rect = ui.cursor().translate(Vec2::new(text_indent, 10.0)); + // ui.allocate_exact_size( + // title.size() + Vec2::new(text_indent, 10.0), + // Sense::focusable_noninteractive(), + // ); + // ui.painter().galley(rect.min, title, visuals.text_color()); + // } // ui.label(format!("before_wrap_width: {before_wrap_width}")); // ui.label(format!("panel_width: {panel_width}")); @@ -340,13 +341,13 @@ impl SelectionPanels { responce }; - let mut response = ui + let response = ui .indent_with_size("selection-panels", indent, Box::new(add_contents)) .response; - response |= ui - .vertical_centered(|ui| (self.build_footer)(ui, current_value)) - .response; - ui.label(" "); + // response |= ui + // .vertical_centered(|ui| (self.build_footer)(ui, current_value)) + // .response; + // ui.label(" "); // ui.label(format!(" vertical: {vertical}")); // ui.label(format!("panels_width {}", panels_width)); response diff --git a/core/src/imports.rs b/core/src/imports.rs index c3e7329..81f65b4 100644 --- a/core/src/imports.rs +++ b/core/src/imports.rs @@ -5,7 +5,7 @@ pub use kaspa_consensus_core::network::{NetworkId, NetworkType}; pub use kaspa_consensus_core::Hash as KaspaHash; pub use kaspa_metrics_core::MetricsSnapshot; pub use kaspa_rpc_core::api::rpc::RpcApi; -pub use kaspa_rpc_core::{RpcFeeEstimate,RpcFeerateBucket}; +pub use kaspa_rpc_core::{RpcFeeEstimate, RpcFeerateBucket}; pub use kaspa_utils::hex::{FromHex, ToHex}; pub use kaspa_utils::{hashmap::GroupExtension, networking::ContextualNetAddress}; pub use kaspa_wallet_core::prelude::{ diff --git a/core/src/modules/account_manager/estimator.rs b/core/src/modules/account_manager/estimator.rs index 6213ce7..eea3c10 100644 --- a/core/src/modules/account_manager/estimator.rs +++ b/core/src/modules/account_manager/estimator.rs @@ -16,6 +16,7 @@ impl<'context> Estimator<'context> { use egui_phosphor::light::{CHECK, X}; let RenderContext { network_type, .. } = rc; + let network_id = NetworkId::from(core.network()); let mut request_send = false; let mut request_estimate = self.context.request_estimate.take().unwrap_or_default(); @@ -48,11 +49,11 @@ impl<'context> Estimator<'context> { .build(ui); if response.text_edit_submit(ui) { - if self.context.enable_priority_fees { + // if self.context.enable_priority_fees { self.context.focus.next(Focus::Fees); - } else if self.update_user_args() { - request_send = true; - } + // } else if self.update_user_args() { + // request_send = true; + // } } // TODO - improve the logic @@ -72,6 +73,7 @@ impl<'context> Estimator<'context> { + // self.context.focus.next(Focus::Fees); // if ui // .checkbox(&mut self.context.enable_priority_fees,i18n("Include QoS Priority Fees")) @@ -85,103 +87,35 @@ impl<'context> Estimator<'context> { // } // if self.context.enable_priority_fees { - // TextEditor::new( - // &mut self.context.priority_fees_text, - // &mut self.context.focus, - // Focus::Fees, - // |ui, text| { - // ui.add_space(8.); - // ui.label(RichText::new("Enter priority fees").size(12.).raised()); - // ui.add_sized(Overview::editor_size(ui), TextEdit::singleline(text) - // .vertical_align(Align::Center)) - // }, - // ) - // .change(|_| { - // request_estimate = true; - // }) - // .submit(|_,_|{ - // request_send = true; - // }) - // .build(ui); + TextEditor::new( + &mut self.context.priority_fees_text, + &mut self.context.focus, + Focus::Fees, + |ui, text| { + ui.add_space(8.); + ui.label(RichText::new("Enter priority fees").size(12.).raised()); + ui.add_sized(Overview::editor_size(ui), TextEdit::singleline(text) + .vertical_align(Align::Center)) + }, + ) + .change(|_| { + request_estimate = true; + }) + .submit(|_,_|{ + request_send = true; + }) + .build(ui); // } - // ui.add_space(8.); - ui.add_space(2.); - let ready_to_send = match &*self.context.estimate.lock().unwrap() { - EstimatorStatus::GeneratorSummary(estimate) => { - - core.apply_default_style(ui); - - if core.settings.developer.enable{ - let estimate = estimate.clone(); - let network_type = *network_type; - let network_pressure = core.network_pressure.capacity(); - - let usd_rate = if core.settings.market_monitor { - core.market.as_ref().and_then(|market| { - market.price.as_ref().and_then(|price_list| { - price_list.get("usd").map(|market_data| market_data.price) - }) - }) - } else { None }; - - let buckets = if let Some(fees) = core.feerate.as_ref() { - [Some((fees.low.value(), FeeMode::Low)), Some((fees.economic.value(), FeeMode::Economic)), Some((fees.priority.value(),FeeMode::Priority))] - } else { [None, None, None] }; - - let mut fee_selection = SelectionPanels::new( - 120.0, - 150.0, - i18n("Transaction Fees"), - move |ui, _value| { - // ui.label("1 in / 2 outputs, ~1.2 Kg"); - // ui.label(format!("Fee Mode: {:?}", value)); - ui.label(format!("{} {} • {} {} • {} {}g • {} ~{}%", - i18n("Transactions:"), - estimate.number_of_generated_transactions, - i18n("UTXOs:"), - estimate.aggregated_utxos, - i18n("Mass:"), - estimate.aggregate_mass, - i18n("Network Load:"), - network_pressure, - )); - - if let Some(final_transaction_amount) = estimate.final_transaction_amount { - ui.label(format!("{} {}",i18n("Final Amount:"), sompi_to_kaspa_string_with_suffix(final_transaction_amount + estimate.aggregate_fees, &network_type))); - } - }); - - for (bucket,mode) in buckets.into_iter().filter_map(|v|v) { - let aggregate_mass = estimate.aggregate_mass; - let number_of_generated_stages = estimate.number_of_generated_stages; - let feerate = bucket.feerate; - let seconds = bucket.seconds.max(1.0) * number_of_generated_stages as f64; - let network_type = network_type; - let total_kas = feerate * aggregate_mass as f64 * 1e-8; - let total_sompi = (feerate * aggregate_mass as f64) as u64; - let total_usd = usd_rate.map(|rate| total_kas * rate); - // println!("total_kas: {:?}, total_usd: {:?} usd_rate: {:?}", total_kas, total_usd, usd_rate); - fee_selection = fee_selection.add_with_footer(mode, i18n(mode.to_string().as_str()), format_duration_estimate(seconds), move |ui| { - // ui.label(format!("{} µKAS", feerate * aggregate_mass as f64 * 0.01)); - ui.label(format!("{}",sompi_to_kaspa_string_with_suffix(total_sompi, &network_type))); - if let Some(usd) = total_usd { - let usd = format_currency(usd, 6); - ui.label(RichText::new(format!("~{} USD", usd)).strong()); - } - // ui.label(RichText::new("~0.00000215 USD").strong()); - ui.label(format!("{} SOMPI/g", format_with_precision(feerate))); - }); - } - - if fee_selection.render(ui, &mut self.context.fee_mode).clicked(){ - log_info!("clicked: self.fee_mode: {:?}", self.context.fee_mode); - runtime().toast(UserNotification::success(format!("selection: {:?}", self.context.fee_mode)).short()) - } - ui.add_space(8.); - } - core.apply_mobile_style(ui); + + core.apply_default_style(ui); + + // ui.add_space(8.); + // ui.add_space(2.); + let (ready_to_send, _base_estimate, actual_estimate) = match &*self.context.estimate.lock().unwrap() { + // let ready_to_send = match estimator_status { + EstimatorStatus::GeneratorSummary { base_estimate, actual_estimate } => { // if let Some(final_transaction_amount) = estimate.final_transaction_amount { @@ -195,18 +129,153 @@ impl<'context> Estimator<'context> { // ui.label(format!("{} {}", fee_title, sompi_to_kaspa_string_with_suffix(estimate.aggregated_fees, network_type))); // ui.label(format!("{} {} {} {}",i18n("Transactions:"), estimate.number_of_generated_transactions, i18n("UTXOs:"), estimate.aggregated_utxos)); - self.context.address_status == AddressStatus::Valid || (self.context.transaction_kind == Some(TransactionKind::Transfer) && self.context.transfer_to_account.is_some()) + let ready_to_send = self.context.address_status == AddressStatus::Valid || (self.context.transaction_kind == Some(TransactionKind::Transfer) && self.context.transfer_to_account.is_some()); + (ready_to_send, base_estimate.clone(), actual_estimate.clone()) } EstimatorStatus::Error(error) => { ui.label(RichText::new(error.to_string()).color(theme_color().error_color)); - false + (false, GeneratorSummary::new(network_id), GeneratorSummary::new(network_id)) } EstimatorStatus::None => { - ui.label(format!("{} {} {}", i18n("Please enter"), kaspa_suffix(network_type), i18n("amount to send"))); - false + ui.label(format!("{} {} {}", i18n("Please enter"), kaspa_suffix(&network_type), i18n("amount to send"))); + (false, GeneratorSummary::new(network_id), GeneratorSummary::new(network_id)) } }; + // ui.add_space(2.); + + + // let estimator_status = { + // (*self.context.estimate.lock().unwrap()).clone() + // }; + + // let estimate = match &estimator_status { + // EstimatorStatus::GeneratorSummary(estimate) => { + // estimate.clone() + // }, + // _ => { + // GeneratorSummary::new(core.network().into()) + // } + // }; + + // if core.settings.developer.enable{ + // let estimate = estimate.clone(); + let network_type = *network_type; + let network_pressure = core.network_pressure.capacity(); + + let usd_rate = if core.settings.market_monitor { + core.market.as_ref().and_then(|market| { + market.price.as_ref().and_then(|price_list| { + price_list.get("usd").map(|market_data| market_data.price) + }) + }) + } else { None }; + + let buckets = if let Some(fees) = core.feerate.as_ref() { + // [Some((fees.low.value(), FeeMode::Low(fees.economic.value()))), Some((fees.economic.value(), FeeMode::Economic(fees.economic.value()))), Some((fees.priority.value(),FeeMode::Priority(fees.economic.value())))] + [Some(FeeMode::Low(fees.low.value())), Some(FeeMode::Economic(fees.economic.value())), Some(FeeMode::Priority(fees.priority.value()))] + } else { [None, None, None] }; + ui.add_space(8.); + ui.heading("Priority Fee Estimator"); + + let mut fee_selection = SelectionPanels::new( + 120.0, + 150.0, + // i18n("Priority Fee Estimator"), + // move |ui, _value| { + // // ui.label("1 in / 2 outputs, ~1.2 Kg"); + // // ui.label(format!("Fee Mode: {:?}", value)); + // // ui.label(format!("{} {} • {} {} • {} {}g • {} ~{}%", + // // i18n("Transactions:"), + // // actual_estimate.number_of_generated_transactions, + // // i18n("UTXOs:"), + // // actual_estimate.aggregated_utxos, + // // i18n("Mass:"), + // // actual_estimate.aggregate_mass, + // // i18n("Network Load:"), + // // network_pressure, + // // )); + + // // if let Some(final_transaction_amount) = actual_estimate.final_transaction_amount { + // // ui.label(RichText::new(format!("{} {}",i18n("Final Amount:"), sompi_to_kaspa_string_with_suffix(final_transaction_amount + actual_estimate.aggregate_fees, &network_type))).strong()); + // // } + // } + ); + + for mode in buckets.into_iter().filter_map(|v|v) { + let bucket = mode.bucket(); + let aggregate_mass = actual_estimate.aggregate_mass; + let number_of_generated_stages = actual_estimate.number_of_generated_stages; + let feerate = bucket.feerate; + let seconds = bucket.seconds.max(1.0) * number_of_generated_stages as f64; + let network_type = network_type; + let total_kas = feerate * aggregate_mass as f64 * 1e-8; + let total_sompi = (feerate * aggregate_mass as f64) as u64; + let total_usd = usd_rate.map(|rate| total_kas * rate); + // println!("total_kas: {:?}, total_usd: {:?} usd_rate: {:?}", total_kas, total_usd, usd_rate); + fee_selection = fee_selection.add_with_footer(mode, i18n(mode.to_string().as_str()), format_duration_estimate(seconds), move |ui| { + // ui.label(format!("{} µKAS", feerate * aggregate_mass as f64 * 0.01)); + ui.label(RichText::new(format!("{}",sompi_to_kaspa_string_with_suffix(total_sompi, &network_type))).strong()); + if let Some(usd) = total_usd { + let usd = format_currency(usd, 6); + ui.label(RichText::new(format!("~{} USD", usd)).strong()); + } + // ui.label(RichText::new("~0.00000215 USD").strong()); + ui.label(format!("{} SOMPI/g", format_with_precision(feerate))); + }); + } + + // if fee_selection.render(ui, &mut self.context.fee_mode).clicked() { + let mode = self.context.fee_mode; + fee_selection.render(ui, &mut self.context.fee_mode); + if mode != self.context.fee_mode { + // println!("clicked: self.fee_mode: {:?}", self.context.fee_mode); + log_info!("clicked: self.fee_mode: {:?}", self.context.fee_mode); + runtime().toast(UserNotification::success(format!("selection: {:?}", self.context.fee_mode)).short()); + + // Calculate desired fee rate + let bucket = self.context.fee_mode.bucket(); + println!("bucket: {:?}", bucket); + let priority_feerate = (bucket.feerate - 1.0).max(0.0); + println!("priority_feerate: {:?}", priority_feerate); + println!("estimate.aggregate_mass: {:?}", actual_estimate.aggregate_mass); + let total_fees_sompi = (priority_feerate * actual_estimate.aggregate_mass as f64) as u64; + println!("total_fees: {:?}", total_fees_sompi); + self.context.priority_fees_text = format!("{:0.2}", sompi_to_kaspa(total_fees_sompi)); + self.context.fee_mode = FeeMode::None; + request_estimate = true; + } + + + ui.vertical_centered(|ui| { + + ui.label(format!("{} {} • {} {} • {} {}g • {} ~{}%", + i18n("Transactions:"), + actual_estimate.number_of_generated_transactions, + i18n("UTXOs:"), + actual_estimate.aggregated_utxos, + i18n("Mass:"), + actual_estimate.aggregate_mass, + i18n("Network Load:"), + network_pressure, + )); + + ui.add_space(8.); + + if let Some(final_transaction_amount) = actual_estimate.final_transaction_amount { + ui.heading(RichText::new(format!("{} {}",i18n("Final Amount:"), sompi_to_kaspa_string_with_suffix(final_transaction_amount + actual_estimate.aggregate_fees, &network_type))).strong()); + } + + }); + + + + ui.add_space(16.); + // } + + core.apply_mobile_style(ui); + + if request_send { if ready_to_send { @@ -261,9 +330,19 @@ impl<'context> Estimator<'context> { match try_kaspa_str_to_sompi(self.context.priority_fees_text.as_str()) { Ok(Some(sompi)) => { + + // let fee_rate = ; + self.context.priority_fees_sompi = sompi; + + // self.context.priority_fee_rate = if base_estimate.aggregate_mass == 0 { + // 0.0 + // } else { + // sompi as f64 / base_estimate.aggregate_mass as f64 + // }; } Ok(None) => { + // self.context.priority_fee_rate = 0.0; self.context.priority_fees_sompi = 0; } Err(err) => { diff --git a/core/src/modules/account_manager/mod.rs b/core/src/modules/account_manager/mod.rs index 59fc8f5..73dc72d 100644 --- a/core/src/modules/account_manager/mod.rs +++ b/core/src/modules/account_manager/mod.rs @@ -94,11 +94,12 @@ enum Focus { PaymentSecret, } -#[derive(Default)] +#[derive(Default, Clone)] pub enum EstimatorStatus { #[default] None, - GeneratorSummary(GeneratorSummary), + // GeneratorSummary(GeneratorSummary), + GeneratorSummary { base_estimate : GeneratorSummary, actual_estimate : GeneratorSummary }, Error(String), } @@ -111,21 +112,54 @@ enum AddressStatus { Invalid(String), } -#[derive(PartialEq, Debug, Default, Clone, Copy)] +#[derive(Debug, Clone, Copy)] pub enum FeeMode{ - // None, - Low, - #[default] - Economic, - Priority, + None, + Low(FeerateBucket), + // #[default] + Economic(FeerateBucket), + Priority(FeerateBucket), +} + +impl FeeMode { + pub fn bucket(&self) -> FeerateBucket { + match self { + FeeMode::Low(bucket) => *bucket, + FeeMode::Economic(bucket) => *bucket, + FeeMode::Priority(bucket) => *bucket, + FeeMode::None => FeerateBucket::default(), + } + } +} + +impl Default for FeeMode { + fn default() -> Self { + // FeeMode::Economic(FeerateBucket::default()) + FeeMode::None + } +} + +impl Eq for FeeMode {} + +impl PartialEq for FeeMode { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (FeeMode::None, FeeMode::None) => true, + (FeeMode::Low(_), FeeMode::Low(_)) => true, + (FeeMode::Economic(_), FeeMode::Economic(_)) => true, + (FeeMode::Priority(_), FeeMode::Priority(_)) => true, + _ => false, + } + } } impl std::fmt::Display for FeeMode { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - FeeMode::Low => write!(f, "Low"), - FeeMode::Economic => write!(f, "Economic"), - FeeMode::Priority => write!(f, "Priority"), + FeeMode::None => write!(f, "N/A"), + FeeMode::Low(_) => write!(f, "Low"), + FeeMode::Economic(_) => write!(f, "Economic"), + FeeMode::Priority(_) => write!(f, "Priority"), } } } @@ -139,6 +173,7 @@ pub struct ManagerContext { enable_priority_fees : bool, priority_fees_text : String, priority_fees_sompi : u64, + // priority_fee_rate : f64, estimate : Arc>, request_estimate : Option, address_status : AddressStatus, @@ -175,6 +210,7 @@ impl Zeroize for ManagerContext { self.enable_priority_fees = false; self.priority_fees_text = String::default(); self.priority_fees_sompi = 0; + // self.priority_fee_rate = 0.0; *self.estimate.lock().unwrap() = EstimatorStatus::None; self.address_status = AddressStatus::None; self.transaction_kind = None; diff --git a/core/src/modules/account_manager/processor.rs b/core/src/modules/account_manager/processor.rs index 7598c2a..dafee00 100644 --- a/core/src/modules/account_manager/processor.rs +++ b/core/src/modules/account_manager/processor.rs @@ -13,6 +13,7 @@ impl<'context> Processor<'context> { pub fn render(&mut self, core : &mut Core, ui: &mut Ui, rc : &RenderContext) { let RenderContext { account, network_type, .. } = rc; + let network_type = network_type.clone(); ui.add_space(8.); match self.context.transaction_kind.as_ref().unwrap() { @@ -34,9 +35,12 @@ impl<'context> Processor<'context> { if request_estimate { - let priority_fees_sompi = if self.context.enable_priority_fees { - self.context.priority_fees_sompi - } else { 0 }; + // let priority_fees_sompi = if self.context.enable_priority_fees { + // let priority_fees_sompi = self.context.priority_fees_sompi; + // } else { 0 }; + // let fee_rate = self.context.priority_fee_rate + 1.0; + // println!("PROCESSOR: fee_rate: {}", fee_rate); + let address = match network_type { NetworkType::Testnet => Address::try_from("kaspatest:qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqhqrxplya").unwrap(), @@ -50,23 +54,51 @@ impl<'context> Processor<'context> { amount: self.context.send_amount_sompi, }; + let priority_fee_sompi = self.context.priority_fees_sompi; + let status = self.context.estimate.clone(); spawn(async move { - let request = AccountsEstimateRequest { + let base_request = AccountsEstimateRequest { + account_id, + destination: payment_output.clone().into(), + // priority_fee_sompi: Fees::SenderPays(priority_fees_sompi), + priority_fee_sompi: Fees::SenderPays(0), + fee_rate: Some(0.0), + payload: None, + }; + + let base_result = runtime().wallet().accounts_estimate_call(base_request).await; + + let base_mass = base_result.as_ref().map(|r| r.generator_summary.aggregate_mass).unwrap_or_default(); + + let fee_rate = if base_mass == 0 { + 1.0 + } else { + (priority_fee_sompi as f64 / base_mass as f64) + 1.0 + }; + + let actual_request = AccountsEstimateRequest { account_id, destination: payment_output.into(), - priority_fee_sompi: Fees::SenderPays(priority_fees_sompi), - fee_rate: None, + // priority_fee_sompi: Fees::SenderPays(priority_fees_sompi), + priority_fee_sompi: Fees::SenderPays(0), + fee_rate: Some(fee_rate), payload: None, }; - match runtime().wallet().accounts_estimate_call(request).await { - Ok(response) => { - *status.lock().unwrap() = EstimatorStatus::GeneratorSummary(response.generator_summary); + let actual_result = runtime().wallet().accounts_estimate_call(actual_request).await; + + // match runtime().wallet().accounts_estimate_call(request).await { + match (base_result, actual_result) { + (Ok(base_estimate_response), Ok(actual_estimate_response)) => { + *status.lock().unwrap() = EstimatorStatus::GeneratorSummary { base_estimate : base_estimate_response.generator_summary, actual_estimate : actual_estimate_response.generator_summary }; } - Err(error) => { + (_,Err(error)) => { *status.lock().unwrap() = EstimatorStatus::Error(error.to_string()); - } + } + (_,_) => { + *status.lock().unwrap() = EstimatorStatus::Error("Unknown error".to_string()); + } } runtime().egui_ctx().request_repaint(); @@ -86,9 +118,19 @@ impl<'context> Processor<'context> { unreachable!("expecting only one of destination address or transfer to account"); } - let priority_fees_sompi = if self.context.enable_priority_fees { - self.context.priority_fees_sompi - } else { 0 }; + let priority_fee_sompi = self.context.priority_fees_sompi; + + // let priority_fees_sompi = if self.context.enable_priority_fees { + // self.context.priority_fees_sompi + // } else { 0 }; + // let fee_rate = self.context.priority_fee_rate + 1.0; + + // let fee_rate = { + + // }; + + // --- + let wallet_secret = Secret::from(self.context.wallet_secret.clone()); let payment_secret = account.requires_bip39_passphrase(core).then_some(Secret::from(self.context.payment_secret.clone())); @@ -98,19 +140,24 @@ impl<'context> Processor<'context> { let address = Address::try_from(self.context.destination_address_string.as_str()).expect("invalid address"); let account_id = account.id(); + let send_amount_sompi = self.context.send_amount_sompi; let payment_output = PaymentOutput { address, - amount: self.context.send_amount_sompi, + amount: send_amount_sompi, }; spawn_with_result(&send_result, async move { + + let fee_rate = calculate_fee_rate(network_type.clone(), account_id, send_amount_sompi, priority_fee_sompi).await; + let request = AccountsSendRequest { account_id, destination: payment_output.into(), wallet_secret, payment_secret, - fee_rate: None, - priority_fee_sompi: Fees::SenderPays(priority_fees_sompi), + fee_rate: Some(fee_rate), + // priority_fee_sompi: Fees::SenderPays(priority_fees_sompi), + priority_fee_sompi: Fees::SenderPays(0), payload: None, }; @@ -127,13 +174,15 @@ impl<'context> Processor<'context> { let transfer_amount_sompi = self.context.send_amount_sompi; spawn_with_result(&send_result, async move { + let fee_rate = calculate_fee_rate(network_type.clone(), source_account_id, transfer_amount_sompi, priority_fee_sompi).await; + let request = AccountsTransferRequest { source_account_id, destination_account_id, wallet_secret, payment_secret, - fee_rate: None, - priority_fee_sompi: Some(Fees::SenderPays(priority_fees_sompi)), + fee_rate: Some(fee_rate), + priority_fee_sompi: Some(Fees::SenderPays(0)), transfer_amount_sompi, }; @@ -172,4 +221,39 @@ impl<'context> Processor<'context> { } } +} + +async fn calculate_fee_rate(network_type : NetworkType, account_id : AccountId, send_amount_sompi : u64, priority_fee_sompi : u64) -> f64 { + + let address = match network_type { + NetworkType::Testnet => Address::try_from("kaspatest:qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqhqrxplya").unwrap(), + NetworkType::Mainnet => Address::try_from("kaspa:qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqkx9awp4e").unwrap(), + _ => panic!("Unsupported network"), + }; + + // let account_id = account_id; + let payment_output = PaymentOutput { + address, + amount: send_amount_sompi, + }; + + + let base_request = AccountsEstimateRequest { + account_id, + destination: payment_output.clone().into(), + // priority_fee_sompi: Fees::SenderPays(priority_fees_sompi), + priority_fee_sompi: Fees::SenderPays(0), + fee_rate: Some(0.0), + payload: None, + }; + + let base_result = runtime().wallet().accounts_estimate_call(base_request).await; + + let base_mass = base_result.as_ref().map(|r| r.generator_summary.aggregate_mass).unwrap_or_default(); + + if base_mass == 0 { + 1.0 + } else { + (priority_fee_sompi as f64 / base_mass as f64) + 1.0 + } } \ No newline at end of file diff --git a/core/src/modules/testing.rs b/core/src/modules/testing.rs index 1ffdad1..dc5c4d7 100644 --- a/core/src/modules/testing.rs +++ b/core/src/modules/testing.rs @@ -108,12 +108,12 @@ impl ModuleT for Testing { let fee_selection = SelectionPanels::new( 100.0, - 130.0, - i18n("Miner Fee"), - |ui, value|{ - ui.label("1 in / 2 outputs, ~1.2 Kg"); - ui.label(format!("Fee Mode: {:?}", value)); - }) + 130.0) + // i18n("Miner Fee"), + // |ui, value|{ + // ui.label("1 in / 2 outputs, ~1.2 Kg"); + // ui.label(format!("Fee Mode: {:?}", value)); + // }) //.panel_min_height(300.) //.vertical(true) //.add(FeeMode::LowPriority, i18n("Low-priority"), i18n("3 hours or more")) @@ -167,11 +167,12 @@ impl ModuleT for Testing { let fee_selection = SelectionPanels::new( 100.0, 150.0, - i18n("Miner Fee"), - |ui, value|{ - ui.label("1 in / 2 outputs, ~1.2 Kg"); - ui.label(format!("Fee Mode: {:?}", value)); - }) + // i18n("Miner Fee"), + // |ui, value|{ + // ui.label("1 in / 2 outputs, ~1.2 Kg"); + // ui.label(format!("Fee Mode: {:?}", value)); + // } + ) //.panel_min_height(300.) //.vertical(true) //.add(FeeMode::LowPriority, i18n("Low-priority"), i18n("3 hours or more")) diff --git a/core/src/runtime/services/feerate_monitor.rs b/core/src/runtime/services/feerate_monitor.rs index d6ff345..56dcf23 100644 --- a/core/src/runtime/services/feerate_monitor.rs +++ b/core/src/runtime/services/feerate_monitor.rs @@ -56,12 +56,13 @@ impl FeerateMonitorService { async fn fetch(self: &Arc) -> Result<()> { if let Some(rpc_api) = self.rpc_api() { if let Ok(resp) = rpc_api.get_fee_estimate().await { - // println!("{}",resp.priority_bucket.feerate); // let feerate = Arc::new(resp); // self.feerate.lock().unwrap().replace(feerate.clone()); self.application_events - .send(Events::Feerate { feerate : Some(Arc::new(resp)) }) + .send(Events::Feerate { + feerate: Some(Arc::new(resp)), + }) .await .unwrap(); } @@ -76,7 +77,6 @@ impl FeerateMonitorService { .await .unwrap(); } - } #[async_trait] @@ -97,7 +97,6 @@ impl Service for FeerateMonitorService { Ok(()) } - async fn spawn(self: Arc) -> Result<()> { // let this = self.clone(); // let application_events_sender = self.application_events.sender.clone(); diff --git a/core/src/utils/average.rs b/core/src/utils/average.rs index c35bdef..f460b8e 100644 --- a/core/src/utils/average.rs +++ b/core/src/utils/average.rs @@ -5,9 +5,9 @@ const AVERAGE_ALPHA_HIGH: f64 = 0.8; const AVERAGE_ALPHA_LOW: f64 = 0.5; pub struct FeerateEstimate { - pub low : FeerateBucketAverage, - pub economic : FeerateBucketAverage, - pub priority : FeerateBucketAverage, + pub low: FeerateBucketAverage, + pub economic: FeerateBucketAverage, + pub priority: FeerateBucketAverage, } impl Default for FeerateEstimate { @@ -21,7 +21,7 @@ impl Default for FeerateEstimate { } impl FeerateEstimate { - pub fn new(estimate : &RpcFeeEstimate) -> Self { + pub fn new(estimate: &RpcFeeEstimate) -> Self { let mut feerate = Self { low: FeerateBucketAverage::default(), economic: FeerateBucketAverage::default(), @@ -32,25 +32,33 @@ impl FeerateEstimate { } pub fn insert(&mut self, estimate: &RpcFeeEstimate) { - self.low.insert(estimate.low_buckets.first().map(FeerateBucket::from).unwrap_or_default()); - self.economic.insert(estimate.normal_buckets.first().map(FeerateBucket::from).unwrap_or_default()); + self.low.insert( + estimate + .low_buckets + .first() + .map(FeerateBucket::from) + .unwrap_or_default(), + ); + self.economic.insert( + estimate + .normal_buckets + .first() + .map(FeerateBucket::from) + .unwrap_or_default(), + ); self.priority.insert(estimate.priority_bucket.into()); } - } #[derive(Debug, Clone, Copy)] pub struct FeerateBucket { - pub feerate : f64, - pub seconds : f64, + pub feerate: f64, + pub seconds: f64, } impl FeerateBucket { pub fn new(feerate: f64, seconds: f64) -> Self { - Self { - feerate, - seconds, - } + Self { feerate, seconds } } } @@ -107,14 +115,12 @@ impl From for FeerateBucket { pub type FeerateBucketAverage = FeerateBucketAverageN; #[derive(Default, Debug, Clone)] -pub struct FeerateBucketAverageN -{ +pub struct FeerateBucketAverageN { pub samples: VecDeque, pub average: FeerateBucket, } -impl FeerateBucketAverageN -{ +impl FeerateBucketAverageN { pub fn clear(&mut self) { self.samples.clear(); self.average = FeerateBucket::default(); @@ -127,12 +133,16 @@ impl FeerateBucketAverageN let delta = self.average; if value > self.value() { - let feerate = AVERAGE_ALPHA_HIGH * value.feerate + (1.0 - AVERAGE_ALPHA_HIGH) * delta.feerate; - let seconds = AVERAGE_ALPHA_HIGH * value.seconds + (1.0 - AVERAGE_ALPHA_HIGH) * delta.seconds; + let feerate = + AVERAGE_ALPHA_HIGH * value.feerate + (1.0 - AVERAGE_ALPHA_HIGH) * delta.feerate; + let seconds = + AVERAGE_ALPHA_HIGH * value.seconds + (1.0 - AVERAGE_ALPHA_HIGH) * delta.seconds; self.samples.push_back(FeerateBucket::new(feerate, seconds)); } else { - let feerate = AVERAGE_ALPHA_LOW * value.feerate + (1.0 - AVERAGE_ALPHA_LOW) * delta.feerate; - let seconds = AVERAGE_ALPHA_LOW * value.seconds + (1.0 - AVERAGE_ALPHA_LOW) * delta.seconds; + let feerate = + AVERAGE_ALPHA_LOW * value.feerate + (1.0 - AVERAGE_ALPHA_LOW) * delta.feerate; + let seconds = + AVERAGE_ALPHA_LOW * value.seconds + (1.0 - AVERAGE_ALPHA_LOW) * delta.seconds; self.samples.push_back(FeerateBucket::new(feerate, seconds)); } } @@ -141,15 +151,17 @@ impl FeerateBucketAverageN self.samples.pop_front(); } self.update(); - } pub fn update(&mut self) { let len = self.samples.len() as f64; - let sum = self.samples.iter().fold(FeerateBucket::default(), |a, b| a + *b); + let sum = self + .samples + .iter() + .fold(FeerateBucket::default(), |a, b| a + *b); self.average = FeerateBucket { - feerate : sum.feerate / len, - seconds : sum.seconds / len, + feerate: sum.feerate / len, + seconds: sum.seconds / len, }; } @@ -160,7 +172,6 @@ impl FeerateBucketAverageN FeerateBucket::default() } } - } #[cfg(test)] @@ -169,8 +180,6 @@ mod test { #[test] fn test_average() { - - let values = [ 248155.3514557079, 215263.2471686213, @@ -491,8 +500,8 @@ mod test { let mut average = FeerateBucketAverageN::<6>::default(); for value in values.iter() { - average.insert(FeerateBucket::new(*value,1.0)); + average.insert(FeerateBucket::new(*value, 1.0)); println!("{} -> {:?}", value, average.value()); } } -} \ No newline at end of file +} diff --git a/core/src/utils/format.rs b/core/src/utils/format.rs index 12b8b04..5bc324a 100644 --- a/core/src/utils/format.rs +++ b/core/src/utils/format.rs @@ -201,7 +201,6 @@ pub fn precision_from_symbol(symbol: &str) -> usize { } } - // /// Format supplied value as a float with 2 decimal places. // fn format_as_float(f: f64, short: bool) -> String { // if short {