]> Untitled Git - bdk/commitdiff
Add docs for `Wallet`
authorAlekos Filini <alekos.filini@gmail.com>
Fri, 4 Sep 2020 14:29:25 +0000 (16:29 +0200)
committerAlekos Filini <alekos.filini@gmail.com>
Fri, 4 Sep 2020 14:29:25 +0000 (16:29 +0200)
examples/compiler.rs
src/wallet/address_validator.rs
src/wallet/mod.rs
src/wallet/utils.rs

index 2d50830c67cf92f1b91d4dc2690196a861dadb8e..874fc4b91ce6d8db9e1b330b36744e5a77833212 100644 (file)
@@ -40,7 +40,7 @@ use miniscript::policy::Concrete;
 use miniscript::Descriptor;
 
 use magical_bitcoin_wallet::database::memory::MemoryDatabase;
-use magical_bitcoin_wallet::{OfflineWallet, Wallet, ScriptType};
+use magical_bitcoin_wallet::{OfflineWallet, ScriptType, Wallet};
 
 fn main() {
     env_logger::init_from_env(
index d40343bc8833f69e7f8a9b28142992f466ef2164..d83699c247615c1a65708661f58dd3016bdd659b 100644 (file)
@@ -55,9 +55,9 @@
 //!         script: &Script
 //!     ) -> Result<(), AddressValidatorError> {
 //!         let address = Address::from_script(script, Network::Testnet)
-//!                           .as_ref()
-//!                           .map(Address::to_string)
-//!                           .unwrap_or(script.to_string());
+//!             .as_ref()
+//!             .map(Address::to_string)
+//!             .unwrap_or(script.to_string());
 //!         println!("New address of type {:?}: {}", script_type, address);
 //!         println!("HD keypaths: {:#?}", hd_keypaths);
 //!
index 21d3a1611f04610c81d1da9ab5f6a8e4d9ba943c..1dfe6018c333b5bc1ebbf9eeb181096e3853d008 100644 (file)
 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 // SOFTWARE.
 
+//! Wallet
+//!
+//! This module defines the [`Wallet`] structure.
+
 use std::cell::RefCell;
 use std::collections::HashMap;
 use std::collections::{BTreeMap, HashSet};
@@ -65,8 +69,19 @@ use crate::types::*;
 
 const CACHE_ADDR_BATCH_SIZE: u32 = 100;
 
+/// Type alias for a [`Wallet`] that uses [`OfflineBlockchain`]
 pub type OfflineWallet<D> = Wallet<OfflineBlockchain, D>;
 
+/// A Bitcoin wallet
+///
+/// A wallet takes descriptors, a [`database`](crate::database) and a
+/// [`blockchain`](crate::blockchain) and implements the basic functions that a Bitcoin wallets
+/// needs to operate, like [generating addresses](Wallet::get_new_address), [returning the balance](Wallet::get_balance),
+/// [creating transactions](Wallet::create_tx), etc.
+///
+/// A wallet can be either "online" if the [`blockchain`](crate::blockchain) type provided
+/// implements [`OnlineBlockchain`], or "offline" if it doesn't. Offline wallets only expose
+/// methods that don't need any interaction with the blockchain to work.
 pub struct Wallet<B: Blockchain, D: BatchDatabase> {
     descriptor: ExtendedDescriptor,
     change_descriptor: Option<ExtendedDescriptor>,
@@ -90,6 +105,7 @@ where
     B: Blockchain,
     D: BatchDatabase,
 {
+    /// Create a new "offline" wallet
     pub fn new_offline(
         descriptor: &str,
         change_descriptor: Option<&str>,
@@ -136,6 +152,7 @@ where
         })
     }
 
+    /// Return a newly generated address using the external descriptor
     pub fn get_new_address(&self) -> Result<Address, Error> {
         let index = self.fetch_and_increment_index(ScriptType::External)?;
 
@@ -145,18 +162,34 @@ where
             .ok_or(Error::ScriptDoesntHaveAddressForm)
     }
 
+    /// Return whether or not a `script` is part of this wallet (either internal or external)
     pub fn is_mine(&self, script: &Script) -> Result<bool, Error> {
         self.database.borrow().is_mine(script)
     }
 
+    /// Return the list of unspent outputs of this wallet
+    ///
+    /// Note that this methods only operate on the internal database, which first needs to be
+    /// [`Wallet::sync`] manually.
     pub fn list_unspent(&self) -> Result<Vec<UTXO>, Error> {
         self.database.borrow().iter_utxos()
     }
 
+    /// Return the list of transactions made and received by the wallet
+    ///
+    /// Optionally fill the [`TransactionDetails::transaction`] field with the raw transaction if
+    /// `include_raw` is `true`.
+    ///
+    /// Note that this methods only operate on the internal database, which first needs to be
+    /// [`Wallet::sync`] manually.
     pub fn list_transactions(&self, include_raw: bool) -> Result<Vec<TransactionDetails>, Error> {
         self.database.borrow().iter_txs(include_raw)
     }
 
+    /// Return the balance, meaning the sum of this wallet's unspent outputs' values
+    ///
+    /// Note that this methods only operate on the internal database, which first needs to be
+    /// [`Wallet::sync`] manually.
     pub fn get_balance(&self) -> Result<u64, Error> {
         Ok(self
             .list_unspent()?
@@ -164,6 +197,9 @@ where
             .fold(0, |sum, i| sum + i.txout.value))
     }
 
+    /// Add an external signer
+    ///
+    /// See [the `signer` module](signer) for an example.
     pub fn add_signer(
         &mut self,
         script_type: ScriptType,
@@ -179,10 +215,31 @@ where
         signers.add_external(id, ordering, signer);
     }
 
+    /// Add an address validator
+    ///
+    /// See [the `address_validator` module](address_validator) for an example.
     pub fn add_address_validator(&mut self, validator: Arc<Box<dyn AddressValidator>>) {
         self.address_validators.push(validator);
     }
 
+    /// Create a new transaction following the options specified in the `builder`
+    ///
+    /// ## Example
+    ///
+    /// ```no_run
+    /// # use std::str::FromStr;
+    /// # use bitcoin::*;
+    /// # use magical_bitcoin_wallet::*;
+    /// # use magical_bitcoin_wallet::database::*;
+    /// # let descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/*)";
+    /// # let wallet: OfflineWallet<_> = Wallet::new_offline(descriptor, None, Network::Testnet, MemoryDatabase::default())?;
+    /// # let to_address = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap();
+    /// let (psbt, details) = wallet.create_tx(
+    ///     TxBuilder::with_recipients(vec![(to_address.script_pubkey(), 50_000)])
+    /// )?;
+    /// // sign and broadcast ...
+    /// # Ok::<(), magical_bitcoin_wallet::Error>(())
+    /// ```
     pub fn create_tx<Cs: coin_selection::CoinSelectionAlgorithm>(
         &self,
         builder: TxBuilder<Cs>,
@@ -383,6 +440,31 @@ where
         Ok((psbt, transaction_details))
     }
 
+    /// Bump the fee of a transaction following the options specified in the `builder`
+    ///
+    /// Return an error if the transaction is already confirmed or doesn't explicitly signal RBF.
+    ///
+    /// **NOTE**: if the original transaction was made with [`TxBuilder::send_all`], the same
+    /// option must be enabled when bumping its fees to correctly reduce the only output's value to
+    /// increase the fees.
+    ///
+    /// ## Example
+    ///
+    /// ```no_run
+    /// # use std::str::FromStr;
+    /// # use bitcoin::*;
+    /// # use magical_bitcoin_wallet::*;
+    /// # use magical_bitcoin_wallet::database::*;
+    /// # let descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/*)";
+    /// # let wallet: OfflineWallet<_> = Wallet::new_offline(descriptor, None, Network::Testnet, MemoryDatabase::default())?;
+    /// let txid = Txid::from_str("faff0a466b70f5d5f92bd757a92c1371d4838bdd5bc53a06764e2488e51ce8f8").unwrap();
+    /// let (psbt, details) = wallet.bump_fee(
+    ///     &txid,
+    ///     TxBuilder::new().fee_rate(FeeRate::from_sat_per_vb(5.0)),
+    /// )?;
+    /// // sign and broadcast ...
+    /// # Ok::<(), magical_bitcoin_wallet::Error>(())
+    /// ```
     // TODO: support for merging multiple transactions while bumping the fees
     // TODO: option to force addition of an extra output? seems bad for privacy to update the
     // change
@@ -601,6 +683,21 @@ where
         Ok((psbt, details))
     }
 
+    /// Sign a transaction with all the wallet's signers, in the order specified by every signer's
+    /// [`SignerOrdering`]
+    ///
+    /// ## Example
+    ///
+    /// ```no_run
+    /// # use std::str::FromStr;
+    /// # use bitcoin::*;
+    /// # use magical_bitcoin_wallet::*;
+    /// # use magical_bitcoin_wallet::database::*;
+    /// # let descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/*)";
+    /// # let wallet: OfflineWallet<_> = Wallet::new_offline(descriptor, None, Network::Testnet, MemoryDatabase::default())?;
+    /// # let (psbt, _) = wallet.create_tx(TxBuilder::new())?;
+    /// let (signed_psbt, finalized) = wallet.sign(psbt, None)?;
+    /// # Ok::<(), magical_bitcoin_wallet::Error>(())
     pub fn sign(&self, mut psbt: PSBT, assume_height: Option<u32>) -> Result<(PSBT, bool), Error> {
         // this helps us doing our job later
         self.add_input_hd_keypaths(&mut psbt)?;
@@ -624,6 +721,7 @@ where
         self.finalize_psbt(psbt, assume_height)
     }
 
+    /// Return the spending policies for the wallet's descriptor
     pub fn policies(&self, script_type: ScriptType) -> Result<Option<Policy>, Error> {
         match (script_type, self.change_descriptor.as_ref()) {
             (ScriptType::External, _) => {
@@ -636,6 +734,10 @@ where
         }
     }
 
+    /// Return the "public" version of the wallet's descriptor, meaning a new descriptor that has
+    /// the same structure but with every secret key removed
+    ///
+    /// This can be used to build a watch-only version of a wallet
     pub fn public_descriptor(
         &self,
         script_type: ScriptType,
@@ -647,6 +749,7 @@ where
         }
     }
 
+    /// Try to finalize a PSBT
     pub fn finalize_psbt(
         &self,
         mut psbt: PSBT,
@@ -976,6 +1079,7 @@ where
     B: OnlineBlockchain,
     D: BatchDatabase,
 {
+    /// Create a new "online" wallet
     #[maybe_async]
     pub fn new(
         descriptor: &str,
@@ -992,6 +1096,7 @@ where
         Ok(wallet)
     }
 
+    /// Sync the internal database with the blockchain
     #[maybe_async]
     pub fn sync<P: 'static + Progress>(
         &self,
@@ -1025,7 +1130,10 @@ where
             if self
                 .database
                 .borrow()
-                .get_script_pubkey_from_path(ScriptType::Internal, max_address)?
+                .get_script_pubkey_from_path(
+                    ScriptType::Internal,
+                    max_address.checked_sub(1).unwrap_or(0),
+                )?
                 .is_none()
             {
                 run_setup = true;
@@ -1050,10 +1158,12 @@ where
         }
     }
 
+    /// Return a reference to the internal blockchain client
     pub fn client(&self) -> &B {
         &self.client
     }
 
+    /// Broadcast a transaction to the network
     #[maybe_async]
     pub fn broadcast(&self, tx: Transaction) -> Result<Txid, Error> {
         maybe_await!(self.client.broadcast(&tx))?;
index bdf98364ca2cba759b4b0af97cf87e5de8d76d64..b6d2c95a58d255ed99a1bbdeff25af0ad5a34cd9 100644 (file)
@@ -27,10 +27,12 @@ use miniscript::{MiniscriptKey, Satisfier};
 // De-facto standard "dust limit" (even though it should change based on the output type)
 const DUST_LIMIT_SATOSHI: u64 = 546;
 
+/// Trait to check if a value is below the dust limit
 // we implement this trait to make sure we don't mess up the comparison with off-by-one like a <
 // instead of a <= etc. The constant value for the dust limit is not public on purpose, to
 // encourage the usage of this trait.
 pub trait IsDust {
+    /// Check whether or not a value is below dust limit
     fn is_dust(&self) -> bool;
 }