Merge bitcoindevkit/bdk-ffi#208: Add FeeRate struct and PSBT fee_amount and fee_rate functions
ae1ea99ed3f26f33ead92d4a91588ae32d9e110b Add FeeRate struct and fee_amount() and fee_rate() functions on PartiallySignedTransaction (Steve Myers)
9a381f6d32321f2a726092bd2b54ab10d105e9c6 Rename PartiallySignedBitcoinTransaction to PartiallySignedTransaction (Steve Myers)
Pull request description:
<!-- You can erase any parts of this template not applicable to your Pull Request. -->
### Description
Add FeeRate struct and fee_amount() and fee_rate() functions on PartiallySignedTransaction.
### Notes to the reviewers
This PR is dependent on https://github.com/bitcoindevkit/bdk/pull/782.
### Changelog notice
- Breaking Changes
- Renamed PartiallySignedBitcoinTransaction to PartiallySignedTransaction to be consistent with `rust-bitcoin`
- APIs Added
- Add FeeRate struct
- Add fee_amount() and fee_rate() functions on PartiallySignedTransaction
### Checklists
#### All Submissions:
* [x] I've signed all my commits
* [x] I followed the [contribution guidelines](https://github.com/bitcoindevkit/bdk/blob/master/CONTRIBUTING.md)
* [x] I ran `cargo fmt` and `cargo clippy` before committing
#### New Features:
* [x] I've added tests for the new feature
* [x] I've added docs for the new feature
ACKs for top commit:
thunderbiscuit:
Re-ACK [ae1ea99](ae1ea99ed3
).
Tree-SHA512: 2c3f792e9ef092cd3ba233601122f4960c496d132caad54ef2f7f41d7113dd16600a863bb8fd78d2e5b978adebdb7ddd9529c21b3d46cd0b16e0db4eb90de01d
This commit is contained in:
commit
a25fb1348d
23
src/bdk.udl
23
src/bdk.udl
@ -137,7 +137,7 @@ interface Blockchain {
|
|||||||
constructor(BlockchainConfig config);
|
constructor(BlockchainConfig config);
|
||||||
|
|
||||||
[Throws=BdkError]
|
[Throws=BdkError]
|
||||||
void broadcast([ByRef] PartiallySignedBitcoinTransaction psbt);
|
void broadcast([ByRef] PartiallySignedTransaction psbt);
|
||||||
|
|
||||||
[Throws=BdkError]
|
[Throws=BdkError]
|
||||||
u32 get_height();
|
u32 get_height();
|
||||||
@ -188,7 +188,7 @@ interface Wallet {
|
|||||||
Balance get_balance();
|
Balance get_balance();
|
||||||
|
|
||||||
[Throws=BdkError]
|
[Throws=BdkError]
|
||||||
boolean sign([ByRef] PartiallySignedBitcoinTransaction psbt);
|
boolean sign([ByRef] PartiallySignedTransaction psbt);
|
||||||
|
|
||||||
[Throws=BdkError]
|
[Throws=BdkError]
|
||||||
sequence<TransactionDetails> list_transactions();
|
sequence<TransactionDetails> list_transactions();
|
||||||
@ -202,7 +202,14 @@ interface Wallet {
|
|||||||
sequence<LocalUtxo> list_unspent();
|
sequence<LocalUtxo> list_unspent();
|
||||||
};
|
};
|
||||||
|
|
||||||
interface PartiallySignedBitcoinTransaction {
|
interface FeeRate {
|
||||||
|
[Name=from_sat_per_vb]
|
||||||
|
constructor(float sat_per_vb);
|
||||||
|
|
||||||
|
float as_sat_per_vb();
|
||||||
|
};
|
||||||
|
|
||||||
|
interface PartiallySignedTransaction {
|
||||||
[Throws=BdkError]
|
[Throws=BdkError]
|
||||||
constructor(string psbt_base64);
|
constructor(string psbt_base64);
|
||||||
|
|
||||||
@ -213,11 +220,15 @@ interface PartiallySignedBitcoinTransaction {
|
|||||||
sequence<u8> extract_tx();
|
sequence<u8> extract_tx();
|
||||||
|
|
||||||
[Throws=BdkError]
|
[Throws=BdkError]
|
||||||
PartiallySignedBitcoinTransaction combine(PartiallySignedBitcoinTransaction other);
|
PartiallySignedTransaction combine(PartiallySignedTransaction other);
|
||||||
|
|
||||||
|
u64? fee_amount();
|
||||||
|
|
||||||
|
FeeRate? fee_rate();
|
||||||
};
|
};
|
||||||
|
|
||||||
dictionary TxBuilderResult {
|
dictionary TxBuilderResult {
|
||||||
PartiallySignedBitcoinTransaction psbt;
|
PartiallySignedTransaction psbt;
|
||||||
TransactionDetails transaction_details;
|
TransactionDetails transaction_details;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -270,7 +281,7 @@ interface BumpFeeTxBuilder {
|
|||||||
BumpFeeTxBuilder enable_rbf_with_sequence(u32 nsequence);
|
BumpFeeTxBuilder enable_rbf_with_sequence(u32 nsequence);
|
||||||
|
|
||||||
[Throws=BdkError]
|
[Throws=BdkError]
|
||||||
PartiallySignedBitcoinTransaction finish([ByRef] Wallet wallet);
|
PartiallySignedTransaction finish([ByRef] Wallet wallet);
|
||||||
};
|
};
|
||||||
|
|
||||||
interface Mnemonic {
|
interface Mnemonic {
|
||||||
|
74
src/lib.rs
74
src/lib.rs
@ -3,7 +3,7 @@ use bdk::bitcoin::hashes::hex::ToHex;
|
|||||||
use bdk::bitcoin::secp256k1::Secp256k1;
|
use bdk::bitcoin::secp256k1::Secp256k1;
|
||||||
use bdk::bitcoin::util::bip32::DerivationPath as BdkDerivationPath;
|
use bdk::bitcoin::util::bip32::DerivationPath as BdkDerivationPath;
|
||||||
use bdk::bitcoin::util::psbt::serialize::Serialize;
|
use bdk::bitcoin::util::psbt::serialize::Serialize;
|
||||||
use bdk::bitcoin::util::psbt::PartiallySignedTransaction;
|
use bdk::bitcoin::util::psbt::PartiallySignedTransaction as BdkPartiallySignedTransaction;
|
||||||
use bdk::bitcoin::Sequence;
|
use bdk::bitcoin::Sequence;
|
||||||
use bdk::bitcoin::{Address as BdkAddress, Network, OutPoint as BdkOutPoint, Txid};
|
use bdk::bitcoin::{Address as BdkAddress, Network, OutPoint as BdkOutPoint, Txid};
|
||||||
use bdk::blockchain::any::{AnyBlockchain, AnyBlockchainConfig};
|
use bdk::blockchain::any::{AnyBlockchain, AnyBlockchainConfig};
|
||||||
@ -22,6 +22,7 @@ use bdk::keys::{
|
|||||||
DescriptorSecretKey as BdkDescriptorSecretKey, ExtendedKey, GeneratableKey, GeneratedKey,
|
DescriptorSecretKey as BdkDescriptorSecretKey, ExtendedKey, GeneratableKey, GeneratedKey,
|
||||||
};
|
};
|
||||||
use bdk::miniscript::BareCtx;
|
use bdk::miniscript::BareCtx;
|
||||||
|
use bdk::psbt::PsbtUtils;
|
||||||
use bdk::wallet::tx_builder::ChangeSpendPolicy;
|
use bdk::wallet::tx_builder::ChangeSpendPolicy;
|
||||||
use bdk::wallet::AddressIndex as BdkAddressIndex;
|
use bdk::wallet::AddressIndex as BdkAddressIndex;
|
||||||
use bdk::wallet::AddressInfo as BdkAddressInfo;
|
use bdk::wallet::AddressInfo as BdkAddressInfo;
|
||||||
@ -210,7 +211,7 @@ impl Blockchain {
|
|||||||
self.blockchain_mutex.lock().expect("blockchain")
|
self.blockchain_mutex.lock().expect("blockchain")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn broadcast(&self, psbt: &PartiallySignedBitcoinTransaction) -> Result<(), BdkError> {
|
fn broadcast(&self, psbt: &PartiallySignedTransaction) -> Result<(), BdkError> {
|
||||||
let tx = psbt.internal.lock().unwrap().clone().extract_tx();
|
let tx = psbt.internal.lock().unwrap().clone().extract_tx();
|
||||||
self.get_blockchain().broadcast(&tx)
|
self.get_blockchain().broadcast(&tx)
|
||||||
}
|
}
|
||||||
@ -341,14 +342,15 @@ impl fmt::Debug for ProgressHolder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct PartiallySignedBitcoinTransaction {
|
pub struct PartiallySignedTransaction {
|
||||||
internal: Mutex<PartiallySignedTransaction>,
|
internal: Mutex<BdkPartiallySignedTransaction>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PartiallySignedBitcoinTransaction {
|
impl PartiallySignedTransaction {
|
||||||
fn new(psbt_base64: String) -> Result<Self, BdkError> {
|
fn new(psbt_base64: String) -> Result<Self, BdkError> {
|
||||||
let psbt: PartiallySignedTransaction = PartiallySignedTransaction::from_str(&psbt_base64)?;
|
let psbt: BdkPartiallySignedTransaction =
|
||||||
Ok(PartiallySignedBitcoinTransaction {
|
BdkPartiallySignedTransaction::from_str(&psbt_base64)?;
|
||||||
|
Ok(PartiallySignedTransaction {
|
||||||
internal: Mutex::new(psbt),
|
internal: Mutex::new(psbt),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -379,16 +381,30 @@ impl PartiallySignedBitcoinTransaction {
|
|||||||
/// In accordance with BIP 174 this function is commutative i.e., `A.combine(B) == B.combine(A)`
|
/// In accordance with BIP 174 this function is commutative i.e., `A.combine(B) == B.combine(A)`
|
||||||
fn combine(
|
fn combine(
|
||||||
&self,
|
&self,
|
||||||
other: Arc<PartiallySignedBitcoinTransaction>,
|
other: Arc<PartiallySignedTransaction>,
|
||||||
) -> Result<Arc<PartiallySignedBitcoinTransaction>, BdkError> {
|
) -> Result<Arc<PartiallySignedTransaction>, BdkError> {
|
||||||
let other_psbt = other.internal.lock().unwrap().clone();
|
let other_psbt = other.internal.lock().unwrap().clone();
|
||||||
let mut original_psbt = self.internal.lock().unwrap().clone();
|
let mut original_psbt = self.internal.lock().unwrap().clone();
|
||||||
|
|
||||||
original_psbt.combine(other_psbt)?;
|
original_psbt.combine(other_psbt)?;
|
||||||
Ok(Arc::new(PartiallySignedBitcoinTransaction {
|
Ok(Arc::new(PartiallySignedTransaction {
|
||||||
internal: Mutex::new(original_psbt),
|
internal: Mutex::new(original_psbt),
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The total transaction fee amount, sum of input amounts minus sum of output amounts, in Sats.
|
||||||
|
/// If the PSBT is missing a TxOut for an input returns None.
|
||||||
|
fn fee_amount(&self) -> Option<u64> {
|
||||||
|
self.internal.lock().unwrap().fee_amount()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The transaction's fee rate. This value will only be accurate if calculated AFTER the
|
||||||
|
/// `PartiallySignedTransaction` is finalized and all witness/signature data is added to the
|
||||||
|
/// transaction.
|
||||||
|
/// If the PSBT is missing a TxOut for an input returns None.
|
||||||
|
fn fee_rate(&self) -> Option<Arc<FeeRate>> {
|
||||||
|
self.internal.lock().unwrap().fee_rate().map(Arc::new)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A Bitcoin wallet.
|
/// A Bitcoin wallet.
|
||||||
@ -460,7 +476,7 @@ impl Wallet {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Sign a transaction with all the wallet’s signers.
|
/// Sign a transaction with all the wallet’s signers.
|
||||||
fn sign(&self, psbt: &PartiallySignedBitcoinTransaction) -> Result<bool, BdkError> {
|
fn sign(&self, psbt: &PartiallySignedTransaction) -> Result<bool, BdkError> {
|
||||||
let mut psbt = psbt.internal.lock().unwrap();
|
let mut psbt = psbt.internal.lock().unwrap();
|
||||||
self.get_wallet().sign(&mut psbt, SignOptions::default())
|
self.get_wallet().sign(&mut psbt, SignOptions::default())
|
||||||
}
|
}
|
||||||
@ -532,7 +548,7 @@ enum RbfValue {
|
|||||||
/// The result after calling the TxBuilder finish() function. Contains unsigned PSBT and
|
/// The result after calling the TxBuilder finish() function. Contains unsigned PSBT and
|
||||||
/// transaction details.
|
/// transaction details.
|
||||||
pub struct TxBuilderResult {
|
pub struct TxBuilderResult {
|
||||||
pub psbt: Arc<PartiallySignedBitcoinTransaction>,
|
pub psbt: Arc<PartiallySignedTransaction>,
|
||||||
pub transaction_details: TransactionDetails,
|
pub transaction_details: TransactionDetails,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -770,7 +786,7 @@ impl TxBuilder {
|
|||||||
tx_builder
|
tx_builder
|
||||||
.finish()
|
.finish()
|
||||||
.map(|(psbt, tx_details)| TxBuilderResult {
|
.map(|(psbt, tx_details)| TxBuilderResult {
|
||||||
psbt: Arc::new(PartiallySignedBitcoinTransaction {
|
psbt: Arc::new(PartiallySignedTransaction {
|
||||||
internal: Mutex::new(psbt),
|
internal: Mutex::new(psbt),
|
||||||
}),
|
}),
|
||||||
transaction_details: TransactionDetails::from(&tx_details),
|
transaction_details: TransactionDetails::from(&tx_details),
|
||||||
@ -828,7 +844,7 @@ impl BumpFeeTxBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Finish building the transaction. Returns the BIP174 PSBT.
|
/// Finish building the transaction. Returns the BIP174 PSBT.
|
||||||
fn finish(&self, wallet: &Wallet) -> Result<Arc<PartiallySignedBitcoinTransaction>, BdkError> {
|
fn finish(&self, wallet: &Wallet) -> Result<Arc<PartiallySignedTransaction>, BdkError> {
|
||||||
let wallet = wallet.get_wallet();
|
let wallet = wallet.get_wallet();
|
||||||
let txid = Txid::from_str(self.txid.as_str())?;
|
let txid = Txid::from_str(self.txid.as_str())?;
|
||||||
let mut tx_builder = wallet.build_fee_bump(txid)?;
|
let mut tx_builder = wallet.build_fee_bump(txid)?;
|
||||||
@ -851,7 +867,7 @@ impl BumpFeeTxBuilder {
|
|||||||
}
|
}
|
||||||
tx_builder
|
tx_builder
|
||||||
.finish()
|
.finish()
|
||||||
.map(|(psbt, _)| PartiallySignedBitcoinTransaction {
|
.map(|(psbt, _)| PartiallySignedTransaction {
|
||||||
internal: Mutex::new(psbt),
|
internal: Mutex::new(psbt),
|
||||||
})
|
})
|
||||||
.map(Arc::new)
|
.map(Arc::new)
|
||||||
@ -1246,4 +1262,32 @@ mod test {
|
|||||||
"e93315d6ce401eb4db803a56232f0ed3e69b053774e6047df54f1bd00e5ea936"
|
"e93315d6ce401eb4db803a56232f0ed3e69b053774e6047df54f1bd00e5ea936"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_psbt_fee() {
|
||||||
|
let test_wpkh = "wpkh(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW)";
|
||||||
|
let (funded_wallet, _, _) = get_funded_wallet(test_wpkh);
|
||||||
|
let test_wallet = Wallet {
|
||||||
|
wallet_mutex: Mutex::new(funded_wallet),
|
||||||
|
};
|
||||||
|
let drain_to_address = "tb1ql7w62elx9ucw4pj5lgw4l028hmuw80sndtntxt".to_string();
|
||||||
|
let tx_builder = TxBuilder::new()
|
||||||
|
.fee_rate(2.0)
|
||||||
|
.drain_wallet()
|
||||||
|
.drain_to(drain_to_address.clone());
|
||||||
|
//dbg!(&tx_builder);
|
||||||
|
assert!(tx_builder.drain_wallet);
|
||||||
|
assert_eq!(tx_builder.drain_to, Some(drain_to_address));
|
||||||
|
|
||||||
|
let tx_builder_result = tx_builder.finish(&test_wallet).unwrap();
|
||||||
|
|
||||||
|
assert!(tx_builder_result.psbt.fee_rate().is_some());
|
||||||
|
assert_eq!(
|
||||||
|
tx_builder_result.psbt.fee_rate().unwrap().as_sat_per_vb(),
|
||||||
|
2.682927
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(tx_builder_result.psbt.fee_amount().is_some());
|
||||||
|
assert_eq!(tx_builder_result.psbt.fee_amount().unwrap(), 220);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user