Skip to content

Commit 0af4fe3

Browse files
committed
gui(spend): display warnings for draft PSBTs
1 parent a34070d commit 0af4fe3

File tree

4 files changed

+109
-23
lines changed

4 files changed

+109
-23
lines changed

gui/src/app/message.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ pub enum Message {
2929
Coins(Result<Vec<Coin>, Error>),
3030
Labels(Result<HashMap<String, String>, Error>),
3131
SpendTxs(Result<Vec<SpendTx>, Error>),
32-
Psbt(Result<Psbt, Error>),
32+
Psbt(Result<(Psbt, Vec<String>), Error>),
3333
Recovery(Result<SpendTx, Error>),
3434
Signed(Fingerprint, Result<Psbt, Error>),
3535
WalletRegistered(Result<Fingerprint, Error>),

gui/src/app/state/spend/step.rs

+25-19
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ pub struct TransactionDraft {
3232
network: Network,
3333
inputs: Vec<Coin>,
3434
recipients: Vec<Recipient>,
35-
generated: Option<Psbt>,
35+
generated: Option<(Psbt, Vec<String>)>,
3636
batch_label: Option<String>,
3737
labels: HashMap<String, String>,
3838
}
@@ -83,7 +83,7 @@ pub struct DefineSpend {
8383
batch_label: form::Value<String>,
8484
amount_left_to_select: Option<Amount>,
8585
feerate: form::Value<String>,
86-
generated: Option<Psbt>,
86+
generated: Option<(Psbt, Vec<String>)>,
8787
warning: Option<Error>,
8888
}
8989

@@ -384,7 +384,9 @@ impl Step for DefineSpend {
384384
.create_spend_tx(&inputs, &outputs, feerate_vb)
385385
.map_err(|e| e.into())
386386
.and_then(|res| match res {
387-
CreateSpendResult::Success { psbt, .. } => Ok(psbt),
387+
CreateSpendResult::Success { psbt, warnings } => {
388+
Ok((psbt, warnings))
389+
}
388390
CreateSpendResult::InsufficientFunds { missing } => {
389391
Err(SpendCreationError::CoinSelection(
390392
liana::spend::InsufficientFunds { missing },
@@ -438,7 +440,7 @@ impl Step for DefineSpend {
438440
.filter_map(|(coin, selected)| if *selected { Some(coin) } else { None })
439441
.cloned()
440442
.collect();
441-
if let Some(psbt) = &self.generated {
443+
if let Some((psbt, _)) = &self.generated {
442444
draft.labels = self.coins_labels.clone();
443445
for (i, output) in psbt.unsigned_tx.output.iter().enumerate() {
444446
if let Some(label) = self
@@ -583,7 +585,7 @@ impl Recipient {
583585

584586
pub struct SaveSpend {
585587
wallet: Arc<Wallet>,
586-
spend: Option<psbt::PsbtState>,
588+
spend: Option<(psbt::PsbtState, Vec<String>)>,
587589
curve: secp256k1::Secp256k1<secp256k1::VerifyOnly>,
588590
}
589591

@@ -599,7 +601,7 @@ impl SaveSpend {
599601

600602
impl Step for SaveSpend {
601603
fn load(&mut self, draft: &TransactionDraft) {
602-
let psbt = draft.generated.clone().unwrap();
604+
let (psbt, warnings) = draft.generated.clone().unwrap();
603605
let mut tx = SpendTx::new(
604606
None,
605607
psbt,
@@ -623,12 +625,15 @@ impl Step for SaveSpend {
623625
}
624626
}
625627

626-
self.spend = Some(psbt::PsbtState::new(self.wallet.clone(), tx, false));
628+
self.spend = Some((
629+
psbt::PsbtState::new(self.wallet.clone(), tx, false),
630+
warnings,
631+
));
627632
}
628633

629634
fn subscription(&self) -> Subscription<Message> {
630-
if let Some(spend) = &self.spend {
631-
spend.subscription()
635+
if let Some((psbt_state, _)) = &self.spend {
636+
psbt_state.subscription()
632637
} else {
633638
Subscription::none()
634639
}
@@ -640,26 +645,27 @@ impl Step for SaveSpend {
640645
cache: &Cache,
641646
message: Message,
642647
) -> Command<Message> {
643-
if let Some(spend) = &mut self.spend {
644-
spend.update(daemon, cache, message)
648+
if let Some((psbt_state, _)) = &mut self.spend {
649+
psbt_state.update(daemon, cache, message)
645650
} else {
646651
Command::none()
647652
}
648653
}
649654

650655
fn view<'a>(&'a self, cache: &'a Cache) -> Element<'a, view::Message> {
651-
let spend = self.spend.as_ref().unwrap();
656+
let (psbt_state, warnings) = self.spend.as_ref().unwrap();
652657
let content = view::spend::spend_view(
653658
cache,
654-
&spend.tx,
655-
spend.saved,
656-
&spend.desc_policy,
657-
&spend.wallet.keys_aliases,
658-
spend.labels_edited.cache(),
659+
&psbt_state.tx,
660+
warnings,
661+
psbt_state.saved,
662+
&psbt_state.desc_policy,
663+
&psbt_state.wallet.keys_aliases,
664+
psbt_state.labels_edited.cache(),
659665
cache.network,
660-
spend.warning.as_ref(),
666+
psbt_state.warning.as_ref(),
661667
);
662-
if let Some(action) = &spend.action {
668+
if let Some(action) = &psbt_state.action {
663669
action.as_ref().view(content)
664670
} else {
665671
content

gui/src/app/view/psbt.rs

+76-2
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ pub fn psbt_view<'a>(
7373
}),
7474
)
7575
.push(spend_header(tx, labels_editing))
76-
.push(spend_overview_view(tx, desc_info, key_aliases))
76+
.push(spend_overview_view(tx, desc_info, key_aliases, None))
7777
.push(
7878
Column::new()
7979
.spacing(20)
@@ -255,6 +255,7 @@ pub fn spend_overview_view<'a>(
255255
tx: &'a SpendTx,
256256
desc_info: &'a LianaPolicy,
257257
key_aliases: &'a HashMap<Fingerprint, String>,
258+
warnings: Option<&'a Vec<String>>,
258259
) -> Element<'a, Message> {
259260
Column::new()
260261
.spacing(20)
@@ -306,7 +307,8 @@ pub fn spend_overview_view<'a>(
306307
.align_items(Alignment::Center),
307308
),
308309
)
309-
.push(signatures(tx, desc_info, key_aliases)),
310+
.push(signatures(tx, desc_info, key_aliases))
311+
.push_maybe(warnings.map(|w| spend_warnings_view(w))),
310312
)
311313
.style(theme::Container::Card(theme::Card::Simple)),
312314
)
@@ -336,6 +338,78 @@ pub fn spend_overview_view<'a>(
336338
.into()
337339
}
338340

341+
fn spend_warnings_button<'a>(
342+
num_warnings: usize,
343+
is_collapsed: bool,
344+
) -> impl Fn() -> Button<'a, liana_ui::component::collapse::Event<Message>> {
345+
let color = if num_warnings >= 1 {
346+
color::ORANGE
347+
} else {
348+
color::GREEN
349+
};
350+
move || {
351+
Button::new(
352+
Row::new()
353+
.align_items(Alignment::Center)
354+
.spacing(20)
355+
.push(p1_bold("Warnings"))
356+
.push(
357+
Row::new()
358+
.spacing(5)
359+
.align_items(Alignment::Center)
360+
.push(if num_warnings >= 1 {
361+
icon::warning_icon().style(color)
362+
} else {
363+
icon::circle_check_icon().style(color)
364+
})
365+
.push(
366+
text(format!(
367+
"{} warning{}",
368+
num_warnings,
369+
if num_warnings == 1 { "" } else { "s" }
370+
))
371+
.style(color),
372+
)
373+
.width(Length::Fill),
374+
)
375+
.push(if is_collapsed {
376+
icon::collapsed_icon()
377+
} else {
378+
icon::collapse_icon()
379+
}),
380+
)
381+
.padding(15)
382+
.width(Length::Fill)
383+
.style(theme::Button::TransparentBorder)
384+
}
385+
}
386+
387+
fn spend_warnings_view(warnings: &Vec<String>) -> Element<Message> {
388+
Container::new(Collapse::new(
389+
spend_warnings_button(warnings.len(), false),
390+
spend_warnings_button(warnings.len(), true),
391+
move || {
392+
Into::<Element<Message>>::into(
393+
warnings.iter().fold(
394+
Column::new()
395+
.padding(15)
396+
.spacing(10)
397+
.push(text(if warnings.is_empty() {
398+
"No warnings were generated.".to_string()
399+
} else {
400+
format!(
401+
"The following warning{} generated:",
402+
if warnings.len() > 1 { "s were" } else { " was" }
403+
)
404+
})),
405+
|col, warning| col.push(text(warning).style(color::GREY_3)),
406+
),
407+
)
408+
},
409+
))
410+
.into()
411+
}
412+
339413
pub fn signatures<'a>(
340414
tx: &'a SpendTx,
341415
desc_info: &'a LianaPolicy,

gui/src/app/view/spend/mod.rs

+7-1
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ use crate::{
3333
pub fn spend_view<'a>(
3434
cache: &'a Cache,
3535
tx: &'a SpendTx,
36+
spend_warnings: &'a Vec<String>,
3637
saved: bool,
3738
desc_info: &'a LianaPolicy,
3839
key_aliases: &'a HashMap<Fingerprint, String>,
@@ -48,7 +49,12 @@ pub fn spend_view<'a>(
4849
.spacing(20)
4950
.push(Container::new(h3("Send")).width(Length::Fill))
5051
.push(psbt::spend_header(tx, labels_editing))
51-
.push(psbt::spend_overview_view(tx, desc_info, key_aliases))
52+
.push(psbt::spend_overview_view(
53+
tx,
54+
desc_info,
55+
key_aliases,
56+
if saved { None } else { Some(spend_warnings) },
57+
))
5258
.push(
5359
Column::new()
5460
.spacing(20)

0 commit comments

Comments
 (0)