diff --git a/app/gui/block_explorer.rs b/app/gui/block_explorer.rs
index d119226..9c0a082 100644
--- a/app/gui/block_explorer.rs
+++ b/app/gui/block_explorer.rs
@@ -19,11 +19,13 @@ impl BlockExplorer {
Self { height }
}
- pub fn show(&mut self, app: &mut App, ui: &mut egui::Ui) {
- let max_height = app.node.get_height().unwrap_or(0);
+ pub fn show(&mut self, app: Option<&App>, ui: &mut egui::Ui) {
+ let max_height =
+ app.and_then(|app| app.node.get_height().ok()).unwrap_or(0);
let block: Option<(Header, Body)> = {
- if let Ok(Some(block_hash)) =
- app.node.try_get_block_hash(self.height)
+ if let Some(app) = app
+ && let Ok(Some(block_hash)) =
+ app.node.try_get_block_hash(self.height)
&& let Ok(header) = app.node.get_header(block_hash)
&& let Ok(body) = app.node.get_body(block_hash)
{
@@ -97,8 +99,9 @@ impl BlockExplorer {
let body_sigops_limit = State::body_sigops_limit(self.height);
ui.monospace(format!("Body sigops limit: {body_sigops_limit}"));
- if let Ok(Some(acc)) =
- app.node.try_get_accumulator(header.hash())
+ if let Some(app) = app
+ && let Ok(Some(acc)) =
+ app.node.try_get_accumulator(header.hash())
{
ui.monospace_selectable_multiline(format!(
"Utreexo accumulator: \n{}",
diff --git a/app/gui/coins/mod.rs b/app/gui/coins/mod.rs
index e29982d..c31c525 100644
--- a/app/gui/coins/mod.rs
+++ b/app/gui/coins/mod.rs
@@ -28,7 +28,7 @@ pub struct Coins {
}
impl Coins {
- pub fn new(app: &App) -> Self {
+ pub fn new(app: Option<&App>) -> Self {
Self {
transfer_receive: TransferReceive::new(app),
tab: Tab::default(),
@@ -36,7 +36,7 @@ impl Coins {
}
}
- pub fn show(&mut self, app: &mut App, ui: &mut egui::Ui) {
+ pub fn show(&mut self, app: Option<&App>, ui: &mut egui::Ui) {
egui::TopBottomPanel::top("coins_tabs").show(ui.ctx(), |ui| {
ui.horizontal(|ui| {
Tab::iter().for_each(|tab_variant| {
diff --git a/app/gui/coins/transfer_receive.rs b/app/gui/coins/transfer_receive.rs
index bc39b94..ddd92f6 100644
--- a/app/gui/coins/transfer_receive.rs
+++ b/app/gui/coins/transfer_receive.rs
@@ -1,4 +1,4 @@
-use eframe::egui;
+use eframe::egui::{self, Button};
use thunder::types::Address;
use crate::{app::App, gui::util::UiExt};
@@ -25,7 +25,7 @@ fn create_transfer(
}
impl Transfer {
- fn show(&mut self, app: &App, ui: &mut egui::Ui) {
+ fn show(&mut self, app: Option<&App>, ui: &mut egui::Ui) {
ui.add_sized((250., 10.), |ui: &mut egui::Ui| {
ui.horizontal(|ui| {
let dest_edit = egui::TextEdit::singleline(&mut self.dest)
@@ -66,13 +66,16 @@ impl Transfer {
);
if ui
.add_enabled(
- dest.is_some() && amount.is_ok() && fee.is_ok(),
+ app.is_some()
+ && dest.is_some()
+ && amount.is_ok()
+ && fee.is_ok(),
egui::Button::new("transfer"),
)
.clicked()
{
if let Err(err) = create_transfer(
- app,
+ app.unwrap(),
dest.expect("should not happen"),
amount.expect("should not happen"),
fee.expect("should not happen"),
@@ -87,29 +90,38 @@ impl Transfer {
#[derive(Debug)]
struct Receive {
- address: anyhow::Result
,
+ address: Option>,
}
impl Receive {
- fn new(app: &App) -> Self {
+ fn new(app: Option<&App>) -> Self {
+ let Some(app) = app else {
+ return Self { address: None };
+ };
let address = app
.wallet
.get_new_address()
.map_err(anyhow::Error::from)
.inspect_err(|err| tracing::error!("{err:#}"));
- Self { address }
+ Self {
+ address: Some(address),
+ }
}
- fn show(&mut self, app: &App, ui: &mut egui::Ui) {
+ fn show(&mut self, app: Option<&App>, ui: &mut egui::Ui) {
match &self.address {
- Ok(address) => {
+ Some(Ok(address)) => {
ui.monospace_selectable_singleline(false, address.to_string());
}
- Err(err) => {
+ Some(Err(err)) => {
ui.monospace_selectable_multiline(format!("{err:#}"));
}
+ None => (),
}
- if ui.button("generate").clicked() {
+ if ui
+ .add_enabled(app.is_some(), Button::new("generate"))
+ .clicked()
+ {
*self = Self::new(app)
}
}
@@ -122,14 +134,14 @@ pub(super) struct TransferReceive {
}
impl TransferReceive {
- pub fn new(app: &App) -> Self {
+ pub fn new(app: Option<&App>) -> Self {
Self {
transfer: Transfer::default(),
receive: Receive::new(app),
}
}
- pub fn show(&mut self, app: &mut App, ui: &mut egui::Ui) {
+ pub fn show(&mut self, app: Option<&App>, ui: &mut egui::Ui) {
egui::SidePanel::left("transfer")
.exact_width(ui.available_width() / 2.)
.resizable(false)
diff --git a/app/gui/coins/tx_builder.rs b/app/gui/coins/tx_builder.rs
index 2dd9bb8..1aad177 100644
--- a/app/gui/coins/tx_builder.rs
+++ b/app/gui/coins/tx_builder.rs
@@ -21,8 +21,11 @@ pub struct TxBuilder {
}
impl TxBuilder {
- pub fn show_value_in(&mut self, app: &mut App, ui: &mut egui::Ui) {
+ pub fn show_value_in(&mut self, app: Option<&App>, ui: &mut egui::Ui) {
ui.heading("Value In");
+ let Some(app) = app else {
+ return;
+ };
let selected: HashSet<_> = self
.base_tx
.inputs
@@ -102,7 +105,7 @@ impl TxBuilder {
pub fn show(
&mut self,
- app: &mut App,
+ app: Option<&App>,
ui: &mut egui::Ui,
) -> anyhow::Result<()> {
egui::SidePanel::left("spend_utxo")
diff --git a/app/gui/coins/tx_creator.rs b/app/gui/coins/tx_creator.rs
index 6bac416..60e3644 100644
--- a/app/gui/coins/tx_creator.rs
+++ b/app/gui/coins/tx_creator.rs
@@ -1,4 +1,4 @@
-use eframe::egui;
+use eframe::egui::{self, Button};
use thunder::types::{Transaction, Txid};
@@ -22,7 +22,7 @@ fn send_tx(app: &App, tx: &mut Transaction) -> anyhow::Result<()> {
impl TxCreator {
pub fn show(
&mut self,
- app: &mut App,
+ app: Option<&App>,
ui: &mut egui::Ui,
base_tx: &mut Transaction,
) -> anyhow::Result<()> {
@@ -48,8 +48,11 @@ impl TxCreator {
if self.value_in >= self.value_out {
let fee = self.value_in - self.value_out;
ui.monospace(format!("fee: {fee}"));
- if ui.button("sign and send").clicked() {
- if let Err(err) = send_tx(app, final_tx) {
+ if ui
+ .add_enabled(app.is_some(), Button::new("sign and send"))
+ .clicked()
+ {
+ if let Err(err) = send_tx(app.unwrap(), final_tx) {
tracing::error!("{err:#}");
} else {
*base_tx = Transaction::default();
diff --git a/app/gui/coins/utxo_creator.rs b/app/gui/coins/utxo_creator.rs
index 2c7dc53..42cf70f 100644
--- a/app/gui/coins/utxo_creator.rs
+++ b/app/gui/coins/utxo_creator.rs
@@ -1,4 +1,4 @@
-use eframe::egui;
+use eframe::egui::{self, Button};
use thunder::types::{self, Output, OutputContent, Transaction};
use crate::app::App;
@@ -42,7 +42,7 @@ impl Default for UtxoCreator {
impl UtxoCreator {
pub fn show(
&mut self,
- app: &mut App,
+ app: Option<&App>,
ui: &mut egui::Ui,
tx: &mut Transaction,
) {
@@ -73,8 +73,12 @@ impl UtxoCreator {
ui.horizontal(|ui| {
ui.monospace("Address: ");
ui.add(egui::TextEdit::singleline(&mut self.address));
- if ui.button("generate").clicked() {
+ if ui
+ .add_enabled(app.is_some(), Button::new("generate"))
+ .clicked()
+ {
self.address = app
+ .unwrap()
.wallet
.get_new_address()
.map(|address| format!("{address}"))
@@ -85,8 +89,11 @@ impl UtxoCreator {
ui.horizontal(|ui| {
ui.monospace("Main Address:");
ui.add(egui::TextEdit::singleline(&mut self.main_address));
- if ui.button("generate").clicked() {
- match app.get_new_main_address() {
+ if ui
+ .add_enabled(app.is_some(), Button::new("generate"))
+ .clicked()
+ {
+ match app.unwrap().get_new_main_address() {
Ok(main_address) => {
self.main_address = format!("{main_address}");
}
@@ -171,8 +178,10 @@ impl UtxoCreator {
}
}
}
- let num_addresses = app.wallet.get_num_addresses().unwrap();
- ui.label(format!("{num_addresses} addresses generated"));
+ if let Some(app) = app {
+ let num_addresses = app.wallet.get_num_addresses().unwrap();
+ ui.label(format!("{num_addresses} addresses generated"));
+ }
});
}
}
diff --git a/app/gui/coins/utxo_selector.rs b/app/gui/coins/utxo_selector.rs
index 1a07518..e4c1240 100644
--- a/app/gui/coins/utxo_selector.rs
+++ b/app/gui/coins/utxo_selector.rs
@@ -13,22 +13,28 @@ pub struct UtxoSelector;
impl UtxoSelector {
pub fn show(
&mut self,
- app: &mut App,
+ app: Option<&App>,
ui: &mut egui::Ui,
tx: &mut Transaction,
) {
ui.heading("Spend UTXO");
let selected: HashSet<_> =
tx.inputs.iter().map(|(outpoint, _)| *outpoint).collect();
- let utxos_read = app.utxos.read();
- let total: bitcoin::Amount = utxos_read
- .iter()
- .filter(|(outpoint, _)| !selected.contains(outpoint))
- .map(|(_, output)| output.get_value())
- .sum();
- let mut utxos: Vec<_> = (*utxos_read).clone().into_iter().collect();
- drop(utxos_read);
- utxos.sort_by_key(|(outpoint, _)| format!("{outpoint}"));
+ let (total, utxos): (bitcoin::Amount, Vec<_>) = app
+ .map(|app| {
+ let utxos_read = app.utxos.read();
+ let total: bitcoin::Amount = utxos_read
+ .iter()
+ .filter(|(outpoint, _)| !selected.contains(outpoint))
+ .map(|(_, output)| output.get_value())
+ .sum();
+ let mut utxos: Vec<_> =
+ (*utxos_read).clone().into_iter().collect();
+ drop(utxos_read);
+ utxos.sort_by_key(|(outpoint, _)| format!("{outpoint}"));
+ (total, utxos)
+ })
+ .unwrap_or_default();
ui.separator();
ui.monospace(format!("Total: {}", total));
ui.separator();
diff --git a/app/gui/console_logs.rs b/app/gui/console_logs.rs
index 14aadec..78c65fc 100644
--- a/app/gui/console_logs.rs
+++ b/app/gui/console_logs.rs
@@ -89,7 +89,7 @@ impl ConsoleLogs {
});
}
- pub fn show(&mut self, app: &App, ui: &mut egui::Ui) {
+ pub fn show(&mut self, app: Option<&App>, ui: &mut egui::Ui) {
ScrollArea::vertical().stick_to_bottom(true).show(ui, |ui| {
let line_buffer_read = self.line_buffer.as_str();
let mut logs: &str = &line_buffer_read;
@@ -106,7 +106,8 @@ impl ConsoleLogs {
.hint_text("help")
.return_key(SHIFT_ENTER);
let command_input_resp = ui.add_enabled(
- !self.running_command.load(atomic::Ordering::SeqCst),
+ app.is_some()
+ && !self.running_command.load(atomic::Ordering::SeqCst),
command_input,
);
if command_input_resp.ctx.input_mut(|input| {
@@ -114,7 +115,7 @@ impl ConsoleLogs {
&& input.consume_key(Modifiers::NONE, Key::Enter)
&& !self.running_command.load(atomic::Ordering::SeqCst)
}) {
- self.console_command(app);
+ self.console_command(app.unwrap());
}
});
}
diff --git a/app/gui/mempool_explorer.rs b/app/gui/mempool_explorer.rs
index 9fad396..50444fa 100644
--- a/app/gui/mempool_explorer.rs
+++ b/app/gui/mempool_explorer.rs
@@ -10,9 +10,13 @@ pub struct MemPoolExplorer {
}
impl MemPoolExplorer {
- pub fn show(&mut self, app: &mut App, ui: &mut egui::Ui) {
- let transactions = app.node.get_all_transactions().unwrap_or_default();
- let utxos = app.wallet.get_utxos().unwrap_or_default();
+ pub fn show(&mut self, app: Option<&App>, ui: &mut egui::Ui) {
+ let transactions = app
+ .and_then(|app| app.node.get_all_transactions().ok())
+ .unwrap_or_default();
+ let utxos = app
+ .and_then(|app| app.wallet.get_utxos().ok())
+ .unwrap_or_default();
egui::SidePanel::left("transaction_picker")
.resizable(false)
.show_inside(ui, |ui| {
diff --git a/app/gui/miner.rs b/app/gui/miner.rs
index 08def35..650cfcc 100644
--- a/app/gui/miner.rs
+++ b/app/gui/miner.rs
@@ -21,18 +21,22 @@ impl Default for Miner {
}
impl Miner {
- pub fn show(&mut self, app: &App, ui: &mut egui::Ui) {
- let block_height = app.node.get_height().unwrap_or(0);
- let best_hash = app.node.get_best_hash().unwrap_or([0; 32].into());
+ pub fn show(&mut self, app: Option<&App>, ui: &mut egui::Ui) {
+ let block_height =
+ app.and_then(|app| app.node.get_height().ok()).unwrap_or(0);
+ let best_hash = app
+ .and_then(|app| app.node.get_best_hash().ok())
+ .unwrap_or([0; 32].into());
ui.label("Block height: ");
ui.monospace(format!("{block_height}"));
ui.label("Best hash: ");
let best_hash = &format!("{best_hash}")[0..8];
ui.monospace(format!("{best_hash}..."));
let running = self.running.load(atomic::Ordering::SeqCst);
- if ui
- .add_enabled(!running, Button::new("Mine / Refresh Block"))
- .clicked()
+ if let Some(app) = app
+ && ui
+ .add_enabled(!running, Button::new("Mine / Refresh Block"))
+ .clicked()
{
self.running.store(true, atomic::Ordering::SeqCst);
app.local_pool.spawn_pinned({
diff --git a/app/gui/mod.rs b/app/gui/mod.rs
index 6df5dbc..c3fbbe4 100644
--- a/app/gui/mod.rs
+++ b/app/gui/mod.rs
@@ -31,7 +31,7 @@ use withdrawals::Withdrawals;
use self::util::UiExt;
pub struct EguiApp {
- app: App,
+ app: Option,
block_explorer: BlockExplorer,
bottom_panel: BottomPanel,
coins: Coins,
@@ -61,8 +61,29 @@ enum Tab {
ConsoleLogs,
}
-struct BottomPanel {
+/// Bottom panel, if initialized
+struct BottomPanelInitialized {
+ app: App,
wallet_updated: PromiseStream<>::WatchStream>,
+}
+
+impl BottomPanelInitialized {
+ fn new(app: App) -> Self {
+ let wallet_updated = {
+ let rt_guard = app.runtime.enter();
+ let wallet_updated = PromiseStream::from(app.wallet.watch());
+ drop(rt_guard);
+ wallet_updated
+ };
+ Self {
+ app,
+ wallet_updated,
+ }
+ }
+}
+
+struct BottomPanel {
+ initialized: Option,
/// None if uninitialized
/// Some(None) if failed to initialize
balance: Option