Skip to content

Commit

Permalink
Merge #819: Verify receive address
Browse files Browse the repository at this point in the history
e9a217b Add verify address modal to receive panel (edouardparis)
fa992aa gui: move receive panel to its own file (edouardparis)

Pull request description:

ACKs for top commit:
  jp1ac4:
    utACK e9a217b.

Tree-SHA512: 4b34260304f6417146c920861c63f4c4f5f9823a3f73538a398eba0144dee9c4630fe91007dfc34ae07e5957926cc30464abd8c55679b899dc9612e8aa4d98ce
  • Loading branch information
darosior committed Nov 20, 2023
2 parents f512f3c + e9a217b commit 1df4ab8
Show file tree
Hide file tree
Showing 9 changed files with 637 additions and 266 deletions.
256 changes: 144 additions & 112 deletions gui/Cargo.lock

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion gui/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ name = "liana-gui"
path = "src/main.rs"

[dependencies]
async-hwi = "0.0.12"
async-hwi = "0.0.13"
liana = { git = "https://github.com/wizardsardine/liana", branch = "master", default-features = false, features = ["nonblocking_shutdown"] }
liana_ui = { path = "ui" }
backtrace = "0.3"
Expand Down
9 changes: 7 additions & 2 deletions gui/src/app/message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@ use std::sync::Arc;

use liana::{
config::Config as DaemonConfig,
miniscript::bitcoin::{bip32::Fingerprint, psbt::Psbt, Address},
miniscript::bitcoin::{
bip32::{ChildNumber, Fingerprint},
psbt::Psbt,
Address,
},
};

