Use spend state in recovery panel
This commit is contained in:
parent
a2ac34e6b0
commit
9a1cda2f5e
@ -23,6 +23,7 @@ pub enum Message {
|
||||
Coins(Result<Vec<Coin>, Error>),
|
||||
SpendTxs(Result<Vec<SpendTx>, Error>),
|
||||
Psbt(Result<Psbt, Error>),
|
||||
Recovery(Result<SpendTx, Error>),
|
||||
Signed(Result<(Psbt, Fingerprint), Error>),
|
||||
Updated(Result<(), Error>),
|
||||
Saved(Result<(), Error>),
|
||||
|
||||
@ -10,19 +10,19 @@ use crate::{
|
||||
error::Error,
|
||||
menu::Menu,
|
||||
message::Message,
|
||||
state::spend::detail,
|
||||
state::{redirect, State},
|
||||
view,
|
||||
wallet::Wallet,
|
||||
},
|
||||
daemon::{
|
||||
model::{remaining_sequence, Coin},
|
||||
model::{remaining_sequence, Coin, SpendTx},
|
||||
Daemon,
|
||||
},
|
||||
hw::{list_hardware_wallets, HardwareWallet},
|
||||
ui::component::form,
|
||||
};
|
||||
|
||||
use liana::miniscript::bitcoin::{util::psbt::Psbt, Address, Amount, Network};
|
||||
use liana::miniscript::bitcoin::{Address, Amount, Network};
|
||||
|
||||
pub struct RecoveryPanel {
|
||||
wallet: Wallet,
|
||||
@ -32,10 +32,7 @@ pub struct RecoveryPanel {
|
||||
warning: Option<Error>,
|
||||
feerate: form::Value<String>,
|
||||
recipient: form::Value<String>,
|
||||
generated: Option<Psbt>,
|
||||
hws: Vec<HardwareWallet>,
|
||||
selected_hw: Option<usize>,
|
||||
signed: bool,
|
||||
generated: Option<detail::SpendTxState>,
|
||||
/// timelock value to pass for the heir to consume a coin.
|
||||
timelock: u32,
|
||||
}
|
||||
@ -72,30 +69,27 @@ impl RecoveryPanel {
|
||||
recipient: form::Value::default(),
|
||||
generated: None,
|
||||
timelock,
|
||||
hws: Vec::new(),
|
||||
selected_hw: None,
|
||||
signed: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl State for RecoveryPanel {
|
||||
fn view<'a>(&'a self, _cache: &'a Cache) -> Element<'a, view::Message> {
|
||||
view::modal(
|
||||
false,
|
||||
self.warning.as_ref(),
|
||||
view::recovery::recovery(
|
||||
&self.locked_coins,
|
||||
&self.recoverable_coins,
|
||||
&self.feerate,
|
||||
&self.recipient,
|
||||
self.generated.as_ref(),
|
||||
&self.hws,
|
||||
self.selected_hw,
|
||||
self.signed,
|
||||
),
|
||||
None::<Element<view::Message>>,
|
||||
)
|
||||
fn view<'a>(&'a self, cache: &'a Cache) -> Element<'a, view::Message> {
|
||||
if let Some(generated) = &self.generated {
|
||||
generated.view(cache)
|
||||
} else {
|
||||
view::modal(
|
||||
false,
|
||||
self.warning.as_ref(),
|
||||
view::recovery::recovery(
|
||||
&self.locked_coins,
|
||||
&self.recoverable_coins,
|
||||
&self.feerate,
|
||||
&self.recipient,
|
||||
),
|
||||
None::<Element<view::Message>>,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn update(
|
||||
@ -127,27 +121,18 @@ impl State for RecoveryPanel {
|
||||
}
|
||||
}
|
||||
},
|
||||
// We add the new hws without dropping the reference of the previous ones.
|
||||
Message::ConnectedHardwareWallets(hws) => {
|
||||
for h in hws {
|
||||
if !self.hws.iter().any(|hw| hw.fingerprint == h.fingerprint) {
|
||||
self.hws.push(h);
|
||||
}
|
||||
Message::Recovery(res) => match res {
|
||||
Ok(tx) => {
|
||||
self.generated = Some(detail::SpendTxState::new(
|
||||
self.wallet.clone(),
|
||||
self.config.clone(),
|
||||
tx,
|
||||
false,
|
||||
))
|
||||
}
|
||||
}
|
||||
Message::Psbt(res) => match res {
|
||||
Ok(psbt) => self.generated = Some(psbt),
|
||||
Err(e) => self.warning = Some(e),
|
||||
},
|
||||
Message::Updated(res) => match res {
|
||||
Err(e) => self.warning = Some(e),
|
||||
Ok(()) => {
|
||||
self.warning = None;
|
||||
self.signed = true;
|
||||
}
|
||||
},
|
||||
Message::View(msg) => match msg {
|
||||
view::Message::Reload => return self.load(daemon),
|
||||
view::Message::Close => return redirect(Menu::Settings),
|
||||
view::Message::Previous => self.generated = None,
|
||||
view::Message::CreateSpend(view::CreateSpendMessage::RecipientEdited(
|
||||
@ -177,68 +162,52 @@ impl State for RecoveryPanel {
|
||||
self.warning = None;
|
||||
return Command::perform(
|
||||
async move {
|
||||
daemon
|
||||
.create_recovery(address, feerate_vb)
|
||||
.map_err(|e| e.into())
|
||||
let psbt = daemon.create_recovery(address, feerate_vb)?;
|
||||
let coins = daemon.list_coins().map(|res| res.coins)?;
|
||||
let coins = coins
|
||||
.iter()
|
||||
.filter(|coin| {
|
||||
psbt.unsigned_tx
|
||||
.input
|
||||
.iter()
|
||||
.any(|input| input.previous_output == coin.outpoint)
|
||||
})
|
||||
.copied()
|
||||
.collect();
|
||||
Ok(SpendTx::new(psbt, coins))
|
||||
},
|
||||
Message::Psbt,
|
||||
Message::Recovery,
|
||||
);
|
||||
}
|
||||
view::Message::Spend(view::SpendTxMessage::SelectHardwareWallet(i)) => {
|
||||
if let Some(hw) = self.hws.get(i) {
|
||||
let device = hw.device.clone();
|
||||
self.selected_hw = Some(i);
|
||||
let psbt = self.generated.clone().unwrap();
|
||||
return Command::perform(
|
||||
send_funds(daemon, device, psbt),
|
||||
Message::Updated,
|
||||
);
|
||||
_ => {
|
||||
if let Some(generated) = &mut self.generated {
|
||||
return generated.update(daemon, cache, Message::View(msg));
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
_ => {}
|
||||
_ => {
|
||||
if let Some(generated) = &mut self.generated {
|
||||
return generated.update(daemon, cache, message);
|
||||
}
|
||||
}
|
||||
};
|
||||
Command::none()
|
||||
}
|
||||
|
||||
fn load(&self, daemon: Arc<dyn Daemon + Sync + Send>) -> Command<Message> {
|
||||
let config = self.config.clone();
|
||||
let desc = self.wallet.main_descriptor.to_string();
|
||||
let daemon = daemon.clone();
|
||||
Command::batch(vec![
|
||||
Command::perform(
|
||||
async move {
|
||||
daemon
|
||||
.list_coins()
|
||||
.map(|res| res.coins)
|
||||
.map_err(|e| e.into())
|
||||
},
|
||||
Message::Coins,
|
||||
),
|
||||
Command::perform(
|
||||
list_hws(config, self.wallet.name.clone(), desc),
|
||||
Message::ConnectedHardwareWallets,
|
||||
),
|
||||
])
|
||||
Command::perform(
|
||||
async move {
|
||||
daemon
|
||||
.list_coins()
|
||||
.map(|res| res.coins)
|
||||
.map_err(|e| e.into())
|
||||
},
|
||||
Message::Coins,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
async fn list_hws(config: Config, wallet_name: String, descriptor: String) -> Vec<HardwareWallet> {
|
||||
list_hardware_wallets(&config.hardware_wallets, Some((&wallet_name, &descriptor))).await
|
||||
}
|
||||
|
||||
async fn send_funds(
|
||||
daemon: Arc<dyn Daemon + Sync + Send>,
|
||||
hw: std::sync::Arc<dyn async_hwi::HWI + Send + Sync>,
|
||||
mut psbt: Psbt,
|
||||
) -> Result<(), Error> {
|
||||
hw.sign_tx(&mut psbt).await.map_err(Error::from)?;
|
||||
daemon.update_spend_tx(&psbt)?;
|
||||
daemon.broadcast_spend_tx(&psbt.unsigned_tx.txid())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl From<RecoveryPanel> for Box<dyn State> {
|
||||
fn from(s: RecoveryPanel) -> Box<dyn State> {
|
||||
Box::new(s)
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
mod detail;
|
||||
pub mod detail;
|
||||
mod step;
|
||||
use std::sync::Arc;
|
||||
|
||||
|
||||
@ -1,18 +1,14 @@
|
||||
use iced::{
|
||||
widget::{Button, Column, Container, Row, Space},
|
||||
widget::{Column, Container, Row, Space},
|
||||
Alignment, Element, Length,
|
||||
};
|
||||
|
||||
use liana::miniscript::bitcoin::{util::psbt::Psbt, Amount};
|
||||
use liana::miniscript::bitcoin::Amount;
|
||||
|
||||
use crate::{
|
||||
app::view::{
|
||||
hw::hw_list_view,
|
||||
message::{CreateSpendMessage, Message},
|
||||
},
|
||||
hw::HardwareWallet,
|
||||
app::view::message::{CreateSpendMessage, Message},
|
||||
ui::{
|
||||
component::{button, card, form, text::*},
|
||||
component::{button, form, text::*},
|
||||
icon,
|
||||
util::Collection,
|
||||
},
|
||||
@ -24,10 +20,6 @@ pub fn recovery<'a>(
|
||||
recoverable_coins: &(usize, Amount),
|
||||
feerate: &form::Value<String>,
|
||||
address: &'a form::Value<String>,
|
||||
generated: Option<&Psbt>,
|
||||
hws: &[HardwareWallet],
|
||||
chosen_hw: Option<usize>,
|
||||
done: bool,
|
||||
) -> Element<'a, Message> {
|
||||
Column::new()
|
||||
.push(Space::with_height(Length::Units(100)))
|
||||
@ -59,173 +51,7 @@ pub fn recovery<'a>(
|
||||
None
|
||||
})
|
||||
.push(Space::with_height(Length::Units(20)))
|
||||
.push(if let Some(psbt) = generated {
|
||||
if done {
|
||||
Column::new()
|
||||
.spacing(20)
|
||||
.align_items(Alignment::Center)
|
||||
.push(text("Funds were sweeped"))
|
||||
.push(card::simple(
|
||||
Column::new()
|
||||
.push(
|
||||
Row::new()
|
||||
.spacing(5)
|
||||
.align_items(Alignment::Center)
|
||||
.push(
|
||||
text(format!(
|
||||
"{}",
|
||||
Amount::from_sat(psbt.unsigned_tx.output[0].value)
|
||||
))
|
||||
.small()
|
||||
.bold(),
|
||||
)
|
||||
.push(text(" to ").small())
|
||||
.push(text(&address.value).small().bold()),
|
||||
)
|
||||
.push(
|
||||
Row::new()
|
||||
.spacing(5)
|
||||
.align_items(Alignment::Center)
|
||||
.push(
|
||||
text(format!("Txid: {}", psbt.unsigned_tx.txid())).small(),
|
||||
)
|
||||
.push(
|
||||
Button::new(icon::clipboard_icon().small())
|
||||
.on_press(Message::Clipboard(
|
||||
psbt.unsigned_tx.txid().to_string(),
|
||||
))
|
||||
.style(button::Style::Border.into()),
|
||||
),
|
||||
)
|
||||
.push_maybe(
|
||||
if recoverable_coins.1.to_sat() > psbt.unsigned_tx.output[0].value {
|
||||
Some(
|
||||
Row::new().push(
|
||||
text(format!(
|
||||
"Fees: {}",
|
||||
recoverable_coins.1
|
||||
- Amount::from_sat(
|
||||
psbt.unsigned_tx.output[0].value
|
||||
)
|
||||
))
|
||||
.small(),
|
||||
),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
),
|
||||
))
|
||||
} else {
|
||||
Column::new()
|
||||
.spacing(20)
|
||||
.align_items(Alignment::Center)
|
||||
.push_maybe(if chosen_hw.is_none() {
|
||||
Some(button::border(None, "< Previous").on_press(Message::Previous))
|
||||
} else {
|
||||
None
|
||||
})
|
||||
.push(text("Sign the transaction to sweep the funds").bold())
|
||||
.push(card::simple(
|
||||
Column::new()
|
||||
.push(
|
||||
Row::new()
|
||||
.spacing(5)
|
||||
.align_items(Alignment::Center)
|
||||
.push(
|
||||
text(format!(
|
||||
"{}",
|
||||
Amount::from_sat(psbt.unsigned_tx.output[0].value)
|
||||
))
|
||||
.small()
|
||||
.bold(),
|
||||
)
|
||||
.push(text(" to ").small())
|
||||
.push(text(&address.value).small().bold()),
|
||||
)
|
||||
.push(
|
||||
Row::new()
|
||||
.spacing(5)
|
||||
.align_items(Alignment::Center)
|
||||
.push(
|
||||
text(format!("Txid: {}", psbt.unsigned_tx.txid())).small(),
|
||||
)
|
||||
.push(
|
||||
Button::new(icon::clipboard_icon().small())
|
||||
.on_press(Message::Clipboard(
|
||||
psbt.unsigned_tx.txid().to_string(),
|
||||
))
|
||||
.style(button::Style::Border.into()),
|
||||
),
|
||||
)
|
||||
.push_maybe(
|
||||
if recoverable_coins.1.to_sat() > psbt.unsigned_tx.output[0].value {
|
||||
Some(
|
||||
Row::new().push(
|
||||
text(format!(
|
||||
"Fees: {}",
|
||||
recoverable_coins.1
|
||||
- Amount::from_sat(
|
||||
psbt.unsigned_tx.output[0].value
|
||||
)
|
||||
))
|
||||
.small(),
|
||||
),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
),
|
||||
))
|
||||
.push(if !hws.is_empty() {
|
||||
Column::new()
|
||||
.push(
|
||||
Row::new()
|
||||
.align_items(Alignment::Center)
|
||||
.push(
|
||||
text("Select hardware wallet to sign with:")
|
||||
.bold()
|
||||
.width(Length::Fill),
|
||||
)
|
||||
.push_maybe(if chosen_hw.is_none() {
|
||||
Some(
|
||||
button::border(None, "Refresh")
|
||||
.on_press(Message::Reload),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
}),
|
||||
)
|
||||
.spacing(10)
|
||||
.push(hws.iter().enumerate().fold(
|
||||
Column::new().spacing(10),
|
||||
|col, (i, hw)| {
|
||||
col.push(hw_list_view(
|
||||
i,
|
||||
hw,
|
||||
Some(i) == chosen_hw,
|
||||
chosen_hw.is_some(),
|
||||
false,
|
||||
))
|
||||
},
|
||||
))
|
||||
.max_width(500)
|
||||
} else {
|
||||
Column::new()
|
||||
.push(
|
||||
Column::new()
|
||||
.spacing(20)
|
||||
.width(Length::Fill)
|
||||
.push("Please connect a hardware wallet")
|
||||
.push(
|
||||
button::primary(None, "Refresh").on_press(Message::Reload),
|
||||
)
|
||||
.align_items(Alignment::Center),
|
||||
)
|
||||
.width(Length::Fill)
|
||||
})
|
||||
}
|
||||
} else {
|
||||
.push(
|
||||
Column::new()
|
||||
.push(text("Enter destination address and feerate:").bold())
|
||||
.push(
|
||||
@ -267,8 +93,8 @@ pub fn recovery<'a>(
|
||||
},
|
||||
)
|
||||
.spacing(20)
|
||||
.align_items(Alignment::Center)
|
||||
})
|
||||
.align_items(Alignment::Center),
|
||||
)
|
||||
.align_items(Alignment::Center)
|
||||
.spacing(20)
|
||||
.into()
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user