From 5318750be90ba1efb9b33f0248f537408e86d3a6 Mon Sep 17 00:00:00 2001 From: Anton Yemelyanov Date: Wed, 20 Dec 2023 17:38:24 +0200 Subject: [PATCH] rusty kaspa PR compatibility; market rendering --- Cargo.toml | 17 +++--- app/index.html | 9 --- core/Cargo.toml | 1 + core/src/app.rs | 2 +- core/src/core.rs | 20 ++++--- core/src/egui/panel.rs | 1 - core/src/egui/theme/color.rs | 9 +++ core/src/imports.rs | 18 +++--- core/src/market.rs | 24 ++++++-- core/src/modules/account_create.rs | 28 +++------ core/src/modules/account_manager/balance.rs | 18 ++++-- core/src/modules/account_manager/details.rs | 59 ++++++++----------- core/src/modules/account_manager/menus.rs | 2 +- core/src/modules/account_manager/mod.rs | 2 +- core/src/modules/account_manager/processor.rs | 5 +- core/src/modules/account_manager/secret.rs | 6 +- core/src/modules/changelog.rs | 17 +++++- core/src/modules/export.rs | 2 +- core/src/modules/metrics.rs | 6 +- core/src/modules/overview.rs | 47 +++++++++++++-- core/src/modules/private_key_create.rs | 14 ++--- core/src/modules/scanner.rs | 19 +++--- core/src/modules/wallet_create.rs | 7 ++- core/src/modules/wallet_open.rs | 1 - core/src/modules/welcome.rs | 4 +- core/src/primitives/account.rs | 54 +++++++++++------ .../{descriptors.rs => descriptor.rs} | 31 ++++++---- core/src/primitives/mod.rs | 4 +- core/src/runtime/services/kaspa/mod.rs | 8 +-- .../services/market_monitor/coingecko.rs | 23 ++++---- .../services/market_monitor/coinmarketcap.rs | 19 +++--- core/src/settings.rs | 4 +- core/src/utils/format.rs | 37 ++++++++++++ extensions/chrome/src/server.rs | 9 +-- resources/i18n/i18n.json | 11 +++- 35 files changed, 336 insertions(+), 202 deletions(-) rename core/src/primitives/{descriptors.rs => descriptor.rs} (79%) diff --git a/Cargo.toml b/Cargo.toml index 7d86a1d..6d35e3e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -94,13 +94,14 @@ kaspad = { path = "../rusty-kaspa/kaspad" } # |_|_| |__| | \ | \_ | |___ |__| |_|_| | \ ___] # _________________________________________________________ -# workflow-chrome = "0.9.0" -# workflow-dom = "0.9.0" -# workflow-http = "0.9.0" -# workflow-log = "0.9.0" -# workflow-store = "0.9.0" -# workflow-core = { version = "0.9.0", features = ["no-unsafe-eval"] } -# workflow-i18n = { version = "0.9.0", features = ["preserve-order"] } +# workflow-chrome = "0.10.1" +# workflow-dom = "0.10.1" +# workflow-http = "0.10.1" +# workflow-log = "0.10.1" +# workflow-store = "0.10.1" +# workflow-core = { version = "0.10.1", features = ["no-unsafe-eval"] } +# workflow-i18n = { version = "0.10.1", features = ["preserve-order"] } + workflow-chrome = { path = "../workflow-rs/chrome" } workflow-dom = { path = "../workflow-rs/dom" } workflow-http = { path = "../workflow-rs/http" } @@ -108,6 +109,7 @@ workflow-log = { path = "../workflow-rs/log" } workflow-store = { path = "../workflow-rs/store" } workflow-core = { path = "../workflow-rs/core", features = ["no-unsafe-eval"] } workflow-i18n = { path = "../workflow-rs/i18n", features = ["preserve-order"] } + # workflow-chrome = { git = "https://github.com/workflow-rs/workflow-rs.git", branch = "master" } # workflow-dom = { git = "https://github.com/workflow-rs/workflow-rs.git", branch = "master" } # workflow-http = { git = "https://github.com/workflow-rs/workflow-rs.git", branch = "master" } @@ -126,6 +128,7 @@ cfg-if = "1.0.0" chrome-sys = "0.1.0" # chrome-sys = {path = "../chrome-sys"} chrono = "0.4.31" +convert_case = "0.6.0" clap = { version = "4.4.7", features = ["derive", "string", "cargo"] } ctrlc = { version = "3.2", features = ["termination"] } derivative = "2.2.0" diff --git a/app/index.html b/app/index.html index c0cdab7..afa71b8 100644 --- a/app/index.html +++ b/app/index.html @@ -14,15 +14,6 @@ - - diff --git a/core/Cargo.toml b/core/Cargo.toml index b2aa927..1916756 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -39,6 +39,7 @@ workflow-i18n.workspace = true ahash.workspace = true async-trait.workspace = true cfg-if.workspace = true +convert_case.workspace = true derivative.workspace = true downcast-rs.workspace = true downcast.workspace = true diff --git a/core/src/app.rs b/core/src/app.rs index 677b3f8..0c8d549 100644 --- a/core/src/app.rs +++ b/core/src/app.rs @@ -3,7 +3,6 @@ use cfg_if::cfg_if; use kaspa_ng_core::runtime; use kaspa_ng_core::settings::Settings; use kaspa_wallet_core::api::WalletApi; -use std::iter::once; use std::sync::Arc; use workflow_i18n::*; use workflow_log::*; @@ -60,6 +59,7 @@ cfg_if! { #[allow(unused)] use clap::{arg, command, Arg, Command}; use std::env::{args,var}; + use std::iter::once; if args().any(|arg| arg == "--daemon") || var("KASPA_NG_DAEMON").is_ok() { let args = once("kaspad".to_string()).chain(args().skip(1).filter(|arg| arg != "--daemon"));//.collect::>(); diff --git a/core/src/core.rs b/core/src/core.rs index 8070724..66c2d0c 100644 --- a/core/src/core.rs +++ b/core/src/core.rs @@ -46,7 +46,7 @@ pub struct Core { pub release: Option, pub device: Device, - pub market: Market, + pub market: Option, pub debug: bool, } @@ -179,7 +179,7 @@ impl Core { release: None, device: Device::default(), - market: Market::default(), + market: None, debug: false, }; @@ -582,12 +582,18 @@ impl Core { _frame: &mut eframe::Frame, ) -> Result<()> { match event { - Events::Market(update) => match update { - MarketUpdate::Price(price) => { - self.market.price.replace(price); + Events::Market(update) => { + if self.market.is_none() { + self.market = Some(Market::default()); } - MarketUpdate::Ohlc(ohlc) => { - self.market.ohlc.replace(ohlc); + + match update { + MarketUpdate::Price(price) => { + self.market.as_mut().unwrap().price.replace(price); + } + MarketUpdate::Ohlc(ohlc) => { + self.market.as_mut().unwrap().ohlc.replace(ohlc); + } } }, Events::ThemeChange => { diff --git a/core/src/egui/panel.rs b/core/src/egui/panel.rs index 38ade7d..e3e0355 100644 --- a/core/src/egui/panel.rs +++ b/core/src/egui/panel.rs @@ -127,7 +127,6 @@ impl<'panel, Context> Panel<'panel, Context> { ui.horizontal(|ui| { match self.back { Some(back) if self.back_enabled => { - println!("Back is enabled"); let icon = CompositeIcon::new(egui_phosphor::bold::ARROW_BEND_UP_LEFT) .icon_size(icon_size.inner.x) .padding(Some(icon_padding)); diff --git a/core/src/egui/theme/color.rs b/core/src/egui/theme/color.rs index fcfff14..a592b77 100644 --- a/core/src/egui/theme/color.rs +++ b/core/src/egui/theme/color.rs @@ -19,6 +19,9 @@ pub struct ThemeColor { pub icon_color_default: Color32, pub ack_color: Color32, pub nack_color: Color32, + pub market_default_color: Color32, + pub market_up_color: Color32, + pub market_down_color: Color32, pub raised_text_color: Color32, pub raised_text_shadow: Color32, @@ -86,6 +89,9 @@ impl ThemeColor { icon_color_default: Color32::from_rgb(240, 240, 240), ack_color: Color32::from_rgb(100, 200, 100), nack_color: Color32::from_rgb(200, 100, 100), + market_default_color: Color32::from_rgb(240,240,240), + market_up_color: Color32::from_rgb(136, 255, 136), + market_down_color: Color32::from_rgb(255, 136, 136), raised_text_color: Color32::from_rgb(255, 255, 255), raised_text_shadow: Color32::from_rgb(0, 0, 0), @@ -150,6 +156,9 @@ impl ThemeColor { icon_color_default: Color32::from_rgb(32, 32, 32), ack_color: Color32::from_rgb(100, 200, 100), nack_color: Color32::from_rgb(200, 100, 100), + market_default_color: Color32::from_rgb(20,20,20), + market_up_color: Color32::from_rgb(41, 77, 41), + market_down_color: Color32::from_rgb(77, 41, 41), raised_text_color: Color32::from_rgb(0, 0, 0), raised_text_shadow: Color32::from_rgb(255, 255, 255), diff --git a/core/src/imports.rs b/core/src/imports.rs index 773bf48..2519abd 100644 --- a/core/src/imports.rs +++ b/core/src/imports.rs @@ -6,19 +6,14 @@ pub use kaspa_consensus_core::Hash as KaspaHash; pub use kaspa_rpc_core::api::rpc::RpcApi; pub use kaspa_utils::hex::{FromHex, ToHex}; pub use kaspa_utils::{hashmap::GroupExtension, networking::ContextualNetAddress}; -pub use kaspa_wallet_core::api; -pub use kaspa_wallet_core::api::WalletApi; -pub use kaspa_wallet_core::events::SyncState; -pub use kaspa_wallet_core::prelude::Address; -pub use kaspa_wallet_core::rpc::DynRpcApi; -pub use kaspa_wallet_core::runtime::{Account as KaspaAccount, Wallet as KaspaWallet}; -pub use kaspa_wallet_core::runtime::{AccountDescriptor, AccountId, Balance}; -pub use kaspa_wallet_core::secret::Secret; -pub use kaspa_wallet_core::storage::{ - IdT, PrvKeyDataId, TransactionId, TransactionRecord, WalletDescriptor, +pub use kaspa_wallet_core::prelude::{ + Account as CoreAccount, AccountCreateArgs, AccountCreateArgsBip32, AccountDescriptor, + AccountId, AccountKind, Address, Balance, DynRpcApi, IdT, KaspaRpcClient, Language, Mnemonic, + PrvKeyDataArgs, PrvKeyDataCreateArgs, PrvKeyDataId, PrvKeyDataInfo, Secret, SyncState, + TransactionId, TransactionRecord, Wallet as CoreWallet, WalletApi, WalletCreateArgs, + WalletDescriptor, WordCount, WrpcEncoding, }; pub use kaspa_wallet_core::utils::*; -pub use kaspa_wrpc_client::{KaspaRpcClient, WrpcEncoding}; pub use async_trait::async_trait; pub use futures::{pin_mut, select, FutureExt, StreamExt}; @@ -69,6 +64,7 @@ pub use crate::events::{ApplicationEventsChannel, Events}; pub use crate::menu::Menu; pub use crate::modules; pub use crate::modules::{Module, ModuleCaps, ModuleStyle, ModuleT}; +pub use crate::market::MarketData; pub use crate::network::Network; pub use crate::notifications::{UserNotification, UserNotifyKind}; pub use crate::primitives::{ diff --git a/core/src/market.rs b/core/src/market.rs index e2ea251..0abaa3e 100644 --- a/core/src/market.rs +++ b/core/src/market.rs @@ -1,11 +1,25 @@ use crate::imports::*; -#[derive(Default, Debug)] +#[derive(Debug)] pub struct MarketData { - pub price: Option, - pub market_cap: Option, - pub volume: Option, - pub change: Option, + pub price: f64, + pub market_cap: f64, + pub volume: f64, + pub change: f64, + pub precision : usize, +} + +impl MarketData { + pub fn new(symbol : &str) -> Self { + let precision = precision_from_symbol(symbol); + Self { + price : 0.0, + market_cap : 0.0, + volume : 0.0, + change : 0.0, + precision, + } + } } pub type MarketDataMap = AHashMap; diff --git a/core/src/modules/account_create.rs b/core/src/modules/account_create.rs index c49b48d..b66a285 100644 --- a/core/src/modules/account_create.rs +++ b/core/src/modules/account_create.rs @@ -1,11 +1,12 @@ #![allow(unused_imports)] use crate::imports::*; -use kaspa_bip32::Mnemonic; -use kaspa_wallet_core::api::AccountsCreateRequest; -use kaspa_wallet_core::runtime::wallet::AccountCreateArgsBip32; -use kaspa_wallet_core::runtime::{AccountCreateArgs, PrvKeyDataCreateArgs, WalletCreateArgs, PrvKeyDataArgs}; -use kaspa_wallet_core::storage::{AccountKind, PrvKeyDataInfo}; +// use kaspa_bip32::Mnemonic; +// use kaspa_wallet_core::api::AccountsCreateRequest; +// use kaspa_wallet_core::wallet::AccountCreateArgsBip32; +// use kaspa_wallet_core::wallet::args::{AccountCreateArgs, PrvKeyDataCreateArgs, WalletCreateArgs, PrvKeyDataArgs}; +// use kaspa_wallet_core::storage::{AccountKind, PrvKeyDataInfo}; +// use kaspa_wallet_core::prelude::{AccountKind, PrvKeyDataInfo}; #[derive(Clone)] @@ -32,7 +33,7 @@ pub enum State { AccountError(Arc), PresentMnemonic(Arc), ConfirmMnemonic(Arc), - Finish(Arc), + Finish(Arc), } #[derive(Clone)] @@ -42,28 +43,15 @@ pub enum CreationData { }, Bip32 { mnemonic: Option, - // account: Arc, }, Keypair { private_key: Secret, - // account: Arc, }, MultiSig { mnemonics: Vec, - // account: Arc, }, } -// impl CreationData { -// pub fn account(&self) -> Arc { -// match self { -// Self::Bip32 { account, .. } => account.clone(), -// Self::Keypair { account, .. } => account.clone(), -// Self::MultiSig { account, .. } => account.clone(), -// } -// } -// } - #[derive(Default, Debug, Clone, Copy, Eq, PartialEq)] enum Focus { #[default] @@ -380,7 +368,7 @@ impl ModuleT for AccountCreate { if let Some(result) = account_create_result.take() { match result { - Ok(descriptor) => { + Ok(_descriptor) => { println!("Account created successfully"); // let account = Account::from(descriptor); // core.account_collection.as_mut().expect("account collection").push_unchecked(account.clone()); diff --git a/core/src/modules/account_manager/balance.rs b/core/src/modules/account_manager/balance.rs index 842e59b..b14bf8a 100644 --- a/core/src/modules/account_manager/balance.rs +++ b/core/src/modules/account_manager/balance.rs @@ -33,12 +33,18 @@ impl<'context> BalancePane<'context> { ); } - if let Some(price_list) = core.market.price.as_ref() { - for (symbol, data) in price_list.iter() { - if let Some(price) = data.price { - let text = format!("{:.8} {}", sompi_to_kaspa(balance.mature) * price, symbol.to_uppercase()); - ui.label(RichText::new(text).font(FontId::proportional(16.))); - } + if let Some(market) = core.market.as_ref() { + if let Some(price_list) = market.price.as_ref() { + let mut symbols = price_list.keys().collect::>(); + symbols.sort(); + symbols.into_iter().for_each(|symbol| { + if let Some(data) = price_list.get(symbol) { + let symbol = symbol.to_uppercase(); + let MarketData { price, change : _, .. } = data; + let text = format!("{:.8} {}", sompi_to_kaspa(balance.mature) * (*price), symbol.as_str()); + ui.label(RichText::new(text).font(FontId::proportional(16.))); + } + }); } } diff --git a/core/src/modules/account_manager/details.rs b/core/src/modules/account_manager/details.rs index 0a5ee9a..07cd062 100644 --- a/core/src/modules/account_manager/details.rs +++ b/core/src/modules/account_manager/details.rs @@ -16,40 +16,33 @@ impl Details { let descriptor = account.descriptor(); - match &*descriptor { - AccountDescriptor::Bip32(descriptor) => { - descriptor.render(ui); - ui.add_space(8.); - - let mut address_kind : Option = None; - - ui.horizontal(|ui|{ - if ui.medium_button("Generate New Receive Address").clicked() { - address_kind = Some(NewAddressKind::Receive); - } - if ui.medium_button("Generate New Change Address").clicked() { - address_kind = Some(NewAddressKind::Change); - } - }); - - if let Some(address_kind) = address_kind { - let account_id = account.id(); - spawn(async move { - runtime() - .wallet() - .accounts_create_new_address(account_id, address_kind) - .await - .map_err(|err|Error::custom(format!("Failed to create new address\n{err}")))?; - - runtime().request_repaint(); - - Ok(()) - }); - } - }, - _ => { - ui.label("Unknown descriptor type"); + descriptor.render(ui); + ui.add_space(8.); + + let mut address_kind : Option = None; + + ui.horizontal(|ui|{ + if ui.medium_button("Generate New Receive Address").clicked() { + address_kind = Some(NewAddressKind::Receive); } + if ui.medium_button("Generate New Change Address").clicked() { + address_kind = Some(NewAddressKind::Change); + } + }); + + if let Some(address_kind) = address_kind { + let account_id = account.id(); + spawn(async move { + runtime() + .wallet() + .accounts_create_new_address(account_id, address_kind) + .await + .map_err(|err|Error::custom(format!("Failed to create new address\n{err}")))?; + + runtime().request_repaint(); + + Ok(()) + }); } }); } diff --git a/core/src/modules/account_manager/menus.rs b/core/src/modules/account_manager/menus.rs index 7582be7..a1244bb 100644 --- a/core/src/modules/account_manager/menus.rs +++ b/core/src/modules/account_manager/menus.rs @@ -120,7 +120,7 @@ impl AccountMenu { .show(ui, |ui| { account_list.retain(|selectable_account|{ - if selectable_account.descriptor().prv_key_data_id() == Some(&prv_key_data_info.id) { + if selectable_account.descriptor().prv_key_data_ids().contains(&prv_key_data_info.id) { if ui.account_selector_button(selectable_account, network_type, account.id() == selectable_account.id(), core.balance_padding()).clicked() { account_manager.request_estimate(); diff --git a/core/src/modules/account_manager/mod.rs b/core/src/modules/account_manager/mod.rs index 20f32ee..80c28bd 100644 --- a/core/src/modules/account_manager/mod.rs +++ b/core/src/modules/account_manager/mod.rs @@ -4,7 +4,7 @@ use std::borrow::Cow; use egui_phosphor::thin::{CLOUD_ARROW_DOWN, CLOUD_SLASH}; use kaspa_wallet_core::tx::{GeneratorSummary, PaymentOutput, Fees}; use kaspa_wallet_core::api::*; -use crate::primitives::descriptors::*; +use crate::primitives::descriptor::*; mod address; mod balance; diff --git a/core/src/modules/account_manager/processor.rs b/core/src/modules/account_manager/processor.rs index aa3f3ad..dd5f2a8 100644 --- a/core/src/modules/account_manager/processor.rs +++ b/core/src/modules/account_manager/processor.rs @@ -53,7 +53,6 @@ impl<'context> Processor<'context> { let status = self.context.estimate.clone(); spawn(async move { let request = AccountsEstimateRequest { - task_id: None, account_id, destination: payment_output.into(), priority_fee_sompi: Fees::SenderPaysAll(priority_fees_sompi), @@ -78,7 +77,7 @@ impl<'context> Processor<'context> { Action::Sending => { - let proceed_with_send = WalletSecret::new(self.context).render(ui, rc); + let proceed_with_send = WalletSecret::new(self.context).render(ui, core, rc); if proceed_with_send { @@ -91,7 +90,7 @@ impl<'context> Processor<'context> { } else { 0 }; let wallet_secret = Secret::try_from(self.context.wallet_secret.clone()).expect("expecting wallet secret"); - let payment_secret = account.requires_bip39_passphrase().then_some(Secret::try_from(self.context.payment_secret.clone()).expect("expecting payment secret")); + let payment_secret = account.requires_bip39_passphrase(core).then_some(Secret::try_from(self.context.payment_secret.clone()).expect("expecting payment secret")); match self.context.transaction_kind.unwrap() { TransactionKind::Send => { diff --git a/core/src/modules/account_manager/secret.rs b/core/src/modules/account_manager/secret.rs index 76dfbaf..48cbe12 100644 --- a/core/src/modules/account_manager/secret.rs +++ b/core/src/modules/account_manager/secret.rs @@ -11,12 +11,12 @@ impl<'context> WalletSecret<'context> { Self { context } } - pub fn render(&mut self, ui : &mut Ui, rc : &RenderContext<'_>) -> bool { + pub fn render(&mut self, ui : &mut Ui, core: &mut Core, rc : &RenderContext<'_>) -> bool { use egui_phosphor::light::{CHECK, X}; let RenderContext { account, .. } = rc; - let requires_payment_passphrase = account.requires_bip39_passphrase(); + let requires_payment_passphrase = account.requires_bip39_passphrase(core); let mut proceed_with_send = false; let response = TextEditor::new( @@ -34,7 +34,7 @@ impl<'context> WalletSecret<'context> { .build(ui); if response.text_edit_submit(ui) { - if account.requires_bip39_passphrase() { + if requires_payment_passphrase { self.context.focus.next(Focus::PaymentSecret); } else if !self.context.wallet_secret.is_empty() { proceed_with_send = true; diff --git a/core/src/modules/changelog.rs b/core/src/modules/changelog.rs index 0a52391..3e697a4 100644 --- a/core/src/modules/changelog.rs +++ b/core/src/modules/changelog.rs @@ -1,5 +1,6 @@ use crate::imports::*; use crate::egui::easy_mark; +use egui_phosphor::light::X; pub struct Changelog { #[allow(dead_code)] runtime: Runtime, @@ -24,7 +25,7 @@ impl ModuleT for Changelog { fn render( &mut self, - _core: &mut Core, + core: &mut Core, _ctx: &egui::Context, _frame: &mut eframe::Frame, ui: &mut egui::Ui, @@ -37,5 +38,19 @@ impl ModuleT for Changelog { .show(ui, |ui| { easy_mark(ui, self.changelog.as_str()); }); + + let close = Label::new(RichText::new(format!(" {X} ")).size(20.)).sense(Sense::click()); + + let screen_rect = ui.ctx().screen_rect(); + let close_rect = Rect::from_min_size( + pos2(screen_rect.max.x - 48.0, screen_rect.min.y + 32.0), + vec2(42.0, 42.0), + ); + + if ui.put(close_rect, close) + .clicked() { + core.select::(); + } + } } diff --git a/core/src/modules/export.rs b/core/src/modules/export.rs index c3ddc81..b96af39 100644 --- a/core/src/modules/export.rs +++ b/core/src/modules/export.rs @@ -315,7 +315,7 @@ impl ModuleT for Export { // wallet.export_mnemonic(prv_key_data_id.unwrap(), wallet_secret).await?; if let Some(prv_key_data_id) = prv_key_data_id { - let result = wallet.prv_key_data_get(prv_key_data_id, wallet_secret).await?; + let _result = wallet.prv_key_data_get(prv_key_data_id, wallet_secret).await?; // let prv_key_data = wallet.prv_key_data(prv_key_data_id).await?; // let mnemonic = prv_key_data.mnemonic().await?; // println!("mnemonic: {}", mnemonic); diff --git a/core/src/modules/metrics.rs b/core/src/modules/metrics.rs index a32d526..59fdce0 100644 --- a/core/src/modules/metrics.rs +++ b/core/src/modules/metrics.rs @@ -120,12 +120,12 @@ impl ModuleT for Metrics { for metric in group.metrics() { ui.space(); - let mut state = !core.settings.user_interface.metrics.disabled.contains(&metric); + let mut state = !core.settings.user_interface.metrics.disabled.contains(metric); if ui.checkbox(&mut state, i18n(metric.title().0)).changed() { if state { - core.settings.user_interface.metrics.disabled.remove(&metric); + core.settings.user_interface.metrics.disabled.remove(metric); } else { - core.settings.user_interface.metrics.disabled.insert(metric); + core.settings.user_interface.metrics.disabled.insert(*metric); } // core.store_settings(); store_settings = true; diff --git a/core/src/modules/overview.rs b/core/src/modules/overview.rs index ddb722a..bea8eb4 100644 --- a/core/src/modules/overview.rs +++ b/core/src/modules/overview.rs @@ -110,11 +110,46 @@ impl Overview { .default_open(true) .show(ui, |ui| { - if let Some(price_list) = core.market.price.as_ref() { - for (symbol, data) in price_list.iter() { - if let Some(price) = data.price { - ui.label(RichText::new(format!("{} {} ",price,symbol.to_uppercase())));//.font(FontId::proportional(14.))); - } + if let Some(market) = core.market.as_ref() { + if let Some(price_list) = market.price.as_ref() { + let mut symbols = price_list.keys().collect::>(); + symbols.sort(); + symbols.into_iter().for_each(|symbol| { + if let Some(data) = price_list.get(symbol) { + let symbol = symbol.to_uppercase(); + CollapsingHeader::new(symbol.as_str()) + .default_open(true) + .show(ui, |ui| { + Grid::new("market_price_info_grid") + .num_columns(2) + .spacing([16.0,4.0]) + .show(ui, |ui| { + let MarketData { price, volume, change, market_cap , precision } = *data; + ui.label(i18n("Price")); + // ui.colored_label(theme_color().market_default_color, RichText::new(format_price_with_symbol(price, precision, symbol.as_str())).font(FontId::monospace(14.))); // + ui.colored_label(theme_color().market_default_color, RichText::new(format_price_with_symbol(price, precision, symbol.as_str()))); // + ui.end_row(); + + ui.label(i18n("Change")); + if change > 0. { + ui.colored_label(theme_color().market_up_color, RichText::new(format!("+{:.2}% ",change))); + } else { + ui.colored_label(theme_color().market_down_color, RichText::new(format!("{:.2}% ",change))); + }; + ui.end_row(); + + ui.label(i18n("Volume")); + ui.colored_label(theme_color().market_default_color, RichText::new(format!("{} {}",volume.trunc().separated_string(),symbol.to_uppercase()))); + ui.end_row(); + + ui.label(i18n("Market Cap")); + ui.colored_label(theme_color().market_default_color, RichText::new(format!("{} {}",market_cap.trunc().separated_string(),symbol.to_uppercase()))); + ui.end_row(); + }); + + }); + } + }) } } @@ -143,7 +178,7 @@ impl Overview { ); ui.hyperlink_to_tab( format!("• {}",i18n("Rust Wallet SDK")), - "https://docs.rs/kaspa-wallet-core/0.0.4/kaspa_wallet_core/", + "https://docs.rs/kaspa-wallet-core/", ); ui.hyperlink_to_tab( format!("• {}",i18n("Kaspa Discord")), diff --git a/core/src/modules/private_key_create.rs b/core/src/modules/private_key_create.rs index b0b966c..70e5b1b 100644 --- a/core/src/modules/private_key_create.rs +++ b/core/src/modules/private_key_create.rs @@ -2,8 +2,8 @@ use crate::imports::*; use kaspa_bip32::Mnemonic; -use kaspa_wallet_core::runtime::{PrvKeyDataCreateArgs, WalletCreateArgs}; -use kaspa_wallet_core::storage::AccountKind; +// use kaspa_wallet_core::runtime::{PrvKeyDataCreateArgs, WalletCreateArgs}; +// use kaspa_wallet_core::storage::AccountKind; pub enum MnemonicSize { Words12, @@ -45,26 +45,26 @@ pub enum State { AccountError(Arc), PresentMnemonic(Arc), ConfirmMnemonic(Arc), - Finish(Arc), + Finish(Arc), } pub enum CreationData { Bip32 { mnemonic: Option, - account: Arc, + account: Arc, }, Keypair { private_key: Secret, - account: Arc, + account: Arc, }, MultiSig { mnemonics: Vec, - account: Arc, + account: Arc, }, } impl CreationData { - pub fn account(&self) -> Arc { + pub fn account(&self) -> Arc { match self { Self::Bip32 { account, .. } => account.clone(), Self::Keypair { account, .. } => account.clone(), diff --git a/core/src/modules/scanner.rs b/core/src/modules/scanner.rs index f36c99d..6f9a86d 100644 --- a/core/src/modules/scanner.rs +++ b/core/src/modules/scanner.rs @@ -1,6 +1,6 @@ use crate::imports::*; use egui_phosphor::thin::*; -use kaspa_wallet_core::runtime::Wallet; +use kaspa_wallet_core::{wallet::Wallet, account::{BIP32_ACCOUNT_KIND, LEGACY_ACCOUNT_KIND}}; #[derive(Clone)] pub enum State { @@ -166,16 +166,17 @@ impl ModuleT for Scanner { for selectable_account in account_collection.list() { - let account_kind = selectable_account.account_kind(); - if !account_kind.is_derivation_capable() { - continue; + match selectable_account.account_kind().as_ref() { + BIP32_ACCOUNT_KIND | LEGACY_ACCOUNT_KIND => { + if ui.account_selector_button(selectable_account, &network_type, false, core.balance_padding()).clicked() { + this.state = State::Settings { + account: selectable_account.clone(), + }; + } + } + _ => {}, } - if ui.account_selector_button(selectable_account, &network_type, false, core.balance_padding()).clicked() { - this.state = State::Settings { - account: selectable_account.clone(), - }; - } } } diff --git a/core/src/modules/wallet_create.rs b/core/src/modules/wallet_create.rs index cfd3546..f38d287 100644 --- a/core/src/modules/wallet_create.rs +++ b/core/src/modules/wallet_create.rs @@ -1,6 +1,6 @@ use crate::imports::*; -use kaspa_wallet_core::runtime::{AccountCreateArgs, PrvKeyDataCreateArgs, WalletCreateArgs}; +use kaspa_wallet_core::{wallet::{AccountCreateArgs, PrvKeyDataCreateArgs, WalletCreateArgs}, encryption::EncryptionKind}; use slug::slugify; use kaspa_bip32::{WordCount, Mnemonic, Language}; use crate::utils::{secret_score, secret_score_to_text}; @@ -36,9 +36,9 @@ pub enum State { Finish, } -enum PrivateKeyImportKind { +// enum PrivateKeyImportKind { -} +// } #[derive(Clone, Default)] @@ -733,6 +733,7 @@ impl ModuleT for WalletCreate { let wallet_args = WalletCreateArgs::new( args.wallet_name.is_not_empty().then_some(args.wallet_name), args.wallet_filename.is_not_empty().then_some(args.wallet_filename), + EncryptionKind::XChaCha20Poly1305, args.enable_phishing_hint.then_some(args.phishing_hint.into()), // wallet_secret.clone(), false diff --git a/core/src/modules/wallet_open.rs b/core/src/modules/wallet_open.rs index e46b87b..970c388 100644 --- a/core/src/modules/wallet_open.rs +++ b/core/src/modules/wallet_open.rs @@ -59,7 +59,6 @@ impl ModuleT for WalletOpen { State::Select => { let has_stack = core.has_stack(); - println!("stack: {:?}", core.stack); let core = Rc::new(RefCell::new(core)); Panel::new(self) diff --git a/core/src/modules/welcome.rs b/core/src/modules/welcome.rs index a46cc1c..d631554 100644 --- a/core/src/modules/welcome.rs +++ b/core/src/modules/welcome.rs @@ -138,12 +138,12 @@ impl ModuleT for Welcome { if ui.medium_button(format!("{} {}", egui_phosphor::light::CHECK, "Apply")).clicked() { let mut settings = self.settings.clone(); settings.initialized = true; - settings.version.clear(); // triggers changelog + // settings.version.clear(); // triggers changelog settings.store_sync().expect("Unable to store settings"); self.runtime.kaspa_service().update_services(&self.settings.node); core.settings = settings.clone(); core.get_mut::().load(settings); - core.select::(); + core.select::(); } }); ui.separator(); diff --git a/core/src/primitives/account.rs b/core/src/primitives/account.rs index 9c966c3..2ba5b5f 100644 --- a/core/src/primitives/account.rs +++ b/core/src/primitives/account.rs @@ -1,4 +1,8 @@ -use kaspa_wallet_core::storage::AccountKind; +// use kaspa_wallet_core::storage::AccountKind; + +use kaspa_wallet_core::account::{ + BIP32_ACCOUNT_KIND, KEYPAIR_ACCOUNT_KIND, LEGACY_ACCOUNT_KIND, MULTISIG_ACCOUNT_KIND, +}; use crate::imports::*; @@ -16,7 +20,7 @@ impl AccountContext { let uri = format!("bytes://{}-{}.svg", address_string, theme_color().name); Some(Arc::new(Self { qr: qr.as_bytes().to_vec().into(), - receive_address, + receive_address: receive_address.clone(), uri, })) } else { @@ -40,6 +44,7 @@ impl AccountContext { struct Inner { // runtime: Arc, id: AccountId, + account_kind: AccountKind, balance: Mutex>, descriptor: Mutex, context: Mutex>>, @@ -47,26 +52,27 @@ struct Inner { total_transaction_count: AtomicU64, is_loading: AtomicBool, // for bip32 accounts only - bip39_passphrase: bool, + // bip39_passphrase: bool, } impl Inner { fn new(descriptor: AccountDescriptor) -> Self { - let bip39_passphrase = match &descriptor { - AccountDescriptor::Bip32(bip32) => bip32.bip39_passphrase, - _ => false, - }; + // let bip39_passphrase = match &descriptor { + // AccountDescriptor::Bip32(bip32) => bip32.bip39_passphrase, + // _ => false, + // }; let context = AccountContext::new(&descriptor); Self { id: *descriptor.account_id(), + account_kind: *descriptor.account_kind(), balance: Mutex::new(None), descriptor: Mutex::new(descriptor), context: Mutex::new(context), transactions: Mutex::new(TransactionCollection::default()), total_transaction_count: AtomicU64::new(0), is_loading: AtomicBool::new(true), - bip39_passphrase, + // bip39_passphrase, } } } @@ -111,12 +117,25 @@ impl Account { self.descriptor().name_or_id() } - pub fn requires_bip39_passphrase(&self) -> bool { - self.inner.bip39_passphrase + pub fn requires_bip39_passphrase(&self, core: &Core) -> bool { + let descriptor = self.descriptor(); + let prv_key_data_ids = descriptor.prv_key_data_ids(); + core.prv_key_data_map() + .as_ref() + .map(|prv_key_data_map| { + prv_key_data_ids.into_iter().any(|prv_key_data_id| { + prv_key_data_map + .get(&prv_key_data_id) + .map(|prv_key_data_info| prv_key_data_info.requires_bip39_passphrase()) + .unwrap_or(false) + }) + }) + .unwrap_or(false) + // self.inner.bip39_passphrase } - pub fn account_kind(&self) -> AccountKind { - self.descriptor().account_kind() + pub fn account_kind(&self) -> &AccountKind { + &self.inner.account_kind } pub fn balance(&self) -> Option { @@ -196,12 +215,11 @@ pub trait DescribeAccount { impl DescribeAccount for AccountKind { fn describe(&self) -> (&'static str, &'static str) { - match self { - AccountKind::Legacy => ("Legacy Account", "KDX, PWA (kaspanet.io)"), - AccountKind::Bip32 => ("Kaspa Core BIP32", "kaspawallet, kaspium"), - AccountKind::MultiSig => ("Multi-Signature", ""), - AccountKind::Keypair => ("Keypair", "secp256k1"), - AccountKind::Hardware => ("Hardware", ""), + match self.as_ref() { + LEGACY_ACCOUNT_KIND => ("Legacy Account", "KDX, PWA (kaspanet.io)"), + BIP32_ACCOUNT_KIND => ("Kaspa Core BIP32", "kaspawallet, kaspium"), + MULTISIG_ACCOUNT_KIND => ("Multi-Signature", ""), + KEYPAIR_ACCOUNT_KIND => ("Keypair", "secp256k1"), _ => ("", ""), } } diff --git a/core/src/primitives/descriptors.rs b/core/src/primitives/descriptor.rs similarity index 79% rename from core/src/primitives/descriptors.rs rename to core/src/primitives/descriptor.rs index 31eea25..10e2209 100644 --- a/core/src/primitives/descriptors.rs +++ b/core/src/primitives/descriptor.rs @@ -1,5 +1,5 @@ use crate::imports::*; - +use convert_case::{Case, Casing}; // #[derive(Clone, Debug, Serialize, Deserialize, BorshSerialize, BorshDeserialize)] // pub struct Bip32 { // pub account_id: AccountId, @@ -49,7 +49,7 @@ use crate::imports::*; // pub public_key: String, // } -use kaspa_wallet_core::runtime::account::descriptor::*; +// use kaspa_wallet_core::account::descriptor::*; fn grid(ui: &mut Ui, id: &AccountId, add_contents: impl FnOnce(&mut Ui)) { CollapsingHeader::new(id.to_string()) @@ -66,11 +66,11 @@ fn grid(ui: &mut Ui, id: &AccountId, add_contents: impl FnOnce(&mut Ui)) { }); } -pub trait RenderDescriptor { +pub trait RenderAccountDescriptor { fn render(&self, ui: &mut Ui); } -impl RenderDescriptor for Bip32 { +impl RenderAccountDescriptor for AccountDescriptor { fn render(&self, ui: &mut Ui) { grid(ui, &self.account_id, |ui| { let color = Color32::WHITE; @@ -82,15 +82,18 @@ impl RenderDescriptor for Bip32 { ); ui.end_row(); ui.label(i18n("Type")); - ui.colored_label(color, "BIP-32 / Kaspa Core"); + ui.colored_label( + color, + self.account_kind().as_ref().to_case(Case::UpperCamel), + ); ui.end_row(); // TODO bip39 info - ui.label(i18n("Derivation Index")); - ui.colored_label(color, format!("BIP-44 / {}", self.account_index)); - ui.end_row(); - ui.label(i18n("Signature Type")); - ui.colored_label(color, if self.ecdsa { "ECDSA" } else { "Schnorr" }); - ui.end_row(); + // ui.label(i18n("Derivation Index")); + // ui.colored_label(color, format!("BIP-44 / {}", self.account_index)); + // ui.end_row(); + // ui.label(i18n("Signature Type")); + // ui.colored_label(color, if self.ecdsa { "ECDSA" } else { "Schnorr" }); + // ui.end_row(); ui.label(i18n("Receive Address")); ui.colored_label( color, @@ -109,6 +112,12 @@ impl RenderDescriptor for Bip32 { .unwrap_or("N/A".to_string()), ); ui.end_row(); + + for (prop, value) in self.properties.iter() { + ui.label(i18n(prop.to_string().as_str())); + ui.colored_label(color, value.to_string()); + ui.end_row(); + } }); } } diff --git a/core/src/primitives/mod.rs b/core/src/primitives/mod.rs index b656834..2c0e900 100644 --- a/core/src/primitives/mod.rs +++ b/core/src/primitives/mod.rs @@ -4,5 +4,5 @@ pub mod transaction; pub use transaction::{Transaction, TransactionCollection}; pub mod block; pub use block::{BlockDagGraphSettings, DaaBucket, DagBlock}; -pub mod descriptors; -pub use descriptors::*; +pub mod descriptor; +pub use descriptor::*; diff --git a/core/src/runtime/services/kaspa/mod.rs b/core/src/runtime/services/kaspa/mod.rs index 346cf72..b084ad7 100644 --- a/core/src/runtime/services/kaspa/mod.rs +++ b/core/src/runtime/services/kaspa/mod.rs @@ -76,7 +76,7 @@ pub struct KaspaService { pub service_events: Channel, pub task_ctl: Channel<()>, pub network: Mutex, - pub wallet: Arc, + pub wallet: Arc, #[cfg(not(target_arch = "wasm32"))] pub kaspad: Mutex>>, #[cfg(not(target_arch = "wasm32"))] @@ -87,11 +87,11 @@ impl KaspaService { pub fn new(application_events: ApplicationEventsChannel, settings: &Settings) -> Self { // -- // create wallet instance - let storage = KaspaWallet::local_store().unwrap_or_else(|e| { + let storage = CoreWallet::local_store().unwrap_or_else(|e| { panic!("Failed to open local store: {}", e); }); - let wallet = KaspaWallet::try_with_rpc(None, storage, Some(settings.node.network.into())) + let wallet = CoreWallet::try_with_rpc(None, storage, Some(settings.node.network.into())) .unwrap_or_else(|e| { panic!("Failed to create wallet instance: {}", e); }); @@ -192,7 +192,7 @@ impl KaspaService { Ok(()) } - pub fn wallet(&self) -> Arc { + pub fn wallet(&self) -> Arc { self.wallet.clone() } diff --git a/core/src/runtime/services/market_monitor/coingecko.rs b/core/src/runtime/services/market_monitor/coingecko.rs index 84b5d92..bac43bf 100644 --- a/core/src/runtime/services/market_monitor/coingecko.rs +++ b/core/src/runtime/services/market_monitor/coingecko.rs @@ -1,6 +1,6 @@ use super::*; -// use crate::imports::*; use workflow_http::get_json; +use std::collections::hash_map::Entry; // https://api.coingecko.com/api/v3/simple/price?ids=kaspa&vs_currencies=usd%2Ccny&include_market_cap=true&include_24hr_vol=true&include_24hr_change=true // { @@ -68,20 +68,23 @@ pub async fn fetch_market_price_list(currencies: &[&str]) -> Result) -> MarketDataMap { let mut grouped_data: MarketDataMap = AHashMap::new(); - for (coin, info) in data.iter() { - let mut parts: Vec<&str> = coin.split('_').collect(); + for (tag, info) in data.iter() { + let mut parts: Vec<&str> = tag.split('_').collect(); if parts.is_empty() { continue; } - let currency_prefix = parts.remove(0).to_lowercase(); + let symbol = parts.remove(0).to_lowercase(); let suffix = parts.join("_"); - let existing_data = grouped_data.entry(currency_prefix.clone()).or_default(); - + let data = match grouped_data.entry(symbol.clone()) { + Entry::Occupied(entry) => entry.into_mut(), + Entry::Vacant(entry) => entry.insert(MarketData::new(symbol.as_str())), + }; + match suffix.as_str() { - "" => existing_data.price = Some(*info), - "market_cap" => existing_data.market_cap = Some(*info), - "24h_vol" => existing_data.volume = Some(*info), - "24h_change" => existing_data.change = Some(*info), + "" => data.price = *info, + "market_cap" => data.market_cap = *info, + "24h_vol" => data.volume = *info, + "24h_change" => data.change = *info, _ => (), } } diff --git a/core/src/runtime/services/market_monitor/coinmarketcap.rs b/core/src/runtime/services/market_monitor/coinmarketcap.rs index bbae97a..ade6410 100644 --- a/core/src/runtime/services/market_monitor/coinmarketcap.rs +++ b/core/src/runtime/services/market_monitor/coinmarketcap.rs @@ -1,5 +1,6 @@ use super::*; use workflow_http::get_json; +use std::collections::hash_map::Entry; #[derive(Default, Serialize, Deserialize)] struct CoinGeckoSimplePrice { @@ -43,16 +44,20 @@ pub async fn fetch_market_price_list(currencies: &[&str]) -> Result) -> MarketDataMap { let mut grouped_data: MarketDataMap = AHashMap::new(); - for (coin, info) in data.iter() { - let parts: Vec<&str> = coin.split('_').collect(); + for (symbol, info) in data.iter() { + let parts: Vec<&str> = symbol.split('_').collect(); let currency_prefix = parts[0].to_lowercase(); let suffix = parts.last().map(|suffix| suffix.to_lowercase()); - let existing_data = grouped_data.entry(currency_prefix.clone()).or_default(); + let data = match grouped_data.entry(currency_prefix.clone()) { + Entry::Occupied(entry) => entry.into_mut(), + Entry::Vacant(entry) => entry.insert(MarketData::new(symbol.as_str())), + }; + match suffix.as_deref() { - None => existing_data.price = Some(*info), - Some("market_cap") => existing_data.market_cap = Some(*info), - Some("24h_vol") => existing_data.volume = Some(*info), - Some("24h_change") => existing_data.change = Some(*info), + None => data.price = *info, + Some("market_cap") => data.market_cap = *info, + Some("24h_vol") => data.volume = *info, + Some("24h_change") => data.change = *info, _ => (), } } diff --git a/core/src/settings.rs b/core/src/settings.rs index 9047abf..0bb0057 100644 --- a/core/src/settings.rs +++ b/core/src/settings.rs @@ -221,8 +221,8 @@ impl Default for NodeSettings { grpc_network_interface: NetworkInterfaceConfig::default(), enable_upnp: true, // network: Network::Mainnet, - // network: Network::Testnet10, - network: Network::default(), + network: Network::Testnet10, + // network: Network::default(), node_kind: KaspadNodeKind::default(), kaspad_daemon_binary: String::default(), kaspad_daemon_args: String::default(), diff --git a/core/src/utils/format.rs b/core/src/utils/format.rs index 047c1c9..1a59eee 100644 --- a/core/src/utils/format.rs +++ b/core/src/utils/format.rs @@ -120,3 +120,40 @@ pub fn s2kws_layout_job( layout_job } } + +pub fn format_price(price: f64, precision : usize) -> String { + if precision == 0 { + price.trunc().separated_string() + } else { + let string = price.to_string(); + if let Some(idx) = string.find('.') { + let (left,right) = string.split_at(idx+1); + if right.len() < precision { + let mut right = right.to_string(); + while right.len() < precision { + right.push('0'); + } + separated_float!(format!("{left}{right}")) + } else { + let right = &right[0..precision]; + separated_float!(format!("{left}{right}")) + } + } else { + price.separated_string() + } + } +} + +pub fn format_price_with_symbol(price: f64, precision : usize, symbol : &str) -> String { + let price = format_price(price, precision); + format!("{price} {symbol}") +} + +pub fn precision_from_symbol(symbol : &str) -> usize { + match symbol { + "kas" => 8, + "btc" => 8, + // "usd" => 2, + _ => 6 + } +} \ No newline at end of file diff --git a/extensions/chrome/src/server.rs b/extensions/chrome/src/server.rs index 85d1e4d..c1c47f7 100644 --- a/extensions/chrome/src/server.rs +++ b/extensions/chrome/src/server.rs @@ -6,8 +6,9 @@ use kaspa_ng_core::events::ApplicationEventsChannel; use kaspa_ng_core::settings::Settings; use kaspa_wallet_core::api::transport::WalletServer; use kaspa_wallet_core::error::Error; +use kaspa_wallet_core::prelude::Wallet as CoreWallet; use kaspa_wallet_core::result::Result; -use kaspa_wallet_core::runtime; +// use kaspa_wallet_core::runtime; //use kaspa_wallet_core::runtime::api::transport::*; use std::rc::Rc; use std::sync::{Arc, Mutex}; @@ -19,7 +20,7 @@ type ListenerClosure = Closure JsValue>; pub struct Server { #[allow(dead_code)] - wallet: Arc, + wallet: Arc, wallet_server: Arc, closure: Mutex>>, // runtime: Runtime, @@ -47,7 +48,7 @@ impl Server { settings.store().await.unwrap(); workflow_chrome::storage::__chrome_storage_unit_test().await; - let storage = runtime::Wallet::local_store().unwrap_or_else(|e| { + let storage = CoreWallet::local_store().unwrap_or_else(|e| { panic!("Failed to open local store: {}", e); }); @@ -57,7 +58,7 @@ impl Server { log_info!("storage storage: {:?}", storage.descriptor()); let wallet = Arc::new( - runtime::Wallet::try_with_rpc(None, storage, None).unwrap_or_else(|e| { + CoreWallet::try_with_rpc(None, storage, None).unwrap_or_else(|e| { panic!("Failed to create wallet instance: {}", e); }), ); diff --git a/resources/i18n/i18n.json b/resources/i18n/i18n.json index d40e14c..9e1e7ee 100644 --- a/resources/i18n/i18n.json +++ b/resources/i18n/i18n.json @@ -17,10 +17,10 @@ "pa": "Panjabi", "fa": "Farsi", "fi": "Finnish", + "es": "Español", "lt": "Lithuanian", - "sv": "Swedish", "uk": "Ukrainian", - "es": "Español", + "sv": "Swedish", "af": "Afrikaans", "et": "Esti", "en": "English", @@ -64,15 +64,16 @@ "fil": {}, "pa": {}, "fa": {}, + "sv": {}, "fi": {}, "es": {}, "lt": {}, - "sv": {}, "uk": {}, "af": {}, "et": {}, "en": { "Very weak": "Very weak", + "Change": "Change", "Mainnet (Main Kaspa network)": "Mainnet (Main Kaspa network)", "Network Tx/s": "Network Tx/s", "Network Rx/s": "Network Rx/s", @@ -100,6 +101,7 @@ "Update Available to version": "Update Available to version", "DB Headers": "DB Headers", "Virtual Parent Hashes": "Virtual Parent Hashes", + "Volume": "Volume", "Spread": "Spread", "Track in the background": "Track in the background", "Node Status": "Node Status", @@ -193,6 +195,7 @@ "Allow custom arguments for the Rusty Kaspa daemon": "Allow custom arguments for the Rusty Kaspa daemon", "User Agent": "User Agent", "Total Rx/s": "Total Rx/s", + "MT": "MT", "bye!": "bye!", "The node is spawned as a child daemon process (recommended).": "The node is spawned as a child daemon process (recommended).", "Receive Address": "Receive Address", @@ -240,6 +243,7 @@ "Disable password score restrictions": "Disable password score restrictions", "Database Headers": "Database Headers", "Tip Hashes": "Tip Hashes", + "Market Cap": "Market Cap", "Resources": "Resources", "Center VSPC": "Center VSPC", "An update is available to version": "An update is available to version", @@ -259,6 +263,7 @@ "Enables features currently in development": "Enables features currently in development", "Show VSPC": "Show VSPC", "Update Available": "Update Available", + "Price": "Price", "Unable to change node settings until the problem is resolved.": "Unable to change node settings until the problem is resolved.", "Enter the password to unlock your wallet": "Enter the password to unlock your wallet", "Borsh Active Connections": "Borsh Active Connections",