Skip to content

Commit 802e0ee

Browse files
committed
gui(psbt): check for conflicting txs before broadcast
1 parent c30485b commit 802e0ee

File tree

2 files changed

+85
-6
lines changed

2 files changed

+85
-6
lines changed

gui/src/app/state/psbt.rs

+18-3
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use iced::Subscription;
88
use iced::Command;
99
use liana::{
1010
descriptors::LianaPolicy,
11-
miniscript::bitcoin::{bip32::Fingerprint, psbt::Psbt, Network},
11+
miniscript::bitcoin::{bip32::Fingerprint, psbt::Psbt, Network, Txid},
1212
};
1313

1414
use liana_ui::component::toast;
@@ -172,7 +172,16 @@ impl PsbtState {
172172
return cmd;
173173
}
174174
view::SpendTxMessage::Broadcast => {
175-
self.action = Some(PsbtAction::Broadcast(BroadcastAction::default()));
175+
let conflicting_txids: HashSet<_> = self
176+
.tx
177+
.coins
178+
.iter()
179+
.filter_map(|(_, c)| c.spend_info.map(|info| info.txid))
180+
.collect();
181+
self.action = Some(PsbtAction::Broadcast(BroadcastAction {
182+
conflicting_txids,
183+
..Default::default()
184+
}));
176185
}
177186
view::SpendTxMessage::Save => {
178187
self.action = Some(PsbtAction::Save(SaveAction::default()));
@@ -290,6 +299,8 @@ impl Action for SaveAction {
290299
pub struct BroadcastAction {
291300
broadcast: bool,
292301
error: Option<Error>,
302+
/// IDs of any directly conflicting transactions.
303+
conflicting_txids: HashSet<Txid>,
293304
}
294305

295306
impl Action for BroadcastAction {
@@ -327,7 +338,11 @@ impl Action for BroadcastAction {
327338
fn view<'a>(&'a self, content: Element<'a, view::Message>) -> Element<'a, view::Message> {
328339
modal::Modal::new(
329340
content,
330-
view::psbt::broadcast_action(self.error.as_ref(), self.broadcast),
341+
view::psbt::broadcast_action(
342+
&self.conflicting_txids,
343+
self.error.as_ref(),
344+
self.broadcast,
345+
),
331346
)
332347
.on_blur(Some(view::Message::Spend(view::SpendTxMessage::Cancel)))
333348
.into()

gui/src/app/view/psbt.rs

+67-3
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,15 @@ pub fn save_action<'a>(warning: Option<&Error>, saved: bool) -> Element<'a, Mess
140140
}
141141
}
142142

143-
pub fn broadcast_action<'a>(warning: Option<&Error>, saved: bool) -> Element<'a, Message> {
143+
/// Return the modal view to broadcast a transaction.
144+
///
145+
/// `conflicting_txids` contains the IDs of any directly conflicting transactions
146+
/// of the transaction to be broadcast.
147+
pub fn broadcast_action<'a>(
148+
conflicting_txids: &HashSet<Txid>,
149+
warning: Option<&Error>,
150+
saved: bool,
151+
) -> Element<'a, Message> {
144152
if saved {
145153
card::simple(text("Transaction is broadcast"))
146154
.width(Length::Fixed(400.0))
@@ -151,15 +159,71 @@ pub fn broadcast_action<'a>(warning: Option<&Error>, saved: bool) -> Element<'a,
151159
Column::new()
152160
.spacing(10)
153161
.push_maybe(warning.map(|w| warn(Some(w))))
154-
.push(text("Broadcast the transaction"))
162+
.push(Container::new(h4_bold("Broadcast the transaction")).width(Length::Fill))
163+
.push_maybe(if conflicting_txids.is_empty() {
164+
None
165+
} else {
166+
Some(
167+
conflicting_txids.iter().fold(
168+
Column::new()
169+
.spacing(5)
170+
.push(Row::new().spacing(10).push(icon::warning_icon()).push(text(
171+
if conflicting_txids.len() > 1 {
172+
"WARNING: Broadcasting this transaction \
173+
will invalidate some pending payments."
174+
} else {
175+
"WARNING: Broadcasting this transaction \
176+
will invalidate a pending payment."
177+
},
178+
)))
179+
.push(Row::new().padding([0, 30]).push(text(
180+
if conflicting_txids.len() > 1 {
181+
"The following transactions are \
182+
spending one or more inputs \
183+
from the transaction to be \
184+
broadcast and will be \
185+
dropped, along with any other \
186+
transactions that depend on them:"
187+
} else {
188+
"The following transaction is \
189+
spending one or more inputs \
190+
from the transaction to be \
191+
broadcast and will be \
192+
dropped, along with any other \
193+
transactions that depend on it:"
194+
},
195+
))),
196+
|col, txid| {
197+
col.push(
198+
Row::new()
199+
.padding([0, 30])
200+
.spacing(5)
201+
.align_items(Alignment::Center)
202+
.push(text(txid.to_string()))
203+
.push(
204+
Button::new(
205+
icon::clipboard_icon().style(color::GREY_3),
206+
)
207+
.on_press(Message::Clipboard(txid.to_string()))
208+
.style(theme::Button::TransparentBorder),
209+
),
210+
)
211+
},
212+
),
213+
)
214+
})
155215
.push(
156216
Row::new().push(Column::new().width(Length::Fill)).push(
157217
button::primary(None, "Broadcast")
158218
.on_press(Message::Spend(SpendTxMessage::Confirm)),
159219
),
160220
),
161221
)
162-
.width(Length::Fixed(400.0))
222+
.width(Length::Fixed(if conflicting_txids.is_empty() {
223+
400.0
224+
} else {
225+
800.0
226+
}))
163227
.into()
164228
}
165229
}

0 commit comments

Comments
 (0)