Skip to content

Commit

Permalink
gui: enable use of RBF on pending transactions
Browse files Browse the repository at this point in the history
  • Loading branch information
jp1ac4 committed Dec 6, 2023
1 parent c1aeade commit 2f9568c
Show file tree
Hide file tree
Showing 9 changed files with 298 additions and 10 deletions.
3 changes: 2 additions & 1 deletion gui/src/app/menu.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use liana::miniscript::bitcoin::OutPoint;
use liana::miniscript::bitcoin::{OutPoint, Txid};
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Menu {
Home,
Expand All @@ -10,4 +10,5 @@ pub enum Menu {
CreateSpendTx,
Recovery,
RefreshCoins(Vec<OutPoint>),
PsbtPreSelected(Txid),
}
4 changes: 4 additions & 0 deletions gui/src/app/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,10 @@ impl App {
}
menu::Menu::Transactions => TransactionsPanel::new().into(),
menu::Menu::PSBTs => PsbtsPanel::new(self.wallet.clone(), &self.cache.spend_txs).into(),
menu::Menu::PsbtPreSelected(txid) => {
PsbtsPanel::new_preselected_txid(self.wallet.clone(), &self.cache.spend_txs, txid)
.into()
}
menu::Menu::CreateSpendTx => CreateSpendPanel::new(
self.wallet.clone(),
&self.cache.coins,
Expand Down
31 changes: 30 additions & 1 deletion gui/src/app/state/psbts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@ use liana_ui::{
use super::{psbt, State};
use crate::{
app::{cache::Cache, error::Error, menu::Menu, message::Message, view, wallet::Wallet},
daemon::{model::SpendTx, Daemon},
daemon::{
model::{SpendTx, Txid},
Daemon,
},
};

pub struct PsbtsPanel {
Expand All @@ -20,6 +23,7 @@ pub struct PsbtsPanel {
spend_txs: Vec<SpendTx>,
warning: Option<Error>,
import_tx: Option<ImportPsbtModal>,
preselected_txid: Option<Txid>,
}

impl PsbtsPanel {
Expand All @@ -30,6 +34,18 @@ impl PsbtsPanel {
warning: None,
selected_tx: None,
import_tx: None,
preselected_txid: None,
}
}

pub fn new_preselected_txid(wallet: Arc<Wallet>, spend_txs: &[SpendTx], txid: &Txid) -> Self {
Self {
wallet,
spend_txs: spend_txs.to_vec(),
warning: None,
selected_tx: None,
import_tx: None,
preselected_txid: Some(*txid),
}
}
}
Expand Down Expand Up @@ -71,6 +87,19 @@ impl State for PsbtsPanel {
Ok(txs) => {
self.warning = None;
self.spend_txs = txs;
if let Some(txid) = self.preselected_txid {
if let Some(spend_tx) = self
.spend_txs
.iter()
.find(|spend_tx| spend_tx.psbt.unsigned_tx.txid() == txid)
{
let psbt_state =
psbt::PsbtState::new(self.wallet.clone(), spend_tx.clone(), true);
let cmd = psbt_state.load(daemon);
self.selected_tx = Some(psbt_state);
return cmd;
}
}
}
},
Message::View(view::Message::ImportSpend(view::ImportSpendMessage::Import)) => {
Expand Down
134 changes: 130 additions & 4 deletions gui/src/app/state/transactions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@ use std::{
};

use iced::Command;
use liana_ui::widget::*;
use liana::miniscript::bitcoin::Txid;
use liana_ui::{
component::{form, modal::Modal},
widget::*,
};

use crate::app::{
cache::Cache,
Expand All @@ -27,6 +31,7 @@ pub struct TransactionsPanel {
labels_edited: LabelsEdited,
selected_tx: Option<usize>,
warning: Option<Error>,
create_rbf_modal: Option<CreateRbfModal>,
}

impl TransactionsPanel {
Expand All @@ -37,6 +42,7 @@ impl TransactionsPanel {
pending_txs: Vec::new(),
labels_edited: LabelsEdited::default(),
warning: None,
create_rbf_modal: None,
}
}
}
Expand All @@ -49,12 +55,21 @@ impl State for TransactionsPanel {
} else {
&self.txs[i - self.pending_txs.len()]
};
view::transactions::tx_view(
let content = view::transactions::tx_view(
cache,
tx,
self.labels_edited.cache(),
self.warning.as_ref(),
)
);
if let Some(modal) = &self.create_rbf_modal {
Modal::new(content, modal.view())
.on_blur(Some(view::Message::CreateRbf(
view::CreateRbfMessage::Cancel,
)))
.into()
} else {
content
}
} else {
view::transactions::transactions_view(
cache,
Expand Down Expand Up @@ -100,6 +115,23 @@ impl State for TransactionsPanel {
Message::View(view::Message::Select(i)) => {
self.selected_tx = Some(i);
}
Message::View(view::Message::CreateRbf(view::CreateRbfMessage::Cancel)) => {
self.create_rbf_modal = None;
}
Message::View(view::Message::CreateRbf(view::CreateRbfMessage::New(is_cancel))) => {
if let Some(idx) = self.selected_tx {
if let Some(tx) = self.pending_txs.get(idx) {
let prev_feerate_vb = tx
.fee_amount
.expect("fee_amount must be set for pending transaction")
.to_sat()
.checked_div(tx.tx.vsize().try_into().unwrap())
.unwrap();
let modal = CreateRbfModal::new(tx.tx.txid(), is_cancel, prev_feerate_vb);
self.create_rbf_modal = Some(modal);
}
}
}
Message::View(view::Message::Label(_, _)) | Message::LabelsUpdated(_) => {
match self.labels_edited.update(
daemon,
Expand Down Expand Up @@ -154,7 +186,11 @@ impl State for TransactionsPanel {
);
}
}
_ => {}
_ => {
if let Some(modal) = &mut self.create_rbf_modal {
return modal.update(daemon, _cache, message);
}
}
};
Command::none()
}
Expand Down Expand Up @@ -200,3 +236,93 @@ impl From<TransactionsPanel> for Box<dyn State> {
Box::new(s)
}
}

pub struct CreateRbfModal {
/// Transaction to replace.
txid: Txid,
/// Whether to cancel or bump fee.
is_cancel: bool,
/// Min feerate required for RBF.
min_feerate_vb: u64,
/// Feerate form value.
feerate_val: form::Value<String>,
/// Parsed feerate.
feerate_vb: Option<u64>,
warning: Option<Error>,
/// Replacement transaction ID.
replacement_txid: Option<Txid>,
}

impl CreateRbfModal {
fn new(txid: Txid, is_cancel: bool, prev_feerate_vb: u64) -> Self {
let min_feerate_vb = prev_feerate_vb.checked_add(1).unwrap();
Self {
txid,
is_cancel,
min_feerate_vb,
feerate_val: form::Value {
valid: true,
value: min_feerate_vb.to_string(),
},
// For cancel, we let `rbfpsbt` set the feerate.
feerate_vb: if is_cancel {
None
} else {
Some(min_feerate_vb)
},
warning: None,
replacement_txid: None,
}
}

fn update(
&mut self,
daemon: Arc<dyn Daemon + Sync + Send>,
_cache: &Cache,
message: Message,
) -> Command<Message> {
match message {
Message::View(view::Message::CreateRbf(view::CreateRbfMessage::FeerateEdited(s))) => {
self.warning = None;
if let Ok(value) = s.parse::<u64>() {
self.feerate_val.value = s;
self.feerate_val.valid = value >= self.min_feerate_vb;
if self.feerate_val.valid {
self.feerate_vb = Some(value);
}
} else {
self.feerate_val.valid = false;
}
if !self.feerate_val.valid {
self.feerate_vb = None;
}
}
Message::View(view::Message::CreateRbf(view::CreateRbfMessage::Confirm)) => {
self.warning = None;

let psbt = match daemon.rbf_psbt(&self.txid, self.is_cancel, self.feerate_vb) {
Ok(res) => res.psbt,
Err(e) => {
self.warning = Some(e.into());
return Command::none();
}
};
if let Err(e) = daemon.update_spend_tx(&psbt) {
self.warning = Some(e.into());
return Command::none();
}
self.replacement_txid = Some(psbt.unsigned_tx.txid());
}
_ => {}
}
Command::none()
}
fn view(&self) -> Element<view::Message> {
view::transactions::create_rbf_modal(
self.is_cancel,
&self.feerate_val,
self.replacement_txid,
self.warning.as_ref(),
)
}
}
9 changes: 9 additions & 0 deletions gui/src/app/view/message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ pub enum Message {
Next,
Previous,
SelectHardwareWallet(usize),
CreateRbf(CreateRbfMessage),
}

#[derive(Debug, Clone)]
Expand Down Expand Up @@ -77,3 +78,11 @@ pub enum SettingsEditMessage {
Cancel,
Confirm,
}

#[derive(Debug, Clone)]
pub enum CreateRbfMessage {
New(bool),
FeerateEdited(String),
Cancel,
Confirm,
}
Loading

0 comments on commit 2f9568c

Please sign in to comment.