feat!: Rework sqlite, changesets, persistence and wallet-construction
Rework sqlite: Instead of only supported one schema (defined in `bdk_sqlite`), we have a schema per changeset type for more flexiblity. * rm `bdk_sqlite` crate (as we don't need `bdk_sqlite::Store` anymore). * add `sqlite` feature on `bdk_chain` which adds methods on each changeset type for initializing tables, loading the changeset and writing. Rework changesets: Some callers may want to use `KeychainTxOutIndex` where `K` may change per descriptor on every run. So we only want to persist the last revealed indices by `DescriptorId` (which uniquely-ish identifies the descriptor). * rm `keychain_added` field from `keychain_txout`'s changeset. * Add `keychain_added` to `CombinedChangeSet` (which is renamed to `WalletChangeSet`). Rework persistence: add back some safety and convenience when persisting our types. Working with changeset directly (as we were doing before) can be cumbersome. * Intoduce `struct Persisted<T>` which wraps a type `T` which stores staged changes to it. This adds safety when creating and or loading `T` from db. * `struct Persisted<T>` methods, `create`, `load` and `persist`, are avaliable if `trait PersistWith<Db>` is implemented for `T`. `Db` represents the database connection and `PersistWith` should be implemented per database-type. * For async, we have `trait PersistedAsyncWith<Db>`. * `Wallet` has impls of `PersistedWith<rusqlite::Connection>`, `PersistedWith<rusqlite::Transaction>` and `PersistedWith<bdk_file_store::Store>` by default. Rework wallet-construction: Before, we had multiple methods for loading and creating with different input-counts so it would be unwieldly to add more parameters in the future. This also makes it difficult to impl `PersistWith` (which has a single method for `load` that takes in `PersistWith::LoadParams` and a single method for `create` that takes in `PersistWith::CreateParams`). * Introduce a builder pattern when constructing a `Wallet`. For loading from persistence or `ChangeSet`, we have `LoadParams`. For creating a new wallet, we have `CreateParams`.
This commit is contained in:
@@ -281,15 +281,10 @@ impl IntoWalletDescriptor for DescriptorTemplateOut {
|
||||
}
|
||||
}
|
||||
|
||||
/// Wrapper for `IntoWalletDescriptor` that performs additional checks on the keys contained in the
|
||||
/// descriptor
|
||||
pub(crate) fn into_wallet_descriptor_checked<T: IntoWalletDescriptor>(
|
||||
inner: T,
|
||||
secp: &SecpCtx,
|
||||
network: Network,
|
||||
) -> Result<(ExtendedDescriptor, KeyMap), DescriptorError> {
|
||||
let (descriptor, keymap) = inner.into_wallet_descriptor(secp, network)?;
|
||||
|
||||
/// Extra checks for [`ExtendedDescriptor`].
|
||||
pub(crate) fn check_wallet_descriptor(
|
||||
descriptor: &Descriptor<DescriptorPublicKey>,
|
||||
) -> Result<(), DescriptorError> {
|
||||
// Ensure the keys don't contain any hardened derivation steps or hardened wildcards
|
||||
let descriptor_contains_hardened_steps = descriptor.for_any_key(|k| {
|
||||
if let DescriptorPublicKey::XPub(DescriptorXKey {
|
||||
@@ -316,7 +311,7 @@ pub(crate) fn into_wallet_descriptor_checked<T: IntoWalletDescriptor>(
|
||||
// issues
|
||||
descriptor.sanity_check()?;
|
||||
|
||||
Ok((descriptor, keymap))
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
@@ -855,22 +850,31 @@ mod test {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_into_wallet_descriptor_checked() {
|
||||
fn test_check_wallet_descriptor() {
|
||||
let secp = Secp256k1::new();
|
||||
|
||||
let descriptor = "wpkh(tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/0'/1/2/*)";
|
||||
let result = into_wallet_descriptor_checked(descriptor, &secp, Network::Testnet);
|
||||
let (descriptor, _) = descriptor
|
||||
.into_wallet_descriptor(&secp, Network::Testnet)
|
||||
.expect("must parse");
|
||||
let result = check_wallet_descriptor(&descriptor);
|
||||
|
||||
assert_matches!(result, Err(DescriptorError::HardenedDerivationXpub));
|
||||
|
||||
let descriptor = "wpkh(tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/<0;1>/*)";
|
||||
let result = into_wallet_descriptor_checked(descriptor, &secp, Network::Testnet);
|
||||
let (descriptor, _) = descriptor
|
||||
.into_wallet_descriptor(&secp, Network::Testnet)
|
||||
.expect("must parse");
|
||||
let result = check_wallet_descriptor(&descriptor);
|
||||
|
||||
assert_matches!(result, Err(DescriptorError::MultiPath));
|
||||
|
||||
// repeated pubkeys
|
||||
let descriptor = "wsh(multi(2,tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/0/*,tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/0/*))";
|
||||
let result = into_wallet_descriptor_checked(descriptor, &secp, Network::Testnet);
|
||||
let (descriptor, _) = descriptor
|
||||
.into_wallet_descriptor(&secp, Network::Testnet)
|
||||
.expect("must parse");
|
||||
let result = check_wallet_descriptor(&descriptor);
|
||||
|
||||
assert!(result.is_err());
|
||||
}
|
||||
@@ -882,8 +886,10 @@ mod test {
|
||||
let secp = Secp256k1::new();
|
||||
|
||||
let descriptor = "sh(wsh(sortedmulti(3,tpubDEsqS36T4DVsKJd9UH8pAKzrkGBYPLEt9jZMwpKtzh1G6mgYehfHt9WCgk7MJG5QGSFWf176KaBNoXbcuFcuadAFKxDpUdMDKGBha7bY3QM/0/*,tpubDF3cpwfs7fMvXXuoQbohXtLjNM6ehwYT287LWtmLsd4r77YLg6MZg4vTETx5MSJ2zkfigbYWu31VA2Z2Vc1cZugCYXgS7FQu6pE8V6TriEH/0/*,tpubDE1SKfcW76Tb2AASv5bQWMuScYNAdoqLHoexw13sNDXwmUhQDBbCD3QAedKGLhxMrWQdMDKENzYtnXPDRvexQPNuDrLj52wAjHhNEm8sJ4p/0/*,tpubDFLc6oXwJmhm3FGGzXkfJNTh2KitoY3WhmmQvuAjMhD8YbyWn5mAqckbxXfm2etM3p5J6JoTpSrMqRSTfMLtNW46poDaEZJ1kjd3csRSjwH/0/*,tpubDEWD9NBeWP59xXmdqSNt4VYdtTGwbpyP8WS962BuqpQeMZmX9Pur14dhXdZT5a7wR1pK6dPtZ9fP5WR493hPzemnBvkfLLYxnUjAKj1JCQV/0/*,tpubDEHyZkkwd7gZWCTgQuYQ9C4myF2hMEmyHsBCCmLssGqoqUxeT3gzohF5uEVURkf9TtmeepJgkSUmteac38FwZqirjApzNX59XSHLcwaTZCH/0/*,tpubDEqLouCekwnMUWN486kxGzD44qVgeyuqHyxUypNEiQt5RnUZNJe386TKPK99fqRV1vRkZjYAjtXGTECz98MCsdLcnkM67U6KdYRzVubeCgZ/0/*)))";
|
||||
let (descriptor, _) =
|
||||
into_wallet_descriptor_checked(descriptor, &secp, Network::Testnet).unwrap();
|
||||
let (descriptor, _) = descriptor
|
||||
.into_wallet_descriptor(&secp, Network::Testnet)
|
||||
.unwrap();
|
||||
check_wallet_descriptor(&descriptor).expect("descriptor");
|
||||
|
||||
let descriptor = descriptor.at_derivation_index(0).unwrap();
|
||||
|
||||
|
||||
@@ -73,7 +73,7 @@ impl<T: DescriptorTemplate> IntoWalletDescriptor for T {
|
||||
///
|
||||
/// ```
|
||||
/// # use bdk_wallet::bitcoin::{PrivateKey, Network};
|
||||
/// # use bdk_wallet::Wallet;
|
||||
/// # use bdk_wallet::CreateParams;
|
||||
/// # use bdk_wallet::KeychainKind;
|
||||
/// use bdk_wallet::template::P2Pkh;
|
||||
///
|
||||
@@ -81,7 +81,8 @@ impl<T: DescriptorTemplate> IntoWalletDescriptor for T {
|
||||
/// bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")?;
|
||||
/// let key_internal =
|
||||
/// bitcoin::PrivateKey::from_wif("cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW")?;
|
||||
/// let mut wallet = Wallet::new(P2Pkh(key_external), P2Pkh(key_internal), Network::Testnet)?;
|
||||
/// let mut wallet = CreateParams::new(P2Pkh(key_external), P2Pkh(key_internal), Network::Testnet)?
|
||||
/// .create_wallet_no_persist()?;
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// wallet
|
||||
@@ -105,7 +106,7 @@ impl<K: IntoDescriptorKey<Legacy>> DescriptorTemplate for P2Pkh<K> {
|
||||
///
|
||||
/// ```
|
||||
/// # use bdk_wallet::bitcoin::{PrivateKey, Network};
|
||||
/// # use bdk_wallet::Wallet;
|
||||
/// # use bdk_wallet::CreateParams;
|
||||
/// # use bdk_wallet::KeychainKind;
|
||||
/// use bdk_wallet::template::P2Wpkh_P2Sh;
|
||||
///
|
||||
@@ -113,11 +114,12 @@ impl<K: IntoDescriptorKey<Legacy>> DescriptorTemplate for P2Pkh<K> {
|
||||
/// bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")?;
|
||||
/// let key_internal =
|
||||
/// bitcoin::PrivateKey::from_wif("cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW")?;
|
||||
/// let mut wallet = Wallet::new(
|
||||
/// let mut wallet = CreateParams::new(
|
||||
/// P2Wpkh_P2Sh(key_external),
|
||||
/// P2Wpkh_P2Sh(key_internal),
|
||||
/// Network::Testnet,
|
||||
/// )?;
|
||||
/// )?
|
||||
/// .create_wallet_no_persist()?;
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// wallet
|
||||
@@ -142,7 +144,7 @@ impl<K: IntoDescriptorKey<Segwitv0>> DescriptorTemplate for P2Wpkh_P2Sh<K> {
|
||||
///
|
||||
/// ```
|
||||
/// # use bdk_wallet::bitcoin::{PrivateKey, Network};
|
||||
/// # use bdk_wallet::{Wallet};
|
||||
/// # use bdk_wallet::CreateParams;
|
||||
/// # use bdk_wallet::KeychainKind;
|
||||
/// use bdk_wallet::template::P2Wpkh;
|
||||
///
|
||||
@@ -150,7 +152,9 @@ impl<K: IntoDescriptorKey<Segwitv0>> DescriptorTemplate for P2Wpkh_P2Sh<K> {
|
||||
/// bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")?;
|
||||
/// let key_internal =
|
||||
/// bitcoin::PrivateKey::from_wif("cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW")?;
|
||||
/// let mut wallet = Wallet::new(P2Wpkh(key_external), P2Wpkh(key_internal), Network::Testnet)?;
|
||||
/// let mut wallet =
|
||||
/// CreateParams::new(P2Wpkh(key_external), P2Wpkh(key_internal), Network::Testnet)?
|
||||
/// .create_wallet_no_persist()?;
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// wallet
|
||||
@@ -174,7 +178,7 @@ impl<K: IntoDescriptorKey<Segwitv0>> DescriptorTemplate for P2Wpkh<K> {
|
||||
///
|
||||
/// ```
|
||||
/// # use bdk_wallet::bitcoin::{PrivateKey, Network};
|
||||
/// # use bdk_wallet::Wallet;
|
||||
/// # use bdk_wallet::CreateParams;
|
||||
/// # use bdk_wallet::KeychainKind;
|
||||
/// use bdk_wallet::template::P2TR;
|
||||
///
|
||||
@@ -182,7 +186,8 @@ impl<K: IntoDescriptorKey<Segwitv0>> DescriptorTemplate for P2Wpkh<K> {
|
||||
/// bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")?;
|
||||
/// let key_internal =
|
||||
/// bitcoin::PrivateKey::from_wif("cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW")?;
|
||||
/// let mut wallet = Wallet::new(P2TR(key_external), P2TR(key_internal), Network::Testnet)?;
|
||||
/// let mut wallet = CreateParams::new(P2TR(key_external), P2TR(key_internal), Network::Testnet)?
|
||||
/// .create_wallet_no_persist()?;
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// wallet
|
||||
@@ -211,15 +216,16 @@ impl<K: IntoDescriptorKey<Tap>> DescriptorTemplate for P2TR<K> {
|
||||
/// ```
|
||||
/// # use std::str::FromStr;
|
||||
/// # use bdk_wallet::bitcoin::{PrivateKey, Network};
|
||||
/// # use bdk_wallet::{Wallet, KeychainKind};
|
||||
/// # use bdk_wallet::{CreateParams, KeychainKind};
|
||||
/// use bdk_wallet::template::Bip44;
|
||||
///
|
||||
/// let key = bitcoin::bip32::Xpriv::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?;
|
||||
/// let mut wallet = Wallet::new(
|
||||
/// let mut wallet = CreateParams::new(
|
||||
/// Bip44(key.clone(), KeychainKind::External),
|
||||
/// Bip44(key, KeychainKind::Internal),
|
||||
/// Network::Testnet,
|
||||
/// )?;
|
||||
/// )?
|
||||
/// .create_wallet_no_persist()?;
|
||||
///
|
||||
/// assert_eq!(wallet.next_unused_address(KeychainKind::External).to_string(), "mmogjc7HJEZkrLqyQYqJmxUqFaC7i4uf89");
|
||||
/// assert_eq!(wallet.public_descriptor(KeychainKind::External).to_string(), "pkh([c55b303f/44'/1'/0']tpubDCuorCpzvYS2LCD75BR46KHE8GdDeg1wsAgNZeNr6DaB5gQK1o14uErKwKLuFmeemkQ6N2m3rNgvctdJLyr7nwu2yia7413Hhg8WWE44cgT/0/*)#5wrnv0xt");
|
||||
@@ -247,16 +253,17 @@ impl<K: DerivableKey<Legacy>> DescriptorTemplate for Bip44<K> {
|
||||
/// ```
|
||||
/// # use std::str::FromStr;
|
||||
/// # use bdk_wallet::bitcoin::{PrivateKey, Network};
|
||||
/// # use bdk_wallet::{Wallet, KeychainKind};
|
||||
/// # use bdk_wallet::{CreateParams, KeychainKind};
|
||||
/// use bdk_wallet::template::Bip44Public;
|
||||
///
|
||||
/// let key = bitcoin::bip32::Xpub::from_str("tpubDDDzQ31JkZB7VxUr9bjvBivDdqoFLrDPyLWtLapArAi51ftfmCb2DPxwLQzX65iNcXz1DGaVvyvo6JQ6rTU73r2gqdEo8uov9QKRb7nKCSU")?;
|
||||
/// let fingerprint = bitcoin::bip32::Fingerprint::from_str("c55b303f")?;
|
||||
/// let mut wallet = Wallet::new(
|
||||
/// let mut wallet = CreateParams::new(
|
||||
/// Bip44Public(key.clone(), fingerprint, KeychainKind::External),
|
||||
/// Bip44Public(key, fingerprint, KeychainKind::Internal),
|
||||
/// Network::Testnet,
|
||||
/// )?;
|
||||
/// )?
|
||||
/// .create_wallet_no_persist()?;
|
||||
///
|
||||
/// assert_eq!(wallet.next_unused_address(KeychainKind::External).to_string(), "miNG7dJTzJqNbFS19svRdTCisC65dsubtR");
|
||||
/// assert_eq!(wallet.public_descriptor(KeychainKind::External).to_string(), "pkh([c55b303f/44'/1'/0']tpubDDDzQ31JkZB7VxUr9bjvBivDdqoFLrDPyLWtLapArAi51ftfmCb2DPxwLQzX65iNcXz1DGaVvyvo6JQ6rTU73r2gqdEo8uov9QKRb7nKCSU/0/*)#cfhumdqz");
|
||||
@@ -284,15 +291,16 @@ impl<K: DerivableKey<Legacy>> DescriptorTemplate for Bip44Public<K> {
|
||||
/// ```
|
||||
/// # use std::str::FromStr;
|
||||
/// # use bdk_wallet::bitcoin::{PrivateKey, Network};
|
||||
/// # use bdk_wallet::{Wallet, KeychainKind};
|
||||
/// # use bdk_wallet::{CreateParams, KeychainKind};
|
||||
/// use bdk_wallet::template::Bip49;
|
||||
///
|
||||
/// let key = bitcoin::bip32::Xpriv::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?;
|
||||
/// let mut wallet = Wallet::new(
|
||||
/// let mut wallet = CreateParams::new(
|
||||
/// Bip49(key.clone(), KeychainKind::External),
|
||||
/// Bip49(key, KeychainKind::Internal),
|
||||
/// Network::Testnet,
|
||||
/// )?;
|
||||
/// )?
|
||||
/// .create_wallet_no_persist()?;
|
||||
///
|
||||
/// assert_eq!(wallet.next_unused_address(KeychainKind::External).to_string(), "2N4zkWAoGdUv4NXhSsU8DvS5MB36T8nKHEB");
|
||||
/// assert_eq!(wallet.public_descriptor(KeychainKind::External).to_string(), "sh(wpkh([c55b303f/49'/1'/0']tpubDDYr4kdnZgjjShzYNjZUZXUUtpXaofdkMaipyS8ThEh45qFmhT4hKYways7UXmg6V7het1QiFo9kf4kYUXyDvV4rHEyvSpys9pjCB3pukxi/0/*))#s9vxlc8e");
|
||||
@@ -320,16 +328,17 @@ impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for Bip49<K> {
|
||||
/// ```
|
||||
/// # use std::str::FromStr;
|
||||
/// # use bdk_wallet::bitcoin::{PrivateKey, Network};
|
||||
/// # use bdk_wallet::{Wallet, KeychainKind};
|
||||
/// # use bdk_wallet::{CreateParams, KeychainKind};
|
||||
/// use bdk_wallet::template::Bip49Public;
|
||||
///
|
||||
/// let key = bitcoin::bip32::Xpub::from_str("tpubDC49r947KGK52X5rBWS4BLs5m9SRY3pYHnvRrm7HcybZ3BfdEsGFyzCMzayi1u58eT82ZeyFZwH7DD6Q83E3fM9CpfMtmnTygnLfP59jL9L")?;
|
||||
/// let fingerprint = bitcoin::bip32::Fingerprint::from_str("c55b303f")?;
|
||||
/// let mut wallet = Wallet::new(
|
||||
/// let mut wallet = CreateParams::new(
|
||||
/// Bip49Public(key.clone(), fingerprint, KeychainKind::External),
|
||||
/// Bip49Public(key, fingerprint, KeychainKind::Internal),
|
||||
/// Network::Testnet,
|
||||
/// )?;
|
||||
/// )?
|
||||
/// .create_wallet_no_persist()?;
|
||||
///
|
||||
/// assert_eq!(wallet.next_unused_address(KeychainKind::External).to_string(), "2N3K4xbVAHoiTQSwxkZjWDfKoNC27pLkYnt");
|
||||
/// assert_eq!(wallet.public_descriptor(KeychainKind::External).to_string(), "sh(wpkh([c55b303f/49'/1'/0']tpubDC49r947KGK52X5rBWS4BLs5m9SRY3pYHnvRrm7HcybZ3BfdEsGFyzCMzayi1u58eT82ZeyFZwH7DD6Q83E3fM9CpfMtmnTygnLfP59jL9L/0/*))#3tka9g0q");
|
||||
@@ -357,15 +366,16 @@ impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for Bip49Public<K> {
|
||||
/// ```
|
||||
/// # use std::str::FromStr;
|
||||
/// # use bdk_wallet::bitcoin::{PrivateKey, Network};
|
||||
/// # use bdk_wallet::{Wallet, KeychainKind};
|
||||
/// # use bdk_wallet::{CreateParams, KeychainKind};
|
||||
/// use bdk_wallet::template::Bip84;
|
||||
///
|
||||
/// let key = bitcoin::bip32::Xpriv::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?;
|
||||
/// let mut wallet = Wallet::new(
|
||||
/// let mut wallet = CreateParams::new(
|
||||
/// Bip84(key.clone(), KeychainKind::External),
|
||||
/// Bip84(key, KeychainKind::Internal),
|
||||
/// Network::Testnet,
|
||||
/// )?;
|
||||
/// )?
|
||||
/// .create_wallet_no_persist()?;
|
||||
///
|
||||
/// assert_eq!(wallet.next_unused_address(KeychainKind::External).to_string(), "tb1qhl85z42h7r4su5u37rvvw0gk8j2t3n9y7zsg4n");
|
||||
/// assert_eq!(wallet.public_descriptor(KeychainKind::External).to_string(), "wpkh([c55b303f/84'/1'/0']tpubDDc5mum24DekpNw92t6fHGp8Gr2JjF9J7i4TZBtN6Vp8xpAULG5CFaKsfugWa5imhrQQUZKXe261asP5koDHo5bs3qNTmf3U3o4v9SaB8gg/0/*)#6kfecsmr");
|
||||
@@ -393,16 +403,16 @@ impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for Bip84<K> {
|
||||
/// ```
|
||||
/// # use std::str::FromStr;
|
||||
/// # use bdk_wallet::bitcoin::{PrivateKey, Network};
|
||||
/// # use bdk_wallet::{Wallet, KeychainKind};
|
||||
/// # use bdk_wallet::{CreateParams, KeychainKind};
|
||||
/// use bdk_wallet::template::Bip84Public;
|
||||
///
|
||||
/// let key = bitcoin::bip32::Xpub::from_str("tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q")?;
|
||||
/// let fingerprint = bitcoin::bip32::Fingerprint::from_str("c55b303f")?;
|
||||
/// let mut wallet = Wallet::new(
|
||||
/// let mut wallet = CreateParams::new(
|
||||
/// Bip84Public(key.clone(), fingerprint, KeychainKind::External),
|
||||
/// Bip84Public(key, fingerprint, KeychainKind::Internal),
|
||||
/// Network::Testnet,
|
||||
/// )?;
|
||||
/// )?.create_wallet_no_persist()?;
|
||||
///
|
||||
/// assert_eq!(wallet.next_unused_address(KeychainKind::External).to_string(), "tb1qedg9fdlf8cnnqfd5mks6uz5w4kgpk2pr6y4qc7");
|
||||
/// assert_eq!(wallet.public_descriptor(KeychainKind::External).to_string(), "wpkh([c55b303f/84'/1'/0']tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q/0/*)#dhu402yv");
|
||||
@@ -430,15 +440,16 @@ impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for Bip84Public<K> {
|
||||
/// ```
|
||||
/// # use std::str::FromStr;
|
||||
/// # use bdk_wallet::bitcoin::{PrivateKey, Network};
|
||||
/// # use bdk_wallet::{Wallet, KeychainKind};
|
||||
/// # use bdk_wallet::{CreateParams, KeychainKind};
|
||||
/// use bdk_wallet::template::Bip86;
|
||||
///
|
||||
/// let key = bitcoin::bip32::Xpriv::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?;
|
||||
/// let mut wallet = Wallet::new(
|
||||
/// let mut wallet = CreateParams::new(
|
||||
/// Bip86(key.clone(), KeychainKind::External),
|
||||
/// Bip86(key, KeychainKind::Internal),
|
||||
/// Network::Testnet,
|
||||
/// )?;
|
||||
/// )?
|
||||
/// .create_wallet_no_persist()?;
|
||||
///
|
||||
/// assert_eq!(wallet.next_unused_address(KeychainKind::External).to_string(), "tb1p5unlj09djx8xsjwe97269kqtxqpwpu2epeskgqjfk4lnf69v4tnqpp35qu");
|
||||
/// assert_eq!(wallet.public_descriptor(KeychainKind::External).to_string(), "tr([c55b303f/86'/1'/0']tpubDCiHofpEs47kx358bPdJmTZHmCDqQ8qw32upCSxHrSEdeeBs2T5Mq6QMB2ukeMqhNBiyhosBvJErteVhfURPGXPv3qLJPw5MVpHUewsbP2m/0/*)#dkgvr5hm");
|
||||
@@ -466,16 +477,17 @@ impl<K: DerivableKey<Tap>> DescriptorTemplate for Bip86<K> {
|
||||
/// ```
|
||||
/// # use std::str::FromStr;
|
||||
/// # use bdk_wallet::bitcoin::{PrivateKey, Network};
|
||||
/// # use bdk_wallet::{Wallet, KeychainKind};
|
||||
/// # use bdk_wallet::{CreateParams, KeychainKind};
|
||||
/// use bdk_wallet::template::Bip86Public;
|
||||
///
|
||||
/// let key = bitcoin::bip32::Xpub::from_str("tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q")?;
|
||||
/// let fingerprint = bitcoin::bip32::Fingerprint::from_str("c55b303f")?;
|
||||
/// let mut wallet = Wallet::new(
|
||||
/// let mut wallet = CreateParams::new(
|
||||
/// Bip86Public(key.clone(), fingerprint, KeychainKind::External),
|
||||
/// Bip86Public(key, fingerprint, KeychainKind::Internal),
|
||||
/// Network::Testnet,
|
||||
/// )?;
|
||||
/// )?
|
||||
/// .create_wallet_no_persist()?;
|
||||
///
|
||||
/// assert_eq!(wallet.next_unused_address(KeychainKind::External).to_string(), "tb1pwjp9f2k5n0xq73ecuu0c5njvgqr3vkh7yaylmpqvsuuaafymh0msvcmh37");
|
||||
/// assert_eq!(wallet.public_descriptor(KeychainKind::External).to_string(), "tr([c55b303f/86'/1'/0']tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q/0/*)#2p65srku");
|
||||
|
||||
@@ -36,12 +36,22 @@ pub use types::*;
|
||||
pub use wallet::signer;
|
||||
pub use wallet::signer::SignOptions;
|
||||
pub use wallet::tx_builder::TxBuilder;
|
||||
pub use wallet::ChangeSet;
|
||||
pub use wallet::CreateParams;
|
||||
pub use wallet::LoadParams;
|
||||
pub use wallet::PersistedWallet;
|
||||
pub use wallet::Wallet;
|
||||
|
||||
/// Get the version of BDK at runtime
|
||||
/// Get the version of [`bdk_wallet`](crate) at runtime.
|
||||
pub fn version() -> &'static str {
|
||||
env!("CARGO_PKG_VERSION", "unknown")
|
||||
}
|
||||
|
||||
pub use bdk_chain as chain;
|
||||
pub(crate) use bdk_chain::collections;
|
||||
#[cfg(feature = "sqlite")]
|
||||
pub use bdk_chain::rusqlite;
|
||||
#[cfg(feature = "sqlite")]
|
||||
pub use bdk_chain::sqlite;
|
||||
|
||||
pub use chain::WalletChangeSet;
|
||||
|
||||
@@ -29,11 +29,12 @@
|
||||
//! }"#;
|
||||
//!
|
||||
//! let import = FullyNodedExport::from_str(import)?;
|
||||
//! let wallet = Wallet::new(
|
||||
//! let wallet = CreateParams::new(
|
||||
//! &import.descriptor(),
|
||||
//! &import.change_descriptor().expect("change descriptor"),
|
||||
//! Network::Testnet,
|
||||
//! )?;
|
||||
//! )?
|
||||
//! .create_wallet_no_persist()?;
|
||||
//! # Ok::<_, Box<dyn std::error::Error>>(())
|
||||
//! ```
|
||||
//!
|
||||
@@ -42,11 +43,12 @@
|
||||
//! # use bitcoin::*;
|
||||
//! # use bdk_wallet::wallet::export::*;
|
||||
//! # use bdk_wallet::*;
|
||||
//! let wallet = Wallet::new(
|
||||
//! let wallet = CreateParams::new(
|
||||
//! "wpkh([c258d2e4/84h/1h/0h]tpubDD3ynpHgJQW8VvWRzQ5WFDCrs4jqVFGHB3vLC3r49XHJSqP8bHKdK4AriuUKLccK68zfzowx7YhmDN8SiSkgCDENUFx9qVw65YyqM78vyVe/0/*)",
|
||||
//! "wpkh([c258d2e4/84h/1h/0h]tpubDD3ynpHgJQW8VvWRzQ5WFDCrs4jqVFGHB3vLC3r49XHJSqP8bHKdK4AriuUKLccK68zfzowx7YhmDN8SiSkgCDENUFx9qVw65YyqM78vyVe/1/*)",
|
||||
//! Network::Testnet,
|
||||
//! )?;
|
||||
//! )?
|
||||
//! .create_wallet_no_persist()?;
|
||||
//! let export = FullyNodedExport::export_wallet(&wallet, "exported wallet", true).unwrap();
|
||||
//!
|
||||
//! println!("Exported: {}", export.to_string());
|
||||
@@ -219,12 +221,15 @@ mod test {
|
||||
use bitcoin::{transaction, BlockHash, Network, Transaction};
|
||||
|
||||
use super::*;
|
||||
use crate::wallet::Wallet;
|
||||
use crate::wallet::{CreateParams, Wallet};
|
||||
|
||||
fn get_test_wallet(descriptor: &str, change_descriptor: &str, network: Network) -> Wallet {
|
||||
use crate::wallet::Update;
|
||||
use bdk_chain::TxGraph;
|
||||
let mut wallet = Wallet::new(descriptor, change_descriptor, network).unwrap();
|
||||
let mut wallet = CreateParams::new(descriptor, change_descriptor, network)
|
||||
.expect("must parse descriptors")
|
||||
.create_wallet_no_persist()
|
||||
.expect("must create wallet");
|
||||
let transaction = Transaction {
|
||||
input: vec![],
|
||||
output: vec![],
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
//! # use bdk_wallet::signer::SignerOrdering;
|
||||
//! # use bdk_wallet::wallet::hardwaresigner::HWISigner;
|
||||
//! # use bdk_wallet::wallet::AddressIndex::New;
|
||||
//! # use bdk_wallet::{KeychainKind, SignOptions, Wallet};
|
||||
//! # use bdk_wallet::{CreateParams, KeychainKind, SignOptions};
|
||||
//! # use hwi::HWIClient;
|
||||
//! # use std::sync::Arc;
|
||||
//! #
|
||||
@@ -30,11 +30,7 @@
|
||||
//! let first_device = devices.remove(0)?;
|
||||
//! let custom_signer = HWISigner::from_device(&first_device, Network::Testnet.into())?;
|
||||
//!
|
||||
//! # let mut wallet = Wallet::new(
|
||||
//! # "",
|
||||
//! # None,
|
||||
//! # Network::Testnet,
|
||||
//! # )?;
|
||||
//! # let mut wallet = CreateParams::new("", "", Network::Testnet)?.create_wallet_no_persist()?;
|
||||
//! #
|
||||
//! // Adding the hardware signer to the BDK wallet
|
||||
//! wallet.add_signer(
|
||||
|
||||
@@ -12,7 +12,10 @@
|
||||
//! Wallet
|
||||
//!
|
||||
//! This module defines the [`Wallet`].
|
||||
use crate::collections::{BTreeMap, HashMap};
|
||||
use crate::{
|
||||
collections::{BTreeMap, HashMap},
|
||||
descriptor::check_wallet_descriptor,
|
||||
};
|
||||
use alloc::{
|
||||
boxed::Box,
|
||||
string::{String, ToString},
|
||||
@@ -28,8 +31,8 @@ use bdk_chain::{
|
||||
},
|
||||
spk_client::{FullScanRequest, FullScanResult, SyncRequest, SyncResult},
|
||||
tx_graph::{CanonicalTx, TxGraph, TxNode},
|
||||
BlockId, ChainPosition, ConfirmationBlockTime, ConfirmationTime, FullTxOut, Indexed,
|
||||
IndexedTxGraph, Merge,
|
||||
BlockId, ChainPosition, ConfirmationBlockTime, ConfirmationTime, DescriptorExt, FullTxOut,
|
||||
Indexed, IndexedTxGraph, Merge,
|
||||
};
|
||||
use bitcoin::sighash::{EcdsaSighashType, TapSighashType};
|
||||
use bitcoin::{
|
||||
@@ -38,24 +41,28 @@ use bitcoin::{
|
||||
};
|
||||
use bitcoin::{consensus::encode::serialize, transaction, BlockHash, Psbt};
|
||||
use bitcoin::{constants::genesis_block, Amount};
|
||||
use bitcoin::{
|
||||
secp256k1::{All, Secp256k1},
|
||||
Weight,
|
||||
};
|
||||
use bitcoin::{secp256k1::Secp256k1, Weight};
|
||||
use core::fmt;
|
||||
use core::mem;
|
||||
use core::ops::Deref;
|
||||
use rand_core::RngCore;
|
||||
|
||||
use descriptor::error::Error as DescriptorError;
|
||||
use miniscript::psbt::{PsbtExt, PsbtInputExt, PsbtInputSatisfier};
|
||||
use miniscript::{
|
||||
descriptor::KeyMap,
|
||||
psbt::{PsbtExt, PsbtInputExt, PsbtInputSatisfier},
|
||||
};
|
||||
|
||||
use bdk_chain::tx_graph::CalculateFeeError;
|
||||
|
||||
pub mod coin_selection;
|
||||
pub mod export;
|
||||
mod params;
|
||||
pub mod signer;
|
||||
pub mod tx_builder;
|
||||
pub use params::*;
|
||||
mod persisted;
|
||||
pub use persisted::*;
|
||||
pub(crate) mod utils;
|
||||
|
||||
pub mod error;
|
||||
@@ -69,8 +76,8 @@ use utils::{check_nsequence_rbf, After, Older, SecpCtx};
|
||||
|
||||
use crate::descriptor::policy::BuildSatisfaction;
|
||||
use crate::descriptor::{
|
||||
self, calc_checksum, into_wallet_descriptor_checked, DerivedDescriptor, DescriptorMeta,
|
||||
ExtendedDescriptor, ExtractPolicy, IntoWalletDescriptor, Policy, XKeyUtils,
|
||||
self, calc_checksum, DerivedDescriptor, DescriptorMeta, ExtendedDescriptor, ExtractPolicy,
|
||||
IntoWalletDescriptor, Policy, XKeyUtils,
|
||||
};
|
||||
use crate::psbt::PsbtUtils;
|
||||
use crate::signer::SignerError;
|
||||
@@ -149,7 +156,7 @@ impl From<SyncResult> for Update {
|
||||
}
|
||||
|
||||
/// The changes made to a wallet by applying an [`Update`].
|
||||
pub type ChangeSet = bdk_chain::CombinedChangeSet<KeychainKind, ConfirmationBlockTime>;
|
||||
pub type ChangeSet = bdk_chain::WalletChangeSet;
|
||||
|
||||
/// A derived address and the index it was found at.
|
||||
/// For convenience this automatically derefs to `Address`
|
||||
@@ -177,34 +184,7 @@ impl fmt::Display for AddressInfo {
|
||||
}
|
||||
}
|
||||
|
||||
/// The error type when constructing a fresh [`Wallet`].
|
||||
///
|
||||
/// Methods [`new`] and [`new_with_genesis_hash`] may return this error.
|
||||
///
|
||||
/// [`new`]: Wallet::new
|
||||
/// [`new_with_genesis_hash`]: Wallet::new_with_genesis_hash
|
||||
#[derive(Debug)]
|
||||
pub enum NewError {
|
||||
/// There was problem with the passed-in descriptor(s).
|
||||
Descriptor(crate::descriptor::DescriptorError),
|
||||
}
|
||||
|
||||
impl fmt::Display for NewError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
NewError::Descriptor(e) => e.fmt(f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
impl std::error::Error for NewError {}
|
||||
|
||||
/// The error type when loading a [`Wallet`] from a [`ChangeSet`].
|
||||
///
|
||||
/// Method [`load_from_changeset`] may return this error.
|
||||
///
|
||||
/// [`load_from_changeset`]: Wallet::load_from_changeset
|
||||
#[derive(Debug)]
|
||||
pub enum LoadError {
|
||||
/// There was a problem with the passed-in descriptor(s).
|
||||
@@ -215,6 +195,8 @@ pub enum LoadError {
|
||||
MissingGenesis,
|
||||
/// Data loaded from persistence is missing descriptor.
|
||||
MissingDescriptor(KeychainKind),
|
||||
/// Data loaded is unexpected.
|
||||
Mismatch(LoadMismatch),
|
||||
}
|
||||
|
||||
impl fmt::Display for LoadError {
|
||||
@@ -226,6 +208,7 @@ impl fmt::Display for LoadError {
|
||||
LoadError::MissingDescriptor(k) => {
|
||||
write!(f, "loaded data is missing descriptor for keychain {k:?}")
|
||||
}
|
||||
LoadError::Mismatch(mismatch) => write!(f, "data mismatch: {mismatch:?}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -233,63 +216,34 @@ impl fmt::Display for LoadError {
|
||||
#[cfg(feature = "std")]
|
||||
impl std::error::Error for LoadError {}
|
||||
|
||||
/// Error type for when we try load a [`Wallet`] from persistence and creating it if non-existent.
|
||||
///
|
||||
/// Methods [`new_or_load`] and [`new_or_load_with_genesis_hash`] may return this error.
|
||||
///
|
||||
/// [`new_or_load`]: Wallet::new_or_load
|
||||
/// [`new_or_load_with_genesis_hash`]: Wallet::new_or_load_with_genesis_hash
|
||||
/// Represents a mismatch with what is loaded and what is expected from [`LoadParams`].
|
||||
#[derive(Debug)]
|
||||
pub enum NewOrLoadError {
|
||||
/// There is a problem with the passed-in descriptor.
|
||||
Descriptor(crate::descriptor::DescriptorError),
|
||||
/// The loaded genesis hash does not match what was provided.
|
||||
LoadedGenesisDoesNotMatch {
|
||||
/// The expected genesis block hash.
|
||||
expected: BlockHash,
|
||||
/// The block hash loaded from persistence.
|
||||
got: Option<BlockHash>,
|
||||
},
|
||||
/// The loaded network type does not match what was provided.
|
||||
LoadedNetworkDoesNotMatch {
|
||||
/// The expected network type.
|
||||
pub enum LoadMismatch {
|
||||
/// Network does not match.
|
||||
Network {
|
||||
/// The network that is loaded.
|
||||
loaded: Network,
|
||||
/// The expected network.
|
||||
expected: Network,
|
||||
/// The network type loaded from persistence.
|
||||
got: Option<Network>,
|
||||
},
|
||||
/// The loaded desccriptor does not match what was provided.
|
||||
LoadedDescriptorDoesNotMatch {
|
||||
/// The descriptor loaded from persistence.
|
||||
got: Option<ExtendedDescriptor>,
|
||||
/// The keychain of the descriptor not matching
|
||||
/// Genesis hash does not match.
|
||||
Genesis {
|
||||
/// The genesis hash that is loaded.
|
||||
loaded: BlockHash,
|
||||
/// The expected genesis hash.
|
||||
expected: BlockHash,
|
||||
},
|
||||
/// Descriptor's [`DescriptorId`](bdk_chain::DescriptorId) does not match.
|
||||
Descriptor {
|
||||
/// Keychain identifying the descriptor.
|
||||
keychain: KeychainKind,
|
||||
/// The loaded descriptor.
|
||||
loaded: ExtendedDescriptor,
|
||||
/// The expected descriptor.
|
||||
expected: ExtendedDescriptor,
|
||||
},
|
||||
}
|
||||
|
||||
impl fmt::Display for NewOrLoadError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
NewOrLoadError::Descriptor(e) => e.fmt(f),
|
||||
NewOrLoadError::LoadedGenesisDoesNotMatch { expected, got } => {
|
||||
write!(f, "loaded genesis hash is not {}, got {:?}", expected, got)
|
||||
}
|
||||
NewOrLoadError::LoadedNetworkDoesNotMatch { expected, got } => {
|
||||
write!(f, "loaded network type is not {}, got {:?}", expected, got)
|
||||
}
|
||||
NewOrLoadError::LoadedDescriptorDoesNotMatch { got, keychain } => {
|
||||
write!(
|
||||
f,
|
||||
"loaded descriptor is different from what was provided, got {:?} for keychain {:?}",
|
||||
got, keychain
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
impl std::error::Error for NewOrLoadError {}
|
||||
|
||||
/// An error that may occur when applying a block to [`Wallet`].
|
||||
#[derive(Debug)]
|
||||
pub enum ApplyBlockError {
|
||||
@@ -324,39 +278,81 @@ impl fmt::Display for ApplyBlockError {
|
||||
impl std::error::Error for ApplyBlockError {}
|
||||
|
||||
impl Wallet {
|
||||
/// Initialize an empty [`Wallet`].
|
||||
pub fn new<E: IntoWalletDescriptor>(
|
||||
/// Build a new [`Wallet`].
|
||||
///
|
||||
/// If you have previously created a wallet, use [`load`](Self::load) instead.
|
||||
///
|
||||
/// # Synopsis
|
||||
///
|
||||
/// ```rust
|
||||
/// # use bdk_wallet::Wallet;
|
||||
/// # use bitcoin::Network;
|
||||
/// # fn main() -> anyhow::Result<()> {
|
||||
/// # const EXTERNAL_DESC: &str = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/0/*)";
|
||||
/// # const INTERNAL_DESC: &str = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/1/*)";
|
||||
/// // Create a non-persisted wallet.
|
||||
/// let wallet = Wallet::create(EXTERNAL_DESC, INTERNAL_DESC, Network::Testnet)?
|
||||
/// .create_wallet_no_persist()?;
|
||||
///
|
||||
/// // Create a wallet that is persisted to SQLite database.
|
||||
/// # let temp_dir = tempfile::tempdir().expect("must create tempdir");
|
||||
/// # let file_path = temp_dir.path().join("store.db");
|
||||
/// use bdk_wallet::rusqlite::Connection;
|
||||
/// let mut conn = Connection::open(file_path)?;
|
||||
/// let wallet = Wallet::create(EXTERNAL_DESC, INTERNAL_DESC, Network::Testnet)?
|
||||
/// .create_wallet(&mut conn)?;
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn create<E: IntoWalletDescriptor>(
|
||||
descriptor: E,
|
||||
change_descriptor: E,
|
||||
network: Network,
|
||||
) -> Result<Self, NewError> {
|
||||
let genesis_hash = genesis_block(network).block_hash();
|
||||
Self::new_with_genesis_hash(descriptor, change_descriptor, network, genesis_hash)
|
||||
) -> Result<CreateParams, DescriptorError> {
|
||||
CreateParams::new(descriptor, change_descriptor, network)
|
||||
}
|
||||
|
||||
/// Initialize an empty [`Wallet`] with a custom genesis hash.
|
||||
/// Create a new [`Wallet`] with given `params`.
|
||||
///
|
||||
/// This is like [`Wallet::new`] with an additional `genesis_hash` parameter. This is useful
|
||||
/// for syncing from alternative networks.
|
||||
pub fn new_with_genesis_hash<E: IntoWalletDescriptor>(
|
||||
descriptor: E,
|
||||
change_descriptor: E,
|
||||
network: Network,
|
||||
genesis_hash: BlockHash,
|
||||
) -> Result<Self, NewError> {
|
||||
let secp = Secp256k1::new();
|
||||
/// If you have previously created a wallet, use [`load`](Self::load) instead.
|
||||
pub fn create_with_params(params: CreateParams) -> Result<Self, DescriptorError> {
|
||||
let secp = params.secp;
|
||||
let network = params.network;
|
||||
let genesis_hash = params
|
||||
.genesis_hash
|
||||
.unwrap_or(genesis_block(network).block_hash());
|
||||
|
||||
let (chain, chain_changeset) = LocalChain::from_genesis_hash(genesis_hash);
|
||||
let mut index = KeychainTxOutIndex::<KeychainKind>::default();
|
||||
|
||||
let (signers, change_signers) =
|
||||
create_signers(&mut index, &secp, descriptor, change_descriptor, network)
|
||||
.map_err(NewError::Descriptor)?;
|
||||
check_wallet_descriptor(¶ms.descriptor)?;
|
||||
check_wallet_descriptor(¶ms.change_descriptor)?;
|
||||
let signers = Arc::new(SignersContainer::build(
|
||||
params.descriptor_keymap,
|
||||
¶ms.descriptor,
|
||||
&secp,
|
||||
));
|
||||
let change_signers = Arc::new(SignersContainer::build(
|
||||
params.change_descriptor_keymap,
|
||||
¶ms.change_descriptor,
|
||||
&secp,
|
||||
));
|
||||
let index = create_indexer(
|
||||
params.descriptor,
|
||||
params.change_descriptor,
|
||||
params.lookahead,
|
||||
)?;
|
||||
|
||||
let descriptor = index.get_descriptor(&KeychainKind::External).cloned();
|
||||
let change_descriptor = index.get_descriptor(&KeychainKind::Internal).cloned();
|
||||
let indexed_graph = IndexedTxGraph::new(index);
|
||||
let indexed_graph_changeset = indexed_graph.initial_changeset();
|
||||
|
||||
let staged = ChangeSet {
|
||||
chain: chain_changeset,
|
||||
indexed_tx_graph: indexed_graph.initial_changeset(),
|
||||
let stage = ChangeSet {
|
||||
descriptor,
|
||||
change_descriptor,
|
||||
local_chain: chain_changeset,
|
||||
tx_graph: indexed_graph_changeset.tx_graph,
|
||||
indexer: indexed_graph_changeset.indexer,
|
||||
network: Some(network),
|
||||
};
|
||||
|
||||
@@ -366,11 +362,79 @@ impl Wallet {
|
||||
network,
|
||||
chain,
|
||||
indexed_graph,
|
||||
stage: staged,
|
||||
stage,
|
||||
secp,
|
||||
})
|
||||
}
|
||||
|
||||
/// Build [`Wallet`] by loading from persistence or [`ChangeSet`].
|
||||
///
|
||||
/// Note that the descriptor secret keys are not persisted to the db. You can either add
|
||||
/// signers after-the-fact with [`Wallet::add_signer`] or [`Wallet::set_keymap`]. Or you can
|
||||
/// construct wallet using [`Wallet::load_with_descriptors`].
|
||||
///
|
||||
/// # Synopsis
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// # use bdk_wallet::{Wallet, ChangeSet, KeychainKind};
|
||||
/// # use bitcoin::{BlockHash, Network, hashes::Hash};
|
||||
/// # fn main() -> anyhow::Result<()> {
|
||||
/// # const EXTERNAL_DESC: &str = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/0/*)";
|
||||
/// # const INTERNAL_DESC: &str = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/1/*)";
|
||||
/// # let changeset = ChangeSet::default();
|
||||
/// // Load a wallet from changeset (no persistence).
|
||||
/// let wallet = Wallet::load()
|
||||
/// .load_wallet_no_persist(changeset)?
|
||||
/// .expect("must have data to load wallet");
|
||||
///
|
||||
/// // Load a wallet that is persisted to SQLite database.
|
||||
/// # let temp_dir = tempfile::tempdir().expect("must create tempdir");
|
||||
/// # let file_path = temp_dir.path().join("store.db");
|
||||
/// # let external_keymap = Default::default();
|
||||
/// # let internal_keymap = Default::default();
|
||||
/// # let genesis_hash = BlockHash::all_zeros();
|
||||
/// let mut conn = bdk_wallet::rusqlite::Connection::open(file_path)?;
|
||||
/// let mut wallet = Wallet::load()
|
||||
/// // manually include private keys
|
||||
/// // the alternative is to use `Wallet::load_with_descriptors`
|
||||
/// .keymap(KeychainKind::External, external_keymap)
|
||||
/// .keymap(KeychainKind::Internal, internal_keymap)
|
||||
/// // set a lookahead for our indexer
|
||||
/// .lookahead(101)
|
||||
/// // ensure loaded wallet's genesis hash matches this value
|
||||
/// .genesis_hash(genesis_hash)
|
||||
/// .load_wallet(&mut conn)?
|
||||
/// .expect("must have data to load wallet");
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn load() -> LoadParams {
|
||||
LoadParams::new()
|
||||
}
|
||||
|
||||
/// Build [`Wallet`] by loading from persistence or [`ChangeSet`]. This fails if the loaded
|
||||
/// wallet has a different `network`.
|
||||
///
|
||||
/// Note that the descriptor secret keys are not persisted to the db. You can either add
|
||||
/// signers after-the-fact with [`Wallet::add_signer`] or [`Wallet::set_keymap`]. Or you can
|
||||
/// construct wallet using [`Wallet::load_with_descriptors`].
|
||||
pub fn load_with_network(network: Network) -> LoadParams {
|
||||
LoadParams::with_network(network)
|
||||
}
|
||||
|
||||
/// Build [`Wallet`] by loading from persistence or [`ChangeSet`]. This fails if the loaded
|
||||
/// wallet has a different `network`, `descriptor` or `change_descriptor`.
|
||||
///
|
||||
/// If the passed-in descriptors contains secret keys, the keys will be included in the
|
||||
/// constructed wallet (which means you can sign transactions).
|
||||
pub fn load_with_descriptors<E: IntoWalletDescriptor>(
|
||||
descriptor: E,
|
||||
change_descriptor: E,
|
||||
network: Network,
|
||||
) -> Result<LoadParams, DescriptorError> {
|
||||
LoadParams::with_descriptors(descriptor, change_descriptor, network)
|
||||
}
|
||||
|
||||
/// Load [`Wallet`] from the given previously persisted [`ChangeSet`].
|
||||
///
|
||||
/// Note that the descriptor secret keys are not persisted to the db; this means that after
|
||||
@@ -382,68 +446,102 @@ impl Wallet {
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// # use bdk_wallet::Wallet;
|
||||
/// # use bdk_wallet::signer::{SignersContainer, SignerOrdering};
|
||||
/// # use bdk_wallet::descriptor::Descriptor;
|
||||
/// # use bitcoin::key::Secp256k1;
|
||||
/// # use bdk_wallet::KeychainKind;
|
||||
/// use bdk_sqlite::{Store, rusqlite::Connection};
|
||||
/// # use bitcoin::Network;
|
||||
/// # use bdk_wallet::{LoadParams, KeychainKind, PersistedWallet};
|
||||
/// use bdk_chain::sqlite::Connection;
|
||||
/// #
|
||||
/// # fn main() -> Result<(), anyhow::Error> {
|
||||
/// # fn main() -> anyhow::Result<()> {
|
||||
/// # let temp_dir = tempfile::tempdir().expect("must create tempdir");
|
||||
/// # let file_path = temp_dir.path().join("store.db");
|
||||
/// let conn = Connection::open(file_path).expect("must open connection");
|
||||
/// let mut db = Store::new(conn).expect("must create db");
|
||||
/// let secp = Secp256k1::new();
|
||||
/// const EXTERNAL_DESC: &str = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/0/*)";
|
||||
/// const INTERNAL_DESC: &str = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/1/*)";
|
||||
///
|
||||
/// let (external_descriptor, external_keymap) = Descriptor::parse_descriptor(&secp, "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/0/*)").unwrap();
|
||||
/// let (internal_descriptor, internal_keymap) = Descriptor::parse_descriptor(&secp, "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/1/*)").unwrap();
|
||||
/// let mut conn = Connection::open(file_path)?;
|
||||
/// let mut wallet: PersistedWallet =
|
||||
/// LoadParams::with_descriptors(EXTERNAL_DESC, INTERNAL_DESC, Network::Testnet)?
|
||||
/// .load_wallet(&mut conn)?
|
||||
/// .expect("db should have data to load wallet");
|
||||
///
|
||||
/// let external_signer_container = SignersContainer::build(external_keymap, &external_descriptor, &secp);
|
||||
/// let internal_signer_container = SignersContainer::build(internal_keymap, &internal_descriptor, &secp);
|
||||
/// let changeset = db.read()?.expect("there must be an existing changeset");
|
||||
/// let mut wallet = Wallet::load_from_changeset(changeset)?;
|
||||
///
|
||||
/// external_signer_container.signers().into_iter()
|
||||
/// .for_each(|s| wallet.add_signer(KeychainKind::External, SignerOrdering::default(), s.clone()));
|
||||
/// internal_signer_container.signers().into_iter()
|
||||
/// .for_each(|s| wallet.add_signer(KeychainKind::Internal, SignerOrdering::default(), s.clone()));
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// Alternatively, you can call [`Wallet::new_or_load`], which will add the private keys of the
|
||||
/// passed-in descriptors to the [`Wallet`].
|
||||
pub fn load_from_changeset(changeset: ChangeSet) -> Result<Self, LoadError> {
|
||||
pub fn load_with_params(
|
||||
changeset: ChangeSet,
|
||||
params: LoadParams,
|
||||
) -> Result<Option<Self>, LoadError> {
|
||||
if changeset.is_empty() {
|
||||
return Ok(None);
|
||||
}
|
||||
let secp = Secp256k1::new();
|
||||
let network = changeset.network.ok_or(LoadError::MissingNetwork)?;
|
||||
let chain =
|
||||
LocalChain::from_changeset(changeset.chain).map_err(|_| LoadError::MissingGenesis)?;
|
||||
let mut index = KeychainTxOutIndex::<KeychainKind>::default();
|
||||
let descriptor = changeset
|
||||
.indexed_tx_graph
|
||||
.indexer
|
||||
.keychains_added
|
||||
.get(&KeychainKind::External)
|
||||
.ok_or(LoadError::MissingDescriptor(KeychainKind::External))?
|
||||
.clone();
|
||||
let change_descriptor = changeset
|
||||
.indexed_tx_graph
|
||||
.indexer
|
||||
.keychains_added
|
||||
.get(&KeychainKind::Internal)
|
||||
.ok_or(LoadError::MissingDescriptor(KeychainKind::Internal))?
|
||||
.clone();
|
||||
let chain = LocalChain::from_changeset(changeset.local_chain)
|
||||
.map_err(|_| LoadError::MissingGenesis)?;
|
||||
|
||||
let (signers, change_signers) =
|
||||
create_signers(&mut index, &secp, descriptor, change_descriptor, network)
|
||||
.expect("Can't fail: we passed in valid descriptors, recovered from the changeset");
|
||||
let descriptor = changeset
|
||||
.descriptor
|
||||
.ok_or(LoadError::MissingDescriptor(KeychainKind::External))?;
|
||||
let change_descriptor = changeset
|
||||
.change_descriptor
|
||||
.ok_or(LoadError::MissingDescriptor(KeychainKind::Internal))?;
|
||||
check_wallet_descriptor(&descriptor).map_err(LoadError::Descriptor)?;
|
||||
check_wallet_descriptor(&change_descriptor).map_err(LoadError::Descriptor)?;
|
||||
|
||||
// checks
|
||||
if let Some(exp_network) = params.check_network {
|
||||
if network != exp_network {
|
||||
return Err(LoadError::Mismatch(LoadMismatch::Network {
|
||||
loaded: network,
|
||||
expected: exp_network,
|
||||
}));
|
||||
}
|
||||
}
|
||||
if let Some(exp_genesis_hash) = params.check_genesis_hash {
|
||||
if chain.genesis_hash() != exp_genesis_hash {
|
||||
return Err(LoadError::Mismatch(LoadMismatch::Genesis {
|
||||
loaded: chain.genesis_hash(),
|
||||
expected: exp_genesis_hash,
|
||||
}));
|
||||
}
|
||||
}
|
||||
if let Some(exp_descriptor) = params.check_descriptor {
|
||||
if descriptor.descriptor_id() != exp_descriptor.descriptor_id() {
|
||||
return Err(LoadError::Mismatch(LoadMismatch::Descriptor {
|
||||
keychain: KeychainKind::External,
|
||||
loaded: descriptor,
|
||||
expected: exp_descriptor,
|
||||
}));
|
||||
}
|
||||
}
|
||||
if let Some(exp_change_descriptor) = params.check_change_descriptor {
|
||||
if change_descriptor.descriptor_id() != exp_change_descriptor.descriptor_id() {
|
||||
return Err(LoadError::Mismatch(LoadMismatch::Descriptor {
|
||||
keychain: KeychainKind::External,
|
||||
loaded: change_descriptor,
|
||||
expected: exp_change_descriptor,
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
let signers = Arc::new(SignersContainer::build(
|
||||
params.descriptor_keymap,
|
||||
&descriptor,
|
||||
&secp,
|
||||
));
|
||||
let change_signers = Arc::new(SignersContainer::build(
|
||||
params.change_descriptor_keymap,
|
||||
&change_descriptor,
|
||||
&secp,
|
||||
));
|
||||
let index = create_indexer(descriptor, change_descriptor, params.lookahead)
|
||||
.map_err(LoadError::Descriptor)?;
|
||||
|
||||
let mut indexed_graph = IndexedTxGraph::new(index);
|
||||
indexed_graph.apply_changeset(changeset.indexed_tx_graph);
|
||||
indexed_graph.apply_changeset(changeset.indexer.into());
|
||||
indexed_graph.apply_changeset(changeset.tx_graph.into());
|
||||
|
||||
let stage = ChangeSet::default();
|
||||
|
||||
Ok(Wallet {
|
||||
Ok(Some(Wallet {
|
||||
signers,
|
||||
change_signers,
|
||||
chain,
|
||||
@@ -451,146 +549,7 @@ impl Wallet {
|
||||
stage,
|
||||
network,
|
||||
secp,
|
||||
})
|
||||
}
|
||||
|
||||
/// Either loads [`Wallet`] from the given [`ChangeSet`] or initializes it if one does not exist.
|
||||
///
|
||||
/// This method will fail if the loaded [`ChangeSet`] has different parameters to those provided.
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// # use bdk_wallet::Wallet;
|
||||
/// use bdk_sqlite::{Store, rusqlite::Connection};
|
||||
/// # use bitcoin::Network::Testnet;
|
||||
/// let conn = Connection::open_in_memory().expect("must open connection");
|
||||
/// let mut db = Store::new(conn).expect("must create db");
|
||||
/// let changeset = db.read()?;
|
||||
///
|
||||
/// let external_descriptor = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/0/*)";
|
||||
/// let internal_descriptor = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/1/*)";
|
||||
///
|
||||
/// let mut wallet = Wallet::new_or_load(external_descriptor, internal_descriptor, changeset, Testnet)?;
|
||||
/// # Ok::<(), anyhow::Error>(())
|
||||
/// ```
|
||||
pub fn new_or_load<E: IntoWalletDescriptor>(
|
||||
descriptor: E,
|
||||
change_descriptor: E,
|
||||
changeset: Option<ChangeSet>,
|
||||
network: Network,
|
||||
) -> Result<Self, NewOrLoadError> {
|
||||
let genesis_hash = genesis_block(network).block_hash();
|
||||
Self::new_or_load_with_genesis_hash(
|
||||
descriptor,
|
||||
change_descriptor,
|
||||
changeset,
|
||||
network,
|
||||
genesis_hash,
|
||||
)
|
||||
}
|
||||
|
||||
/// Either loads [`Wallet`] from a [`ChangeSet`] or initializes it if one does not exist, using the
|
||||
/// provided descriptor, change descriptor, network, and custom genesis hash.
|
||||
///
|
||||
/// This method will fail if the loaded [`ChangeSet`] has different parameters to those provided.
|
||||
/// This is like [`Wallet::new_or_load`] with an additional `genesis_hash` parameter. This is
|
||||
/// useful for syncing from alternative networks.
|
||||
pub fn new_or_load_with_genesis_hash<E: IntoWalletDescriptor>(
|
||||
descriptor: E,
|
||||
change_descriptor: E,
|
||||
changeset: Option<ChangeSet>,
|
||||
network: Network,
|
||||
genesis_hash: BlockHash,
|
||||
) -> Result<Self, NewOrLoadError> {
|
||||
if let Some(changeset) = changeset {
|
||||
let mut wallet = Self::load_from_changeset(changeset).map_err(|e| match e {
|
||||
LoadError::Descriptor(e) => NewOrLoadError::Descriptor(e),
|
||||
LoadError::MissingNetwork => NewOrLoadError::LoadedNetworkDoesNotMatch {
|
||||
expected: network,
|
||||
got: None,
|
||||
},
|
||||
LoadError::MissingGenesis => NewOrLoadError::LoadedGenesisDoesNotMatch {
|
||||
expected: genesis_hash,
|
||||
got: None,
|
||||
},
|
||||
LoadError::MissingDescriptor(keychain) => {
|
||||
NewOrLoadError::LoadedDescriptorDoesNotMatch {
|
||||
got: None,
|
||||
keychain,
|
||||
}
|
||||
}
|
||||
})?;
|
||||
if wallet.network != network {
|
||||
return Err(NewOrLoadError::LoadedNetworkDoesNotMatch {
|
||||
expected: network,
|
||||
got: Some(wallet.network),
|
||||
});
|
||||
}
|
||||
if wallet.chain.genesis_hash() != genesis_hash {
|
||||
return Err(NewOrLoadError::LoadedGenesisDoesNotMatch {
|
||||
expected: genesis_hash,
|
||||
got: Some(wallet.chain.genesis_hash()),
|
||||
});
|
||||
}
|
||||
|
||||
let (expected_descriptor, expected_descriptor_keymap) = descriptor
|
||||
.into_wallet_descriptor(&wallet.secp, network)
|
||||
.map_err(NewOrLoadError::Descriptor)?;
|
||||
let wallet_descriptor = wallet.public_descriptor(KeychainKind::External);
|
||||
if wallet_descriptor != &expected_descriptor {
|
||||
return Err(NewOrLoadError::LoadedDescriptorDoesNotMatch {
|
||||
got: Some(wallet_descriptor.clone()),
|
||||
keychain: KeychainKind::External,
|
||||
});
|
||||
}
|
||||
// if expected descriptor has private keys add them as new signers
|
||||
if !expected_descriptor_keymap.is_empty() {
|
||||
let signer_container = SignersContainer::build(
|
||||
expected_descriptor_keymap,
|
||||
&expected_descriptor,
|
||||
&wallet.secp,
|
||||
);
|
||||
signer_container.signers().into_iter().for_each(|signer| {
|
||||
wallet.add_signer(
|
||||
KeychainKind::External,
|
||||
SignerOrdering::default(),
|
||||
signer.clone(),
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
let (expected_change_descriptor, expected_change_descriptor_keymap) = change_descriptor
|
||||
.into_wallet_descriptor(&wallet.secp, network)
|
||||
.map_err(NewOrLoadError::Descriptor)?;
|
||||
let wallet_change_descriptor = wallet.public_descriptor(KeychainKind::Internal);
|
||||
if wallet_change_descriptor != &expected_change_descriptor {
|
||||
return Err(NewOrLoadError::LoadedDescriptorDoesNotMatch {
|
||||
got: Some(wallet_change_descriptor.clone()),
|
||||
keychain: KeychainKind::Internal,
|
||||
});
|
||||
}
|
||||
// if expected change descriptor has private keys add them as new signers
|
||||
if !expected_change_descriptor_keymap.is_empty() {
|
||||
let signer_container = SignersContainer::build(
|
||||
expected_change_descriptor_keymap,
|
||||
&expected_change_descriptor,
|
||||
&wallet.secp,
|
||||
);
|
||||
signer_container.signers().into_iter().for_each(|signer| {
|
||||
wallet.add_signer(
|
||||
KeychainKind::Internal,
|
||||
SignerOrdering::default(),
|
||||
signer.clone(),
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
Ok(wallet)
|
||||
} else {
|
||||
Self::new_with_genesis_hash(descriptor, change_descriptor, network, genesis_hash)
|
||||
.map_err(|e| match e {
|
||||
NewError::Descriptor(e) => NewOrLoadError::Descriptor(e),
|
||||
})
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
/// Get the Bitcoin network the wallet is using.
|
||||
@@ -642,17 +601,15 @@ impl Wallet {
|
||||
/// calls to this method before closing the wallet. For example:
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// # use bdk_wallet::wallet::{Wallet, ChangeSet};
|
||||
/// # use bdk_wallet::KeychainKind;
|
||||
/// use bdk_sqlite::{rusqlite::Connection, Store};
|
||||
/// let conn = Connection::open_in_memory().expect("must open connection");
|
||||
/// let mut db = Store::new(conn).expect("must create store");
|
||||
/// # let changeset = ChangeSet::default();
|
||||
/// # let mut wallet = Wallet::load_from_changeset(changeset).expect("load wallet");
|
||||
/// # use bdk_wallet::{LoadParams, ChangeSet, KeychainKind};
|
||||
/// use bdk_chain::sqlite::Connection;
|
||||
/// let mut conn = Connection::open_in_memory().expect("must open connection");
|
||||
/// let mut wallet = LoadParams::new()
|
||||
/// .load_wallet(&mut conn)
|
||||
/// .expect("database is okay")
|
||||
/// .expect("database has data");
|
||||
/// let next_address = wallet.reveal_next_address(KeychainKind::External);
|
||||
/// if let Some(changeset) = wallet.take_staged() {
|
||||
/// db.write(&changeset)?;
|
||||
/// }
|
||||
/// wallet.persist(&mut conn).expect("write is okay");
|
||||
///
|
||||
/// // Now it's safe to show the user their next address!
|
||||
/// println!("Next address: {}", next_address.address);
|
||||
@@ -666,7 +623,7 @@ impl Wallet {
|
||||
.reveal_next_spk(&keychain)
|
||||
.expect("keychain must exist");
|
||||
|
||||
stage.merge(indexed_tx_graph::ChangeSet::from(index_changeset).into());
|
||||
stage.merge(index_changeset.into());
|
||||
|
||||
AddressInfo {
|
||||
index,
|
||||
@@ -1110,16 +1067,38 @@ impl Wallet {
|
||||
signers.add_external(signer.id(&self.secp), ordering, signer);
|
||||
}
|
||||
|
||||
/// Set the keymap for a given keychain.
|
||||
pub fn set_keymap(&mut self, keychain: KeychainKind, keymap: KeyMap) {
|
||||
let wallet_signers = match keychain {
|
||||
KeychainKind::External => Arc::make_mut(&mut self.signers),
|
||||
KeychainKind::Internal => Arc::make_mut(&mut self.change_signers),
|
||||
};
|
||||
let descriptor = self
|
||||
.indexed_graph
|
||||
.index
|
||||
.get_descriptor(&keychain)
|
||||
.expect("keychain must exist");
|
||||
*wallet_signers = SignersContainer::build(keymap, descriptor, &self.secp);
|
||||
}
|
||||
|
||||
/// Set the keymap for each keychain.
|
||||
pub fn set_keymaps(&mut self, keymaps: impl IntoIterator<Item = (KeychainKind, KeyMap)>) {
|
||||
for (keychain, keymap) in keymaps {
|
||||
self.set_keymap(keychain, keymap);
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the signers
|
||||
///
|
||||
/// ## Example
|
||||
///
|
||||
/// ```
|
||||
/// # use bdk_wallet::{Wallet, KeychainKind};
|
||||
/// # use bdk_wallet::{CreateParams, KeychainKind};
|
||||
/// # use bdk_wallet::bitcoin::Network;
|
||||
/// let descriptor = "wpkh(tprv8ZgxMBicQKsPe73PBRSmNbTfbcsZnwWhz5eVmhHpi31HW29Z7mc9B4cWGRQzopNUzZUT391DeDJxL2PefNunWyLgqCKRMDkU1s2s8bAfoSk/84'/1'/0'/0/*)";
|
||||
/// let change_descriptor = "wpkh(tprv8ZgxMBicQKsPe73PBRSmNbTfbcsZnwWhz5eVmhHpi31HW29Z7mc9B4cWGRQzopNUzZUT391DeDJxL2PefNunWyLgqCKRMDkU1s2s8bAfoSk/84'/1'/0'/1/*)";
|
||||
/// let wallet = Wallet::new(descriptor, change_descriptor, Network::Testnet)?;
|
||||
/// let wallet = CreateParams::new(descriptor, change_descriptor, Network::Testnet)?
|
||||
/// .create_wallet_no_persist()?;
|
||||
/// for secret_key in wallet.get_signers(KeychainKind::External).signers().iter().filter_map(|s| s.descriptor_secret_key()) {
|
||||
/// // secret_key: tprv8ZgxMBicQKsPe73PBRSmNbTfbcsZnwWhz5eVmhHpi31HW29Z7mc9B4cWGRQzopNUzZUT391DeDJxL2PefNunWyLgqCKRMDkU1s2s8bAfoSk/84'/0'/0'/0/*
|
||||
/// println!("secret_key: {}", secret_key);
|
||||
@@ -2424,25 +2403,23 @@ fn new_local_utxo(
|
||||
}
|
||||
}
|
||||
|
||||
fn create_signers<E: IntoWalletDescriptor>(
|
||||
index: &mut KeychainTxOutIndex<KeychainKind>,
|
||||
secp: &Secp256k1<All>,
|
||||
descriptor: E,
|
||||
change_descriptor: E,
|
||||
network: Network,
|
||||
) -> Result<(Arc<SignersContainer>, Arc<SignersContainer>), DescriptorError> {
|
||||
let descriptor = into_wallet_descriptor_checked(descriptor, secp, network)?;
|
||||
let change_descriptor = into_wallet_descriptor_checked(change_descriptor, secp, network)?;
|
||||
let (descriptor, keymap) = descriptor;
|
||||
let signers = Arc::new(SignersContainer::build(keymap, &descriptor, secp));
|
||||
let _ = index
|
||||
.insert_descriptor(KeychainKind::External, descriptor)
|
||||
.expect("this is the first descriptor we're inserting");
|
||||
fn create_indexer(
|
||||
descriptor: ExtendedDescriptor,
|
||||
change_descriptor: ExtendedDescriptor,
|
||||
lookahead: u32,
|
||||
) -> Result<KeychainTxOutIndex<KeychainKind>, DescriptorError> {
|
||||
let mut indexer = KeychainTxOutIndex::<KeychainKind>::new(lookahead);
|
||||
|
||||
let (descriptor, keymap) = change_descriptor;
|
||||
let change_signers = Arc::new(SignersContainer::build(keymap, &descriptor, secp));
|
||||
let _ = index
|
||||
.insert_descriptor(KeychainKind::Internal, descriptor)
|
||||
// let (descriptor, keymap) = descriptor;
|
||||
// let signers = Arc::new(SignersContainer::build(keymap, &descriptor, secp));
|
||||
assert!(indexer
|
||||
.insert_descriptor(KeychainKind::External, descriptor)
|
||||
.expect("first descriptor introduced must succeed"));
|
||||
|
||||
// let (descriptor, keymap) = change_descriptor;
|
||||
// let change_signers = Arc::new(SignersContainer::build(keymap, &descriptor, secp));
|
||||
assert!(indexer
|
||||
.insert_descriptor(KeychainKind::Internal, change_descriptor)
|
||||
.map_err(|e| {
|
||||
use bdk_chain::indexer::keychain_txout::InsertDescriptorError;
|
||||
match e {
|
||||
@@ -2453,9 +2430,9 @@ fn create_signers<E: IntoWalletDescriptor>(
|
||||
unreachable!("this is the first time we're assigning internal")
|
||||
}
|
||||
}
|
||||
})?;
|
||||
})?);
|
||||
|
||||
Ok((signers, change_signers))
|
||||
Ok(indexer)
|
||||
}
|
||||
|
||||
/// Transforms a [`FeeRate`] to `f64` with unit as sat/vb.
|
||||
@@ -2476,16 +2453,18 @@ macro_rules! doctest_wallet {
|
||||
() => {{
|
||||
use $crate::bitcoin::{BlockHash, Transaction, absolute, TxOut, Network, hashes::Hash};
|
||||
use $crate::chain::{ConfirmationBlockTime, BlockId, TxGraph};
|
||||
use $crate::wallet::{Update, Wallet};
|
||||
use $crate::wallet::{Update, CreateParams};
|
||||
use $crate::KeychainKind;
|
||||
let descriptor = "tr([73c5da0a/86'/0'/0']tprv8fMn4hSKPRC1oaCPqxDb1JWtgkpeiQvZhsr8W2xuy3GEMkzoArcAWTfJxYb6Wj8XNNDWEjfYKK4wGQXh3ZUXhDF2NcnsALpWTeSwarJt7Vc/0/*)";
|
||||
let change_descriptor = "tr([73c5da0a/86'/0'/0']tprv8fMn4hSKPRC1oaCPqxDb1JWtgkpeiQvZhsr8W2xuy3GEMkzoArcAWTfJxYb6Wj8XNNDWEjfYKK4wGQXh3ZUXhDF2NcnsALpWTeSwarJt7Vc/1/*)";
|
||||
|
||||
let mut wallet = Wallet::new(
|
||||
let mut wallet = CreateParams::new(
|
||||
descriptor,
|
||||
change_descriptor,
|
||||
Network::Regtest,
|
||||
)
|
||||
.unwrap()
|
||||
.create_wallet_no_persist()
|
||||
.unwrap();
|
||||
let address = wallet.peek_address(KeychainKind::External, 0).address;
|
||||
let tx = Transaction {
|
||||
|
||||
217
crates/wallet/src/wallet/params.rs
Normal file
217
crates/wallet/src/wallet/params.rs
Normal file
@@ -0,0 +1,217 @@
|
||||
use bdk_chain::{keychain_txout::DEFAULT_LOOKAHEAD, PersistAsyncWith, PersistWith};
|
||||
use bitcoin::{BlockHash, Network};
|
||||
use miniscript::descriptor::KeyMap;
|
||||
|
||||
use crate::{
|
||||
descriptor::{DescriptorError, ExtendedDescriptor, IntoWalletDescriptor},
|
||||
KeychainKind, Wallet,
|
||||
};
|
||||
|
||||
use super::{utils::SecpCtx, ChangeSet, LoadError, PersistedWallet};
|
||||
|
||||
/// Parameters for [`Wallet::create`] or [`PersistedWallet::create`].
|
||||
#[derive(Debug, Clone)]
|
||||
#[must_use]
|
||||
pub struct CreateParams {
|
||||
pub(crate) descriptor: ExtendedDescriptor,
|
||||
pub(crate) descriptor_keymap: KeyMap,
|
||||
pub(crate) change_descriptor: ExtendedDescriptor,
|
||||
pub(crate) change_descriptor_keymap: KeyMap,
|
||||
pub(crate) network: Network,
|
||||
pub(crate) genesis_hash: Option<BlockHash>,
|
||||
pub(crate) lookahead: u32,
|
||||
pub(crate) secp: SecpCtx,
|
||||
}
|
||||
|
||||
impl CreateParams {
|
||||
/// Construct parameters with provided `descriptor`, `change_descriptor` and `network`.
|
||||
///
|
||||
/// Default values: `genesis_hash` = `None`, `lookahead` = [`DEFAULT_LOOKAHEAD`]
|
||||
pub fn new<E: IntoWalletDescriptor>(
|
||||
descriptor: E,
|
||||
change_descriptor: E,
|
||||
network: Network,
|
||||
) -> Result<Self, DescriptorError> {
|
||||
let secp = SecpCtx::default();
|
||||
|
||||
let (descriptor, descriptor_keymap) = descriptor.into_wallet_descriptor(&secp, network)?;
|
||||
let (change_descriptor, change_descriptor_keymap) =
|
||||
change_descriptor.into_wallet_descriptor(&secp, network)?;
|
||||
|
||||
Ok(Self {
|
||||
descriptor,
|
||||
descriptor_keymap,
|
||||
change_descriptor,
|
||||
change_descriptor_keymap,
|
||||
network,
|
||||
genesis_hash: None,
|
||||
lookahead: DEFAULT_LOOKAHEAD,
|
||||
secp,
|
||||
})
|
||||
}
|
||||
|
||||
/// Extend the given `keychain`'s `keymap`.
|
||||
pub fn keymap(mut self, keychain: KeychainKind, keymap: KeyMap) -> Self {
|
||||
match keychain {
|
||||
KeychainKind::External => &mut self.descriptor_keymap,
|
||||
KeychainKind::Internal => &mut self.change_descriptor_keymap,
|
||||
}
|
||||
.extend(keymap);
|
||||
self
|
||||
}
|
||||
|
||||
/// Use a custom `genesis_hash`.
|
||||
pub fn genesis_hash(mut self, genesis_hash: BlockHash) -> Self {
|
||||
self.genesis_hash = Some(genesis_hash);
|
||||
self
|
||||
}
|
||||
|
||||
/// Use custom lookahead value.
|
||||
pub fn lookahead(mut self, lookahead: u32) -> Self {
|
||||
self.lookahead = lookahead;
|
||||
self
|
||||
}
|
||||
|
||||
/// Create [`PersistedWallet`] with the given `Db`.
|
||||
pub fn create_wallet<Db>(
|
||||
self,
|
||||
db: &mut Db,
|
||||
) -> Result<PersistedWallet, <Wallet as PersistWith<Db>>::CreateError>
|
||||
where
|
||||
Wallet: PersistWith<Db, CreateParams = Self>,
|
||||
{
|
||||
PersistedWallet::create(db, self)
|
||||
}
|
||||
|
||||
/// Create [`PersistedWallet`] with the given async `Db`.
|
||||
pub async fn create_wallet_async<Db>(
|
||||
self,
|
||||
db: &mut Db,
|
||||
) -> Result<PersistedWallet, <Wallet as PersistAsyncWith<Db>>::CreateError>
|
||||
where
|
||||
Wallet: PersistAsyncWith<Db, CreateParams = Self>,
|
||||
{
|
||||
PersistedWallet::create_async(db, self).await
|
||||
}
|
||||
|
||||
/// Create [`Wallet`] without persistence.
|
||||
pub fn create_wallet_no_persist(self) -> Result<Wallet, DescriptorError> {
|
||||
Wallet::create_with_params(self)
|
||||
}
|
||||
}
|
||||
|
||||
/// Parameters for [`Wallet::load`] or [`PersistedWallet::load`].
|
||||
#[must_use]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct LoadParams {
|
||||
pub(crate) descriptor_keymap: KeyMap,
|
||||
pub(crate) change_descriptor_keymap: KeyMap,
|
||||
pub(crate) lookahead: u32,
|
||||
pub(crate) check_network: Option<Network>,
|
||||
pub(crate) check_genesis_hash: Option<BlockHash>,
|
||||
pub(crate) check_descriptor: Option<ExtendedDescriptor>,
|
||||
pub(crate) check_change_descriptor: Option<ExtendedDescriptor>,
|
||||
pub(crate) secp: SecpCtx,
|
||||
}
|
||||
|
||||
impl LoadParams {
|
||||
/// Construct parameters with default values.
|
||||
///
|
||||
/// Default values: `lookahead` = [`DEFAULT_LOOKAHEAD`]
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
descriptor_keymap: KeyMap::default(),
|
||||
change_descriptor_keymap: KeyMap::default(),
|
||||
lookahead: DEFAULT_LOOKAHEAD,
|
||||
check_network: None,
|
||||
check_genesis_hash: None,
|
||||
check_descriptor: None,
|
||||
check_change_descriptor: None,
|
||||
secp: SecpCtx::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Construct parameters with `network` check.
|
||||
pub fn with_network(network: Network) -> Self {
|
||||
Self {
|
||||
check_network: Some(network),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Construct parameters with descriptor checks.
|
||||
pub fn with_descriptors<E: IntoWalletDescriptor>(
|
||||
descriptor: E,
|
||||
change_descriptor: E,
|
||||
network: Network,
|
||||
) -> Result<Self, DescriptorError> {
|
||||
let mut params = Self::with_network(network);
|
||||
let secp = ¶ms.secp;
|
||||
|
||||
let (descriptor, descriptor_keymap) = descriptor.into_wallet_descriptor(secp, network)?;
|
||||
params.check_descriptor = Some(descriptor);
|
||||
params.descriptor_keymap = descriptor_keymap;
|
||||
|
||||
let (change_descriptor, change_descriptor_keymap) =
|
||||
change_descriptor.into_wallet_descriptor(secp, network)?;
|
||||
params.check_change_descriptor = Some(change_descriptor);
|
||||
params.change_descriptor_keymap = change_descriptor_keymap;
|
||||
|
||||
Ok(params)
|
||||
}
|
||||
|
||||
/// Extend the given `keychain`'s `keymap`.
|
||||
pub fn keymap(mut self, keychain: KeychainKind, keymap: KeyMap) -> Self {
|
||||
match keychain {
|
||||
KeychainKind::External => &mut self.descriptor_keymap,
|
||||
KeychainKind::Internal => &mut self.change_descriptor_keymap,
|
||||
}
|
||||
.extend(keymap);
|
||||
self
|
||||
}
|
||||
|
||||
/// Check for a `genesis_hash`.
|
||||
pub fn genesis_hash(mut self, genesis_hash: BlockHash) -> Self {
|
||||
self.check_genesis_hash = Some(genesis_hash);
|
||||
self
|
||||
}
|
||||
|
||||
/// Use custom lookahead value.
|
||||
pub fn lookahead(mut self, lookahead: u32) -> Self {
|
||||
self.lookahead = lookahead;
|
||||
self
|
||||
}
|
||||
|
||||
/// Load [`PersistedWallet`] with the given `Db`.
|
||||
pub fn load_wallet<Db>(
|
||||
self,
|
||||
db: &mut Db,
|
||||
) -> Result<Option<PersistedWallet>, <Wallet as PersistWith<Db>>::LoadError>
|
||||
where
|
||||
Wallet: PersistWith<Db, LoadParams = Self>,
|
||||
{
|
||||
PersistedWallet::load(db, self)
|
||||
}
|
||||
|
||||
/// Load [`PersistedWallet`] with the given async `Db`.
|
||||
pub async fn load_wallet_async<Db>(
|
||||
self,
|
||||
db: &mut Db,
|
||||
) -> Result<Option<PersistedWallet>, <Wallet as PersistAsyncWith<Db>>::LoadError>
|
||||
where
|
||||
Wallet: PersistAsyncWith<Db, LoadParams = Self>,
|
||||
{
|
||||
PersistedWallet::load_async(db, self).await
|
||||
}
|
||||
|
||||
/// Load [`Wallet`] without persistence.
|
||||
pub fn load_wallet_no_persist(self, changeset: ChangeSet) -> Result<Option<Wallet>, LoadError> {
|
||||
Wallet::load_with_params(changeset, self)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for LoadParams {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
180
crates/wallet/src/wallet/persisted.rs
Normal file
180
crates/wallet/src/wallet/persisted.rs
Normal file
@@ -0,0 +1,180 @@
|
||||
use core::fmt;
|
||||
|
||||
use crate::wallet::{ChangeSet, CreateParams, LoadError, LoadParams};
|
||||
use crate::{descriptor::DescriptorError, Wallet};
|
||||
use bdk_chain::{Merge, PersistWith};
|
||||
|
||||
/// Represents a persisted wallet.
|
||||
pub type PersistedWallet = bdk_chain::Persisted<Wallet>;
|
||||
|
||||
#[cfg(feature = "sqlite")]
|
||||
impl<'c> PersistWith<bdk_chain::sqlite::Transaction<'c>> for Wallet {
|
||||
type CreateParams = CreateParams;
|
||||
type LoadParams = LoadParams;
|
||||
|
||||
type CreateError = CreateWithPersistError<bdk_chain::rusqlite::Error>;
|
||||
type LoadError = LoadWithPersistError<bdk_chain::rusqlite::Error>;
|
||||
type PersistError = bdk_chain::rusqlite::Error;
|
||||
|
||||
fn create(
|
||||
db: &mut bdk_chain::sqlite::Transaction<'c>,
|
||||
params: Self::CreateParams,
|
||||
) -> Result<Self, Self::CreateError> {
|
||||
let mut wallet =
|
||||
Self::create_with_params(params).map_err(CreateWithPersistError::Descriptor)?;
|
||||
if let Some(changeset) = wallet.take_staged() {
|
||||
changeset
|
||||
.persist_to_sqlite(db)
|
||||
.map_err(CreateWithPersistError::Persist)?;
|
||||
}
|
||||
Ok(wallet)
|
||||
}
|
||||
|
||||
fn load(
|
||||
conn: &mut bdk_chain::sqlite::Transaction<'c>,
|
||||
params: Self::LoadParams,
|
||||
) -> Result<Option<Self>, Self::LoadError> {
|
||||
let changeset = ChangeSet::from_sqlite(conn).map_err(LoadWithPersistError::Persist)?;
|
||||
if changeset.is_empty() {
|
||||
return Ok(None);
|
||||
}
|
||||
Self::load_with_params(changeset, params).map_err(LoadWithPersistError::InvalidChangeSet)
|
||||
}
|
||||
|
||||
fn persist(
|
||||
&mut self,
|
||||
conn: &mut bdk_chain::sqlite::Transaction,
|
||||
) -> Result<bool, Self::PersistError> {
|
||||
if let Some(changeset) = self.take_staged() {
|
||||
changeset.persist_to_sqlite(conn)?;
|
||||
return Ok(true);
|
||||
}
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "sqlite")]
|
||||
impl PersistWith<bdk_chain::sqlite::Connection> for Wallet {
|
||||
type CreateParams = CreateParams;
|
||||
type LoadParams = LoadParams;
|
||||
|
||||
type CreateError = CreateWithPersistError<bdk_chain::rusqlite::Error>;
|
||||
type LoadError = LoadWithPersistError<bdk_chain::rusqlite::Error>;
|
||||
type PersistError = bdk_chain::rusqlite::Error;
|
||||
|
||||
fn create(
|
||||
db: &mut bdk_chain::sqlite::Connection,
|
||||
params: Self::CreateParams,
|
||||
) -> Result<Self, Self::CreateError> {
|
||||
let mut db_tx = db.transaction().map_err(CreateWithPersistError::Persist)?;
|
||||
let wallet = PersistWith::create(&mut db_tx, params)?;
|
||||
db_tx.commit().map_err(CreateWithPersistError::Persist)?;
|
||||
Ok(wallet)
|
||||
}
|
||||
|
||||
fn load(
|
||||
db: &mut bdk_chain::sqlite::Connection,
|
||||
params: Self::LoadParams,
|
||||
) -> Result<Option<Self>, Self::LoadError> {
|
||||
let mut db_tx = db.transaction().map_err(LoadWithPersistError::Persist)?;
|
||||
let wallet_opt = PersistWith::load(&mut db_tx, params)?;
|
||||
db_tx.commit().map_err(LoadWithPersistError::Persist)?;
|
||||
Ok(wallet_opt)
|
||||
}
|
||||
|
||||
fn persist(
|
||||
&mut self,
|
||||
db: &mut bdk_chain::sqlite::Connection,
|
||||
) -> Result<bool, Self::PersistError> {
|
||||
let mut db_tx = db.transaction()?;
|
||||
let has_changes = PersistWith::persist(self, &mut db_tx)?;
|
||||
db_tx.commit()?;
|
||||
Ok(has_changes)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "file_store")]
|
||||
impl PersistWith<bdk_file_store::Store<ChangeSet>> for Wallet {
|
||||
type CreateParams = CreateParams;
|
||||
type LoadParams = LoadParams;
|
||||
type CreateError = CreateWithPersistError<std::io::Error>;
|
||||
type LoadError = LoadWithPersistError<bdk_file_store::AggregateChangesetsError<ChangeSet>>;
|
||||
type PersistError = std::io::Error;
|
||||
|
||||
fn create(
|
||||
db: &mut bdk_file_store::Store<ChangeSet>,
|
||||
params: Self::CreateParams,
|
||||
) -> Result<Self, Self::CreateError> {
|
||||
let mut wallet =
|
||||
Self::create_with_params(params).map_err(CreateWithPersistError::Descriptor)?;
|
||||
if let Some(changeset) = wallet.take_staged() {
|
||||
db.append_changeset(&changeset)
|
||||
.map_err(CreateWithPersistError::Persist)?;
|
||||
}
|
||||
Ok(wallet)
|
||||
}
|
||||
|
||||
fn load(
|
||||
db: &mut bdk_file_store::Store<ChangeSet>,
|
||||
params: Self::LoadParams,
|
||||
) -> Result<Option<Self>, Self::LoadError> {
|
||||
let changeset = db
|
||||
.aggregate_changesets()
|
||||
.map_err(LoadWithPersistError::Persist)?
|
||||
.unwrap_or_default();
|
||||
Self::load_with_params(changeset, params).map_err(LoadWithPersistError::InvalidChangeSet)
|
||||
}
|
||||
|
||||
fn persist(
|
||||
&mut self,
|
||||
db: &mut bdk_file_store::Store<ChangeSet>,
|
||||
) -> Result<bool, Self::PersistError> {
|
||||
if let Some(changeset) = self.take_staged() {
|
||||
db.append_changeset(&changeset)?;
|
||||
return Ok(true);
|
||||
}
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
|
||||
/// Error type for [`PersistedWallet::load`].
|
||||
#[derive(Debug)]
|
||||
pub enum LoadWithPersistError<E> {
|
||||
/// Error from persistence.
|
||||
Persist(E),
|
||||
/// Occurs when the loaded changeset cannot construct [`Wallet`].
|
||||
InvalidChangeSet(LoadError),
|
||||
}
|
||||
|
||||
impl<E: fmt::Display> fmt::Display for LoadWithPersistError<E> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Persist(err) => fmt::Display::fmt(err, f),
|
||||
Self::InvalidChangeSet(err) => fmt::Display::fmt(&err, f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
impl<E: fmt::Debug + fmt::Display> std::error::Error for LoadWithPersistError<E> {}
|
||||
|
||||
/// Error type for [`PersistedWallet::create`].
|
||||
#[derive(Debug)]
|
||||
pub enum CreateWithPersistError<E> {
|
||||
/// Error from persistence.
|
||||
Persist(E),
|
||||
/// Occurs when the loaded changeset cannot contruct [`Wallet`].
|
||||
Descriptor(DescriptorError),
|
||||
}
|
||||
|
||||
impl<E: fmt::Display> fmt::Display for CreateWithPersistError<E> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Persist(err) => fmt::Display::fmt(err, f),
|
||||
Self::Descriptor(err) => fmt::Display::fmt(&err, f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
impl<E: fmt::Debug + fmt::Display> std::error::Error for CreateWithPersistError<E> {}
|
||||
@@ -69,7 +69,8 @@
|
||||
//!
|
||||
//! let descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/0/*)";
|
||||
//! let change_descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/1/*)";
|
||||
//! let mut wallet = Wallet::new(descriptor, change_descriptor, Network::Testnet)?;
|
||||
//! let mut wallet = CreateParams::new(descriptor, change_descriptor, Network::Testnet)?
|
||||
//! .create_wallet_no_persist()?;
|
||||
//! wallet.add_signer(
|
||||
//! KeychainKind::External,
|
||||
//! SignerOrdering(200),
|
||||
|
||||
Reference in New Issue
Block a user