use crate::{
Expand All @@ -21,7 +25,7 @@ pub enum Message {
LoadWallet,
WalletLoaded(Result<Arc<Wallet>, Error>),
Info(Result<GetInfoResult, Error>),
ReceiveAddress(Result<Address, Error>),
ReceiveAddress(Result<(Address, ChildNumber), Error>),
Coins(Result<Vec<Coin>, Error>),
Labels(Result<HashMap<String, String>, Error>),
SpendTxs(Result<Vec<SpendTx>, Error>),
Expand All @@ -31,6 +35,7 @@ pub enum Message {
WalletRegistered(Result<Fingerprint, Error>),
Updated(Result<(), Error>),
Saved(Result<(), Error>),
Verified(Fingerprint, Result<(), Error>),
StartRescan(Result<(), Error>),
HardwareWallets(HardwareWalletMessage),
HistoryTransactions(Result<Vec<HistoryTransaction>, Error>),
Expand Down
4 changes: 3 additions & 1 deletion gui/src/app/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,9 @@ impl App {
self.cache.blockheight,
)
.into(),
menu::Menu::Receive => ReceivePanel::default().into(),
menu::Menu::Receive => {
ReceivePanel::new(self.data_dir.clone(), self.wallet.clone()).into()
}
menu::Menu::Transactions => TransactionsPanel::new().into(),
menu::Menu::PSBTs => PsbtsPanel::new(self.wallet.clone(), &self.cache.spend_txs).into(),
menu::Menu::CreateSpendTx => CreateSpendPanel::new(
Expand Down
155 changes: 12 additions & 143 deletions gui/src/app/state/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,30 @@ mod coins;
mod label;
mod psbt;
mod psbts;
mod receive;
mod recovery;
mod settings;
mod spend;
mod transactions;

use std::collections::HashMap;
use std::convert::TryInto;
use std::sync::Arc;
use std::time::{SystemTime, UNIX_EPOCH};

use iced::{widget::qr_code, Command, Subscription};
use liana::miniscript::bitcoin::{Address, Amount, OutPoint};
use iced::{Command, Subscription};
use liana::miniscript::bitcoin::{Amount, OutPoint};
use liana_ui::widget::*;

use super::{cache::Cache, error::Error, menu::Menu, message::Message, view, wallet::Wallet};

use crate::daemon::{
model::{remaining_sequence, Coin, HistoryTransaction, LabelItem, Labelled},
model::{remaining_sequence, Coin, HistoryTransaction, Labelled},
Daemon,
};
pub use coins::CoinsPanel;
use label::LabelsEdited;
pub use psbts::PsbtsPanel;
pub use receive::ReceivePanel;
pub use recovery::RecoveryPanel;
pub use settings::SettingsState;
pub use spend::CreateSpendPanel;
Expand All @@ -48,6 +49,13 @@ pub trait State {
}
}

/// redirect to another state with a message menu
pub fn redirect(menu: Menu) -> Command<Message> {
Command::perform(async { menu }, |menu| {
Message::View(view::Message::Menu(menu))
})
}

pub struct Home {
wallet: Arc<Wallet>,
balance: Amount,
Expand Down Expand Up @@ -292,142 +300,3 @@ impl From<Home> for Box<dyn State> {
Box::new(s)
}
}

#[derive(Debug, Default)]
pub struct Addresses {
list: Vec<Address>,
labels: HashMap<String, String>,
}

impl Labelled for Addresses {
fn labelled(&self) -> Vec<LabelItem> {
self.list
.iter()
.map(|a| LabelItem::Address(a.clone()))
.collect()
}
fn labels(&mut self) -> &mut HashMap<String, String> {
&mut self.labels
}
}

#[derive(Default)]
pub struct ReceivePanel {
addresses: Addresses,
labels_edited: LabelsEdited,
qr_code: Option<qr_code::State>,
warning: Option<Error>,
}

impl State for ReceivePanel {
fn view<'a>(&'a self, cache: &'a Cache) -> Element<'a, view::Message> {
view::dashboard(
&Menu::Receive,
cache,
self.warning.as_ref(),
view::receive::receive(
&self.addresses.list,
self.qr_code.as_ref(),
&self.addresses.labels,
self.labels_edited.cache(),
),
)
}
fn update(
&mut self,
daemon: Arc<dyn Daemon + Sync + Send>,
_cache: &Cache,
message: Message,
) -> Command<Message> {
match message {
Message::View(view::Message::Label(_, _)) | Message::LabelsUpdated(_) => {
match self.labels_edited.update(
daemon,
message,
std::iter::once(&mut self.addresses).map(|a| a as &mut dyn Labelled),
) {
Ok(cmd) => cmd,
Err(e) => {
self.warning = Some(e);
Command::none()
}
}
}
Message::ReceiveAddress(res) => {
match res {
Ok(address) => {
self.warning = None;
self.qr_code = Some(qr_code::State::new(address.to_qr_uri()).unwrap());
self.addresses.list.push(address);
}
Err(e) => self.warning = Some(e),
}
Command::none()
}
Message::View(view::Message::Next) => self.load(daemon),
_ => Command::none(),
}
}

fn load(&self, daemon: Arc<dyn Daemon + Sync + Send>) -> Command<Message> {
let daemon = daemon.clone();
Command::perform(
async move {
daemon
.get_new_address()
.map(|res| res.address().clone())
.map_err(|e| e.into())
},
Message::ReceiveAddress,
)
}
}

impl From<ReceivePanel> for Box<dyn State> {
fn from(s: ReceivePanel) -> Box<dyn State> {
Box::new(s)
}
}

/// redirect to another state with a message menu
pub fn redirect(menu: Menu) -> Command<Message> {
Command::perform(async { menu }, |menu| {
Message::View(view::Message::Menu(menu))
})
}

#[cfg(test)]
mod tests {
use super::*;
use crate::{
app::cache::Cache,
daemon::{
client::{Lianad, Request},
model::*,
},
utils::{mock::Daemon, sandbox::Sandbox},
};

use liana::miniscript::bitcoin::Address;
use serde_json::json;
use std::str::FromStr;

#[tokio::test]
async fn test_receive_panel() {
let addr =
Address::from_str("tb1qkldgvljmjpxrjq2ev5qxe8dvhn0dph9q85pwtfkjeanmwdue2akqj4twxj")
.unwrap()
.assume_checked();
let daemon = Daemon::new(vec![(
Some(json!({"method": "getnewaddress", "params": Option::<Request>::None})),
Ok(json!(GetAddressResult::new(addr.clone()))),
)]);

let sandbox: Sandbox<ReceivePanel> = Sandbox::new(ReceivePanel::default());
let client = Arc::new(Lianad::new(daemon.run()));
let sandbox = sandbox.load(client, &Cache::default()).await;

let panel = sandbox.state();
assert_eq!(panel.addresses.list, vec![addr]);
}
}
Loading

0 comments on commit 1df4ab8

Please sign in to comment.