diff --git a/CHANGELOG.md b/CHANGELOG.md index ddb80403c..757baaa34 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] - BIP39 implementation dependency, in `keys::bip39` changed from tiny-bip39 to rust-bip39. +- Add new method on the `TxBuilder` to embed data in the transaction via `OP_RETURN`. To allow that a fix to check the dust only on spendable output has been introduced. ## [v0.13.0] - [v0.12.0] diff --git a/src/testutils/blockchain_tests.rs b/src/testutils/blockchain_tests.rs index 02276548f..b44a97182 100644 --- a/src/testutils/blockchain_tests.rs +++ b/src/testutils/blockchain_tests.rs @@ -849,6 +849,37 @@ macro_rules! bdk_blockchain_tests { assert_eq!(new_details.received, 0, "incorrect received after add input"); } + + #[test] + fn test_add_data() { + let (wallet, descriptors, mut test_client) = init_single_sig(); + let node_addr = test_client.get_node_address(None); + let _ = test_client.receive(testutils! { + @tx ( (@external descriptors, 0) => 50_000 ) + }); + + wallet.sync(noop_progress(), None).unwrap(); + assert_eq!(wallet.get_balance().unwrap(), 50_000, "incorrect balance"); + + let mut builder = wallet.build_tx(); + let data = [42u8;80]; + builder.add_data(&data); + let (mut psbt, details) = builder.finish().unwrap(); + + let finalized = wallet.sign(&mut psbt, Default::default()).unwrap(); + assert!(finalized, "Cannot finalize transaction"); + let tx = psbt.extract_tx(); + let serialized_tx = bitcoin::consensus::encode::serialize(&tx); + assert!(serialized_tx.windows(data.len()).any(|e| e==data), "cannot find op_return data in transaction"); + let sent_txid = wallet.broadcast(&tx).unwrap(); + test_client.generate(1, Some(node_addr)); + wallet.sync(noop_progress(), None).unwrap(); + assert_eq!(wallet.get_balance().unwrap(), 50_000 - details.fee.unwrap_or(0), "incorrect balance after send"); + + let tx_map = wallet.list_transactions(false).unwrap().into_iter().map(|tx| (tx.txid, tx)).collect::>(); + let _ = tx_map.get(&sent_txid).unwrap(); + } + #[test] fn test_sync_receive_coinbase() { let (wallet, _, mut test_client) = init_single_sig(); diff --git a/src/wallet/mod.rs b/src/wallet/mod.rs index 1f6353cd2..b8d1196d9 100644 --- a/src/wallet/mod.rs +++ b/src/wallet/mod.rs @@ -601,7 +601,7 @@ where let recipients = params.recipients.iter().map(|(r, v)| (r, *v)); for (index, (script_pubkey, value)) in recipients.enumerate() { - if value.is_dust() { + if value.is_dust() && !script_pubkey.is_provably_unspendable() { return Err(Error::OutputBelowDustLimit(index)); } diff --git a/src/wallet/tx_builder.rs b/src/wallet/tx_builder.rs index a0ed93cbe..0c7f7f857 100644 --- a/src/wallet/tx_builder.rs +++ b/src/wallet/tx_builder.rs @@ -560,6 +560,13 @@ impl<'a, B, D: BatchDatabase, Cs: CoinSelectionAlgorithm> TxBuilder<'a, B, D, self } + /// Add data as an output, using OP_RETURN + pub fn add_data(&mut self, data: &[u8]) -> &mut Self { + let script = Script::new_op_return(data); + self.add_recipient(script, 0u64); + self + } + /// Sets the address to *drain* excess coins to. /// /// Usually, when there are excess coins they are sent to a change address generated by the