Migrating from 0.X
So you're ready to migrate to BDK version 1.0, congratulations!
This document contains some helpful tips that, with the help of some automation, should make the process as seamless as possible.
The below steps are for migrating wallet details from the old bdk
v0.30 to the new bdk_wallet
v1.0.
This procedure can be applied to wallets backed by a SQLite database.
Of particular concern is the ability to restore the last known address index for each keychain.
This is important because without that metadata the new wallet may end up reusing receive addresses, which should be avoided for privacy reasons, although it should not cause loss of funds.
Tip
NB: The migration process outlined below will not automatically restore the wallet's transaction data or local view of the blockchain.
Thanks to the public ledger however, we can restore all the pertinent information for this wallet using one of the blockchain client libraries supported by BDK.
Overview
- Load an old database
- Get last revealed addresses
- Create new wallet
- Restore revealed addresses
- Write to new database
- Sync
examples/rust/migrate-version/src/main.rs |
---|
| fn main() -> anyhow::Result<()> {
// Open old wallet
let db = SqliteDatabase::new(BDK_DB_PATH);
let old_wallet = bdk::Wallet::new(
EXTERNAL_DESCRIPTOR,
Some(INTERNAL_DESCRIPTOR),
bdk::bitcoin::Network::Testnet,
db,
)?;
// Get last revealed addresses for each keychain
let addr = old_wallet.get_address(AddressIndex::LastUnused)?;
println!("Last revealed external {} {}", addr.index, addr.address);
let external_derivation_index = addr.index;
let last_revealed_external = addr.address.to_string();
let addr = old_wallet.get_internal_address(AddressIndex::LastUnused)?;
println!("Last revealed internal {} {}", addr.index, addr.address);
let internal_derivation_index = addr.index;
let last_revealed_internal = addr.address.to_string();
// Create new wallet
let mut db = rusqlite::Connection::open(BDK_WALLET_DB_PATH)?;
let mut new_wallet = Wallet::create(EXTERNAL_DESCRIPTOR, INTERNAL_DESCRIPTOR)
.network(NETWORK)
.create_wallet(&mut db)
.context("failed to create wallet")?;
// Retore revealed addresses
let _ = new_wallet.reveal_addresses_to(KeychainKind::External, external_derivation_index);
let _ = new_wallet.reveal_addresses_to(KeychainKind::Internal, internal_derivation_index);
// Persist new wallet
new_wallet.persist(&mut db)?;
println!("\n========== New database created. ==========");
let addr = new_wallet
.list_unused_addresses(KeychainKind::External)
.last()
.unwrap();
assert_eq!(addr.to_string(), last_revealed_external);
println!("Last revealed external {} {}", addr.index, addr.address);
let addr = new_wallet
.list_unused_addresses(KeychainKind::Internal)
.last()
.unwrap();
println!("Last revealed internal {} {}", addr.index, addr.address);
assert_eq!(addr.to_string(), last_revealed_internal);
Ok(())
}
|
Walkthrough
In a new rust project add these dependencies to Cargo.toml
Cargo.toml |
---|
| [dependencies]
anyhow = "1"
bdk = { version = "0.30", features = ["sqlite"] }
bdk_wallet = { version = "1.0.0", features = ["rusqlite"] }
|
Because there are two versions of bdk in the same project, we need to pay attention to how types are imported.
To avoid name clashes or any sort of mismatch resolving types that appear similar, we use fully qualified syntax, for example bdk::bitcoin::Network::Testnet
.
You'll notice in some cases we can get around this annoyance by casting a value to another rust primitive or standard library type such as String
.
examples/rust/migrate-version/src/main.rs |
---|
| use anyhow::Context;
use bdk::database::SqliteDatabase;
use bdk::wallet::AddressIndex;
use bdk_wallet::bitcoin::Network;
use bdk_wallet::rusqlite;
use bdk_wallet::KeychainKind;
use bdk_wallet::Wallet;
|
Take a minute to define a few constants, for example the file path to the current database and the path to be used for the new database.
The descriptors and network shown here are for illustration; you should substitute them with your own.
Note that because we'll be creating a fresh database there should not already exist a persisted wallet at the new path.
examples/rust/migrate-version/src/main.rs |
---|
| const EXTERNAL_DESCRIPTOR: &str = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/0/*)";
const INTERNAL_DESCRIPTOR: &str = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/1/*)";
const NETWORK: Network = Network::Testnet;
// path to old db
const BDK_DB_PATH: &str = "./.bdk-example.sqlite";
// path to new db
const BDK_WALLET_DB_PATH: &str = "./.bdk-wallet-example.sqlite";
|
Now retrieve the last revealed addresses from the old_wallet
.
examples/rust/migrate-version/src/main.rs |
---|
| // Open old wallet
let db = SqliteDatabase::new(BDK_DB_PATH);
let old_wallet = bdk::Wallet::new(
EXTERNAL_DESCRIPTOR,
Some(INTERNAL_DESCRIPTOR),
bdk::bitcoin::Network::Testnet,
db,
)?;
// Get last revealed addresses for each keychain
let addr = old_wallet.get_address(AddressIndex::LastUnused)?;
println!("Last revealed external {} {}", addr.index, addr.address);
let external_derivation_index = addr.index;
let last_revealed_external = addr.address.to_string();
let addr = old_wallet.get_internal_address(AddressIndex::LastUnused)?;
println!("Last revealed internal {} {}", addr.index, addr.address);
let internal_derivation_index = addr.index;
let last_revealed_internal = addr.address.to_string();
|
For the new_wallet
we should be using the same descriptors and network as before.
If the given descriptors contain secret keys, then the wallet will be able to sign transactions as well.
examples/rust/migrate-version/src/main.rs |
---|
| // Create new wallet
let mut db = rusqlite::Connection::open(BDK_WALLET_DB_PATH)?;
let mut new_wallet = Wallet::create(EXTERNAL_DESCRIPTOR, INTERNAL_DESCRIPTOR)
.network(NETWORK)
.create_wallet(&mut db)
.context("failed to create wallet")?;
// Retore revealed addresses
let _ = new_wallet.reveal_addresses_to(KeychainKind::External, external_derivation_index);
let _ = new_wallet.reveal_addresses_to(KeychainKind::Internal, internal_derivation_index);
// Persist new wallet
new_wallet.persist(&mut db)?;
println!("\n========== New database created. ==========");
let addr = new_wallet
.list_unused_addresses(KeychainKind::External)
.last()
.unwrap();
assert_eq!(addr.to_string(), last_revealed_external);
println!("Last revealed external {} {}", addr.index, addr.address);
let addr = new_wallet
.list_unused_addresses(KeychainKind::Internal)
.last()
.unwrap();
println!("Last revealed internal {} {}", addr.index, addr.address);
assert_eq!(addr.to_string(), last_revealed_internal);
|
Now that we have a new database and have properly restored our addresses, you will want to sync with the blockchain to recover the wallet's transactions.
Below is an example of doing a sync
using bdk_esplora
but the exact method of syncing will depend on your application.
Happy migrating and see you on v1.0!
examples/rust/migrate-version/src/main.rs |
---|
| use bdk_esplora::{esplora_client, EsploraExt};
let client = esplora_client::Builder::new(ESPLORA_URL).build_blocking();
let request = wallet
.start_sync_with_revealed_spks()
.inspect(|item, prog| {
if let SyncItem::Spk(index, script) = item {
let address = Address::from_script(script, NETWORK).unwrap();
let progress = prog.consumed() as f32 / prog.total() as f32;
eprintln!("[ SYNCING {:.2}% ] {:?} {}", 100.0 * progress, index, address);
std::io::stdout().flush().unwrap();
}
});
let update = client.sync(request, PARALLEL_REQUESTS)?;
wallet.apply_update(update)?;
wallet.persist(&mut db)?;
|