parent
b5a3e78b38
commit
4ccecd1cdf
2
gui/Cargo.lock
generated
2
gui/Cargo.lock
generated
@ -2431,7 +2431,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "liana"
|
name = "liana"
|
||||||
version = "2.0.0"
|
version = "2.0.0"
|
||||||
source = "git+https://github.com/wizardsardine/liana?branch=master#6151c57af492dacc8502b0ea1ec1cd04580e08dc"
|
source = "git+https://github.com/wizardsardine/liana?branch=master#3e0f82a71e031604fcf8bb0fd757ae95908e8ca6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"backtrace",
|
"backtrace",
|
||||||
"bdk_coin_select",
|
"bdk_coin_select",
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
use std::convert::From;
|
use std::convert::From;
|
||||||
use std::io::ErrorKind;
|
use std::io::ErrorKind;
|
||||||
|
|
||||||
use liana::{config::ConfigError, descriptors::LianaDescError};
|
use liana::{config::ConfigError, descriptors::LianaDescError, spend::SpendCreationError};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
app::{settings::SettingsError, wallet::WalletError},
|
app::{settings::SettingsError, wallet::WalletError},
|
||||||
@ -16,6 +16,7 @@ pub enum Error {
|
|||||||
Unexpected(String),
|
Unexpected(String),
|
||||||
HardwareWallet(async_hwi::Error),
|
HardwareWallet(async_hwi::Error),
|
||||||
Desc(LianaDescError),
|
Desc(LianaDescError),
|
||||||
|
Spend(SpendCreationError),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Display for Error {
|
impl std::fmt::Display for Error {
|
||||||
@ -23,6 +24,7 @@ impl std::fmt::Display for Error {
|
|||||||
match self {
|
match self {
|
||||||
Self::Config(e) => write!(f, "{}", e),
|
Self::Config(e) => write!(f, "{}", e),
|
||||||
Self::Wallet(e) => write!(f, "{}", e),
|
Self::Wallet(e) => write!(f, "{}", e),
|
||||||
|
Self::Spend(e) => write!(f, "{}", e),
|
||||||
Self::Daemon(e) => match e {
|
Self::Daemon(e) => match e {
|
||||||
DaemonError::Unexpected(e) => write!(f, "{}", e),
|
DaemonError::Unexpected(e) => write!(f, "{}", e),
|
||||||
DaemonError::NoAnswer => write!(f, "Daemon did not answer"),
|
DaemonError::NoAnswer => write!(f, "Daemon did not answer"),
|
||||||
@ -84,3 +86,9 @@ impl From<async_hwi::Error> for Error {
|
|||||||
Error::HardwareWallet(error)
|
Error::HardwareWallet(error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<SpendCreationError> for Error {
|
||||||
|
fn from(error: SpendCreationError) -> Self {
|
||||||
|
Error::Spend(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -216,6 +216,8 @@ mod tests {
|
|||||||
spend_info: None,
|
spend_info: None,
|
||||||
is_immature: false,
|
is_immature: false,
|
||||||
address: dummy_address.clone(),
|
address: dummy_address.clone(),
|
||||||
|
derivation_index: 0.into(),
|
||||||
|
is_change: false,
|
||||||
},
|
},
|
||||||
Coin {
|
Coin {
|
||||||
outpoint: bitcoin::OutPoint { txid, vout: 3 },
|
outpoint: bitcoin::OutPoint { txid, vout: 3 },
|
||||||
@ -224,6 +226,8 @@ mod tests {
|
|||||||
spend_info: None,
|
spend_info: None,
|
||||||
is_immature: false,
|
is_immature: false,
|
||||||
address: dummy_address.clone(),
|
address: dummy_address.clone(),
|
||||||
|
derivation_index: 1.into(),
|
||||||
|
is_change: false,
|
||||||
},
|
},
|
||||||
Coin {
|
Coin {
|
||||||
outpoint: bitcoin::OutPoint { txid, vout: 0 },
|
outpoint: bitcoin::OutPoint { txid, vout: 0 },
|
||||||
@ -232,6 +236,8 @@ mod tests {
|
|||||||
spend_info: None,
|
spend_info: None,
|
||||||
is_immature: false,
|
is_immature: false,
|
||||||
address: dummy_address.clone(),
|
address: dummy_address.clone(),
|
||||||
|
derivation_index: 2.into(),
|
||||||
|
is_change: false,
|
||||||
},
|
},
|
||||||
Coin {
|
Coin {
|
||||||
outpoint: bitcoin::OutPoint { txid, vout: 1 },
|
outpoint: bitcoin::OutPoint { txid, vout: 1 },
|
||||||
@ -240,6 +246,8 @@ mod tests {
|
|||||||
spend_info: None,
|
spend_info: None,
|
||||||
is_immature: false,
|
is_immature: false,
|
||||||
address: dummy_address,
|
address: dummy_address,
|
||||||
|
derivation_index: 3.into(),
|
||||||
|
is_change: false,
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|||||||
@ -32,7 +32,7 @@ impl CreateSpendPanel {
|
|||||||
current: 0,
|
current: 0,
|
||||||
steps: vec![
|
steps: vec![
|
||||||
Box::new(
|
Box::new(
|
||||||
step::DefineSpend::new(descriptor, coins, timelock)
|
step::DefineSpend::new(network, descriptor, coins, timelock)
|
||||||
.with_coins_sorted(blockheight),
|
.with_coins_sorted(blockheight),
|
||||||
),
|
),
|
||||||
Box::new(step::SaveSpend::new(wallet)),
|
Box::new(step::SaveSpend::new(wallet)),
|
||||||
@ -54,7 +54,7 @@ impl CreateSpendPanel {
|
|||||||
current: 0,
|
current: 0,
|
||||||
steps: vec![
|
steps: vec![
|
||||||
Box::new(
|
Box::new(
|
||||||
step::DefineSpend::new(descriptor, coins, timelock)
|
step::DefineSpend::new(network, descriptor, coins, timelock)
|
||||||
.with_preselected_coins(preselected_coins)
|
.with_preselected_coins(preselected_coins)
|
||||||
.with_coins_sorted(blockheight)
|
.with_coins_sorted(blockheight)
|
||||||
.self_send(),
|
.self_send(),
|
||||||
|
|||||||
@ -6,7 +6,10 @@ use iced::{Command, Subscription};
|
|||||||
use liana::{
|
use liana::{
|
||||||
descriptors::LianaDescriptor,
|
descriptors::LianaDescriptor,
|
||||||
miniscript::bitcoin::{
|
miniscript::bitcoin::{
|
||||||
self, address, psbt::Psbt, Address, Amount, Denomination, Network, OutPoint,
|
self, address, psbt::Psbt, secp256k1, Address, Amount, Denomination, Network, OutPoint,
|
||||||
|
},
|
||||||
|
spend::{
|
||||||
|
create_spend, CandidateCoin, SpendCreationError, SpendOutputAddress, SpendTxFees, TxGetter,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -19,7 +22,7 @@ use crate::{
|
|||||||
app::{cache::Cache, error::Error, message::Message, state::psbt, view, wallet::Wallet},
|
app::{cache::Cache, error::Error, message::Message, state::psbt, view, wallet::Wallet},
|
||||||
daemon::{
|
daemon::{
|
||||||
model::{remaining_sequence, Coin, SpendTx},
|
model::{remaining_sequence, Coin, SpendTx},
|
||||||
Daemon, DaemonError,
|
Daemon,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -73,7 +76,9 @@ pub struct DefineSpend {
|
|||||||
is_valid: bool,
|
is_valid: bool,
|
||||||
is_duplicate: bool,
|
is_duplicate: bool,
|
||||||
|
|
||||||
|
network: Network,
|
||||||
descriptor: LianaDescriptor,
|
descriptor: LianaDescriptor,
|
||||||
|
curve: secp256k1::Secp256k1<secp256k1::VerifyOnly>,
|
||||||
timelock: u16,
|
timelock: u16,
|
||||||
coins: Vec<(Coin, bool)>,
|
coins: Vec<(Coin, bool)>,
|
||||||
coins_labels: HashMap<String, String>,
|
coins_labels: HashMap<String, String>,
|
||||||
@ -85,7 +90,12 @@ pub struct DefineSpend {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl DefineSpend {
|
impl DefineSpend {
|
||||||
pub fn new(descriptor: LianaDescriptor, coins: &[Coin], timelock: u16) -> Self {
|
pub fn new(
|
||||||
|
network: Network,
|
||||||
|
descriptor: LianaDescriptor,
|
||||||
|
coins: &[Coin],
|
||||||
|
timelock: u16,
|
||||||
|
) -> Self {
|
||||||
let balance_available = coins
|
let balance_available = coins
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|coin| {
|
.filter_map(|coin| {
|
||||||
@ -99,7 +109,7 @@ impl DefineSpend {
|
|||||||
let coins: Vec<(Coin, bool)> = coins
|
let coins: Vec<(Coin, bool)> = coins
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|c| {
|
.filter_map(|c| {
|
||||||
if c.spend_info.is_none() {
|
if c.spend_info.is_none() && !c.is_immature {
|
||||||
Some((c.clone(), false))
|
Some((c.clone(), false))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
@ -109,7 +119,9 @@ impl DefineSpend {
|
|||||||
|
|
||||||
Self {
|
Self {
|
||||||
balance_available,
|
balance_available,
|
||||||
|
network,
|
||||||
descriptor,
|
descriptor,
|
||||||
|
curve: secp256k1::Secp256k1::verification_only(),
|
||||||
timelock,
|
timelock,
|
||||||
generated: None,
|
generated: None,
|
||||||
coins,
|
coins,
|
||||||
@ -175,110 +187,128 @@ impl DefineSpend {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn auto_select_coins(&mut self, daemon: Arc<dyn Daemon + Sync + Send>) {
|
/// redraft calculates the amount left to select and auto selects coins
|
||||||
// Set non-input values in the same way as for user selection.
|
/// if the user did not select a coin manually
|
||||||
let mut outputs: HashMap<Address<address::NetworkUnchecked>, u64> = HashMap::new();
|
fn redraft(&mut self, daemon: Arc<dyn Daemon + Sync + Send>) {
|
||||||
for recipient in &self.recipients {
|
if !self.form_values_are_valid() || self.recipients.is_empty() {
|
||||||
outputs.insert(
|
return;
|
||||||
Address::from_str(&recipient.address.value).expect("Checked before"),
|
|
||||||
recipient.amount().expect("Checked before"),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
let feerate_vb = self.feerate.value.parse::<u64>().unwrap_or(0);
|
|
||||||
// Create a spend with empty inputs in order to use auto-selection.
|
|
||||||
match daemon.create_spend_tx(&[], &outputs, feerate_vb) {
|
|
||||||
Ok(spend) => {
|
|
||||||
self.warning = None;
|
|
||||||
let selected_coins: Vec<OutPoint> = spend
|
|
||||||
.psbt
|
|
||||||
.unsigned_tx
|
|
||||||
.input
|
|
||||||
.iter()
|
|
||||||
.map(|c| c.previous_output)
|
|
||||||
.collect();
|
|
||||||
// Mark coins as selected.
|
|
||||||
for (coin, selected) in &mut self.coins {
|
|
||||||
*selected = selected_coins.contains(&coin.outpoint);
|
|
||||||
}
|
|
||||||
// As coin selection was successful, we can assume there is nothing left to select.
|
|
||||||
self.amount_left_to_select = Some(Amount::from_sat(0));
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
if let DaemonError::CoinSelectionError = e {
|
|
||||||
// For coin selection error (insufficient funds), do not make any changes to
|
|
||||||
// selected coins on screen and just show user how much is left to select.
|
|
||||||
// User can then either:
|
|
||||||
// - modify recipient amounts and/or feerate and let coin selection run again, or
|
|
||||||
// - select coins manually.
|
|
||||||
self.amount_left_to_select();
|
|
||||||
} else {
|
|
||||||
self.warning = Some(e.into());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn amount_left_to_select(&mut self) {
|
|
||||||
// We need the feerate in order to compute the required amount of BTC to
|
|
||||||
// select. Return early if we don't to not do unnecessary computation.
|
|
||||||
let feerate = match self.feerate.value.parse::<u64>() {
|
|
||||||
Ok(f) => f,
|
|
||||||
Err(_) => {
|
|
||||||
self.amount_left_to_select = None;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// The coins to be included in this transaction.
|
let destinations: Vec<(SpendOutputAddress, Amount)> = self
|
||||||
let selected_coins: Vec<_> = self
|
.recipients
|
||||||
.coins
|
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|(c, selected)| if *selected { Some(c) } else { None })
|
.map(|recipient| {
|
||||||
|
(
|
||||||
|
SpendOutputAddress {
|
||||||
|
addr: Address::from_str(&recipient.address.value)
|
||||||
|
.expect("Checked before")
|
||||||
|
.assume_checked(),
|
||||||
|
info: None,
|
||||||
|
},
|
||||||
|
Amount::from_sat(recipient.amount().expect("Checked before")),
|
||||||
|
)
|
||||||
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
// A dummy representation of the transaction that will be computed, for
|
let coins: Vec<CandidateCoin> = if self.is_user_coin_selection {
|
||||||
// the purpose of computing its size in order to anticipate the fees needed.
|
self.coins
|
||||||
// NOTE: we make the conservative estimation a change output will always be
|
|
||||||
// needed.
|
|
||||||
let tx_template = bitcoin::Transaction {
|
|
||||||
version: 2,
|
|
||||||
lock_time: bitcoin::blockdata::locktime::absolute::LockTime::ZERO,
|
|
||||||
input: selected_coins
|
|
||||||
.iter()
|
.iter()
|
||||||
.map(|_| bitcoin::TxIn::default())
|
.filter_map(|(c, selected)| {
|
||||||
.collect(),
|
if *selected {
|
||||||
output: self
|
Some(CandidateCoin {
|
||||||
.recipients
|
amount: c.amount,
|
||||||
.iter()
|
outpoint: c.outpoint,
|
||||||
.filter_map(|recipient| {
|
deriv_index: c.derivation_index,
|
||||||
if recipient.valid() {
|
is_change: c.is_change,
|
||||||
Some(bitcoin::TxOut {
|
sequence: None,
|
||||||
script_pubkey: Address::from_str(&recipient.address.value)
|
must_select: *selected,
|
||||||
.unwrap()
|
|
||||||
.payload
|
|
||||||
.script_pubkey(),
|
|
||||||
value: recipient.amount().unwrap(),
|
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect(),
|
.collect()
|
||||||
|
} else {
|
||||||
|
// For automated coin selection, only confirmed coins are considered
|
||||||
|
self.coins
|
||||||
|
.iter()
|
||||||
|
.filter_map(|(c, _)| {
|
||||||
|
if c.block_height.is_some() {
|
||||||
|
Some(CandidateCoin {
|
||||||
|
amount: c.amount,
|
||||||
|
outpoint: c.outpoint,
|
||||||
|
deriv_index: c.derivation_index,
|
||||||
|
is_change: c.is_change,
|
||||||
|
sequence: None,
|
||||||
|
must_select: false,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
};
|
};
|
||||||
// nValue size + scriptPubKey CompactSize + OP_0 + PUSH32 + <wit program>
|
|
||||||
const CHANGE_TXO_SIZE: usize = 8 + 1 + 1 + 1 + 32;
|
|
||||||
let satisfaction_vsize = self.descriptor.max_sat_weight() / 4;
|
|
||||||
let transaction_size =
|
|
||||||
tx_template.vsize() + satisfaction_vsize * tx_template.input.len() + CHANGE_TXO_SIZE;
|
|
||||||
|
|
||||||
// Now the calculation of the amount left to be selected by the user is a simple
|
let dummy_address = self
|
||||||
// substraction between the value needed by the transaction to be created and the
|
.descriptor
|
||||||
// value that was selected already.
|
.change_descriptor()
|
||||||
let selected_amount = selected_coins.iter().map(|c| c.amount.to_sat()).sum();
|
.derive(0.into(), &self.curve)
|
||||||
let output_sum: u64 = tx_template.output.iter().map(|o| o.value).sum();
|
.address(self.network);
|
||||||
let needed_amount: u64 = transaction_size as u64 * feerate + output_sum;
|
|
||||||
self.amount_left_to_select = Some(Amount::from_sat(
|
let feerate_vb = self.feerate.value.parse::<u64>().expect("Checked before");
|
||||||
needed_amount.saturating_sub(selected_amount),
|
// Create a spend with empty inputs in order to use auto-selection.
|
||||||
));
|
match create_spend(
|
||||||
|
&self.descriptor,
|
||||||
|
&self.curve,
|
||||||
|
&mut DaemonTxGetter(&daemon),
|
||||||
|
&destinations,
|
||||||
|
&coins,
|
||||||
|
SpendTxFees::Regular(feerate_vb),
|
||||||
|
SpendOutputAddress {
|
||||||
|
addr: dummy_address,
|
||||||
|
info: None,
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
Ok(spend) => {
|
||||||
|
self.warning = None;
|
||||||
|
if !self.is_user_coin_selection {
|
||||||
|
let selected_coins: Vec<OutPoint> = spend
|
||||||
|
.psbt
|
||||||
|
.unsigned_tx
|
||||||
|
.input
|
||||||
|
.iter()
|
||||||
|
.map(|c| c.previous_output)
|
||||||
|
.collect();
|
||||||
|
// Mark coins as selected.
|
||||||
|
for (coin, selected) in &mut self.coins {
|
||||||
|
*selected = selected_coins.contains(&coin.outpoint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// As coin selection was successful, we can assume there is nothing left to select.
|
||||||
|
self.amount_left_to_select = Some(Amount::from_sat(0));
|
||||||
|
}
|
||||||
|
// For coin selection error (insufficient funds), do not make any changes to
|
||||||
|
// selected coins on screen and just show user how much is left to select.
|
||||||
|
// User can then either:
|
||||||
|
// - modify recipient amounts and/or feerate and let coin selection run again, or
|
||||||
|
// - select coins manually.
|
||||||
|
Err(SpendCreationError::CoinSelection(amount)) => {
|
||||||
|
self.amount_left_to_select = Some(Amount::from_sat(amount.missing));
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
self.warning = Some(e.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct DaemonTxGetter<'a>(&'a Arc<dyn Daemon + Sync + Send>);
|
||||||
|
impl<'a> TxGetter for DaemonTxGetter<'a> {
|
||||||
|
fn get_tx(&mut self, txid: &bitcoin::Txid) -> Option<bitcoin::Transaction> {
|
||||||
|
self.0
|
||||||
|
.list_txs(&[*txid])
|
||||||
|
.ok()
|
||||||
|
.and_then(|mut txs| txs.transactions.pop().map(|tx| tx.tx))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -317,14 +347,11 @@ impl Step for DefineSpend {
|
|||||||
if let Ok(value) = s.parse::<u64>() {
|
if let Ok(value) = s.parse::<u64>() {
|
||||||
self.feerate.value = s;
|
self.feerate.value = s;
|
||||||
self.feerate.valid = value != 0;
|
self.feerate.valid = value != 0;
|
||||||
self.amount_left_to_select();
|
|
||||||
} else if s.is_empty() {
|
} else if s.is_empty() {
|
||||||
self.feerate.value = "".to_string();
|
self.feerate.value = "".to_string();
|
||||||
self.feerate.valid = true;
|
self.feerate.valid = true;
|
||||||
self.amount_left_to_select = None;
|
|
||||||
} else {
|
} else {
|
||||||
self.feerate.valid = false;
|
self.feerate.valid = false;
|
||||||
self.amount_left_to_select = None;
|
|
||||||
}
|
}
|
||||||
self.warning = None;
|
self.warning = None;
|
||||||
}
|
}
|
||||||
@ -368,7 +395,6 @@ impl Step for DefineSpend {
|
|||||||
coin.1 = !coin.1;
|
coin.1 = !coin.1;
|
||||||
// Once user edits selection, auto-selection can no longer be used.
|
// Once user edits selection, auto-selection can no longer be used.
|
||||||
self.is_user_coin_selection = true;
|
self.is_user_coin_selection = true;
|
||||||
self.amount_left_to_select();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
@ -378,12 +404,7 @@ impl Step for DefineSpend {
|
|||||||
// - all form values have been added and validated
|
// - all form values have been added and validated
|
||||||
// - not a self-send
|
// - not a self-send
|
||||||
// - user has not yet selected coins manually
|
// - user has not yet selected coins manually
|
||||||
if self.form_values_are_valid()
|
self.redraft(daemon);
|
||||||
&& !self.recipients.is_empty()
|
|
||||||
&& !self.is_user_coin_selection
|
|
||||||
{
|
|
||||||
self.auto_select_coins(daemon);
|
|
||||||
}
|
|
||||||
self.check_valid();
|
self.check_valid();
|
||||||
}
|
}
|
||||||
Message::Psbt(res) => match res {
|
Message::Psbt(res) => match res {
|
||||||
|
|||||||
@ -41,6 +41,7 @@ impl From<&Error> for WarningMessage {
|
|||||||
Error::Unexpected(_) => WarningMessage("Unknown error".to_string()),
|
Error::Unexpected(_) => WarningMessage("Unknown error".to_string()),
|
||||||
Error::HardwareWallet(_) => WarningMessage("Hardware wallet error".to_string()),
|
Error::HardwareWallet(_) => WarningMessage("Hardware wallet error".to_string()),
|
||||||
Error::Desc(e) => WarningMessage(format!("Descriptor analysis error: '{}'.", e)),
|
Error::Desc(e) => WarningMessage(format!("Descriptor analysis error: '{}'.", e)),
|
||||||
|
Error::Spend(e) => WarningMessage(format!("Spend creation error: '{}'.", e)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,7 +2,7 @@ use std::collections::{HashMap, HashSet};
|
|||||||
|
|
||||||
use super::{model::*, Daemon, DaemonError};
|
use super::{model::*, Daemon, DaemonError};
|
||||||
use liana::{
|
use liana::{
|
||||||
commands::{CommandError, LabelItem},
|
commands::LabelItem,
|
||||||
config::Config,
|
config::Config,
|
||||||
miniscript::bitcoin::{address, psbt::Psbt, Address, OutPoint, Txid},
|
miniscript::bitcoin::{address, psbt::Psbt, Address, OutPoint, Txid},
|
||||||
DaemonControl, DaemonHandle,
|
DaemonControl, DaemonHandle,
|
||||||
@ -90,10 +90,7 @@ impl Daemon for EmbeddedDaemon {
|
|||||||
) -> Result<CreateSpendResult, DaemonError> {
|
) -> Result<CreateSpendResult, DaemonError> {
|
||||||
self.control()?
|
self.control()?
|
||||||
.create_spend(destinations, coins_outpoints, feerate_vb, None)
|
.create_spend(destinations, coins_outpoints, feerate_vb, None)
|
||||||
.map_err(|e| match e {
|
.map_err(|e| DaemonError::Unexpected(e.to_string()))
|
||||||
CommandError::CoinSelectionError(_) => DaemonError::CoinSelectionError,
|
|
||||||
e => DaemonError::Unexpected(e.to_string()),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn rbf_psbt(
|
fn rbf_psbt(
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user