- Removed default verification from `wallet::sync`. sync-time verification is added in `script_sync` and is activated by `verify` feature flag.
- `verify` flag removed from `TransactionDetails`.
+- Removed Blockchain from Wallet.
+- Removed `Wallet::broadcast` (just use blockchain.broadcast)
+- Depreciated `Wallet::new_offline` (all wallets are offline now)
+- Changed `Wallet::sync` to take a blockchain argument.
## [v0.16.1] - [v0.16.0]
use bdk::wallet::AddressIndex::New;
fn main() -> Result<(), bdk::Error> {
- let wallet = Wallet::new_offline(
+ let wallet = Wallet::new(
"wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)",
Some("wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)"),
bitcoin::Network::Testnet,
use bitcoin::consensus::deserialize;
fn main() -> Result<(), bdk::Error> {
- let wallet = Wallet::new_offline(
+ let wallet = Wallet::new(
"wpkh([c258d2e4/84h/1h/0h]tprv8griRPhA7342zfRyB6CqeKF8CJDXYu5pgnj1cjL1u2ngKcJha5jjTRimG82ABzJQ4MQe71CV54xfn25BbhCNfEGGJZnxvCDQCd6JkbvxW6h/0/*)",
Some("wpkh([c258d2e4/84h/1h/0h]tprv8griRPhA7342zfRyB6CqeKF8CJDXYu5pgnj1cjL1u2ngKcJha5jjTRimG82ABzJQ4MQe71CV54xfn25BbhCNfEGGJZnxvCDQCd6JkbvxW6h/1/*)"),
bitcoin::Network::Testnet,
fn main() -> Result<(), bdk::Error> {
let descriptor = "sh(and_v(v:pk(tpubDDpWvmUrPZrhSPmUzCMBHffvC3HyMAPnWDSAQNBTnj1iZeJa7BZQEttFiP4DS4GCcXQHezdXhn86Hj6LHX5EDstXPWrMaSneRWM8yUf6NFd/*),after(630000)))";
- let mut wallet =
- Wallet::new_offline(descriptor, None, Network::Regtest, MemoryDatabase::new())?;
+ let mut wallet = Wallet::new(descriptor, None, Network::Regtest, MemoryDatabase::new())?;
wallet.add_address_validator(Arc::new(DummyValidator));
let descriptor = "wpkh(tpubD6NzVbkrYhZ4X2yy78HWrr1M9NT8dKeWfzNiQqDdMqqa9UmmGztGGz6TaLFGsLfdft5iu32gxq1T4eMNxExNNWzVCpf9Y6JZi5TnqoC9wJq/*)";
let database = MemoryDatabase::default();
- let wallet =
- Arc::new(Wallet::new(descriptor, None, Network::Testnet, database, blockchain).unwrap());
- wallet.sync(noop_progress(), None).unwrap();
+ let wallet = Arc::new(Wallet::new(descriptor, None, Network::Testnet, database).unwrap());
+ wallet.sync(&blockchain, noop_progress(), None).unwrap();
info!("balance: {}", wallet.get_balance()?);
Ok(())
}
.transpose()
.unwrap()
.unwrap_or(Network::Testnet);
- let wallet = Wallet::new_offline(&format!("{}", descriptor), None, network, database)?;
+ let wallet = Wallet::new(&format!("{}", descriptor), None, network, database)?;
info!("... First address: {}", wallet.get_address(New)?);
//!
//! ## Example
//!
-//! In this example both `wallet_electrum` and `wallet_esplora` have the same type of
-//! `Wallet<AnyBlockchain, MemoryDatabase>`. This means that they could both, for instance, be
-//! assigned to a struct member.
-//!
-//! ```no_run
-//! # use bitcoin::Network;
-//! # use bdk::blockchain::*;
-//! # use bdk::database::MemoryDatabase;
-//! # use bdk::Wallet;
-//! # #[cfg(feature = "electrum")]
-//! # {
-//! let electrum_blockchain = ElectrumBlockchain::from(electrum_client::Client::new("...")?);
-//! let wallet_electrum: Wallet<AnyBlockchain, _> = Wallet::new(
-//! "...",
-//! None,
-//! Network::Testnet,
-//! MemoryDatabase::default(),
-//! electrum_blockchain.into(),
-//! )?;
-//! # }
-//!
-//! # #[cfg(all(feature = "esplora", feature = "ureq"))]
-//! # {
-//! let esplora_blockchain = EsploraBlockchain::new("...", 20);
-//! let wallet_esplora: Wallet<AnyBlockchain, _> = Wallet::new(
-//! "...",
-//! None,
-//! Network::Testnet,
-//! MemoryDatabase::default(),
-//! esplora_blockchain.into(),
-//! )?;
-//! # }
-//!
-//! # Ok::<(), bdk::Error>(())
-//! ```
-//!
//! When paired with the use of [`ConfigurableBlockchain`], it allows creating wallets with any
//! blockchain type supported using a single line of code:
//!
//! ```no_run
//! # use bitcoin::Network;
//! # use bdk::blockchain::*;
-//! # use bdk::database::MemoryDatabase;
-//! # use bdk::Wallet;
//! # #[cfg(all(feature = "esplora", feature = "ureq"))]
//! # {
//! let config = serde_json::from_str("...")?;
//! let blockchain = AnyBlockchain::from_config(&config)?;
-//! let wallet = Wallet::new(
-//! "...",
-//! None,
-//! Network::Testnet,
-//! MemoryDatabase::default(),
-//! blockchain,
-//! )?;
+//! let height = blockchain.get_height();
//! # }
//! # Ok::<(), bdk::Error>(())
//! ```
maybe_await!(impl_inner_method!(self, get_capabilities))
}
- fn setup<D: BatchDatabase, P: 'static + Progress>(
- &self,
- database: &mut D,
- progress_update: P,
- ) -> Result<(), Error> {
- maybe_await!(impl_inner_method!(self, setup, database, progress_update))
- }
- fn sync<D: BatchDatabase, P: 'static + Progress>(
- &self,
- database: &mut D,
- progress_update: P,
- ) -> Result<(), Error> {
- maybe_await!(impl_inner_method!(self, sync, database, progress_update))
- }
-
fn get_tx(&self, txid: &Txid) -> Result<Option<Transaction>, Error> {
maybe_await!(impl_inner_method!(self, get_tx, txid))
}
maybe_await!(impl_inner_method!(self, broadcast, tx))
}
+ fn estimate_fee(&self, target: usize) -> Result<FeeRate, Error> {
+ maybe_await!(impl_inner_method!(self, estimate_fee, target))
+ }
+}
+
+#[maybe_async]
+impl GetHeight for AnyBlockchain {
fn get_height(&self) -> Result<u32, Error> {
maybe_await!(impl_inner_method!(self, get_height))
}
- fn estimate_fee(&self, target: usize) -> Result<FeeRate, Error> {
- maybe_await!(impl_inner_method!(self, estimate_fee, target))
+}
+
+#[maybe_async]
+impl WalletSync for AnyBlockchain {
+ fn wallet_sync<D: BatchDatabase, P: Progress>(
+ &self,
+ database: &mut D,
+ progress_update: P,
+ ) -> Result<(), Error> {
+ maybe_await!(impl_inner_method!(
+ self,
+ wallet_sync,
+ database,
+ progress_update
+ ))
+ }
+
+ fn wallet_setup<D: BatchDatabase, P: Progress>(
+ &self,
+ database: &mut D,
+ progress_update: P,
+ ) -> Result<(), Error> {
+ maybe_await!(impl_inner_method!(
+ self,
+ wallet_setup,
+ database,
+ progress_update
+ ))
}
}
mod store;
mod sync;
-use super::{Blockchain, Capability, ConfigurableBlockchain, Progress};
+use super::{Blockchain, Capability, ConfigurableBlockchain, GetHeight, Progress, WalletSync};
use crate::database::{BatchDatabase, BatchOperations, DatabaseUtils};
use crate::error::Error;
use crate::types::{KeychainKind, LocalUtxo, TransactionDetails};
vec![Capability::FullHistory].into_iter().collect()
}
+ fn get_tx(&self, txid: &Txid) -> Result<Option<Transaction>, Error> {
+ Ok(self.peers[0]
+ .get_mempool()
+ .get_tx(&Inventory::Transaction(*txid)))
+ }
+
+ fn broadcast(&self, tx: &Transaction) -> Result<(), Error> {
+ self.peers[0].broadcast_tx(tx.clone())?;
+
+ Ok(())
+ }
+
+ fn estimate_fee(&self, _target: usize) -> Result<FeeRate, Error> {
+ // TODO
+ Ok(FeeRate::default())
+ }
+}
+
+impl GetHeight for CompactFiltersBlockchain {
+ fn get_height(&self) -> Result<u32, Error> {
+ Ok(self.headers.get_height()? as u32)
+ }
+}
+
+impl WalletSync for CompactFiltersBlockchain {
#[allow(clippy::mutex_atomic)] // Mutex is easier to understand than a CAS loop.
- fn setup<D: BatchDatabase, P: 'static + Progress>(
+ fn wallet_setup<D: BatchDatabase, P: Progress>(
&self,
database: &mut D,
progress_update: P,
Ok(())
}
-
- fn get_tx(&self, txid: &Txid) -> Result<Option<Transaction>, Error> {
- Ok(self.peers[0]
- .get_mempool()
- .get_tx(&Inventory::Transaction(*txid)))
- }
-
- fn broadcast(&self, tx: &Transaction) -> Result<(), Error> {
- self.peers[0].broadcast_tx(tx.clone())?;
-
- Ok(())
- }
-
- fn get_height(&self) -> Result<u32, Error> {
- Ok(self.headers.get_height()? as u32)
- }
-
- fn estimate_fee(&self, _target: usize) -> Result<FeeRate, Error> {
- // TODO
- Ok(FeeRate::default())
- }
}
/// Data to connect to a Bitcoin P2P peer
.collect()
}
- fn setup<D: BatchDatabase, P: Progress>(
+ fn get_tx(&self, txid: &Txid) -> Result<Option<Transaction>, Error> {
+ Ok(self.client.transaction_get(txid).map(Option::Some)?)
+ }
+
+ fn broadcast(&self, tx: &Transaction) -> Result<(), Error> {
+ Ok(self.client.transaction_broadcast(tx).map(|_| ())?)
+ }
+
+ fn estimate_fee(&self, target: usize) -> Result<FeeRate, Error> {
+ Ok(FeeRate::from_btc_per_kvb(
+ self.client.estimate_fee(target)? as f32
+ ))
+ }
+}
+
+impl GetHeight for ElectrumBlockchain {
+ fn get_height(&self) -> Result<u32, Error> {
+ // TODO: unsubscribe when added to the client, or is there a better call to use here?
+
+ Ok(self
+ .client
+ .block_headers_subscribe()
+ .map(|data| data.height as u32)?)
+ }
+}
+
+impl WalletSync for ElectrumBlockchain {
+ fn wallet_setup<D: BatchDatabase, P: Progress>(
&self,
database: &mut D,
_progress_update: P,
database.commit_batch(batch_update)?;
Ok(())
}
-
- fn get_tx(&self, txid: &Txid) -> Result<Option<Transaction>, Error> {
- Ok(self.client.transaction_get(txid).map(Option::Some)?)
- }
-
- fn broadcast(&self, tx: &Transaction) -> Result<(), Error> {
- Ok(self.client.transaction_broadcast(tx).map(|_| ())?)
- }
-
- fn get_height(&self) -> Result<u32, Error> {
- // TODO: unsubscribe when added to the client, or is there a better call to use here?
-
- Ok(self
- .client
- .block_headers_subscribe()
- .map(|data| data.height as u32)?)
- }
-
- fn estimate_fee(&self, target: usize) -> Result<FeeRate, Error> {
- Ok(FeeRate::from_btc_per_kvb(
- self.client.estimate_fee(target)? as f32
- ))
- }
}
struct TxCache<'a, 'b, D> {
.collect()
}
- fn setup<D: BatchDatabase, P: Progress>(
+ fn get_tx(&self, txid: &Txid) -> Result<Option<Transaction>, Error> {
+ Ok(await_or_block!(self.url_client._get_tx(txid))?)
+ }
+
+ fn broadcast(&self, tx: &Transaction) -> Result<(), Error> {
+ Ok(await_or_block!(self.url_client._broadcast(tx))?)
+ }
+
+ fn estimate_fee(&self, target: usize) -> Result<FeeRate, Error> {
+ let estimates = await_or_block!(self.url_client._get_fee_estimates())?;
+ super::into_fee_rate(target, estimates)
+ }
+}
+
+#[maybe_async]
+impl GetHeight for EsploraBlockchain {
+ fn get_height(&self) -> Result<u32, Error> {
+ Ok(await_or_block!(self.url_client._get_height())?)
+ }
+}
+
+#[maybe_async]
+impl WalletSync for EsploraBlockchain {
+ fn wallet_setup<D: BatchDatabase, P: Progress>(
&self,
database: &mut D,
_progress_update: P,
Ok(())
}
-
- fn get_tx(&self, txid: &Txid) -> Result<Option<Transaction>, Error> {
- Ok(await_or_block!(self.url_client._get_tx(txid))?)
- }
-
- fn broadcast(&self, tx: &Transaction) -> Result<(), Error> {
- Ok(await_or_block!(self.url_client._broadcast(tx))?)
- }
-
- fn get_height(&self) -> Result<u32, Error> {
- Ok(await_or_block!(self.url_client._get_height())?)
- }
-
- fn estimate_fee(&self, target: usize) -> Result<FeeRate, Error> {
- let estimates = await_or_block!(self.url_client._get_fee_estimates())?;
- super::into_fee_rate(target, estimates)
- }
}
impl UrlClient {
.collect()
}
- fn setup<D: BatchDatabase, P: Progress>(
+ fn get_tx(&self, txid: &Txid) -> Result<Option<Transaction>, Error> {
+ Ok(self.url_client._get_tx(txid)?)
+ }
+
+ fn broadcast(&self, tx: &Transaction) -> Result<(), Error> {
+ let _txid = self.url_client._broadcast(tx)?;
+ Ok(())
+ }
+
+ fn estimate_fee(&self, target: usize) -> Result<FeeRate, Error> {
+ let estimates = self.url_client._get_fee_estimates()?;
+ super::into_fee_rate(target, estimates)
+ }
+}
+
+impl GetHeight for EsploraBlockchain {
+ fn get_height(&self) -> Result<u32, Error> {
+ Ok(self.url_client._get_height()?)
+ }
+}
+
+impl WalletSync for EsploraBlockchain {
+ fn wallet_setup<D: BatchDatabase, P: Progress>(
&self,
database: &mut D,
_progress_update: P,
Ok(())
}
-
- fn get_tx(&self, txid: &Txid) -> Result<Option<Transaction>, Error> {
- Ok(self.url_client._get_tx(txid)?)
- }
-
- fn broadcast(&self, tx: &Transaction) -> Result<(), Error> {
- let _txid = self.url_client._broadcast(tx)?;
- Ok(())
- }
-
- fn get_height(&self) -> Result<u32, Error> {
- Ok(self.url_client._get_height()?)
- }
-
- fn estimate_fee(&self, target: usize) -> Result<FeeRate, Error> {
- let estimates = self.url_client._get_fee_estimates()?;
- super::into_fee_rate(target, estimates)
- }
}
impl UrlClient {
/// Trait that defines the actions that must be supported by a blockchain backend
#[maybe_async]
-pub trait Blockchain {
+pub trait Blockchain: WalletSync + GetHeight {
/// Return the set of [`Capability`] supported by this backend
fn get_capabilities(&self) -> HashSet<Capability>;
+ /// Fetch a transaction from the blockchain given its txid
+ fn get_tx(&self, txid: &Txid) -> Result<Option<Transaction>, Error>;
+ /// Broadcast a transaction
+ fn broadcast(&self, tx: &Transaction) -> Result<(), Error>;
+ /// Estimate the fee rate required to confirm a transaction in a given `target` of blocks
+ fn estimate_fee(&self, target: usize) -> Result<FeeRate, Error>;
+}
+/// Trait for getting the current height of the blockchain.
+#[maybe_async]
+pub trait GetHeight {
+ /// Return the current height
+ fn get_height(&self) -> Result<u32, Error>;
+}
+
+/// Trait for blockchains that can sync by updating the database directly.
+#[maybe_async]
+pub trait WalletSync {
/// Setup the backend and populate the internal database for the first time
///
- /// This method is the equivalent of [`Blockchain::sync`], but it's guaranteed to only be
+ /// This method is the equivalent of [`Self::wallet_sync`], but it's guaranteed to only be
/// called once, at the first [`Wallet::sync`](crate::wallet::Wallet::sync).
///
/// The rationale behind the distinction between `sync` and `setup` is that some custom backends
/// might need to perform specific actions only the first time they are synced.
///
/// For types that do not have that distinction, only this method can be implemented, since
- /// [`Blockchain::sync`] defaults to calling this internally if not overridden.
- fn setup<D: BatchDatabase, P: 'static + Progress>(
+ /// [`WalletSync::wallet_sync`] defaults to calling this internally if not overridden.
+ /// Populate the internal database with transactions and UTXOs
+ fn wallet_setup<D: BatchDatabase, P: Progress>(
&self,
database: &mut D,
progress_update: P,
) -> Result<(), Error>;
- /// Populate the internal database with transactions and UTXOs
- ///
- /// If not overridden, it defaults to calling [`Blockchain::setup`] internally.
+
+ /// If not overridden, it defaults to calling [`Self::wallet_setup`] internally.
///
/// This method should implement the logic required to iterate over the list of the wallet's
/// script_pubkeys using [`Database::iter_script_pubkeys`] and look for relevant transactions
/// [`BatchOperations::set_tx`]: crate::database::BatchOperations::set_tx
/// [`BatchOperations::set_utxo`]: crate::database::BatchOperations::set_utxo
/// [`BatchOperations::del_utxo`]: crate::database::BatchOperations::del_utxo
- fn sync<D: BatchDatabase, P: 'static + Progress>(
+ fn wallet_sync<D: BatchDatabase, P: Progress>(
&self,
database: &mut D,
progress_update: P,
) -> Result<(), Error> {
- maybe_await!(self.setup(database, progress_update))
+ maybe_await!(self.wallet_setup(database, progress_update))
}
-
- /// Fetch a transaction from the blockchain given its txid
- fn get_tx(&self, txid: &Txid) -> Result<Option<Transaction>, Error>;
- /// Broadcast a transaction
- fn broadcast(&self, tx: &Transaction) -> Result<(), Error>;
-
- /// Return the current height
- fn get_height(&self) -> Result<u32, Error>;
- /// Estimate the fee rate required to confirm a transaction in a given `target` of blocks
- fn estimate_fee(&self, target: usize) -> Result<FeeRate, Error>;
}
/// Trait for [`Blockchain`] types that can be created given a configuration
/// Data sent with a progress update over a [`channel`]
pub type ProgressData = (f32, Option<String>);
-/// Trait for types that can receive and process progress updates during [`Blockchain::sync`] and
-/// [`Blockchain::setup`]
-pub trait Progress: Send {
+/// Trait for types that can receive and process progress updates during [`WalletSync::wallet_sync`] and
+/// [`WalletSync::wallet_setup`]
+pub trait Progress: Send + 'static {
/// Send a new progress update
///
/// The `progress` value should be in the range 0.0 - 100.0, and the `message` value is an
maybe_await!(self.deref().get_capabilities())
}
- fn setup<D: BatchDatabase, P: 'static + Progress>(
- &self,
- database: &mut D,
- progress_update: P,
- ) -> Result<(), Error> {
- maybe_await!(self.deref().setup(database, progress_update))
- }
-
- fn sync<D: BatchDatabase, P: 'static + Progress>(
- &self,
- database: &mut D,
- progress_update: P,
- ) -> Result<(), Error> {
- maybe_await!(self.deref().sync(database, progress_update))
- }
-
fn get_tx(&self, txid: &Txid) -> Result<Option<Transaction>, Error> {
maybe_await!(self.deref().get_tx(txid))
}
maybe_await!(self.deref().broadcast(tx))
}
+ fn estimate_fee(&self, target: usize) -> Result<FeeRate, Error> {
+ maybe_await!(self.deref().estimate_fee(target))
+ }
+}
+
+#[maybe_async]
+impl<T: GetHeight> GetHeight for Arc<T> {
fn get_height(&self) -> Result<u32, Error> {
maybe_await!(self.deref().get_height())
}
- fn estimate_fee(&self, target: usize) -> Result<FeeRate, Error> {
- maybe_await!(self.deref().estimate_fee(target))
+}
+
+#[maybe_async]
+impl<T: WalletSync> WalletSync for Arc<T> {
+ fn wallet_setup<D: BatchDatabase, P: Progress>(
+ &self,
+ database: &mut D,
+ progress_update: P,
+ ) -> Result<(), Error> {
+ maybe_await!(self.deref().wallet_setup(database, progress_update))
+ }
+
+ fn wallet_sync<D: BatchDatabase, P: Progress>(
+ &self,
+ database: &mut D,
+ progress_update: P,
+ ) -> Result<(), Error> {
+ maybe_await!(self.deref().wallet_sync(database, progress_update))
}
}
use crate::bitcoin::consensus::deserialize;
use crate::bitcoin::{Address, Network, OutPoint, Transaction, TxOut, Txid};
-use crate::blockchain::{Blockchain, Capability, ConfigurableBlockchain, Progress};
+use crate::blockchain::{
+ Blockchain, Capability, ConfigurableBlockchain, GetHeight, Progress, WalletSync,
+};
use crate::database::{BatchDatabase, DatabaseUtils};
use crate::{BlockTime, Error, FeeRate, KeychainKind, LocalUtxo, TransactionDetails};
use bitcoincore_rpc::json::{
self.capabilities.clone()
}
- fn setup<D: BatchDatabase, P: 'static + Progress>(
+ fn get_tx(&self, txid: &Txid) -> Result<Option<Transaction>, Error> {
+ Ok(Some(self.client.get_raw_transaction(txid, None)?))
+ }
+
+ fn broadcast(&self, tx: &Transaction) -> Result<(), Error> {
+ Ok(self.client.send_raw_transaction(tx).map(|_| ())?)
+ }
+
+ fn estimate_fee(&self, target: usize) -> Result<FeeRate, Error> {
+ let sat_per_kb = self
+ .client
+ .estimate_smart_fee(target as u16, None)?
+ .fee_rate
+ .ok_or(Error::FeeRateUnavailable)?
+ .as_sat() as f64;
+
+ Ok(FeeRate::from_sat_per_vb((sat_per_kb / 1000f64) as f32))
+ }
+}
+
+impl GetHeight for RpcBlockchain {
+ fn get_height(&self) -> Result<u32, Error> {
+ Ok(self.client.get_blockchain_info().map(|i| i.blocks as u32)?)
+ }
+}
+
+impl WalletSync for RpcBlockchain {
+ fn wallet_setup<D: BatchDatabase, P: Progress>(
&self,
database: &mut D,
progress_update: P,
}
}
- self.sync(database, progress_update)
+ self.wallet_sync(database, progress_update)
}
- fn sync<D: BatchDatabase, P: 'static + Progress>(
+ fn wallet_sync<D: BatchDatabase, P: Progress>(
&self,
db: &mut D,
_progress_update: P,
Ok(())
}
-
- fn get_tx(&self, txid: &Txid) -> Result<Option<Transaction>, Error> {
- Ok(Some(self.client.get_raw_transaction(txid, None)?))
- }
-
- fn broadcast(&self, tx: &Transaction) -> Result<(), Error> {
- Ok(self.client.send_raw_transaction(tx).map(|_| ())?)
- }
-
- fn get_height(&self) -> Result<u32, Error> {
- Ok(self.client.get_blockchain_info().map(|i| i.blocks as u32)?)
- }
-
- fn estimate_fee(&self, target: usize) -> Result<FeeRate, Error> {
- let sat_per_kb = self
- .client
- .estimate_smart_fee(target as u16, None)?
- .fee_rate
- .ok_or(Error::FeeRateUnavailable)?
- .as_sat() as f64;
-
- Ok(FeeRate::from_sat_per_vb((sat_per_kb / 1000f64) as f32))
- }
}
impl ConfigurableBlockchain for RpcBlockchain {
//! # use bdk::database::{AnyDatabase, MemoryDatabase};
//! # use bdk::{Wallet};
//! let memory = MemoryDatabase::default();
-//! let wallet_memory = Wallet::new_offline("...", None, Network::Testnet, memory)?;
+//! let wallet_memory = Wallet::new("...", None, Network::Testnet, memory)?;
//!
//! # #[cfg(feature = "key-value-db")]
//! # {
//! let sled = sled::open("my-database")?.open_tree("default_tree")?;
-//! let wallet_sled = Wallet::new_offline("...", None, Network::Testnet, sled)?;
+//! let wallet_sled = Wallet::new("...", None, Network::Testnet, sled)?;
//! # }
//! # Ok::<(), bdk::Error>(())
//! ```
//! # use bdk::{Wallet};
//! let config = serde_json::from_str("...")?;
//! let database = AnyDatabase::from_config(&config)?;
-//! let wallet = Wallet::new_offline("...", None, Network::Testnet, database)?;
+//! let wallet = Wallet::new("...", None, Network::Testnet, database)?;
//! # Ok::<(), bdk::Error>(())
//! ```
Some(100),
);
- $crate::Wallet::new_offline(
+ $crate::Wallet::new(
&descriptors.0,
descriptors.1.as_ref(),
Network::Regtest,
///
/// let key =
/// bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")?;
-/// let wallet = Wallet::new_offline(
+/// let wallet = Wallet::new(
/// P2Pkh(key),
/// None,
/// Network::Testnet,
///
/// let key =
/// bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")?;
-/// let wallet = Wallet::new_offline(
+/// let wallet = Wallet::new(
/// P2Wpkh_P2Sh(key),
/// None,
/// Network::Testnet,
///
/// let key =
/// bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")?;
-/// let wallet = Wallet::new_offline(
+/// let wallet = Wallet::new(
/// P2Wpkh(key),
/// None,
/// Network::Testnet,
/// use bdk::template::Bip44;
///
/// let key = bitcoin::util::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?;
-/// let wallet = Wallet::new_offline(
+/// let wallet = Wallet::new(
/// Bip44(key.clone(), KeychainKind::External),
/// Some(Bip44(key, KeychainKind::Internal)),
/// Network::Testnet,
///
/// let key = bitcoin::util::bip32::ExtendedPubKey::from_str("tpubDDDzQ31JkZB7VxUr9bjvBivDdqoFLrDPyLWtLapArAi51ftfmCb2DPxwLQzX65iNcXz1DGaVvyvo6JQ6rTU73r2gqdEo8uov9QKRb7nKCSU")?;
/// let fingerprint = bitcoin::util::bip32::Fingerprint::from_str("c55b303f")?;
-/// let wallet = Wallet::new_offline(
+/// let wallet = Wallet::new(
/// Bip44Public(key.clone(), fingerprint, KeychainKind::External),
/// Some(Bip44Public(key, fingerprint, KeychainKind::Internal)),
/// Network::Testnet,
/// use bdk::template::Bip49;
///
/// let key = bitcoin::util::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?;
-/// let wallet = Wallet::new_offline(
+/// let wallet = Wallet::new(
/// Bip49(key.clone(), KeychainKind::External),
/// Some(Bip49(key, KeychainKind::Internal)),
/// Network::Testnet,
///
/// let key = bitcoin::util::bip32::ExtendedPubKey::from_str("tpubDC49r947KGK52X5rBWS4BLs5m9SRY3pYHnvRrm7HcybZ3BfdEsGFyzCMzayi1u58eT82ZeyFZwH7DD6Q83E3fM9CpfMtmnTygnLfP59jL9L")?;
/// let fingerprint = bitcoin::util::bip32::Fingerprint::from_str("c55b303f")?;
-/// let wallet = Wallet::new_offline(
+/// let wallet = Wallet::new(
/// Bip49Public(key.clone(), fingerprint, KeychainKind::External),
/// Some(Bip49Public(key, fingerprint, KeychainKind::Internal)),
/// Network::Testnet,
/// use bdk::template::Bip84;
///
/// let key = bitcoin::util::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?;
-/// let wallet = Wallet::new_offline(
+/// let wallet = Wallet::new(
/// Bip84(key.clone(), KeychainKind::External),
/// Some(Bip84(key, KeychainKind::Internal)),
/// Network::Testnet,
///
/// let key = bitcoin::util::bip32::ExtendedPubKey::from_str("tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q")?;
/// let fingerprint = bitcoin::util::bip32::Fingerprint::from_str("c55b303f")?;
-/// let wallet = Wallet::new_offline(
+/// let wallet = Wallet::new(
/// Bip84Public(key.clone(), fingerprint, KeychainKind::External),
/// Some(Bip84Public(key, fingerprint, KeychainKind::Internal)),
/// Network::Testnet,
fn main() -> Result<(), bdk::Error> {
let client = Client::new("ssl://electrum.blockstream.info:60002")?;
+ let blockchain = ElectrumBlockchain::from(client);
let wallet = Wallet::new(
"wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)",
Some("wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)"),
bitcoin::Network::Testnet,
MemoryDatabase::default(),
- ElectrumBlockchain::from(client)
)?;
- wallet.sync(noop_progress(), None)?;
+ wallet.sync(&blockchain, noop_progress(), None)?;
println!("Descriptor balance: {} SAT", wallet.get_balance()?);
//! use bdk::wallet::AddressIndex::New;
//!
//! fn main() -> Result<(), bdk::Error> {
-//! let wallet = Wallet::new_offline(
+//! let wallet = Wallet::new(
//! "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)",
//! Some("wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)"),
//! bitcoin::Network::Testnet,
Some("wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)"),
bitcoin::Network::Testnet,
MemoryDatabase::default(),
- ElectrumBlockchain::from(client)
)?;
+ let blockchain = ElectrumBlockchain::from(client);
- wallet.sync(noop_progress(), None)?;
+ wallet.sync(&blockchain, noop_progress(), None)?;
let send_to = wallet.get_address(New)?;
let (psbt, details) = {
//! use bdk::database::MemoryDatabase;
//!
//! fn main() -> Result<(), bdk::Error> {
-//! let wallet = Wallet::new_offline(
+//! let wallet = Wallet::new(
//! "wpkh([c258d2e4/84h/1h/0h]tprv8griRPhA7342zfRyB6CqeKF8CJDXYu5pgnj1cjL1u2ngKcJha5jjTRimG82ABzJQ4MQe71CV54xfn25BbhCNfEGGJZnxvCDQCd6JkbvxW6h/0/*)",
//! Some("wpkh([c258d2e4/84h/1h/0h]tprv8griRPhA7342zfRyB6CqeKF8CJDXYu5pgnj1cjL1u2ngKcJha5jjTRimG82ABzJQ4MQe71CV54xfn25BbhCNfEGGJZnxvCDQCd6JkbvxW6h/1/*)"),
//! bitcoin::Network::Testnet,
mod bdk_blockchain_tests {
use $crate::bitcoin::{Transaction, Network};
use $crate::testutils::blockchain_tests::TestClient;
- use $crate::blockchain::noop_progress;
+ use $crate::blockchain::{Blockchain, noop_progress};
use $crate::database::MemoryDatabase;
use $crate::types::KeychainKind;
use $crate::{Wallet, FeeRate};
$block
}
- fn get_wallet_from_descriptors(descriptors: &(String, Option<String>), test_client: &TestClient) -> Wallet<$blockchain, MemoryDatabase> {
- Wallet::new(&descriptors.0.to_string(), descriptors.1.as_ref(), Network::Regtest, MemoryDatabase::new(), get_blockchain(test_client)).unwrap()
+ fn get_wallet_from_descriptors(descriptors: &(String, Option<String>)) -> Wallet<MemoryDatabase> {
+ Wallet::new(&descriptors.0.to_string(), descriptors.1.as_ref(), Network::Regtest, MemoryDatabase::new()).unwrap()
}
- fn init_single_sig() -> (Wallet<$blockchain, MemoryDatabase>, (String, Option<String>), TestClient) {
+ fn init_single_sig() -> (Wallet<MemoryDatabase>, $blockchain, (String, Option<String>), TestClient) {
let _ = env_logger::try_init();
let descriptors = testutils! {
};
let test_client = TestClient::default();
- let wallet = get_wallet_from_descriptors(&descriptors, &test_client);
+ let blockchain = get_blockchain(&test_client);
+ let wallet = get_wallet_from_descriptors(&descriptors);
// rpc need to call import_multi before receiving any tx, otherwise will not see tx in the mempool
#[cfg(feature = "test-rpc")]
- wallet.sync(noop_progress(), None).unwrap();
+ wallet.sync(&blockchain, noop_progress(), None).unwrap();
- (wallet, descriptors, test_client)
+ (wallet, blockchain, descriptors, test_client)
}
#[test]
use std::ops::Deref;
use crate::database::Database;
- let (wallet, descriptors, mut test_client) = init_single_sig();
+ let (wallet, blockchain, descriptors, mut test_client) = init_single_sig();
let tx = testutils! {
@tx ( (@external descriptors, 0) => 50_000 )
#[cfg(not(feature = "test-rpc"))]
assert!(wallet.database().deref().get_sync_time().unwrap().is_none(), "initial sync_time not none");
- wallet.sync(noop_progress(), None).unwrap();
+ wallet.sync(&blockchain, noop_progress(), None).unwrap();
assert!(wallet.database().deref().get_sync_time().unwrap().is_some(), "sync_time hasn't been updated");
assert_eq!(wallet.get_balance().unwrap(), 50_000, "incorrect balance");
#[test]
fn test_sync_stop_gap_20() {
- let (wallet, descriptors, mut test_client) = init_single_sig();
+ let (wallet, blockchain, descriptors, mut test_client) = init_single_sig();
test_client.receive(testutils! {
@tx ( (@external descriptors, 5) => 50_000 )
@tx ( (@external descriptors, 25) => 50_000 )
});
- wallet.sync(noop_progress(), None).unwrap();
+ wallet.sync(&blockchain, noop_progress(), None).unwrap();
assert_eq!(wallet.get_balance().unwrap(), 100_000, "incorrect balance");
assert_eq!(wallet.list_transactions(false).unwrap().len(), 2, "incorrect number of txs");
#[test]
fn test_sync_before_and_after_receive() {
- let (wallet, descriptors, mut test_client) = init_single_sig();
+ let (wallet, blockchain, descriptors, mut test_client) = init_single_sig();
- wallet.sync(noop_progress(), None).unwrap();
+ wallet.sync(&blockchain, noop_progress(), None).unwrap();
assert_eq!(wallet.get_balance().unwrap(), 0);
test_client.receive(testutils! {
@tx ( (@external descriptors, 0) => 50_000 )
});
- wallet.sync(noop_progress(), None).unwrap();
+ wallet.sync(&blockchain, noop_progress(), None).unwrap();
assert_eq!(wallet.get_balance().unwrap(), 50_000, "incorrect balance");
assert_eq!(wallet.list_transactions(false).unwrap().len(), 1, "incorrect number of txs");
#[test]
fn test_sync_multiple_outputs_same_tx() {
- let (wallet, descriptors, mut test_client) = init_single_sig();
+ let (wallet, blockchain, descriptors, mut test_client) = init_single_sig();
let txid = test_client.receive(testutils! {
@tx ( (@external descriptors, 0) => 50_000, (@external descriptors, 1) => 25_000, (@external descriptors, 5) => 30_000 )
});
- wallet.sync(noop_progress(), None).unwrap();
+ wallet.sync(&blockchain, noop_progress(), None).unwrap();
assert_eq!(wallet.get_balance().unwrap(), 105_000, "incorrect balance");
assert_eq!(wallet.list_transactions(false).unwrap().len(), 1, "incorrect number of txs");
#[test]
fn test_sync_receive_multi() {
- let (wallet, descriptors, mut test_client) = init_single_sig();
+ let (wallet, blockchain, descriptors, mut test_client) = init_single_sig();
test_client.receive(testutils! {
@tx ( (@external descriptors, 0) => 50_000 )
@tx ( (@external descriptors, 5) => 25_000 )
});
- wallet.sync(noop_progress(), None).unwrap();
+ wallet.sync(&blockchain, noop_progress(), None).unwrap();
assert_eq!(wallet.get_balance().unwrap(), 75_000, "incorrect balance");
assert_eq!(wallet.list_transactions(false).unwrap().len(), 2, "incorrect number of txs");
#[test]
fn test_sync_address_reuse() {
- let (wallet, descriptors, mut test_client) = init_single_sig();
+ let (wallet, blockchain, descriptors, mut test_client) = init_single_sig();
test_client.receive(testutils! {
@tx ( (@external descriptors, 0) => 50_000 )
});
- wallet.sync(noop_progress(), None).unwrap();
+ wallet.sync(&blockchain, noop_progress(), None).unwrap();
assert_eq!(wallet.get_balance().unwrap(), 50_000);
test_client.receive(testutils! {
@tx ( (@external descriptors, 0) => 25_000 )
});
- wallet.sync(noop_progress(), None).unwrap();
+ wallet.sync(&blockchain, noop_progress(), None).unwrap();
assert_eq!(wallet.get_balance().unwrap(), 75_000, "incorrect balance");
}
#[test]
fn test_sync_receive_rbf_replaced() {
- let (wallet, descriptors, mut test_client) = init_single_sig();
+ let (wallet, blockchain, descriptors, mut test_client) = init_single_sig();
let txid = test_client.receive(testutils! {
@tx ( (@external descriptors, 0) => 50_000 ) ( @replaceable true )
});
- wallet.sync(noop_progress(), None).unwrap();
+ wallet.sync(&blockchain, noop_progress(), None).unwrap();
assert_eq!(wallet.get_balance().unwrap(), 50_000, "incorrect balance");
assert_eq!(wallet.list_transactions(false).unwrap().len(), 1, "incorrect number of txs");
let new_txid = test_client.bump_fee(&txid);
- wallet.sync(noop_progress(), None).unwrap();
+ wallet.sync(&blockchain, noop_progress(), None).unwrap();
assert_eq!(wallet.get_balance().unwrap(), 50_000, "incorrect balance after bump");
assert_eq!(wallet.list_transactions(false).unwrap().len(), 1, "incorrect number of txs after bump");
#[cfg(not(feature = "esplora"))]
#[test]
fn test_sync_reorg_block() {
- let (wallet, descriptors, mut test_client) = init_single_sig();
+ let (wallet, blockchain, descriptors, mut test_client) = init_single_sig();
let txid = test_client.receive(testutils! {
@tx ( (@external descriptors, 0) => 50_000 ) ( @confirmations 1 ) ( @replaceable true )
});
- wallet.sync(noop_progress(), None).unwrap();
+ wallet.sync(&blockchain, noop_progress(), None).unwrap();
assert_eq!(wallet.get_balance().unwrap(), 50_000, "incorrect balance");
assert_eq!(wallet.list_transactions(false).unwrap().len(), 1, "incorrect number of txs");
// Invalidate 1 block
test_client.invalidate(1);
- wallet.sync(noop_progress(), None).unwrap();
+ wallet.sync(&blockchain, noop_progress(), None).unwrap();
assert_eq!(wallet.get_balance().unwrap(), 50_000, "incorrect balance after invalidate");
#[test]
fn test_sync_after_send() {
- let (wallet, descriptors, mut test_client) = init_single_sig();
- println!("{}", descriptors.0);
+ let (wallet, blockchain, descriptors, mut test_client) = init_single_sig();
+ println!("{}", descriptors.0);
let node_addr = test_client.get_node_address(None);
test_client.receive(testutils! {
@tx ( (@external descriptors, 0) => 50_000 )
});
- wallet.sync(noop_progress(), None).unwrap();
+ wallet.sync(&blockchain, noop_progress(), None).unwrap();
assert_eq!(wallet.get_balance().unwrap(), 50_000, "incorrect balance");
let mut builder = wallet.build_tx();
assert!(finalized, "Cannot finalize transaction");
let tx = psbt.extract_tx();
println!("{}", bitcoin::consensus::encode::serialize_hex(&tx));
- wallet.broadcast(&tx).unwrap();
- wallet.sync(noop_progress(), None).unwrap();
+ blockchain.broadcast(&tx).unwrap();
+ wallet.sync(&blockchain, noop_progress(), None).unwrap();
assert_eq!(wallet.get_balance().unwrap(), details.received, "incorrect balance after send");
assert_eq!(wallet.list_transactions(false).unwrap().len(), 2, "incorrect number of txs");
/// The coins should only be received once!
#[test]
fn test_sync_double_receive() {
- let (wallet, descriptors, mut test_client) = init_single_sig();
- let receiver_wallet = get_wallet_from_descriptors(&("wpkh(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW)".to_string(), None), &test_client);
+ let (wallet, blockchain, descriptors, mut test_client) = init_single_sig();
+ let receiver_wallet = get_wallet_from_descriptors(&("wpkh(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW)".to_string(), None));
// need to sync so rpc can start watching
- receiver_wallet.sync(noop_progress(), None).unwrap();
+ receiver_wallet.sync(&blockchain, noop_progress(), None).unwrap();
test_client.receive(testutils! {
@tx ( (@external descriptors, 0) => 50_000, (@external descriptors, 1) => 25_000 ) (@confirmations 1)
});
- wallet.sync(noop_progress(), None).unwrap();
+ wallet.sync(&blockchain, noop_progress(), None).unwrap();
assert_eq!(wallet.get_balance().unwrap(), 75_000, "incorrect balance");
let target_addr = receiver_wallet.get_address($crate::wallet::AddressIndex::New).unwrap().address;
psbt.extract_tx()
};
- wallet.broadcast(&tx1).unwrap();
- wallet.broadcast(&tx2).unwrap();
+ blockchain.broadcast(&tx1).unwrap();
+ blockchain.broadcast(&tx2).unwrap();
- receiver_wallet.sync(noop_progress(), None).unwrap();
+ receiver_wallet.sync(&blockchain, noop_progress(), None).unwrap();
assert_eq!(receiver_wallet.get_balance().unwrap(), 49_000, "should have received coins once and only once");
}
#[test]
fn test_sync_many_sends_to_a_single_address() {
- let (wallet, descriptors, mut test_client) = init_single_sig();
+ let (wallet, blockchain, descriptors, mut test_client) = init_single_sig();
for _ in 0..4 {
// split this up into multiple blocks so rpc doesn't get angry
});
}
- wallet.sync(noop_progress(), None).unwrap();
+ wallet.sync(&blockchain, noop_progress(), None).unwrap();
assert_eq!(wallet.get_balance().unwrap(), 100_000);
}
#[test]
fn test_update_confirmation_time_after_generate() {
- let (wallet, descriptors, mut test_client) = init_single_sig();
- println!("{}", descriptors.0);
+ let (wallet, blockchain, descriptors, mut test_client) = init_single_sig();
+ println!("{}", descriptors.0);
let node_addr = test_client.get_node_address(None);
let received_txid = test_client.receive(testutils! {
@tx ( (@external descriptors, 0) => 50_000 )
});
- wallet.sync(noop_progress(), None).unwrap();
+ wallet.sync(&blockchain, noop_progress(), None).unwrap();
assert_eq!(wallet.get_balance().unwrap(), 50_000, "incorrect balance");
let tx_map = wallet.list_transactions(false).unwrap().into_iter().map(|tx| (tx.txid, tx)).collect::<std::collections::HashMap<_, _>>();
assert!(details.confirmation_time.is_none());
test_client.generate(1, Some(node_addr));
- wallet.sync(noop_progress(), None).unwrap();
+ wallet.sync(&blockchain, noop_progress(), None).unwrap();
let tx_map = wallet.list_transactions(false).unwrap().into_iter().map(|tx| (tx.txid, tx)).collect::<std::collections::HashMap<_, _>>();
let details = tx_map.get(&received_txid).unwrap();
#[test]
fn test_sync_outgoing_from_scratch() {
- let (wallet, descriptors, mut test_client) = init_single_sig();
- let node_addr = test_client.get_node_address(None);
+ let (wallet, blockchain, descriptors, mut test_client) = init_single_sig();
+ let node_addr = test_client.get_node_address(None);
let received_txid = test_client.receive(testutils! {
@tx ( (@external descriptors, 0) => 50_000 )
});
- wallet.sync(noop_progress(), None).unwrap();
+ wallet.sync(&blockchain, noop_progress(), None).unwrap();
assert_eq!(wallet.get_balance().unwrap(), 50_000, "incorrect balance");
let mut builder = wallet.build_tx();
let finalized = wallet.sign(&mut psbt, Default::default()).unwrap();
assert!(finalized, "Cannot finalize transaction");
- let sent_txid = wallet.broadcast(&psbt.extract_tx()).unwrap();
+ let sent_tx = psbt.extract_tx();
+ blockchain.broadcast(&sent_tx).unwrap();
- wallet.sync(noop_progress(), None).unwrap();
+ wallet.sync(&blockchain, noop_progress(), None).unwrap();
assert_eq!(wallet.get_balance().unwrap(), details.received, "incorrect balance after receive");
// empty wallet
- let wallet = get_wallet_from_descriptors(&descriptors, &test_client);
+ let wallet = get_wallet_from_descriptors(&descriptors);
#[cfg(feature = "rpc")] // rpc cannot see mempool tx before importmulti
test_client.generate(1, Some(node_addr));
- wallet.sync(noop_progress(), None).unwrap();
+ wallet.sync(&blockchain, noop_progress(), None).unwrap();
let tx_map = wallet.list_transactions(false).unwrap().into_iter().map(|tx| (tx.txid, tx)).collect::<std::collections::HashMap<_, _>>();
let received = tx_map.get(&received_txid).unwrap();
assert_eq!(received.received, 50_000, "incorrect received from receiver");
assert_eq!(received.sent, 0, "incorrect sent from receiver");
- let sent = tx_map.get(&sent_txid).unwrap();
+ let sent = tx_map.get(&sent_tx.txid()).unwrap();
assert_eq!(sent.received, details.received, "incorrect received from sender");
assert_eq!(sent.sent, details.sent, "incorrect sent from sender");
assert_eq!(sent.fee.unwrap_or(0), details.fee.unwrap_or(0), "incorrect fees from sender");
#[test]
fn test_sync_long_change_chain() {
- let (wallet, descriptors, mut test_client) = init_single_sig();
- let node_addr = test_client.get_node_address(None);
+ let (wallet, blockchain, descriptors, mut test_client) = init_single_sig();
+ let node_addr = test_client.get_node_address(None);
test_client.receive(testutils! {
@tx ( (@external descriptors, 0) => 50_000 )
});
- wallet.sync(noop_progress(), None).unwrap();
+ wallet.sync(&blockchain, noop_progress(), None).unwrap();
assert_eq!(wallet.get_balance().unwrap(), 50_000, "incorrect balance");
let mut total_sent = 0;
let (mut psbt, details) = builder.finish().unwrap();
let finalized = wallet.sign(&mut psbt, Default::default()).unwrap();
assert!(finalized, "Cannot finalize transaction");
- wallet.broadcast(&psbt.extract_tx()).unwrap();
+ blockchain.broadcast(&psbt.extract_tx()).unwrap();
- wallet.sync(noop_progress(), None).unwrap();
+ wallet.sync(&blockchain, noop_progress(), None).unwrap();
total_sent += 5_000 + details.fee.unwrap_or(0);
}
- wallet.sync(noop_progress(), None).unwrap();
+ wallet.sync(&blockchain, noop_progress(), None).unwrap();
assert_eq!(wallet.get_balance().unwrap(), 50_000 - total_sent, "incorrect balance after chain");
// empty wallet
- let wallet = get_wallet_from_descriptors(&descriptors, &test_client);
+ let wallet = get_wallet_from_descriptors(&descriptors);
#[cfg(feature = "rpc")] // rpc cannot see mempool tx before importmulti
test_client.generate(1, Some(node_addr));
- wallet.sync(noop_progress(), None).unwrap();
+ wallet.sync(&blockchain, noop_progress(), None).unwrap();
assert_eq!(wallet.get_balance().unwrap(), 50_000 - total_sent, "incorrect balance empty wallet");
}
#[test]
fn test_sync_bump_fee_basic() {
- let (wallet, descriptors, mut test_client) = init_single_sig();
- let node_addr = test_client.get_node_address(None);
+ let (wallet, blockchain, descriptors, mut test_client) = init_single_sig();
+ let node_addr = test_client.get_node_address(None);
test_client.receive(testutils! {
@tx ( (@external descriptors, 0) => 50_000 ) (@confirmations 1)
});
- wallet.sync(noop_progress(), None).unwrap();
+ wallet.sync(&blockchain, noop_progress(), None).unwrap();
assert_eq!(wallet.get_balance().unwrap(), 50_000, "incorrect balance");
let mut builder = wallet.build_tx();
let (mut psbt, details) = builder.finish().unwrap();
let finalized = wallet.sign(&mut psbt, Default::default()).unwrap();
assert!(finalized, "Cannot finalize transaction");
- wallet.broadcast(&psbt.extract_tx()).unwrap();
- wallet.sync(noop_progress(), None).unwrap();
+ blockchain.broadcast(&psbt.extract_tx()).unwrap();
+ wallet.sync(&blockchain, noop_progress(), None).unwrap();
assert_eq!(wallet.get_balance().unwrap(), 50_000 - details.fee.unwrap_or(0) - 5_000, "incorrect balance from fees");
assert_eq!(wallet.get_balance().unwrap(), details.received, "incorrect balance from received");
let (mut new_psbt, new_details) = builder.finish().unwrap();
let finalized = wallet.sign(&mut new_psbt, Default::default()).unwrap();
assert!(finalized, "Cannot finalize transaction");
- wallet.broadcast(&new_psbt.extract_tx()).unwrap();
- wallet.sync(noop_progress(), None).unwrap();
+ blockchain.broadcast(&new_psbt.extract_tx()).unwrap();
+ wallet.sync(&blockchain, noop_progress(), None).unwrap();
assert_eq!(wallet.get_balance().unwrap(), 50_000 - new_details.fee.unwrap_or(0) - 5_000, "incorrect balance from fees after bump");
assert_eq!(wallet.get_balance().unwrap(), new_details.received, "incorrect balance from received after bump");
#[test]
fn test_sync_bump_fee_remove_change() {
- let (wallet, descriptors, mut test_client) = init_single_sig();
- let node_addr = test_client.get_node_address(None);
+ let (wallet, blockchain, descriptors, mut test_client) = init_single_sig();
+ let node_addr = test_client.get_node_address(None);
test_client.receive(testutils! {
@tx ( (@external descriptors, 0) => 50_000 ) (@confirmations 1)
});
- wallet.sync(noop_progress(), None).unwrap();
+ wallet.sync(&blockchain, noop_progress(), None).unwrap();
assert_eq!(wallet.get_balance().unwrap(), 50_000, "incorrect balance");
let mut builder = wallet.build_tx();
let (mut psbt, details) = builder.finish().unwrap();
let finalized = wallet.sign(&mut psbt, Default::default()).unwrap();
assert!(finalized, "Cannot finalize transaction");
- wallet.broadcast(&psbt.extract_tx()).unwrap();
- wallet.sync(noop_progress(), None).unwrap();
+ blockchain.broadcast(&psbt.extract_tx()).unwrap();
+ wallet.sync(&blockchain, noop_progress(), None).unwrap();
assert_eq!(wallet.get_balance().unwrap(), 1_000 - details.fee.unwrap_or(0), "incorrect balance after send");
assert_eq!(wallet.get_balance().unwrap(), details.received, "incorrect received after send");
let (mut new_psbt, new_details) = builder.finish().unwrap();
let finalized = wallet.sign(&mut new_psbt, Default::default()).unwrap();
assert!(finalized, "Cannot finalize transaction");
- wallet.broadcast(&new_psbt.extract_tx()).unwrap();
- wallet.sync(noop_progress(), None).unwrap();
+ blockchain.broadcast(&new_psbt.extract_tx()).unwrap();
+ wallet.sync(&blockchain, noop_progress(), None).unwrap();
assert_eq!(wallet.get_balance().unwrap(), 0, "incorrect balance after change removal");
assert_eq!(new_details.received, 0, "incorrect received after change removal");
#[test]
fn test_sync_bump_fee_add_input_simple() {
- let (wallet, descriptors, mut test_client) = init_single_sig();
- let node_addr = test_client.get_node_address(None);
+ let (wallet, blockchain, descriptors, mut test_client) = init_single_sig();
+ let node_addr = test_client.get_node_address(None);
test_client.receive(testutils! {
@tx ( (@external descriptors, 0) => 50_000, (@external descriptors, 1) => 25_000 ) (@confirmations 1)
});
- wallet.sync(noop_progress(), None).unwrap();
+ wallet.sync(&blockchain, noop_progress(), None).unwrap();
assert_eq!(wallet.get_balance().unwrap(), 75_000, "incorrect balance");
let mut builder = wallet.build_tx();
let (mut psbt, details) = builder.finish().unwrap();
let finalized = wallet.sign(&mut psbt, Default::default()).unwrap();
assert!(finalized, "Cannot finalize transaction");
- wallet.broadcast(&psbt.extract_tx()).unwrap();
- wallet.sync(noop_progress(), None).unwrap();
+ blockchain.broadcast(&psbt.extract_tx()).unwrap();
+ wallet.sync(&blockchain, noop_progress(), None).unwrap();
assert_eq!(wallet.get_balance().unwrap(), 26_000 - details.fee.unwrap_or(0), "incorrect balance after send");
assert_eq!(details.received, 1_000 - details.fee.unwrap_or(0), "incorrect received after send");
let (mut new_psbt, new_details) = builder.finish().unwrap();
let finalized = wallet.sign(&mut new_psbt, Default::default()).unwrap();
assert!(finalized, "Cannot finalize transaction");
- wallet.broadcast(&new_psbt.extract_tx()).unwrap();
- wallet.sync(noop_progress(), None).unwrap();
+ blockchain.broadcast(&new_psbt.extract_tx()).unwrap();
+ wallet.sync(&blockchain, noop_progress(), None).unwrap();
assert_eq!(new_details.sent, 75_000, "incorrect sent");
assert_eq!(wallet.get_balance().unwrap(), new_details.received, "incorrect balance after add input");
}
#[test]
fn test_sync_bump_fee_add_input_no_change() {
- let (wallet, descriptors, mut test_client) = init_single_sig();
- let node_addr = test_client.get_node_address(None);
+ let (wallet, blockchain, descriptors, mut test_client) = init_single_sig();
+ let node_addr = test_client.get_node_address(None);
test_client.receive(testutils! {
@tx ( (@external descriptors, 0) => 50_000, (@external descriptors, 1) => 25_000 ) (@confirmations 1)
});
- wallet.sync(noop_progress(), None).unwrap();
+ wallet.sync(&blockchain, noop_progress(), None).unwrap();
assert_eq!(wallet.get_balance().unwrap(), 75_000, "incorrect balance");
let mut builder = wallet.build_tx();
let (mut psbt, details) = builder.finish().unwrap();
let finalized = wallet.sign(&mut psbt, Default::default()).unwrap();
assert!(finalized, "Cannot finalize transaction");
- wallet.broadcast(&psbt.extract_tx()).unwrap();
- wallet.sync(noop_progress(), None).unwrap();
+ blockchain.broadcast(&psbt.extract_tx()).unwrap();
+ wallet.sync(&blockchain, noop_progress(), None).unwrap();
assert_eq!(wallet.get_balance().unwrap(), 26_000 - details.fee.unwrap_or(0), "incorrect balance after send");
assert_eq!(details.received, 1_000 - details.fee.unwrap_or(0), "incorrect received after send");
let finalized = wallet.sign(&mut new_psbt, Default::default()).unwrap();
assert!(finalized, "Cannot finalize transaction");
- wallet.broadcast(&new_psbt.extract_tx()).unwrap();
- wallet.sync(noop_progress(), None).unwrap();
+ blockchain.broadcast(&new_psbt.extract_tx()).unwrap();
+ wallet.sync(&blockchain, noop_progress(), None).unwrap();
assert_eq!(new_details.sent, 75_000, "incorrect sent");
assert_eq!(wallet.get_balance().unwrap(), 0, "incorrect balance after add input");
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 (wallet, blockchain, 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();
+ wallet.sync(&blockchain, noop_progress(), None).unwrap();
assert_eq!(wallet.get_balance().unwrap(), 50_000, "incorrect balance");
let mut builder = wallet.build_tx();
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();
+ blockchain.broadcast(&tx).unwrap();
test_client.generate(1, Some(node_addr));
- wallet.sync(noop_progress(), None).unwrap();
+ wallet.sync(&blockchain, 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::<std::collections::HashMap<_, _>>();
- let _ = tx_map.get(&sent_txid).unwrap();
+ let _ = tx_map.get(&tx.txid()).unwrap();
}
#[test]
fn test_sync_receive_coinbase() {
- let (wallet, _, mut test_client) = init_single_sig();
+ let (wallet, blockchain, _, mut test_client) = init_single_sig();
let wallet_addr = wallet.get_address($crate::wallet::AddressIndex::New).unwrap().address;
- wallet.sync(noop_progress(), None).unwrap();
+ wallet.sync(&blockchain, noop_progress(), None).unwrap();
assert_eq!(wallet.get_balance().unwrap(), 0, "incorrect balance");
test_client.generate(1, Some(wallet_addr));
}
- wallet.sync(noop_progress(), None).unwrap();
+ wallet.sync(&blockchain, noop_progress(), None).unwrap();
assert!(wallet.get_balance().unwrap() > 0, "incorrect balance after receiving coinbase");
}
use bitcoincore_rpc::jsonrpc::serde_json::Value;
use bitcoincore_rpc::{Auth, Client, RpcApi};
- let (wallet, descriptors, mut test_client) = init_single_sig();
+ let (wallet, blockchain, descriptors, mut test_client) = init_single_sig();
// TODO remove once rust-bitcoincore-rpc with PR 199 released
// https://github.com/rust-bitcoin/rust-bitcoincore-rpc/pull/199
@tx ( (@external descriptors, 0) => 50_000 )
});
- wallet.sync(noop_progress(), None).unwrap();
+ wallet.sync(&blockchain, noop_progress(), None).unwrap();
assert_eq!(wallet.get_balance().unwrap(), 50_000, "wallet has incorrect balance");
// 4. Send 25_000 sats from test BDK wallet to test bitcoind node taproot wallet
let finalized = wallet.sign(&mut psbt, Default::default()).unwrap();
assert!(finalized, "wallet cannot finalize transaction");
let tx = psbt.extract_tx();
- wallet.broadcast(&tx).unwrap();
- wallet.sync(noop_progress(), None).unwrap();
+ blockchain.broadcast(&tx).unwrap();
+ wallet.sync(&blockchain, noop_progress(), None).unwrap();
assert_eq!(wallet.get_balance().unwrap(), details.received, "wallet has incorrect balance after send");
assert_eq!(wallet.list_transactions(false).unwrap().len(), 2, "wallet has incorrect number of txs");
assert_eq!(wallet.list_unspent().unwrap().len(), 1, "wallet has incorrect number of unspents");
//! }
//!
//! let descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/*)";
-//! let mut wallet = Wallet::new_offline(descriptor, None, Network::Testnet, MemoryDatabase::default())?;
+//! let mut wallet = Wallet::new(descriptor, None, Network::Testnet, MemoryDatabase::default())?;
//! wallet.add_address_validator(Arc::new(PrintAddressAndContinue));
//!
//! let address = wallet.get_address(New)?;
//! }"#;
//!
//! let import = WalletExport::from_str(import)?;
-//! let wallet = Wallet::new_offline(
+//! let wallet = Wallet::new(
//! &import.descriptor(),
//! import.change_descriptor().as_ref(),
//! Network::Testnet,
//! # use bdk::database::*;
//! # use bdk::wallet::export::*;
//! # use bdk::*;
-//! let wallet = Wallet::new_offline(
+//! let wallet = Wallet::new(
//! "wpkh([c258d2e4/84h/1h/0h]tpubDD3ynpHgJQW8VvWRzQ5WFDCrs4jqVFGHB3vLC3r49XHJSqP8bHKdK4AriuUKLccK68zfzowx7YhmDN8SiSkgCDENUFx9qVw65YyqM78vyVe/0/*)",
//! Some("wpkh([c258d2e4/84h/1h/0h]tpubDD3ynpHgJQW8VvWRzQ5WFDCrs4jqVFGHB3vLC3r49XHJSqP8bHKdK4AriuUKLccK68zfzowx7YhmDN8SiSkgCDENUFx9qVw65YyqM78vyVe/1/*)"),
//! Network::Testnet,
///
/// If the database is empty or `include_blockheight` is false, the `blockheight` field
/// returned will be `0`.
- pub fn export_wallet<B, D: BatchDatabase>(
- wallet: &Wallet<B, D>,
+ pub fn export_wallet<D: BatchDatabase>(
+ wallet: &Wallet<D>,
label: &str,
include_blockheight: bool,
) -> Result<Self, &'static str> {
let descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/0/*)";
let change_descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/1/*)";
- let wallet = Wallet::new_offline(
+ let wallet = Wallet::new(
descriptor,
Some(change_descriptor),
Network::Bitcoin,
let descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/0/*)";
- let wallet =
- Wallet::new_offline(descriptor, None, Network::Bitcoin, get_test_db()).unwrap();
+ let wallet = Wallet::new(descriptor, None, Network::Bitcoin, get_test_db()).unwrap();
WalletExport::export_wallet(&wallet, "Test Label", true).unwrap();
}
let descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/0/*)";
let change_descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/50'/0'/1/*)";
- let wallet = Wallet::new_offline(
+ let wallet = Wallet::new(
descriptor,
Some(change_descriptor),
Network::Bitcoin,
[c98b1535/48'/0'/0'/2']tpubDCDi5W4sP6zSnzJeowy8rQDVhBdRARaPhK1axABi8V1661wEPeanpEXj4ZLAUEoikVtoWcyK26TKKJSecSfeKxwHCcRrge9k1ybuiL71z4a/1/*\
))";
- let wallet = Wallet::new_offline(
+ let wallet = Wallet::new(
descriptor,
Some(change_descriptor),
Network::Testnet,
let descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/0/*)";
let change_descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/1/*)";
- let wallet = Wallet::new_offline(
+ let wallet = Wallet::new(
descriptor,
Some(change_descriptor),
Network::Bitcoin,
use tx_builder::{BumpFee, CreateTx, FeePolicy, TxBuilder, TxParams};
use utils::{check_nlocktime, check_nsequence_rbf, After, Older, SecpCtx};
-use crate::blockchain::{Blockchain, Progress};
+use crate::blockchain::{GetHeight, Progress, WalletSync};
use crate::database::memory::MemoryDatabase;
use crate::database::{BatchDatabase, BatchOperations, DatabaseUtils, SyncTime};
use crate::descriptor::derived::AsDerived;
/// A Bitcoin wallet
///
-/// A wallet takes descriptors, a [`database`](trait@crate::database::Database) and a
-/// [`blockchain`](trait@crate::blockchain::Blockchain) and implements the basic functions that a Bitcoin wallets
-/// needs to operate, like [generating addresses](Wallet::get_address), [returning the balance](Wallet::get_balance),
-/// [creating transactions](Wallet::build_tx), etc.
+/// The `Wallet` struct acts as a way of coherently interfacing with output descriptors and related transactions.
+/// Its main components are:
///
-/// A wallet can be either "online" if the [`blockchain`](crate::blockchain) type provided
-/// implements [`Blockchain`], or "offline" if it is the unit type `()`. Offline wallets only expose
-/// methods that don't need any interaction with the blockchain to work.
+/// 1. output *descriptors* from which it can derive addresses.
+/// 2. A [`Database`] where it tracks transactions and utxos related to the descriptors.
+/// 3. [`Signer`]s that can contribute signatures to addresses instantiated from the descriptors.
+///
+/// [`Database`]: crate::database::Database
+/// [`Signer`]: crate::signer::Signer
#[derive(Debug)]
-pub struct Wallet<B, D> {
+pub struct Wallet<D> {
descriptor: ExtendedDescriptor,
change_descriptor: Option<ExtendedDescriptor>,
network: Network,
- current_height: Option<u32>,
-
- client: B,
database: RefCell<D>,
secp: SecpCtx,
}
-impl<D> Wallet<(), D>
-where
- D: BatchDatabase,
-{
- /// Create a new "offline" wallet
- pub fn new_offline<E: IntoWalletDescriptor>(
- descriptor: E,
- change_descriptor: Option<E>,
- network: Network,
- database: D,
- ) -> Result<Self, Error> {
- Self::_new(descriptor, change_descriptor, network, database, (), None)
- }
-}
-
-impl<B, D> Wallet<B, D>
-where
- D: BatchDatabase,
-{
- fn _new<E: IntoWalletDescriptor>(
- descriptor: E,
- change_descriptor: Option<E>,
- network: Network,
- mut database: D,
- client: B,
- current_height: Option<u32>,
- ) -> Result<Self, Error> {
- let secp = Secp256k1::new();
-
- let (descriptor, keymap) = into_wallet_descriptor_checked(descriptor, &secp, network)?;
- database.check_descriptor_checksum(
- KeychainKind::External,
- get_checksum(&descriptor.to_string())?.as_bytes(),
- )?;
- let signers = Arc::new(SignersContainer::from(keymap));
- let (change_descriptor, change_signers) = match change_descriptor {
- Some(desc) => {
- let (change_descriptor, change_keymap) =
- into_wallet_descriptor_checked(desc, &secp, network)?;
- database.check_descriptor_checksum(
- KeychainKind::Internal,
- get_checksum(&change_descriptor.to_string())?.as_bytes(),
- )?;
-
- let change_signers = Arc::new(SignersContainer::from(change_keymap));
- // if !parsed.same_structure(descriptor.as_ref()) {
- // return Err(Error::DifferentDescriptorStructure);
- // }
-
- (Some(change_descriptor), change_signers)
- }
- None => (None, Arc::new(SignersContainer::new())),
- };
-
- Ok(Wallet {
- descriptor,
- change_descriptor,
- signers,
- change_signers,
- address_validators: Vec::new(),
- network,
- current_height,
- client,
- database: RefCell::new(database),
- secp,
- })
- }
-
- /// Get the Bitcoin network the wallet is using.
- pub fn network(&self) -> Network {
- self.network
- }
-}
-
/// The address index selection strategy to use to derived an address from the wallet's external
/// descriptor. See [`Wallet::get_address`]. If you're unsure which one to use use `WalletIndex::New`.
#[derive(Debug)]
}
}
-// offline actions, always available
-impl<B, D> Wallet<B, D>
+impl<D> Wallet<D>
where
D: BatchDatabase,
{
+ /// Create a wallet.
+ ///
+ /// The only way this can fail is if the descriptors passed in do not match the checksums in `database`.
+ pub fn new<E: IntoWalletDescriptor>(
+ descriptor: E,
+ change_descriptor: Option<E>,
+ network: Network,
+ mut database: D,
+ ) -> Result<Self, Error> {
+ let secp = Secp256k1::new();
+
+ let (descriptor, keymap) = into_wallet_descriptor_checked(descriptor, &secp, network)?;
+ database.check_descriptor_checksum(
+ KeychainKind::External,
+ get_checksum(&descriptor.to_string())?.as_bytes(),
+ )?;
+ let signers = Arc::new(SignersContainer::from(keymap));
+ let (change_descriptor, change_signers) = match change_descriptor {
+ Some(desc) => {
+ let (change_descriptor, change_keymap) =
+ into_wallet_descriptor_checked(desc, &secp, network)?;
+ database.check_descriptor_checksum(
+ KeychainKind::Internal,
+ get_checksum(&change_descriptor.to_string())?.as_bytes(),
+ )?;
+
+ let change_signers = Arc::new(SignersContainer::from(change_keymap));
+ // if !parsed.same_structure(descriptor.as_ref()) {
+ // return Err(Error::DifferentDescriptorStructure);
+ // }
+
+ (Some(change_descriptor), change_signers)
+ }
+ None => (None, Arc::new(SignersContainer::new())),
+ };
+
+ Ok(Wallet {
+ descriptor,
+ change_descriptor,
+ signers,
+ change_signers,
+ address_validators: Vec::new(),
+ network,
+ database: RefCell::new(database),
+ secp,
+ })
+ }
+
+ /// Get the Bitcoin network the wallet is using.
+ pub fn network(&self) -> Network {
+ self.network
+ }
+
// Return a newly derived address using the external descriptor
fn get_new_address(&self) -> Result<AddressInfo, Error> {
let incremented_index = self.fetch_and_increment_index(KeychainKind::External)?;
/// ```
///
/// [`TxBuilder`]: crate::TxBuilder
- pub fn build_tx(&self) -> TxBuilder<'_, B, D, DefaultCoinSelectionAlgorithm, CreateTx> {
+ pub fn build_tx(&self) -> TxBuilder<'_, D, DefaultCoinSelectionAlgorithm, CreateTx> {
TxBuilder {
wallet: self,
params: TxParams::default(),
pub fn build_fee_bump(
&self,
txid: Txid,
- ) -> Result<TxBuilder<'_, B, D, DefaultCoinSelectionAlgorithm, BumpFee>, Error> {
+ ) -> Result<TxBuilder<'_, D, DefaultCoinSelectionAlgorithm, BumpFee>, Error> {
let mut details = match self.database.borrow().get_tx(&txid, true)? {
None => return Err(Error::TransactionNotFound),
Some(tx) if tx.transaction.is_none() => return Err(Error::TransactionNotFound),
.borrow()
.get_tx(&input.previous_output.txid, false)?
.map(|tx| tx.confirmation_time.map(|c| c.height).unwrap_or(u32::MAX));
- let current_height = sign_options.assume_height.or(self.current_height);
+ let last_sync_height = self
+ .database()
+ .get_sync_time()?
+ .map(|sync_time| sync_time.block_time.height);
+ let current_height = sign_options.assume_height.or(last_sync_height);
debug!(
"Input #{} - {}, using `create_height` = {:?}, `current_height` = {:?}",
}
}
-impl<B, D> Wallet<B, D>
+impl<D> Wallet<D>
where
- B: Blockchain,
D: BatchDatabase,
{
- /// Create a new "online" wallet
- #[maybe_async]
- pub fn new<E: IntoWalletDescriptor>(
- descriptor: E,
- change_descriptor: Option<E>,
- network: Network,
- database: D,
- client: B,
- ) -> Result<Self, Error> {
- let current_height = Some(maybe_await!(client.get_height())? as u32);
- Self::_new(
- descriptor,
- change_descriptor,
- network,
- database,
- client,
- current_height,
- )
- }
-
/// Sync the internal database with the blockchain
#[maybe_async]
- pub fn sync<P: 'static + Progress>(
+ pub fn sync<P: 'static + Progress, B: WalletSync + GetHeight>(
&self,
+ blockchain: &B,
progress_update: P,
max_address_param: Option<u32>,
) -> Result<(), Error> {
// TODO: what if i generate an address first and cache some addresses?
// TODO: we should sync if generating an address triggers a new batch to be stored
if run_setup {
- maybe_await!(self
- .client
- .setup(self.database.borrow_mut().deref_mut(), progress_update,))?;
+ maybe_await!(
+ blockchain.wallet_setup(self.database.borrow_mut().deref_mut(), progress_update,)
+ )?;
} else {
- maybe_await!(self
- .client
- .sync(self.database.borrow_mut().deref_mut(), progress_update,))?;
+ maybe_await!(
+ blockchain.wallet_sync(self.database.borrow_mut().deref_mut(), progress_update,)
+ )?;
}
let sync_time = SyncTime {
block_time: BlockTime {
- height: maybe_await!(self.client.get_height())?,
+ height: maybe_await!(blockchain.get_height())?,
timestamp: time::get_timestamp(),
},
};
Ok(())
}
-
- /// 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))?;
-
- Ok(tx.txid())
- }
}
/// Return a fake wallet that appears to be funded for testing.
pub fn get_funded_wallet(
descriptor: &str,
) -> (
- Wallet<(), MemoryDatabase>,
+ Wallet<MemoryDatabase>,
(String, Option<String>),
bitcoin::Txid,
) {
let descriptors = testutils!(@descriptors (descriptor));
- let wallet = Wallet::new_offline(
+ let wallet = Wallet::new(
&descriptors.0,
None,
Network::Regtest,
#[test]
fn test_cache_addresses_fixed() {
let db = MemoryDatabase::new();
- let wallet = Wallet::new_offline(
+ let wallet = Wallet::new(
"wpkh(L5EZftvrYaSudiozVRzTqLcHLNDoVn7H5HSfM9BAN6tMJX8oTWz6)",
None,
Network::Testnet,
#[test]
fn test_cache_addresses() {
let db = MemoryDatabase::new();
- let wallet = Wallet::new_offline("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)", None, Network::Testnet, db).unwrap();
+ let wallet = Wallet::new("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)", None, Network::Testnet, db).unwrap();
assert_eq!(
wallet.get_address(New).unwrap().to_string(),
#[test]
fn test_cache_addresses_refill() {
let db = MemoryDatabase::new();
- let wallet = Wallet::new_offline("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)", None, Network::Testnet, db).unwrap();
+ let wallet = Wallet::new("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)", None, Network::Testnet, db).unwrap();
assert_eq!(
wallet.get_address(New).unwrap().to_string(),
#[test]
fn test_unused_address() {
let db = MemoryDatabase::new();
- let wallet = Wallet::new_offline("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)",
+ let wallet = Wallet::new("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)",
None, Network::Testnet, db).unwrap();
assert_eq!(
fn test_next_unused_address() {
let descriptor = "wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)";
let descriptors = testutils!(@descriptors (descriptor));
- let wallet = Wallet::new_offline(
+ let wallet = Wallet::new(
&descriptors.0,
None,
Network::Testnet,
#[test]
fn test_peek_address_at_index() {
let db = MemoryDatabase::new();
- let wallet = Wallet::new_offline("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)",
+ let wallet = Wallet::new("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)",
None, Network::Testnet, db).unwrap();
assert_eq!(
#[test]
fn test_peek_address_at_index_not_derivable() {
let db = MemoryDatabase::new();
- let wallet = Wallet::new_offline("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/1)",
+ let wallet = Wallet::new("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/1)",
None, Network::Testnet, db).unwrap();
assert_eq!(
#[test]
fn test_reset_address_index() {
let db = MemoryDatabase::new();
- let wallet = Wallet::new_offline("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)",
+ let wallet = Wallet::new("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)",
None, Network::Testnet, db).unwrap();
// new index 0
#[test]
fn test_returns_index_and_address() {
let db = MemoryDatabase::new();
- let wallet = Wallet::new_offline("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)",
+ let wallet = Wallet::new("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)",
None, Network::Testnet, db).unwrap();
// new index 0
//! let custom_signer = CustomSigner::connect();
//!
//! let descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/*)";
-//! let mut wallet = Wallet::new_offline(descriptor, None, Network::Testnet, MemoryDatabase::default())?;
+//! let mut wallet = Wallet::new(descriptor, None, Network::Testnet, MemoryDatabase::default())?;
//! wallet.add_signer(
//! KeychainKind::External,
//! SignerOrdering(200),
/// [`finish`]: Self::finish
/// [`coin_selection`]: Self::coin_selection
#[derive(Debug)]
-pub struct TxBuilder<'a, B, D, Cs, Ctx> {
- pub(crate) wallet: &'a Wallet<B, D>,
+pub struct TxBuilder<'a, D, Cs, Ctx> {
+ pub(crate) wallet: &'a Wallet<D>,
pub(crate) params: TxParams,
pub(crate) coin_selection: Cs,
pub(crate) phantom: PhantomData<Ctx>,
}
}
-impl<'a, Cs: Clone, Ctx, B, D> Clone for TxBuilder<'a, B, D, Cs, Ctx> {
+impl<'a, Cs: Clone, Ctx, D> Clone for TxBuilder<'a, D, Cs, Ctx> {
fn clone(&self) -> Self {
TxBuilder {
wallet: self.wallet,
}
// methods supported by both contexts, for any CoinSelectionAlgorithm
-impl<'a, B, D: BatchDatabase, Cs: CoinSelectionAlgorithm<D>, Ctx: TxBuilderContext>
- TxBuilder<'a, B, D, Cs, Ctx>
+impl<'a, D: BatchDatabase, Cs: CoinSelectionAlgorithm<D>, Ctx: TxBuilderContext>
+ TxBuilder<'a, D, Cs, Ctx>
{
/// Set a custom fee rate
pub fn fee_rate(&mut self, fee_rate: FeeRate) -> &mut Self {
pub fn coin_selection<P: CoinSelectionAlgorithm<D>>(
self,
coin_selection: P,
- ) -> TxBuilder<'a, B, D, P, Ctx> {
+ ) -> TxBuilder<'a, D, P, Ctx> {
TxBuilder {
wallet: self.wallet,
params: self.params,
}
}
-impl<'a, B, D: BatchDatabase, Cs: CoinSelectionAlgorithm<D>> TxBuilder<'a, B, D, Cs, CreateTx> {
+impl<'a, D: BatchDatabase, Cs: CoinSelectionAlgorithm<D>> TxBuilder<'a, D, Cs, CreateTx> {
/// Replace the recipients already added with a new list
pub fn set_recipients(&mut self, recipients: Vec<(Script, u64)>) -> &mut Self {
self.params.recipients = recipients;
}
// methods supported only by bump_fee
-impl<'a, B, D: BatchDatabase> TxBuilder<'a, B, D, DefaultCoinSelectionAlgorithm, BumpFee> {
+impl<'a, D: BatchDatabase> TxBuilder<'a, D, DefaultCoinSelectionAlgorithm, BumpFee> {
/// Explicitly tells the wallet that it is allowed to reduce the fee of the output matching this
/// `script_pubkey` in order to bump the transaction fee. Without specifying this the wallet
/// will attempt to find a change output to shrink instead.