diff --git a/gui/Cargo.lock b/gui/Cargo.lock index 17f0f2bd0..6ae01d55b 100644 --- a/gui/Cargo.lock +++ b/gui/Cargo.lock @@ -2668,7 +2668,7 @@ dependencies = [ [[package]] name = "liana" version = "4.0.0" -source = "git+https://github.com/wizardsardine/liana?branch=master#7ca6cc8dfa8dfdde805dc34fa408337f1a3291e2" +source = "git+https://github.com/wizardsardine/liana?branch=master#5a56fdb108351d0a6877a11a1dec7a27a3b0928f" dependencies = [ "backtrace", "bdk_coin_select", @@ -4270,7 +4270,7 @@ version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f622567e3b4b38154fb8190bcf6b160d7a4301d70595a49195b48c116007a27" dependencies = [ - "bitcoin_hashes 0.13.0", + "bitcoin_hashes 0.12.0", "secp256k1-sys", "serde", ] diff --git a/gui/src/app/state/recovery.rs b/gui/src/app/state/recovery.rs index 18623837c..2e7811e84 100644 --- a/gui/src/app/state/recovery.rs +++ b/gui/src/app/state/recovery.rs @@ -4,7 +4,10 @@ use std::sync::Arc; use iced::Command; -use liana::miniscript::bitcoin::bip32::{DerivationPath, Fingerprint}; +use liana::miniscript::bitcoin::{ + bip32::{DerivationPath, Fingerprint}, + secp256k1, +}; use liana_ui::{component::form, widget::Element}; use crate::{ @@ -162,7 +165,14 @@ impl State for RecoveryPanel { .any(|input| input.previous_output == coin.outpoint) }) .collect(); - Ok(SpendTx::new(None, psbt, coins, &desc, network)) + Ok(SpendTx::new( + None, + psbt, + coins, + &desc, + &secp256k1::Secp256k1::verification_only(), + network, + )) }, Message::Recovery, ); diff --git a/gui/src/app/state/spend/step.rs b/gui/src/app/state/spend/step.rs index 886d831a9..d5dd1dadc 100644 --- a/gui/src/app/state/spend/step.rs +++ b/gui/src/app/state/spend/step.rs @@ -19,7 +19,7 @@ use liana_ui::{component::form, widget::Element}; use crate::{ app::{cache::Cache, error::Error, message::Message, state::psbt, view, wallet::Wallet}, daemon::{ - model::{remaining_sequence, Coin, SpendTx}, + model::{remaining_sequence, Coin, CreateSpendResult, SpendTx}, Daemon, }, }; @@ -382,8 +382,16 @@ impl Step for DefineSpend { async move { daemon .create_spend_tx(&inputs, &outputs, feerate_vb) - .map(|res| res.psbt) .map_err(|e| e.into()) + .and_then(|res| match res { + CreateSpendResult::Success { psbt, .. } => Ok(psbt), + CreateSpendResult::InsufficientFunds { missing } => { + Err(SpendCreationError::CoinSelection( + liana::spend::InsufficientFunds { missing }, + ) + .into()) + } + }) }, Message::Psbt, ); @@ -576,6 +584,7 @@ impl Recipient { pub struct SaveSpend { wallet: Arc, spend: Option, + curve: secp256k1::Secp256k1, } impl SaveSpend { @@ -583,6 +592,7 @@ impl SaveSpend { Self { wallet, spend: None, + curve: secp256k1::Secp256k1::verification_only(), } } } @@ -595,6 +605,7 @@ impl Step for SaveSpend { psbt, draft.inputs.clone(), &self.wallet.main_descriptor, + &self.curve, draft.network, ); tx.labels = draft.labels.clone(); diff --git a/gui/src/app/state/transactions.rs b/gui/src/app/state/transactions.rs index 3f314cc41..0967a85bb 100644 --- a/gui/src/app/state/transactions.rs +++ b/gui/src/app/state/transactions.rs @@ -5,7 +5,10 @@ use std::{ }; use iced::Command; -use liana::{miniscript::bitcoin::Txid, spend::MAX_FEERATE}; +use liana::{ + miniscript::bitcoin::Txid, + spend::{SpendCreationError, MAX_FEERATE}, +}; use liana_ui::{ component::{form, modal::Modal}, widget::*, @@ -20,7 +23,7 @@ use crate::app::{ }; use crate::daemon::{ - model::{HistoryTransaction, Labelled}, + model::{CreateSpendResult, HistoryTransaction, Labelled}, Daemon, }; @@ -302,7 +305,18 @@ impl CreateRbfModal { self.warning = None; let psbt = match daemon.rbf_psbt(&self.txid, self.is_cancel, self.feerate_vb) { - Ok(res) => res.psbt, + Ok(res) => match res { + CreateSpendResult::Success { psbt, .. } => psbt, + CreateSpendResult::InsufficientFunds { missing } => { + self.warning = Some( + SpendCreationError::CoinSelection( + liana::spend::InsufficientFunds { missing }, + ) + .into(), + ); + return Command::none(); + } + }, Err(e) => { self.warning = Some(e.into()); return Command::none(); diff --git a/gui/src/daemon/client/mod.rs b/gui/src/daemon/client/mod.rs index 7f7670bcd..1f6a4ac82 100644 --- a/gui/src/daemon/client/mod.rs +++ b/gui/src/daemon/client/mod.rs @@ -2,6 +2,7 @@ use std::collections::{HashMap, HashSet}; use std::fmt::Debug; use std::iter::FromIterator; +use liana::commands::CreateRecoveryResult; use serde::de::DeserializeOwned; use serde::{Deserialize, Serialize}; use serde_json::json; @@ -154,7 +155,7 @@ impl Daemon for Lianad { feerate_vb: u64, sequence: Option, ) -> Result { - let res: CreateSpendResult = self.call( + let res: CreateRecoveryResult = self.call( "createrecovery", Some(vec![json!(address), json!(feerate_vb), json!(sequence)]), )?; diff --git a/gui/src/daemon/mod.rs b/gui/src/daemon/mod.rs index 384526064..72ddf1b2f 100644 --- a/gui/src/daemon/mod.rs +++ b/gui/src/daemon/mod.rs @@ -10,7 +10,7 @@ use std::iter::FromIterator; use liana::{ commands::LabelItem, config::Config, - miniscript::bitcoin::{address, psbt::Psbt, Address, OutPoint, Txid}, + miniscript::bitcoin::{address, psbt::Psbt, secp256k1, Address, OutPoint, Txid}, StartupError, }; @@ -102,6 +102,7 @@ pub trait Daemon: Debug { let info = self.get_info()?; let coins = self.list_coins()?.coins; let mut spend_txs = Vec::new(); + let curve = secp256k1::Secp256k1::verification_only(); for tx in self.list_spend_txs()?.spend_txs { if let Some(txids) = txids { if !txids.contains(&tx.psbt.unsigned_tx.txid()) { @@ -125,6 +126,7 @@ pub trait Daemon: Debug { tx.psbt, coins, &info.descriptors.main, + &curve, info.network, )); } diff --git a/gui/src/daemon/model.rs b/gui/src/daemon/model.rs index f663afc1a..e352b0db1 100644 --- a/gui/src/daemon/model.rs +++ b/gui/src/daemon/model.rs @@ -11,7 +11,7 @@ pub use liana::{ miniscript::bitcoin::{ bip32::{DerivationPath, Fingerprint}, psbt::Psbt, - Address, Amount, Network, OutPoint, Transaction, Txid, + secp256k1, Address, Amount, Network, OutPoint, Transaction, Txid, }, }; @@ -61,15 +61,19 @@ impl SpendTx { psbt: Psbt, coins: Vec, desc: &LianaDescriptor, + secp: &secp256k1::Secp256k1, network: Network, ) -> Self { let max_vbytes = desc.unsigned_tx_max_vbytes(&psbt.unsigned_tx); - let mut change_indexes = Vec::new(); + let change_indexes: Vec = desc + .change_indexes(&psbt, secp) + .into_iter() + .map(|c| c.index()) + .collect(); let (change_amount, spend_amount) = psbt.unsigned_tx.output.iter().enumerate().fold( (Amount::from_sat(0), Amount::from_sat(0)), |(change, spend), (i, output)| { - if !psbt.outputs[i].bip32_derivation.is_empty() { - change_indexes.push(i); + if change_indexes.contains(&i) { (change + output.value, spend) } else { (change, spend + output.value)