]> Untitled Git - bdk/commitdiff
Write more docs, make `TxBuilder::with_recipients` take Scripts
authorAlekos Filini <alekos.filini@gmail.com>
Fri, 4 Sep 2020 13:45:11 +0000 (15:45 +0200)
committerAlekos Filini <alekos.filini@gmail.com>
Fri, 4 Sep 2020 14:07:41 +0000 (16:07 +0200)
13 files changed:
examples/address_validator.rs
examples/compiler.rs
examples/repl.rs
src/cli.rs
src/descriptor/policy.rs
src/wallet/address_validator.rs
src/wallet/coin_selection.rs
src/wallet/export.rs
src/wallet/mod.rs
src/wallet/signer.rs
src/wallet/time.rs
src/wallet/tx_builder.rs
testutils-macros/src/lib.rs

index 639bbdfdd30565bf0a357076ae17b8ef02636830..3e576c08e9f6d8eaa3f85c1519b4142cd3619d67 100644 (file)
@@ -57,7 +57,7 @@ impl AddressValidator for DummyValidator {
     }
 }
 
-fn main() -> Result<(), magical_bitcoin_wallet::error::Error> {
+fn main() -> Result<(), magical_bitcoin_wallet::Error> {
     let descriptor = "sh(and_v(v:pk(tpubDDpWvmUrPZrhSPmUzCMBHffvC3HyMAPnWDSAQNBTnj1iZeJa7BZQEttFiP4DS4GCcXQHezdXhn86Hj6LHX5EDstXPWrMaSneRWM8yUf6NFd/*),after(630000)))";
     let mut wallet: OfflineWallet<_> =
         Wallet::new_offline(descriptor, None, Network::Regtest, MemoryDatabase::new())?;
index f8724cd8daaa41e1a700d0130f20b691d2705647..2d50830c67cf92f1b91d4dc2690196a861dadb8e 100644 (file)
@@ -40,8 +40,7 @@ use miniscript::policy::Concrete;
 use miniscript::Descriptor;
 
 use magical_bitcoin_wallet::database::memory::MemoryDatabase;
-use magical_bitcoin_wallet::types::ScriptType;
-use magical_bitcoin_wallet::{OfflineWallet, Wallet};
+use magical_bitcoin_wallet::{OfflineWallet, Wallet, ScriptType};
 
 fn main() {
     env_logger::init_from_env(
index 8e8b38e0e2783075eea20826614373f24b826484..2756d2d410a66731fb8e399b6795f7e0d0590b70 100644 (file)
@@ -37,10 +37,10 @@ use log::{debug, error, info, trace, LevelFilter};
 use bitcoin::Network;
 
 use magical_bitcoin_wallet::bitcoin;
-use magical_bitcoin_wallet::blockchain::ElectrumBlockchain;
+use magical_bitcoin_wallet::blockchain::compact_filters::*;
 use magical_bitcoin_wallet::cli;
 use magical_bitcoin_wallet::sled;
-use magical_bitcoin_wallet::{Client, Wallet};
+use magical_bitcoin_wallet::Wallet;
 
 fn prepare_home_dir() -> PathBuf {
     let mut dir = PathBuf::new();
@@ -88,19 +88,17 @@ fn main() {
         .unwrap();
     debug!("database opened successfully");
 
-    let client = Client::new(
-        matches.value_of("server").unwrap(),
-        matches.value_of("proxy"),
-    )
-    .unwrap();
-    let wallet = Wallet::new(
-        descriptor,
-        change_descriptor,
-        network,
-        tree,
-        ElectrumBlockchain::from(client),
-    )
-    .unwrap();
+    let num_threads = 1;
+
+    let mempool = Arc::new(Mempool::default());
+    let peers = (0..num_threads)
+        .map(|_| Peer::connect("192.168.1.136:8333", Arc::clone(&mempool), Network::Bitcoin))
+        .collect::<Result<_, _>>()
+        .unwrap();
+    let blockchain =
+        CompactFiltersBlockchain::new(peers, "./wallet-filters", Some(500_000)).unwrap();
+
+    let wallet = Wallet::new(descriptor, change_descriptor, network, tree, blockchain).unwrap();
     let wallet = Arc::new(wallet);
 
     if let Some(_sub_matches) = matches.subcommand_matches("repl") {
index 51c56ef910af59350d0e8ea3832efa76dec7721a..e09fedd4efb8d109f780093aeff5461863e3b106 100644 (file)
@@ -33,14 +33,14 @@ use log::{debug, error, info, trace, LevelFilter};
 use bitcoin::consensus::encode::{deserialize, serialize, serialize_hex};
 use bitcoin::hashes::hex::FromHex;
 use bitcoin::util::psbt::PartiallySignedTransaction;
-use bitcoin::{Address, OutPoint, Txid};
+use bitcoin::{Address, OutPoint, Script, Txid};
 
 use crate::blockchain::log_progress;
 use crate::error::Error;
 use crate::types::ScriptType;
 use crate::{FeeRate, TxBuilder, Wallet};
 
-fn parse_recipient(s: &str) -> Result<(Address, u64), String> {
+fn parse_recipient(s: &str) -> Result<(Script, u64), String> {
     let parts: Vec<_> = s.split(":").collect();
     if parts.len() != 2 {
         return Err("Invalid format".to_string());
@@ -55,7 +55,7 @@ fn parse_recipient(s: &str) -> Result<(Address, u64), String> {
         return Err(format!("{:?}", e));
     }
 
-    Ok((addr.unwrap(), val.unwrap()))
+    Ok((addr.unwrap().script_pubkey(), val.unwrap()))
 }
 
 fn parse_outpoint(s: &str) -> Result<OutPoint, String> {
index 7f54e6eb041b904b4d5b3d2f9aafc735128744dd..7158701d934058dedac82b5199952be62c55b8b6 100644 (file)
@@ -403,12 +403,16 @@ impl From<bool> for Satisfaction {
 /// Descriptor spending policy
 #[derive(Debug, Clone, Serialize)]
 pub struct Policy {
-    id: String,
+    /// Identifier for this policy node
+    pub id: String,
 
+    /// Type of this policy node
     #[serde(flatten)]
-    item: SatisfiableItem,
-    satisfaction: Satisfaction,
-    contribution: Satisfaction,
+    pub item: SatisfiableItem,
+    /// How a much given PSBT already satisfies this polcy node **(currently unused)**
+    pub satisfaction: Satisfaction,
+    /// How the wallet's descriptor can satisfy this policy node
+    pub contribution: Satisfaction,
 }
 
 /// An extra condition that must be satisfied but that is out of control of the user
index 2b0d3ab379718937f44158fb121e4559ec8b1fa6..d40343bc8833f69e7f8a9b28142992f466ef2164 100644 (file)
@@ -153,7 +153,10 @@ mod test {
 
         let addr = testutils!(@external descriptors, 10);
         wallet
-            .create_tx(TxBuilder::with_recipients(vec![(addr, 25_000)]))
+            .create_tx(TxBuilder::with_recipients(vec![(
+                addr.script_pubkey(),
+                25_000,
+            )]))
             .unwrap();
     }
 }
index 9505b222aadc2178d48518e08dc44ce68776815d..ab294d6c984621965cce8d703502160dbf6ad667 100644 (file)
@@ -93,7 +93,7 @@
 //!
 //! let to_address = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap();
 //! let (psbt, details) = wallet.create_tx(
-//!     TxBuilder::with_recipients(vec![(to_address, 50_000)])
+//!     TxBuilder::with_recipients(vec![(to_address.script_pubkey(), 50_000)])
 //!         .coin_selection(AlwaysSpendEverything),
 //! )?;
 //!
index c72f769d2cb04d49393ec7663a2730631d7ff032..7d3d48fa3f6eec4e3a43e8a9a97dee79f25be032 100644 (file)
 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 // SOFTWARE.
 
+//! Wallet export
+//!
+//! This modules implements the wallet export format used by [FullyNoded](https://github.com/Fonta1n3/FullyNoded/blob/10b7808c8b929b171cca537fb50522d015168ac9/Docs/Wallets/Wallet-Export-Spec.md).
+//!
+//! ## Examples
+//!
+//! ### Import from JSON
+//!
+//! ```
+//! # use std::str::FromStr;
+//! # use bitcoin::*;
+//! # use magical_bitcoin_wallet::database::*;
+//! # use magical_bitcoin_wallet::wallet::export::*;
+//! # use magical_bitcoin_wallet::*;
+//! let import = r#"{
+//!     "descriptor": "wpkh([c258d2e4\/84h\/1h\/0h]tpubDD3ynpHgJQW8VvWRzQ5WFDCrs4jqVFGHB3vLC3r49XHJSqP8bHKdK4AriuUKLccK68zfzowx7YhmDN8SiSkgCDENUFx9qVw65YyqM78vyVe\/0\/*)",
+//!     "blockheight":1782088,
+//!     "label":"testnet"
+//! }"#;
+//!
+//! let import = WalletExport::from_str(import)?;
+//! let wallet: OfflineWallet<_> = Wallet::new_offline(&import.descriptor(), import.change_descriptor().as_deref(), Network::Testnet, MemoryDatabase::default())?;
+//! # Ok::<_, magical_bitcoin_wallet::Error>(())
+//! ```
+//!
+//! ### Export a `Wallet`
+//! ```
+//! # use bitcoin::*;
+//! # use magical_bitcoin_wallet::database::*;
+//! # use magical_bitcoin_wallet::wallet::export::*;
+//! # use magical_bitcoin_wallet::*;
+//! let wallet: OfflineWallet<_> = Wallet::new_offline(
+//!     "wpkh([c258d2e4/84h/1h/0h]tpubDD3ynpHgJQW8VvWRzQ5WFDCrs4jqVFGHB3vLC3r49XHJSqP8bHKdK4AriuUKLccK68zfzowx7YhmDN8SiSkgCDENUFx9qVw65YyqM78vyVe/0/*)",
+//!     Some("wpkh([c258d2e4/84h/1h/0h]tpubDD3ynpHgJQW8VvWRzQ5WFDCrs4jqVFGHB3vLC3r49XHJSqP8bHKdK4AriuUKLccK68zfzowx7YhmDN8SiSkgCDENUFx9qVw65YyqM78vyVe/1/*)"),
+//!     Network::Testnet,
+//!     MemoryDatabase::default()
+//! )?;
+//! let export = WalletExport::export_wallet(&wallet, "exported wallet", true)
+//!     .map_err(ToString::to_string)
+//!     .map_err(magical_bitcoin_wallet::Error::Generic)?;
+//!
+//! println!("Exported: {}", export.to_string());
+//! # Ok::<_, magical_bitcoin_wallet::Error>(())
+//! ```
+
 use std::str::FromStr;
 
 use serde::{Deserialize, Serialize};
@@ -32,10 +77,15 @@ use crate::blockchain::Blockchain;
 use crate::database::BatchDatabase;
 use crate::wallet::Wallet;
 
+/// Structure that contains the export of a wallet
+///
+/// For a usage example see [this module](crate::wallet::export)'s documentation.
 #[derive(Debug, Serialize, Deserialize)]
 pub struct WalletExport {
     descriptor: String,
+    /// Earliest block to rescan when looking for the wallet's transactions
     pub blockheight: u32,
+    /// Arbitrary label for the wallet
     pub label: String,
 }
 
@@ -54,6 +104,17 @@ impl FromStr for WalletExport {
 }
 
 impl WalletExport {
+    /// Export a wallet
+    ///
+    /// This function returns an error if it determines that the `wallet`'s descriptor(s) are not
+    /// supported by Bitcoin Core or don't follow the standard derivation paths defined by BIP44
+    /// and others.
+    ///
+    /// If `include_blockheight` is `true`, this function will look into the `wallet`'s database
+    /// for the oldest transaction it knows and use that as the earliest block to rescan.
+    ///
+    /// If the database is empty or `include_blockheight` is false, the `blockheight` field
+    /// returned will be `0`.
     pub fn export_wallet<B: Blockchain, D: BatchDatabase>(
         wallet: &Wallet<B, D>,
         label: &str,
@@ -118,10 +179,12 @@ impl WalletExport {
         }
     }
 
+    /// Return the external descriptor
     pub fn descriptor(&self) -> String {
         self.descriptor.clone()
     }
 
+    /// Return the internal descriptor, if present
     pub fn change_descriptor(&self) -> Option<String> {
         let replaced = self.descriptor.replace("/0/*", "/1/*");
 
index 683533a7a5028266281fcb4dbde024925ab7ab66..21d3a1611f04610c81d1da9ab5f6a8e4d9ba943c 100644 (file)
@@ -229,7 +229,7 @@ where
             (None, Some(csv)) => csv,
             (Some(rbf), Some(csv)) if rbf < csv => return Err(Error::Generic(format!("Cannot enable RBF with nSequence `{}`, since at least `{}` is required to spend with OP_CSV", rbf, csv))),
             (None, _) if requirements.timelock.is_some() => 0xFFFFFFFE,
-            (Some(rbf), _) if rbf >= 0xFFFFFFFE => return Err(Error::Generic("Cannot enable RBF with anumber >= 0xFFFFFFFE".into())),
+            (Some(rbf), _) if rbf >= 0xFFFFFFFE => return Err(Error::Generic("Cannot enable RBF with a nSequence >= 0xFFFFFFFE".into())),
             (Some(rbf), _) => rbf,
             (None, _) => 0xFFFFFFFF,
         };
@@ -254,22 +254,19 @@ where
         let calc_fee_bytes = |wu| (wu as f32) * fee_rate.as_sat_vb() / 4.0;
         fee_amount += calc_fee_bytes(tx.get_weight());
 
-        for (index, (address, satoshi)) in builder.recipients.iter().enumerate() {
+        for (index, (script_pubkey, satoshi)) in builder.recipients.iter().enumerate() {
             let value = match builder.send_all {
                 true => 0,
                 false if satoshi.is_dust() => return Err(Error::OutputBelowDustLimit(index)),
                 false => *satoshi,
             };
 
-            // TODO: proper checks for testnet/regtest p2sh/p2pkh
-            if address.network != self.network && self.network != Network::Regtest {
-                return Err(Error::InvalidAddressNetwork(address.clone()));
-            } else if self.is_mine(&address.script_pubkey())? {
+            if self.is_mine(script_pubkey)? {
                 received += value;
             }
 
             let new_out = TxOut {
-                script_pubkey: address.script_pubkey(),
+                script_pubkey: script_pubkey.clone(),
                 value,
             };
             fee_amount += calc_fee_bytes(serialize(&new_out).len() * 4);
@@ -1251,7 +1248,7 @@ mod test {
         let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
         let addr = wallet.get_new_address().unwrap();
         wallet
-            .create_tx(TxBuilder::with_recipients(vec![(addr, 25_000)]).version(0))
+            .create_tx(TxBuilder::with_recipients(vec![(addr.script_pubkey(), 25_000)]).version(0))
             .unwrap();
     }
 
@@ -1263,7 +1260,7 @@ mod test {
         let (wallet, _, _) = get_funded_wallet(get_test_single_sig_csv());
         let addr = wallet.get_new_address().unwrap();
         wallet
-            .create_tx(TxBuilder::with_recipients(vec![(addr, 25_000)]).version(1))
+            .create_tx(TxBuilder::with_recipients(vec![(addr.script_pubkey(), 25_000)]).version(1))
             .unwrap();
     }
 
@@ -1272,7 +1269,7 @@ mod test {
         let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
         let addr = wallet.get_new_address().unwrap();
         let (psbt, _) = wallet
-            .create_tx(TxBuilder::with_recipients(vec![(addr, 25_000)]).version(42))
+            .create_tx(TxBuilder::with_recipients(vec![(addr.script_pubkey(), 25_000)]).version(42))
             .unwrap();
 
         assert_eq!(psbt.global.unsigned_tx.version, 42);
@@ -1283,7 +1280,10 @@ mod test {
         let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
         let addr = wallet.get_new_address().unwrap();
         let (psbt, _) = wallet
-            .create_tx(TxBuilder::with_recipients(vec![(addr, 25_000)]))
+            .create_tx(TxBuilder::with_recipients(vec![(
+                addr.script_pubkey(),
+                25_000,
+            )]))
             .unwrap();
 
         assert_eq!(psbt.global.unsigned_tx.lock_time, 0);
@@ -1294,7 +1294,10 @@ mod test {
         let (wallet, _, _) = get_funded_wallet(get_test_single_sig_cltv());
         let addr = wallet.get_new_address().unwrap();
         let (psbt, _) = wallet
-            .create_tx(TxBuilder::with_recipients(vec![(addr, 25_000)]))
+            .create_tx(TxBuilder::with_recipients(vec![(
+                addr.script_pubkey(),
+                25_000,
+            )]))
             .unwrap();
 
         assert_eq!(psbt.global.unsigned_tx.lock_time, 100_000);
@@ -1305,7 +1308,9 @@ mod test {
         let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
         let addr = wallet.get_new_address().unwrap();
         let (psbt, _) = wallet
-            .create_tx(TxBuilder::with_recipients(vec![(addr, 25_000)]).nlocktime(630_000))
+            .create_tx(
+                TxBuilder::with_recipients(vec![(addr.script_pubkey(), 25_000)]).nlocktime(630_000),
+            )
             .unwrap();
 
         assert_eq!(psbt.global.unsigned_tx.lock_time, 630_000);
@@ -1316,7 +1321,9 @@ mod test {
         let (wallet, _, _) = get_funded_wallet(get_test_single_sig_cltv());
         let addr = wallet.get_new_address().unwrap();
         let (psbt, _) = wallet
-            .create_tx(TxBuilder::with_recipients(vec![(addr, 25_000)]).nlocktime(630_000))
+            .create_tx(
+                TxBuilder::with_recipients(vec![(addr.script_pubkey(), 25_000)]).nlocktime(630_000),
+            )
             .unwrap();
 
         assert_eq!(psbt.global.unsigned_tx.lock_time, 630_000);
@@ -1330,7 +1337,9 @@ mod test {
         let (wallet, _, _) = get_funded_wallet(get_test_single_sig_cltv());
         let addr = wallet.get_new_address().unwrap();
         wallet
-            .create_tx(TxBuilder::with_recipients(vec![(addr, 25_000)]).nlocktime(50000))
+            .create_tx(
+                TxBuilder::with_recipients(vec![(addr.script_pubkey(), 25_000)]).nlocktime(50000),
+            )
             .unwrap();
     }
 
@@ -1339,7 +1348,10 @@ mod test {
         let (wallet, _, _) = get_funded_wallet(get_test_single_sig_csv());
         let addr = wallet.get_new_address().unwrap();
         let (psbt, _) = wallet
-            .create_tx(TxBuilder::with_recipients(vec![(addr, 25_000)]))
+            .create_tx(TxBuilder::with_recipients(vec![(
+                addr.script_pubkey(),
+                25_000,
+            )]))
             .unwrap();
 
         assert_eq!(psbt.global.unsigned_tx.input[0].sequence, 6);
@@ -1350,7 +1362,9 @@ mod test {
         let (wallet, _, _) = get_funded_wallet(get_test_single_sig_csv());
         let addr = wallet.get_new_address().unwrap();
         let (psbt, _) = wallet
-            .create_tx(TxBuilder::with_recipients(vec![(addr, 25_000)]).enable_rbf())
+            .create_tx(
+                TxBuilder::with_recipients(vec![(addr.script_pubkey(), 25_000)]).enable_rbf(),
+            )
             .unwrap();
 
         assert_eq!(psbt.global.unsigned_tx.input[0].sequence, 0xFFFFFFFD);
@@ -1364,7 +1378,10 @@ mod test {
         let (wallet, _, _) = get_funded_wallet(get_test_single_sig_csv());
         let addr = wallet.get_new_address().unwrap();
         wallet
-            .create_tx(TxBuilder::with_recipients(vec![(addr, 25_000)]).enable_rbf_with_sequence(3))
+            .create_tx(
+                TxBuilder::with_recipients(vec![(addr.script_pubkey(), 25_000)])
+                    .enable_rbf_with_sequence(3),
+            )
             .unwrap();
     }
 
@@ -1373,20 +1390,23 @@ mod test {
         let (wallet, _, _) = get_funded_wallet(get_test_single_sig_cltv());
         let addr = wallet.get_new_address().unwrap();
         let (psbt, _) = wallet
-            .create_tx(TxBuilder::with_recipients(vec![(addr, 25_000)]))
+            .create_tx(TxBuilder::with_recipients(vec![(
+                addr.script_pubkey(),
+                25_000,
+            )]))
             .unwrap();
 
         assert_eq!(psbt.global.unsigned_tx.input[0].sequence, 0xFFFFFFFE);
     }
 
     #[test]
-    #[should_panic(expected = "Cannot enable RBF with anumber >= 0xFFFFFFFE")]
+    #[should_panic(expected = "Cannot enable RBF with a nSequence >= 0xFFFFFFFE")]
     fn test_create_tx_invalid_rbf_sequence() {
         let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
         let addr = wallet.get_new_address().unwrap();
         wallet
             .create_tx(
-                TxBuilder::with_recipients(vec![(addr, 25_000)])
+                TxBuilder::with_recipients(vec![(addr.script_pubkey(), 25_000)])
                     .enable_rbf_with_sequence(0xFFFFFFFE),
             )
             .unwrap();
@@ -1398,7 +1418,7 @@ mod test {
         let addr = wallet.get_new_address().unwrap();
         let (psbt, _) = wallet
             .create_tx(
-                TxBuilder::with_recipients(vec![(addr, 25_000)])
+                TxBuilder::with_recipients(vec![(addr.script_pubkey(), 25_000)])
                     .enable_rbf_with_sequence(0xDEADBEEF),
             )
             .unwrap();
@@ -1411,7 +1431,10 @@ mod test {
         let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
         let addr = wallet.get_new_address().unwrap();
         let (psbt, _) = wallet
-            .create_tx(TxBuilder::with_recipients(vec![(addr, 25_000)]))
+            .create_tx(TxBuilder::with_recipients(vec![(
+                addr.script_pubkey(),
+                25_000,
+            )]))
             .unwrap();
 
         assert_eq!(psbt.global.unsigned_tx.input[0].sequence, 0xFFFFFFFF);
@@ -1426,7 +1449,8 @@ mod test {
         let addr = wallet.get_new_address().unwrap();
         wallet
             .create_tx(
-                TxBuilder::with_recipients(vec![(addr.clone(), 25_000)]).do_not_spend_change(),
+                TxBuilder::with_recipients(vec![(addr.script_pubkey(), 25_000)])
+                    .do_not_spend_change(),
             )
             .unwrap();
     }
@@ -1438,7 +1462,11 @@ mod test {
         let addr = wallet.get_new_address().unwrap();
         wallet
             .create_tx(
-                TxBuilder::with_recipients(vec![(addr.clone(), 25_000), (addr, 10_000)]).send_all(),
+                TxBuilder::with_recipients(vec![
+                    (addr.script_pubkey(), 25_000),
+                    (addr.script_pubkey(), 10_000),
+                ])
+                .send_all(),
             )
             .unwrap();
     }
@@ -1448,7 +1476,7 @@ mod test {
         let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
         let addr = wallet.get_new_address().unwrap();
         let (psbt, details) = wallet
-            .create_tx(TxBuilder::with_recipients(vec![(addr.clone(), 0)]).send_all())
+            .create_tx(TxBuilder::with_recipients(vec![(addr.script_pubkey(), 0)]).send_all())
             .unwrap();
 
         assert_eq!(psbt.global.unsigned_tx.output.len(), 1);
@@ -1463,7 +1491,7 @@ mod test {
         let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
         let addr = wallet.get_new_address().unwrap();
         let (psbt, details) = wallet
-            .create_tx(TxBuilder::with_recipients(vec![(addr.clone(), 0)]).send_all())
+            .create_tx(TxBuilder::with_recipients(vec![(addr.script_pubkey(), 0)]).send_all())
             .unwrap();
 
         assert_fee_rate!(psbt.extract_tx(), details.fees, FeeRate::default(), @add_signature);
@@ -1475,7 +1503,7 @@ mod test {
         let addr = wallet.get_new_address().unwrap();
         let (psbt, details) = wallet
             .create_tx(
-                TxBuilder::with_recipients(vec![(addr.clone(), 0)])
+                TxBuilder::with_recipients(vec![(addr.script_pubkey(), 0)])
                     .fee_rate(FeeRate::from_sat_per_vb(5.0))
                     .send_all(),
             )
@@ -1492,7 +1520,7 @@ mod test {
         let addr = wallet.get_new_address().unwrap();
         let (psbt, details) = wallet
             .create_tx(
-                TxBuilder::with_recipients(vec![(addr.clone(), 25_000)])
+                TxBuilder::with_recipients(vec![(addr.script_pubkey(), 25_000)])
                     .ordering(TxOrdering::Untouched),
             )
             .unwrap();
@@ -1510,7 +1538,10 @@ mod test {
         let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
         let addr = wallet.get_new_address().unwrap();
         let (psbt, _) = wallet
-            .create_tx(TxBuilder::with_recipients(vec![(addr.clone(), 49_800)]))
+            .create_tx(TxBuilder::with_recipients(vec![(
+                addr.script_pubkey(),
+                49_800,
+            )]))
             .unwrap();
 
         assert_eq!(psbt.global.unsigned_tx.output.len(), 1);
@@ -1525,7 +1556,7 @@ mod test {
         // very high fee rate, so that the only output would be below dust
         wallet
             .create_tx(
-                TxBuilder::with_recipients(vec![(addr.clone(), 0)])
+                TxBuilder::with_recipients(vec![(addr.script_pubkey(), 0)])
                     .send_all()
                     .fee_rate(crate::FeeRate::from_sat_per_vb(453.0)),
             )
@@ -1538,8 +1569,11 @@ mod test {
         let addr = wallet.get_new_address().unwrap();
         let (psbt, details) = wallet
             .create_tx(
-                TxBuilder::with_recipients(vec![(addr.clone(), 30_000), (addr.clone(), 10_000)])
-                    .ordering(super::tx_builder::TxOrdering::BIP69Lexicographic),
+                TxBuilder::with_recipients(vec![
+                    (addr.script_pubkey(), 30_000),
+                    (addr.script_pubkey(), 10_000),
+                ])
+                .ordering(super::tx_builder::TxOrdering::BIP69Lexicographic),
             )
             .unwrap();
 
@@ -1557,7 +1591,10 @@ mod test {
         let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
         let addr = wallet.get_new_address().unwrap();
         let (psbt, _) = wallet
-            .create_tx(TxBuilder::with_recipients(vec![(addr.clone(), 30_000)]))
+            .create_tx(TxBuilder::with_recipients(vec![(
+                addr.script_pubkey(),
+                30_000,
+            )]))
             .unwrap();
 
         assert_eq!(psbt.inputs[0].sighash_type, Some(bitcoin::SigHashType::All));
@@ -1569,7 +1606,7 @@ mod test {
         let addr = wallet.get_new_address().unwrap();
         let (psbt, _) = wallet
             .create_tx(
-                TxBuilder::with_recipients(vec![(addr.clone(), 30_000)])
+                TxBuilder::with_recipients(vec![(addr.script_pubkey(), 30_000)])
                     .sighash(bitcoin::SigHashType::Single),
             )
             .unwrap();
@@ -1588,7 +1625,7 @@ mod test {
         let (wallet, _, _) = get_funded_wallet("wpkh([d34db33f/44'/0'/0']xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/0/*)");
         let addr = wallet.get_new_address().unwrap();
         let (psbt, _) = wallet
-            .create_tx(TxBuilder::with_recipients(vec![(addr.clone(), 0)]).send_all())
+            .create_tx(TxBuilder::with_recipients(vec![(addr.script_pubkey(), 0)]).send_all())
             .unwrap();
 
         assert_eq!(psbt.inputs[0].hd_keypaths.len(), 1);
@@ -1612,7 +1649,7 @@ mod test {
 
         let addr = testutils!(@external descriptors, 5);
         let (psbt, _) = wallet
-            .create_tx(TxBuilder::with_recipients(vec![(addr.clone(), 0)]).send_all())
+            .create_tx(TxBuilder::with_recipients(vec![(addr.script_pubkey(), 0)]).send_all())
             .unwrap();
 
         assert_eq!(psbt.outputs[0].hd_keypaths.len(), 1);
@@ -1633,7 +1670,7 @@ mod test {
             get_funded_wallet("sh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW))");
         let addr = wallet.get_new_address().unwrap();
         let (psbt, _) = wallet
-            .create_tx(TxBuilder::with_recipients(vec![(addr.clone(), 0)]).send_all())
+            .create_tx(TxBuilder::with_recipients(vec![(addr.script_pubkey(), 0)]).send_all())
             .unwrap();
 
         assert_eq!(
@@ -1656,7 +1693,7 @@ mod test {
             get_funded_wallet("wsh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW))");
         let addr = wallet.get_new_address().unwrap();
         let (psbt, _) = wallet
-            .create_tx(TxBuilder::with_recipients(vec![(addr.clone(), 0)]).send_all())
+            .create_tx(TxBuilder::with_recipients(vec![(addr.script_pubkey(), 0)]).send_all())
             .unwrap();
 
         assert_eq!(psbt.inputs[0].redeem_script, None);
@@ -1679,7 +1716,7 @@ mod test {
             get_funded_wallet("sh(wsh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW)))");
         let addr = wallet.get_new_address().unwrap();
         let (psbt, _) = wallet
-            .create_tx(TxBuilder::with_recipients(vec![(addr.clone(), 0)]).send_all())
+            .create_tx(TxBuilder::with_recipients(vec![(addr.script_pubkey(), 0)]).send_all())
             .unwrap();
 
         let script = Script::from(
@@ -1699,7 +1736,7 @@ mod test {
             get_funded_wallet("sh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW))");
         let addr = wallet.get_new_address().unwrap();
         let (psbt, _) = wallet
-            .create_tx(TxBuilder::with_recipients(vec![(addr.clone(), 0)]).send_all())
+            .create_tx(TxBuilder::with_recipients(vec![(addr.script_pubkey(), 0)]).send_all())
             .unwrap();
 
         assert!(psbt.inputs[0].non_witness_utxo.is_some());
@@ -1712,7 +1749,7 @@ mod test {
             get_funded_wallet("wsh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW))");
         let addr = wallet.get_new_address().unwrap();
         let (psbt, _) = wallet
-            .create_tx(TxBuilder::with_recipients(vec![(addr.clone(), 0)]).send_all())
+            .create_tx(TxBuilder::with_recipients(vec![(addr.script_pubkey(), 0)]).send_all())
             .unwrap();
 
         assert!(psbt.inputs[0].non_witness_utxo.is_none());
@@ -1726,7 +1763,7 @@ mod test {
         let addr = wallet.get_new_address().unwrap();
         let (psbt, _) = wallet
             .create_tx(
-                TxBuilder::with_recipients(vec![(addr.clone(), 0)])
+                TxBuilder::with_recipients(vec![(addr.script_pubkey(), 0)])
                     .force_non_witness_utxo()
                     .send_all(),
             )
@@ -1742,7 +1779,10 @@ mod test {
         let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
         let addr = wallet.get_new_address().unwrap();
         let (psbt, mut details) = wallet
-            .create_tx(TxBuilder::with_recipients(vec![(addr, 25_000)]))
+            .create_tx(TxBuilder::with_recipients(vec![(
+                addr.script_pubkey(),
+                25_000,
+            )]))
             .unwrap();
         let tx = psbt.extract_tx();
         let txid = tx.txid();
@@ -1759,7 +1799,10 @@ mod test {
         let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
         let addr = wallet.get_new_address().unwrap();
         let (psbt, mut details) = wallet
-            .create_tx(TxBuilder::with_recipients(vec![(addr, 25_000)]))
+            .create_tx(TxBuilder::with_recipients(vec![(
+                addr.script_pubkey(),
+                25_000,
+            )]))
             .unwrap();
         let tx = psbt.extract_tx();
         let txid = tx.txid();
@@ -1777,7 +1820,9 @@ mod test {
         let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
         let addr = wallet.get_new_address().unwrap();
         let (psbt, mut details) = wallet
-            .create_tx(TxBuilder::with_recipients(vec![(addr, 25_000)]).enable_rbf())
+            .create_tx(
+                TxBuilder::with_recipients(vec![(addr.script_pubkey(), 25_000)]).enable_rbf(),
+            )
             .unwrap();
         let tx = psbt.extract_tx();
         let txid = tx.txid();
@@ -1798,7 +1843,9 @@ mod test {
         let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
         let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
         let (psbt, mut original_details) = wallet
-            .create_tx(TxBuilder::with_recipients(vec![(addr.clone(), 25_000)]).enable_rbf())
+            .create_tx(
+                TxBuilder::with_recipients(vec![(addr.script_pubkey(), 25_000)]).enable_rbf(),
+            )
             .unwrap();
         let mut tx = psbt.extract_tx();
         let txid = tx.txid();
@@ -1860,7 +1907,7 @@ mod test {
         let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
         let (psbt, mut original_details) = wallet
             .create_tx(
-                TxBuilder::with_recipients(vec![(addr.clone(), 0)])
+                TxBuilder::with_recipients(vec![(addr.script_pubkey(), 0)])
                     .send_all()
                     .enable_rbf(),
             )
@@ -1914,7 +1961,7 @@ mod test {
         let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
         let (psbt, mut original_details) = wallet
             .create_tx(
-                TxBuilder::with_recipients(vec![(addr.clone(), 0)])
+                TxBuilder::with_recipients(vec![(addr.script_pubkey(), 0)])
                     .utxos(vec![OutPoint {
                         txid: incoming_txid,
                         vout: 0,
@@ -1961,7 +2008,9 @@ mod test {
 
         let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
         let (psbt, mut original_details) = wallet
-            .create_tx(TxBuilder::with_recipients(vec![(addr.clone(), 45_000)]).enable_rbf())
+            .create_tx(
+                TxBuilder::with_recipients(vec![(addr.script_pubkey(), 45_000)]).enable_rbf(),
+            )
             .unwrap();
         let mut tx = psbt.extract_tx();
         let txid = tx.txid();
@@ -2025,7 +2074,7 @@ mod test {
         let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
         let (psbt, mut original_details) = wallet
             .create_tx(
-                TxBuilder::with_recipients(vec![(addr.clone(), 0)])
+                TxBuilder::with_recipients(vec![(addr.script_pubkey(), 0)])
                     .send_all()
                     .add_utxo(OutPoint {
                         txid: incoming_txid,
@@ -2101,7 +2150,9 @@ mod test {
 
         let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
         let (psbt, mut original_details) = wallet
-            .create_tx(TxBuilder::with_recipients(vec![(addr.clone(), 45_000)]).enable_rbf())
+            .create_tx(
+                TxBuilder::with_recipients(vec![(addr.script_pubkey(), 45_000)]).enable_rbf(),
+            )
             .unwrap();
         let mut tx = psbt.extract_tx();
         assert_eq!(tx.input.len(), 1);
@@ -2161,7 +2212,9 @@ mod test {
 
         let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
         let (psbt, mut original_details) = wallet
-            .create_tx(TxBuilder::with_recipients(vec![(addr.clone(), 45_000)]).enable_rbf())
+            .create_tx(
+                TxBuilder::with_recipients(vec![(addr.script_pubkey(), 45_000)]).enable_rbf(),
+            )
             .unwrap();
         let mut tx = psbt.extract_tx();
         let txid = tx.txid();
@@ -2226,7 +2279,7 @@ mod test {
         let (wallet, _, _) = get_funded_wallet("wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)");
         let addr = wallet.get_new_address().unwrap();
         let (psbt, _) = wallet
-            .create_tx(TxBuilder::with_recipients(vec![(addr.clone(), 0)]).send_all())
+            .create_tx(TxBuilder::with_recipients(vec![(addr.script_pubkey(), 0)]).send_all())
             .unwrap();
 
         let (signed_psbt, finalized) = wallet.sign(psbt, None).unwrap();
@@ -2242,7 +2295,7 @@ mod test {
             get_funded_wallet("wpkh(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW)");
         let addr = wallet.get_new_address().unwrap();
         let (psbt, _) = wallet
-            .create_tx(TxBuilder::with_recipients(vec![(addr.clone(), 0)]).send_all())
+            .create_tx(TxBuilder::with_recipients(vec![(addr.script_pubkey(), 0)]).send_all())
             .unwrap();
 
         let (signed_psbt, finalized) = wallet.sign(psbt, None).unwrap();
@@ -2257,7 +2310,7 @@ mod test {
         let (wallet, _, _) = get_funded_wallet("wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)");
         let addr = wallet.get_new_address().unwrap();
         let (mut psbt, _) = wallet
-            .create_tx(TxBuilder::with_recipients(vec![(addr.clone(), 0)]).send_all())
+            .create_tx(TxBuilder::with_recipients(vec![(addr.script_pubkey(), 0)]).send_all())
             .unwrap();
 
         psbt.inputs[0].hd_keypaths.clear();
index 766f4bd27cab73a2281dabc9c6399ba780261846..c90afafbfcec8f61127063e6425f383f86137bf4 100644 (file)
 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 // SOFTWARE.
 
+//! Generalized signers
+//!
+//! This module provides the ability to add customized signers to a [`Wallet`](super::Wallet)
+//! through the [`Wallet::add_signer`](super::Wallet::add_signer) function.
+//!
+//! ```
+//! # use std::sync::Arc;
+//! # use std::str::FromStr;
+//! # use bitcoin::*;
+//! # use bitcoin::util::psbt;
+//! # use bitcoin::util::bip32::Fingerprint;
+//! # use magical_bitcoin_wallet::signer::*;
+//! # use magical_bitcoin_wallet::database::*;
+//! # use magical_bitcoin_wallet::*;
+//! # #[derive(Debug)]
+//! # struct CustomHSM;
+//! # impl CustomHSM {
+//! #     fn sign_input(&self, _psbt: &mut psbt::PartiallySignedTransaction, _input: usize) -> Result<(), SignerError> {
+//! #         Ok(())
+//! #     }
+//! #     fn connect() -> Self {
+//! #         CustomHSM
+//! #     }
+//! # }
+//! #[derive(Debug)]
+//! struct CustomSigner {
+//!     device: CustomHSM,
+//! }
+//!
+//! impl CustomSigner {
+//!     fn connect() -> Self {
+//!         CustomSigner { device: CustomHSM::connect() }
+//!     }
+//! }
+//!
+//! impl Signer for CustomSigner {
+//!     fn sign(
+//!         &self,
+//!         psbt: &mut psbt::PartiallySignedTransaction,
+//!         input_index: Option<usize>,
+//!     ) -> Result<(), SignerError> {
+//!         let input_index = input_index.ok_or(SignerError::InputIndexOutOfRange)?;
+//!         self.device.sign_input(psbt, input_index)?;
+//!
+//!         Ok(())
+//!     }
+//!
+//!     fn sign_whole_tx(&self) -> bool {
+//!         false
+//!     }
+//! }
+//!
+//! let custom_signer = CustomSigner::connect();
+//!
+//! let descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/*)";
+//! let mut wallet: OfflineWallet<_> = Wallet::new_offline(descriptor, None, Network::Testnet, MemoryDatabase::default())?;
+//! wallet.add_signer(
+//!     ScriptType::External,
+//!     Fingerprint::from_str("e30f11b8").unwrap().into(),
+//!     SignerOrdering(200),
+//!     Arc::new(Box::new(custom_signer))
+//! );
+//!
+//! # Ok::<_, magical_bitcoin_wallet::Error>(())
+//! ```
+
 use std::cmp::Ordering;
 use std::collections::BTreeMap;
 use std::fmt;
@@ -42,7 +108,7 @@ use miniscript::{Legacy, MiniscriptKey, Segwitv0};
 use crate::descriptor::XKeyUtils;
 
 /// Identifier of a signer in the `SignersContainers`. Used as a key to find the right signer among
-/// many of them
+/// multiple of them
 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
 pub enum SignerId<Pk: MiniscriptKey> {
     PkHash(<Pk as MiniscriptKey>::Hash),
@@ -93,15 +159,30 @@ impl fmt::Display for SignerError {
 impl std::error::Error for SignerError {}
 
 /// Trait for signers
+///
+/// This trait can be implemented to provide customized signers to the wallet. For an example see
+/// [`this module`](crate::wallet::signer)'s documentation.
 pub trait Signer: fmt::Debug {
+    /// Sign a PSBT
+    ///
+    /// The `input_index` argument is only provided if the wallet doesn't declare to sign the whole
+    /// transaction in one go (see [`Signer::sign_whole_tx`]). Otherwise its value is `None` and
+    /// can be ignored.
     fn sign(
         &self,
         psbt: &mut psbt::PartiallySignedTransaction,
         input_index: Option<usize>,
     ) -> Result<(), SignerError>;
 
+    /// Return whether or not the signer signs the whole transaction in one go instead of every
+    /// input individually
     fn sign_whole_tx(&self) -> bool;
 
+    /// Return the secret key for the signer
+    ///
+    /// This is used internally to reconstruct the original descriptor that may contain secrets.
+    /// External signers that are meant to keep key isolated should just return `None` here (which
+    /// is the default for this method, if not overridden).
     fn descriptor_secret_key(&self) -> Option<DescriptorSecretKey> {
         None
     }
@@ -195,6 +276,11 @@ impl Signer for PrivateKey {
     }
 }
 
+/// Defines the order in which signers are called
+///
+/// The default value is `100`. Signers with an ordering above that will be called later,
+/// and they will thus see the partial signatures added to the transaction once they get to sign
+/// themselves.
 #[derive(Debug, Clone, PartialOrd, PartialEq, Ord, Eq)]
 pub struct SignerOrdering(pub usize);
 
index 0d0a77c73c31224cb8a87bfad32596cb19ac7b2a..6b6c436eeccdc67801f4718c292b191c9ef88620 100644 (file)
 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 // SOFTWARE.
 
+//! Cross-platform time
+//!
+//! This module provides a function to get the current timestamp that works on all the platforms
+//! supported by the library.
+//!
+//! It can be useful to compare it with the timestamps found in
+//! [`TransactionDetails`](crate::types::TransactionDetails).
+
 use std::time::Duration;
 
 #[cfg(target_arch = "wasm32")]
@@ -29,6 +37,7 @@ use js_sys::Date;
 #[cfg(not(target_arch = "wasm32"))]
 use std::time::{Instant as SystemInstant, SystemTime, UNIX_EPOCH};
 
+/// Return the current timestamp in seconds
 #[cfg(not(target_arch = "wasm32"))]
 pub fn get_timestamp() -> u64 {
     SystemTime::now()
@@ -36,6 +45,7 @@ pub fn get_timestamp() -> u64 {
         .unwrap()
         .as_secs()
 }
+/// Return the current timestamp in seconds
 #[cfg(target_arch = "wasm32")]
 pub fn get_timestamp() -> u64 {
     let millis = Date::now();
index aab9951f327ed919337a4295280f2acbc7c64eda..1f3d421fafe32b7d1110ab9c1d7a11665d7a93c7 100644 (file)
 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 // SOFTWARE.
 
+//! Transaction builder
+//!
+//! ## Example
+//!
+//! ```
+//! # use std::str::FromStr;
+//! # use bitcoin::*;
+//! # use magical_bitcoin_wallet::*;
+//! # let to_address = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap();
+//! // Create a transaction with one output to `to_address` of 50_000 satoshi, with a custom fee rate
+//! // of 5.0 satoshi/vbyte, only spending non-change outputs and with RBF signaling
+//! // enabled
+//! let builder = TxBuilder::with_recipients(vec![(to_address.script_pubkey(), 50_000)])
+//!     .fee_rate(FeeRate::from_sat_per_vb(5.0))
+//!     .do_not_spend_change()
+//!     .enable_rbf();
+//! ```
+
 use std::collections::BTreeMap;
 use std::default::Default;
 
-use bitcoin::{Address, OutPoint, SigHashType, Transaction};
+use bitcoin::{OutPoint, Script, SigHashType, Transaction};
 
 use super::coin_selection::{CoinSelectionAlgorithm, DefaultCoinSelectionAlgorithm};
 use crate::types::{FeeRate, UTXO};
 
+/// A transaction builder
+///
+/// This structure contains the configuration that the wallet must follow to build a transaction.
+///
+/// For an example see [this module](super::tx_builder)'s documentation;
 #[derive(Debug, Default)]
 pub struct TxBuilder<Cs: CoinSelectionAlgorithm> {
-    pub(crate) recipients: Vec<(Address, u64)>,
+    pub(crate) recipients: Vec<(Script, u64)>,
     pub(crate) send_all: bool,
     pub(crate) fee_rate: Option<FeeRate>,
     pub(crate) policy_path: Option<BTreeMap<String, Vec<usize>>>,
@@ -49,112 +72,182 @@ pub struct TxBuilder<Cs: CoinSelectionAlgorithm> {
 }
 
 impl TxBuilder<DefaultCoinSelectionAlgorithm> {
+    /// Create an empty builder
     pub fn new() -> Self {
         Self::default()
     }
 
-    pub fn with_recipients(recipients: Vec<(Address, u64)>) -> Self {
+    /// Create a builder starting from a list of recipients
+    pub fn with_recipients(recipients: Vec<(Script, u64)>) -> Self {
         Self::default().set_recipients(recipients)
     }
 }
 
 impl<Cs: CoinSelectionAlgorithm> TxBuilder<Cs> {
-    pub fn set_recipients(mut self, recipients: Vec<(Address, u64)>) -> Self {
+    /// Replace the recipients already added with a new list
+    pub fn set_recipients(mut self, recipients: Vec<(Script, u64)>) -> Self {
         self.recipients = recipients;
         self
     }
 
-    pub fn add_recipient(mut self, address: Address, amount: u64) -> Self {
-        self.recipients.push((address, amount));
+    /// Add a recipient to the internal list
+    pub fn add_recipient(mut self, script_pubkey: Script, amount: u64) -> Self {
+        self.recipients.push((script_pubkey, amount));
         self
     }
 
+    /// Send all the selected utxos to a single output
+    ///
+    /// Adding more than one recipients with this option enabled will result in an error.
+    ///
+    /// The value associated with the only recipient is irrelevant and will be replaced by the wallet.
     pub fn send_all(mut self) -> Self {
         self.send_all = true;
         self
     }
 
+    /// Set a custom fee rate
     pub fn fee_rate(mut self, fee_rate: FeeRate) -> Self {
         self.fee_rate = Some(fee_rate);
         self
     }
 
+    /// Set the policy path to use while creating the transaction
+    ///
+    /// This method accepts a map where the key is the policy node id (see
+    /// [`Policy::id`](crate::descriptor::Policy::id)) and the value is the list of the indexes of
+    /// the items that are intended to be satisfied from the policy node (see
+    /// [`SatisfiableItem::Thresh::items`](crate::descriptor::policy::SatisfiableItem::Thresh::items)).
     pub fn policy_path(mut self, policy_path: BTreeMap<String, Vec<usize>>) -> Self {
         self.policy_path = Some(policy_path);
         self
     }
 
-    /// These have priority over the "unspendable" utxos
+    /// Replace the internal list of utxos that **must** be spent with a new list
+    ///
+    /// These have priority over the "unspendable" utxos, meaning that if a utxo is present both in
+    /// the "utxos" and the "unspendable" list, it will be spent.
     pub fn utxos(mut self, utxos: Vec<OutPoint>) -> Self {
         self.utxos = Some(utxos);
         self
     }
 
-    /// This has priority over the "unspendable" utxos
+    /// Add a utxo to the internal list of utxos that **must** be spent
+    ///
+    /// These have priority over the "unspendable" utxos, meaning that if a utxo is present both in
+    /// the "utxos" and the "unspendable" list, it will be spent.
     pub fn add_utxo(mut self, utxo: OutPoint) -> Self {
         self.utxos.get_or_insert(vec![]).push(utxo);
         self
     }
 
+    /// Replace the internal list of unspendable utxos with a new list
+    ///
+    /// It's important to note that the "must-be-spent" utxos added with [`TxBuilder::utxos`] and
+    /// [`TxBuilder::add_utxo`] have priority over these. See the docs of the two linked methods
+    /// for more details.
     pub fn unspendable(mut self, unspendable: Vec<OutPoint>) -> Self {
         self.unspendable = Some(unspendable);
         self
     }
 
+    /// Add a utxo to the internal list of unspendable utxos
+    ///
+    /// It's important to note that the "must-be-spent" utxos added with [`TxBuilder::utxos`] and
+    /// [`TxBuilder::add_utxo`] have priority over this. See the docs of the two linked methods
+    /// for more details.
     pub fn add_unspendable(mut self, unspendable: OutPoint) -> Self {
         self.unspendable.get_or_insert(vec![]).push(unspendable);
         self
     }
 
+    /// Sign with a specific sig hash
+    ///
+    /// **Use this option very carefully**
     pub fn sighash(mut self, sighash: SigHashType) -> Self {
         self.sighash = Some(sighash);
         self
     }
 
+    /// Choose the ordering for inputs and outputs of the transaction
     pub fn ordering(mut self, ordering: TxOrdering) -> Self {
         self.ordering = ordering;
         self
     }
 
+    /// Use a specific nLockTime while creating the transaction
+    ///
+    /// This can cause conflicts if the wallet's descriptors contain an "after" (OP_CLTV) operator.
     pub fn nlocktime(mut self, locktime: u32) -> Self {
         self.locktime = Some(locktime);
         self
     }
 
+    /// Enable signaling RBF
+    ///
+    /// This will use the default nSequence value of `0xFFFFFFFD`.
     pub fn enable_rbf(self) -> Self {
         self.enable_rbf_with_sequence(0xFFFFFFFD)
     }
 
+    /// Enable signaling RBF with a specific nSequence value
+    ///
+    /// This can cause conflicts if the wallet's descriptors contain an "older" (OP_CSV) operator
+    /// and the given `nsequence` is lower than the CSV value.
+    ///
+    /// If the `nsequence` is higher than `0xFFFFFFFD` an error will be thrown, since it would not
+    /// be a valid nSequence to signal RBF.
     pub fn enable_rbf_with_sequence(mut self, nsequence: u32) -> Self {
         self.rbf = Some(nsequence);
         self
     }
 
+    /// Build a transaction with a specific version
+    ///
+    /// The `version` should always be greater than `0` and greater than `1` if the wallet's
+    /// descriptors contain an "older" (OP_CSV) operator.
     pub fn version(mut self, version: u32) -> Self {
         self.version = Some(Version(version));
         self
     }
 
+    /// Do not spend change outputs
+    ///
+    /// This effectively adds all the change outputs to the "unspendable" list. See
+    /// [`TxBuilder::unspendable`].
     pub fn do_not_spend_change(mut self) -> Self {
         self.change_policy = ChangeSpendPolicy::ChangeForbidden;
         self
     }
 
+    /// Only spend change outputs
+    ///
+    /// This effectively adds all the non-change outputs to the "unspendable" list. See
+    /// [`TxBuilder::unspendable`].
     pub fn only_spend_change(mut self) -> Self {
         self.change_policy = ChangeSpendPolicy::OnlyChange;
         self
     }
 
+    /// Set a specific [`ChangeSpendPolicy`]. See [`TxBuilder::do_not_spend_change`] and
+    /// [`TxBuilder::only_spend_change`] for some shortcuts.
     pub fn change_policy(mut self, change_policy: ChangeSpendPolicy) -> Self {
         self.change_policy = change_policy;
         self
     }
 
+    /// Fill-in the [`psbt::Input::non_witness_utxo`](bitcoin::util::psbt::Input::non_witness_utxo) field even if the wallet only has SegWit
+    /// descriptors.
+    ///
+    /// This is useful for signers which always require it, like Trezor hardware wallets.
     pub fn force_non_witness_utxo(mut self) -> Self {
         self.force_non_witness_utxo = true;
         self
     }
 
+    /// Choose the coin selection algorithm
+    ///
+    /// Overrides the [`DefaultCoinSelectionAlgorithm`](super::coin_selection::DefaultCoinSelectionAlgorithm).
     pub fn coin_selection<P: CoinSelectionAlgorithm>(self, coin_selection: P) -> TxBuilder<P> {
         TxBuilder {
             recipients: self.recipients,
@@ -175,10 +268,14 @@ impl<Cs: CoinSelectionAlgorithm> TxBuilder<Cs> {
     }
 }
 
+/// Ordering of the transaction's inputs and outputs
 #[derive(Debug, Ord, PartialOrd, Eq, PartialEq, Hash, Clone, Copy)]
 pub enum TxOrdering {
+    /// Randomized (default)
     Shuffle,
+    /// Unchanged
     Untouched,
+    /// BIP69 / Lexicographic
     BIP69Lexicographic,
 }
 
@@ -215,7 +312,9 @@ impl TxOrdering {
     }
 }
 
-// Helper type that wraps u32 and has a default value of 1
+/// Transaction version
+///
+/// Has a default value of `1`
 #[derive(Debug, Ord, PartialOrd, Eq, PartialEq, Hash, Clone, Copy)]
 pub(crate) struct Version(pub(crate) u32);
 
@@ -225,10 +324,14 @@ impl Default for Version {
     }
 }
 
+/// Policy regarding the use of change outputs when creating a transaction
 #[derive(Debug, Ord, PartialOrd, Eq, PartialEq, Hash, Clone, Copy)]
 pub enum ChangeSpendPolicy {
+    /// Use both change and non-change outputs (default)
     ChangeAllowed,
+    /// Only use change outputs (see [`TxBuilder::only_spend_change`])
     OnlyChange,
+    /// Only use non-change outputs (see [`TxBuilder::do_not_spend_change`])
     ChangeForbidden,
 }
 
index 678473521ec2fea53de87c7fbdff48af09e16a00..6242f760987573f0c464bf4896a0e6b1075fe5ae 100644 (file)
@@ -307,7 +307,7 @@ pub fn magical_blockchain_tests(attr: TokenStream, item: TokenStream) -> TokenSt
                     wallet.sync(noop_progress(), None).unwrap();
                     assert_eq!(wallet.get_balance().unwrap(), 50_000);
 
-                    let (psbt, details) = wallet.create_tx(TxBuilder::with_recipients(vec![(node_addr, 25_000)])).unwrap();
+                    let (psbt, details) = wallet.create_tx(TxBuilder::with_recipients(vec![(node_addr.script_pubkey(), 25_000)])).unwrap();
                     let (psbt, finalized) = wallet.sign(psbt, None).unwrap();
                     assert!(finalized, "Cannot finalize transaction");
                     let tx = psbt.extract_tx();
@@ -334,7 +334,7 @@ pub fn magical_blockchain_tests(attr: TokenStream, item: TokenStream) -> TokenSt
                     wallet.sync(noop_progress(), None).unwrap();
                     assert_eq!(wallet.get_balance().unwrap(), 50_000);
 
-                    let (psbt, details) = wallet.create_tx(TxBuilder::with_recipients(vec![(node_addr, 25_000)])).unwrap();
+                    let (psbt, details) = wallet.create_tx(TxBuilder::with_recipients(vec![(node_addr.script_pubkey(), 25_000)])).unwrap();
                     let (psbt, finalized) = wallet.sign(psbt, None).unwrap();
                     assert!(finalized, "Cannot finalize transaction");
                     let sent_txid = wallet.broadcast(psbt.extract_tx()).unwrap();
@@ -373,7 +373,7 @@ pub fn magical_blockchain_tests(attr: TokenStream, item: TokenStream) -> TokenSt
 
                     let mut total_sent = 0;
                     for _ in 0..5 {
-                        let (psbt, details) = wallet.create_tx(TxBuilder::with_recipients(vec![(node_addr.clone(), 5_000)])).unwrap();
+                        let (psbt, details) = wallet.create_tx(TxBuilder::with_recipients(vec![(node_addr.script_pubkey().clone(), 5_000)])).unwrap();
                         let (psbt, finalized) = wallet.sign(psbt, None).unwrap();
                         assert!(finalized, "Cannot finalize transaction");
                         wallet.broadcast(psbt.extract_tx()).unwrap();
@@ -405,7 +405,7 @@ pub fn magical_blockchain_tests(attr: TokenStream, item: TokenStream) -> TokenSt
                     wallet.sync(noop_progress(), None).unwrap();
                     assert_eq!(wallet.get_balance().unwrap(), 50_000);
 
-                    let (psbt, details) = wallet.create_tx(TxBuilder::with_recipients(vec![(node_addr.clone(), 5_000)]).enable_rbf()).unwrap();
+                    let (psbt, details) = wallet.create_tx(TxBuilder::with_recipients(vec![(node_addr.script_pubkey().clone(), 5_000)]).enable_rbf()).unwrap();
                     let (psbt, finalized) = wallet.sign(psbt, None).unwrap();
                     assert!(finalized, "Cannot finalize transaction");
                     wallet.broadcast(psbt.extract_tx()).unwrap();
@@ -437,7 +437,7 @@ pub fn magical_blockchain_tests(attr: TokenStream, item: TokenStream) -> TokenSt
                     wallet.sync(noop_progress(), None).unwrap();
                     assert_eq!(wallet.get_balance().unwrap(), 50_000);
 
-                    let (psbt, details) = wallet.create_tx(TxBuilder::with_recipients(vec![(node_addr.clone(), 49_000)]).enable_rbf()).unwrap();
+                    let (psbt, details) = wallet.create_tx(TxBuilder::with_recipients(vec![(node_addr.script_pubkey().clone(), 49_000)]).enable_rbf()).unwrap();
                     let (psbt, finalized) = wallet.sign(psbt, None).unwrap();
                     assert!(finalized, "Cannot finalize transaction");
                     wallet.broadcast(psbt.extract_tx()).unwrap();
@@ -470,7 +470,7 @@ pub fn magical_blockchain_tests(attr: TokenStream, item: TokenStream) -> TokenSt
                     wallet.sync(noop_progress(), None).unwrap();
                     assert_eq!(wallet.get_balance().unwrap(), 75_000);
 
-                    let (psbt, details) = wallet.create_tx(TxBuilder::with_recipients(vec![(node_addr.clone(), 49_000)]).enable_rbf()).unwrap();
+                    let (psbt, details) = wallet.create_tx(TxBuilder::with_recipients(vec![(node_addr.script_pubkey().clone(), 49_000)]).enable_rbf()).unwrap();
                     let (psbt, finalized) = wallet.sign(psbt, None).unwrap();
                     assert!(finalized, "Cannot finalize transaction");
                     wallet.broadcast(psbt.extract_tx()).unwrap();
@@ -501,7 +501,7 @@ pub fn magical_blockchain_tests(attr: TokenStream, item: TokenStream) -> TokenSt
                     wallet.sync(noop_progress(), None).unwrap();
                     assert_eq!(wallet.get_balance().unwrap(), 75_000);
 
-                    let (psbt, details) = wallet.create_tx(TxBuilder::with_recipients(vec![(node_addr.clone(), 49_000)]).enable_rbf()).unwrap();
+                    let (psbt, details) = wallet.create_tx(TxBuilder::with_recipients(vec![(node_addr.script_pubkey().clone(), 49_000)]).enable_rbf()).unwrap();
                     let (psbt, finalized) = wallet.sign(psbt, None).unwrap();
                     assert!(finalized, "Cannot finalize transaction");
                     wallet.broadcast(psbt.extract_tx()).unwrap();