Skip to content

Commit ac06e35

Browse files
committed
Add docs for Wallet
1 parent eee7521 commit ac06e35

4 files changed

Lines changed: 117 additions & 5 deletions

File tree

examples/compiler.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ use miniscript::policy::Concrete;
4040
use miniscript::Descriptor;
4141

4242
use magical_bitcoin_wallet::database::memory::MemoryDatabase;
43-
use magical_bitcoin_wallet::{OfflineWallet, Wallet, ScriptType};
43+
use magical_bitcoin_wallet::{OfflineWallet, ScriptType, Wallet};
4444

4545
fn main() {
4646
env_logger::init_from_env(

src/wallet/address_validator.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,9 +55,9 @@
5555
//! script: &Script
5656
//! ) -> Result<(), AddressValidatorError> {
5757
//! let address = Address::from_script(script, Network::Testnet)
58-
//! .as_ref()
59-
//! .map(Address::to_string)
60-
//! .unwrap_or(script.to_string());
58+
//! .as_ref()
59+
//! .map(Address::to_string)
60+
//! .unwrap_or(script.to_string());
6161
//! println!("New address of type {:?}: {}", script_type, address);
6262
//! println!("HD keypaths: {:#?}", hd_keypaths);
6363
//!

src/wallet/mod.rs

Lines changed: 111 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@
2222
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
2323
// SOFTWARE.
2424

25+
//! Wallet
26+
//!
27+
//! This module defines the [`Wallet`] structure.
28+
2529
use std::cell::RefCell;
2630
use std::collections::HashMap;
2731
use std::collections::{BTreeMap, HashSet};
@@ -65,8 +69,19 @@ use crate::types::*;
6569

6670
const CACHE_ADDR_BATCH_SIZE: u32 = 100;
6771

72+
/// Type alias for a [`Wallet`] that uses [`OfflineBlockchain`]
6873
pub type OfflineWallet<D> = Wallet<OfflineBlockchain, D>;
6974

75+
/// A Bitcoin wallet
76+
///
77+
/// A wallet takes descriptors, a [`database`](crate::database) and a
78+
/// [`blockchain`](crate::blockchain) and implements the basic functions that a Bitcoin wallets
79+
/// needs to operate, like [generating addresses](Wallet::get_new_address), [returning the balance](Wallet::get_balance),
80+
/// [creating transactions](Wallet::create_tx), etc.
81+
///
82+
/// A wallet can be either "online" if the [`blockchain`](crate::blockchain) type provided
83+
/// implements [`OnlineBlockchain`], or "offline" if it doesn't. Offline wallets only expose
84+
/// methods that don't need any interaction with the blockchain to work.
7085
pub struct Wallet<B: Blockchain, D: BatchDatabase> {
7186
descriptor: ExtendedDescriptor,
7287
change_descriptor: Option<ExtendedDescriptor>,
@@ -90,6 +105,7 @@ where
90105
B: Blockchain,
91106
D: BatchDatabase,
92107
{
108+
/// Create a new "offline" wallet
93109
pub fn new_offline(
94110
descriptor: &str,
95111
change_descriptor: Option<&str>,
@@ -136,6 +152,7 @@ where
136152
})
137153
}
138154

155+
/// Return a newly generated address using the external descriptor
139156
pub fn get_new_address(&self) -> Result<Address, Error> {
140157
let index = self.fetch_and_increment_index(ScriptType::External)?;
141158

@@ -145,25 +162,44 @@ where
145162
.ok_or(Error::ScriptDoesntHaveAddressForm)
146163
}
147164

165+
/// Return whether or not a `script` is part of this wallet (either internal or external)
148166
pub fn is_mine(&self, script: &Script) -> Result<bool, Error> {
149167
self.database.borrow().is_mine(script)
150168
}
151169

170+
/// Return the list of unspent outputs of this wallet
171+
///
172+
/// Note that this methods only operate on the internal database, which first needs to be
173+
/// [`Wallet::sync`] manually.
152174
pub fn list_unspent(&self) -> Result<Vec<UTXO>, Error> {
153175
self.database.borrow().iter_utxos()
154176
}
155177

178+
/// Return the list of transactions made and received by the wallet
179+
///
180+
/// Optionally fill the [`TransactionDetails::transaction`] field with the raw transaction if
181+
/// `include_raw` is `true`.
182+
///
183+
/// Note that this methods only operate on the internal database, which first needs to be
184+
/// [`Wallet::sync`] manually.
156185
pub fn list_transactions(&self, include_raw: bool) -> Result<Vec<TransactionDetails>, Error> {
157186
self.database.borrow().iter_txs(include_raw)
158187
}
159188

189+
/// Return the balance, meaning the sum of this wallet's unspent outputs' values
190+
///
191+
/// Note that this methods only operate on the internal database, which first needs to be
192+
/// [`Wallet::sync`] manually.
160193
pub fn get_balance(&self) -> Result<u64, Error> {
161194
Ok(self
162195
.list_unspent()?
163196
.iter()
164197
.fold(0, |sum, i| sum + i.txout.value))
165198
}
166199

200+
/// Add an external signer
201+
///
202+
/// See [the `signer` module](signer) for an example.
167203
pub fn add_signer(
168204
&mut self,
169205
script_type: ScriptType,
@@ -179,10 +215,31 @@ where
179215
signers.add_external(id, ordering, signer);
180216
}
181217

218+
/// Add an address validator
219+
///
220+
/// See [the `address_validator` module](address_validator) for an example.
182221
pub fn add_address_validator(&mut self, validator: Arc<Box<dyn AddressValidator>>) {
183222
self.address_validators.push(validator);
184223
}
185224

225+
/// Create a new transaction following the options specified in the `builder`
226+
///
227+
/// ## Example
228+
///
229+
/// ```no_run
230+
/// # use std::str::FromStr;
231+
/// # use bitcoin::*;
232+
/// # use magical_bitcoin_wallet::*;
233+
/// # use magical_bitcoin_wallet::database::*;
234+
/// # let descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/*)";
235+
/// # let wallet: OfflineWallet<_> = Wallet::new_offline(descriptor, None, Network::Testnet, MemoryDatabase::default())?;
236+
/// # let to_address = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap();
237+
/// let (psbt, details) = wallet.create_tx(
238+
/// TxBuilder::with_recipients(vec![(to_address.script_pubkey(), 50_000)])
239+
/// )?;
240+
/// // sign and broadcast ...
241+
/// # Ok::<(), magical_bitcoin_wallet::Error>(())
242+
/// ```
186243
pub fn create_tx<Cs: coin_selection::CoinSelectionAlgorithm>(
187244
&self,
188245
builder: TxBuilder<Cs>,
@@ -383,6 +440,31 @@ where
383440
Ok((psbt, transaction_details))
384441
}
385442

443+
/// Bump the fee of a transaction following the options specified in the `builder`
444+
///
445+
/// Return an error if the transaction is already confirmed or doesn't explicitly signal RBF.
446+
///
447+
/// **NOTE**: if the original transaction was made with [`TxBuilder::send_all`], the same
448+
/// option must be enabled when bumping its fees to correctly reduce the only output's value to
449+
/// increase the fees.
450+
///
451+
/// ## Example
452+
///
453+
/// ```no_run
454+
/// # use std::str::FromStr;
455+
/// # use bitcoin::*;
456+
/// # use magical_bitcoin_wallet::*;
457+
/// # use magical_bitcoin_wallet::database::*;
458+
/// # let descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/*)";
459+
/// # let wallet: OfflineWallet<_> = Wallet::new_offline(descriptor, None, Network::Testnet, MemoryDatabase::default())?;
460+
/// let txid = Txid::from_str("faff0a466b70f5d5f92bd757a92c1371d4838bdd5bc53a06764e2488e51ce8f8").unwrap();
461+
/// let (psbt, details) = wallet.bump_fee(
462+
/// &txid,
463+
/// TxBuilder::new().fee_rate(FeeRate::from_sat_per_vb(5.0)),
464+
/// )?;
465+
/// // sign and broadcast ...
466+
/// # Ok::<(), magical_bitcoin_wallet::Error>(())
467+
/// ```
386468
// TODO: support for merging multiple transactions while bumping the fees
387469
// TODO: option to force addition of an extra output? seems bad for privacy to update the
388470
// change
@@ -601,6 +683,21 @@ where
601683
Ok((psbt, details))
602684
}
603685

686+
/// Sign a transaction with all the wallet's signers, in the order specified by every signer's
687+
/// [`SignerOrdering`]
688+
///
689+
/// ## Example
690+
///
691+
/// ```no_run
692+
/// # use std::str::FromStr;
693+
/// # use bitcoin::*;
694+
/// # use magical_bitcoin_wallet::*;
695+
/// # use magical_bitcoin_wallet::database::*;
696+
/// # let descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/*)";
697+
/// # let wallet: OfflineWallet<_> = Wallet::new_offline(descriptor, None, Network::Testnet, MemoryDatabase::default())?;
698+
/// # let (psbt, _) = wallet.create_tx(TxBuilder::new())?;
699+
/// let (signed_psbt, finalized) = wallet.sign(psbt, None)?;
700+
/// # Ok::<(), magical_bitcoin_wallet::Error>(())
604701
pub fn sign(&self, mut psbt: PSBT, assume_height: Option<u32>) -> Result<(PSBT, bool), Error> {
605702
// this helps us doing our job later
606703
self.add_input_hd_keypaths(&mut psbt)?;
@@ -624,6 +721,7 @@ where
624721
self.finalize_psbt(psbt, assume_height)
625722
}
626723

724+
/// Return the spending policies for the wallet's descriptor
627725
pub fn policies(&self, script_type: ScriptType) -> Result<Option<Policy>, Error> {
628726
match (script_type, self.change_descriptor.as_ref()) {
629727
(ScriptType::External, _) => {
@@ -636,6 +734,10 @@ where
636734
}
637735
}
638736

737+
/// Return the "public" version of the wallet's descriptor, meaning a new descriptor that has
738+
/// the same structure but with every secret key removed
739+
///
740+
/// This can be used to build a watch-only version of a wallet
639741
pub fn public_descriptor(
640742
&self,
641743
script_type: ScriptType,
@@ -647,6 +749,7 @@ where
647749
}
648750
}
649751

752+
/// Try to finalize a PSBT
650753
pub fn finalize_psbt(
651754
&self,
652755
mut psbt: PSBT,
@@ -976,6 +1079,7 @@ where
9761079
B: OnlineBlockchain,
9771080
D: BatchDatabase,
9781081
{
1082+
/// Create a new "online" wallet
9791083
#[maybe_async]
9801084
pub fn new(
9811085
descriptor: &str,
@@ -992,6 +1096,7 @@ where
9921096
Ok(wallet)
9931097
}
9941098

1099+
/// Sync the internal database with the blockchain
9951100
#[maybe_async]
9961101
pub fn sync<P: 'static + Progress>(
9971102
&self,
@@ -1025,7 +1130,10 @@ where
10251130
if self
10261131
.database
10271132
.borrow()
1028-
.get_script_pubkey_from_path(ScriptType::Internal, max_address)?
1133+
.get_script_pubkey_from_path(
1134+
ScriptType::Internal,
1135+
max_address.checked_sub(1).unwrap_or(0),
1136+
)?
10291137
.is_none()
10301138
{
10311139
run_setup = true;
@@ -1050,10 +1158,12 @@ where
10501158
}
10511159
}
10521160

1161+
/// Return a reference to the internal blockchain client
10531162
pub fn client(&self) -> &B {
10541163
&self.client
10551164
}
10561165

1166+
/// Broadcast a transaction to the network
10571167
#[maybe_async]
10581168
pub fn broadcast(&self, tx: Transaction) -> Result<Txid, Error> {
10591169
maybe_await!(self.client.broadcast(&tx))?;

src/wallet/utils.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,12 @@ use miniscript::{MiniscriptKey, Satisfier};
2727
// De-facto standard "dust limit" (even though it should change based on the output type)
2828
const DUST_LIMIT_SATOSHI: u64 = 546;
2929

30+
/// Trait to check if a value is below the dust limit
3031
// we implement this trait to make sure we don't mess up the comparison with off-by-one like a <
3132
// instead of a <= etc. The constant value for the dust limit is not public on purpose, to
3233
// encourage the usage of this trait.
3334
pub trait IsDust {
35+
/// Check whether or not a value is below dust limit
3436
fn is_dust(&self) -> bool;
3537
}
3638

0 commit comments

Comments
 (0)