diff --git a/bdk-ffi/src/bdk.udl b/bdk-ffi/src/bdk.udl index 2418d8a5..3d001e41 100644 --- a/bdk-ffi/src/bdk.udl +++ b/bdk-ffi/src/bdk.udl @@ -258,6 +258,13 @@ interface PsbtParseError { Base64Encoding(string error_message); }; +[Error] +interface PsbtFinalizeError { + InputError(string reason, u32 index); + WrongInputCount(u32 in_tx, u32 in_map); + InputIdxOutofBounds(u32 psbt_inp, u32 requested); +}; + [Error] interface RequestBuilderError { RequestAlreadyConsumed(); @@ -958,9 +965,17 @@ interface Psbt { [Throws=PsbtError] Psbt combine(Psbt other); + FinalizedPsbtResult finalize(); + string json_serialize(); }; +dictionary FinalizedPsbtResult { + Psbt psbt; + boolean could_finalize; + sequence? errors; +}; + dictionary TxIn { OutPoint previous_output; Script script_sig; diff --git a/bdk-ffi/src/bitcoin.rs b/bdk-ffi/src/bitcoin.rs index 5bb702b0..e0edec31 100644 --- a/bdk-ffi/src/bitcoin.rs +++ b/bdk-ffi/src/bitcoin.rs @@ -1,3 +1,4 @@ +use crate::error::PsbtFinalizeError; use crate::error::{ AddressParseError, FromScriptError, PsbtError, PsbtParseError, TransactionError, }; @@ -10,12 +11,14 @@ use bdk_wallet::bitcoin::consensus::encode::serialize; use bdk_wallet::bitcoin::consensus::Decodable; use bdk_wallet::bitcoin::io::Cursor; use bdk_wallet::bitcoin::psbt::ExtractTxError; +use bdk_wallet::bitcoin::secp256k1::Secp256k1; use bdk_wallet::bitcoin::Address as BdkAddress; use bdk_wallet::bitcoin::Network; use bdk_wallet::bitcoin::Psbt as BdkPsbt; use bdk_wallet::bitcoin::Transaction as BdkTransaction; use bdk_wallet::bitcoin::TxIn as BdkTxIn; use bdk_wallet::bitcoin::TxOut as BdkTxOut; +use bdk_wallet::miniscript::psbt::PsbtExt; use bdk_wallet::serde_json; use std::fmt::Display; @@ -188,6 +191,26 @@ impl Psbt { Ok(Arc::new(Psbt(Mutex::new(original_psbt)))) } + pub(crate) fn finalize(&self) -> FinalizedPsbtResult { + let curve = Secp256k1::verification_only(); + let finalized = self.0.lock().unwrap().clone().finalize(&curve); + match finalized { + Ok(psbt) => FinalizedPsbtResult { + psbt: Arc::new(psbt.into()), + could_finalize: true, + errors: None, + }, + Err((psbt, errors)) => { + let errors = errors.into_iter().map(|e| e.into()).collect(); + FinalizedPsbtResult { + psbt: Arc::new(psbt.into()), + could_finalize: false, + errors: Some(errors), + } + } + } + } + pub(crate) fn json_serialize(&self) -> String { let psbt = self.0.lock().unwrap(); serde_json::to_string(psbt.deref()).unwrap() @@ -200,6 +223,12 @@ impl From for Psbt { } } +pub struct FinalizedPsbtResult { + pub psbt: Arc, + pub could_finalize: bool, + pub errors: Option>, +} + #[derive(Debug, Clone)] pub struct TxIn { pub previous_output: OutPoint, diff --git a/bdk-ffi/src/error.rs b/bdk-ffi/src/error.rs index 7aab6eaa..c7b21489 100644 --- a/bdk-ffi/src/error.rs +++ b/bdk-ffi/src/error.rs @@ -19,6 +19,7 @@ use bdk_wallet::error::BuildFeeBumpError; use bdk_wallet::error::CreateTxError as BdkCreateTxError; use bdk_wallet::keys::bip39::Error as BdkBip39Error; use bdk_wallet::miniscript::descriptor::DescriptorKeyParseError as BdkDescriptorKeyParseError; +use bdk_wallet::miniscript::psbt::Error as BdkPsbtFinalizeError; use bdk_wallet::signer::SignerError as BdkSignerError; use bdk_wallet::tx_builder::AddUtxoError; use bdk_wallet::LoadWithPersistError as BdkLoadWithPersistError; @@ -642,6 +643,16 @@ pub enum PsbtParseError { Base64Encoding { error_message: String }, } +#[derive(Debug, thiserror::Error)] +pub enum PsbtFinalizeError { + #[error("an input at index {index} is invalid: {reason}")] + InputError { reason: String, index: u32 }, + #[error("wrong input count; expected: {in_tx}, got: {in_map}")] + WrongInputCount { in_tx: u32, in_map: u32 }, + #[error("input index out of bounds; inputs: {psbt_inp}, requested: {requested}")] + InputIdxOutofBounds { psbt_inp: u32, requested: u32 }, +} + #[derive(Debug, thiserror::Error)] pub enum SignerError { #[error("missing key for signing")] @@ -1344,6 +1355,29 @@ impl From for PsbtParseError { } } +impl From for PsbtFinalizeError { + fn from(value: BdkPsbtFinalizeError) -> Self { + match value { + BdkPsbtFinalizeError::InputError(input_error, index) => PsbtFinalizeError::InputError { + reason: input_error.to_string(), + index: index as u32, + }, + BdkPsbtFinalizeError::WrongInputCount { in_tx, in_map } => { + PsbtFinalizeError::WrongInputCount { + in_tx: in_tx as u32, + in_map: in_map as u32, + } + } + BdkPsbtFinalizeError::InputIdxOutofBounds { psbt_inp, index } => { + PsbtFinalizeError::InputIdxOutofBounds { + psbt_inp: psbt_inp as u32, + requested: index as u32, + } + } + } + } +} + impl From for SignerError { fn from(error: BdkSignerError) -> Self { match error { diff --git a/bdk-ffi/src/lib.rs b/bdk-ffi/src/lib.rs index 4254ca54..684d0fd0 100644 --- a/bdk-ffi/src/lib.rs +++ b/bdk-ffi/src/lib.rs @@ -10,6 +10,7 @@ mod types; mod wallet; use crate::bitcoin::Address; +use crate::bitcoin::FinalizedPsbtResult; use crate::bitcoin::Psbt; use crate::bitcoin::Transaction; use crate::bitcoin::TxIn; @@ -33,6 +34,7 @@ use crate::error::LoadWithPersistError; use crate::error::MiniscriptError; use crate::error::PersistenceError; use crate::error::PsbtError; +use crate::error::PsbtFinalizeError; use crate::error::PsbtParseError; use crate::error::RequestBuilderError; use crate::error::SignerError;