Welcome to the BDK Wallet Exercise, a hands-on component of Advanced Bitcoin Development with Core & Libraries. This exercise leverages the Bitcoin Development Kit (BDK) to construct a functional wallet module, utilizing its HD wallet capabilities (BIP32/84), SPV blockchain synchronization, and descriptor-based scripts. You’ll implement this in either Python (via bdkpython
) or JavaScript (via @bitcoinerlab/bdk
), targeting Bitcoin’s testnet. The goal is to deepen your understanding of BDK’s application in real-world wallet development while engaging with live blockchain data.
- Initialize an HD wallet with a mnemonic seed and BIP84 (SegWit) derivation.
- Generate receiving addresses and sync wallet state with testnet using SPV.
- Retrieve and display the wallet’s balance, interacting with live testnet transactions.
- Explore BDK’s descriptor-based approach to script management.
- Python: Version 3.8+ with
pip
. - JavaScript: Node.js 16+ with
npm
. - Network: Access to a testnet Electrum server (e.g.,
ssl://electrum.blockstream.info:60002
). - Tools: Git for repository management.
Configure your development environment to use BDK.
- Install dependencies:
pip install bdkpython
(includesbdkpython
). - Verify: Ensure
wallet.py
is present in the repository.
- Install dependencies:
npm install
(installs@bitcoinerlab/bdk
perpackage.json
). - Verify: Ensure
wallet.js
is present.
Execute from the repository root:
# Python
pip install bdkpython
# JavaScript
npm install
Generate a 12-word mnemonic seed to seed your HD wallet.
import bdkpython as bdk
print("Generating mnemonic seed...")
mnemonic = bdk.Mnemonic(word_count=12)
print(f"Mnemonic: {mnemonic}")
const { mnemonic } = require('@bitcoinerlab/bdk');
console.log("Generating mnemonic seed...");
const mn = mnemonic.generate(12);
console.log(`Mnemonic: ${mn}`);
- Python:
python wallet.py
- JavaScript:
node wallet.js
A 12-word mnemonic (e.g., "abandon ability ...").
Note: Securely store this mnemonic; it’s the key to your wallet’s private keys.
Derive three SegWit (BIP84) addresses for receiving testnet funds.
descriptor = bdk.Descriptor(f"wpkh({mnemonic.to_seed_normalized('')[0:64]}/84'/1'/0'/0/*)", bdk.Network.TESTNET)
change_descriptor = bdk.Descriptor(f"wpkh({mnemonic.to_seed_normalized('')[0:64]}/84'/1'/0'/1/*)", bdk.Network.TESTNET)
wallet = bdk.Wallet(descriptor, change_descriptor, bdk.Network.TESTNET, bdk.MemoryDatabase())
print("\nGenerated Addresses:")
for i in range(3):
address = wallet.get_address(bdk.AddressIndex.NEW).address
print(f"Address {i+1}: {address}")
const { Descriptor, Wallet, Network, MemoryDatabase } = require('@bitcoinerlab/bdk');
const descriptor = new Descriptor(`wpkh(${mn.toSeed('').slice(0, 64)}/84'/1'/0'/0/*)`, Network.TESTNET);
const changeDescriptor = new Descriptor(`wpkh(${mn.toSeed('').slice(0, 64)}/84'/1'/0'/1/*)`, Network.TESTNET);
const wallet = new Wallet(descriptor, changeDescriptor, Network.TESTNET, new MemoryDatabase());
console.log("\nGenerated Addresses:");
for (let i = 0; i < 3; i++) {
const address = wallet.getAddress('new').address;
console.log(`Address ${i+1}: ${address}`);
}
Run as in Step 2.
Three testnet SegWit addresses (e.g., tb1q...
).
Note: These addresses follow BIP84 (
m/84'/1'/0'
), optimized for testnet.
Implement blockchain synchronization to retrieve the wallet’s balance.
Add code to connect to an Electrum server and sync the wallet state:
blockchain = bdk.ElectrumBlockchain(config={"url": "ssl://electrum.blockstream.info:60002"})
print("\nSynchronizing with testnet...")
wallet.sync(blockchain)
balance = wallet.get_balance()
print(f"Balance: {balance.total} satoshis ({balance.total / 100_000_000:.8f} BTC)")
const { ElectrumBlockchain } = require('@bitcoinerlab/bdk');
const blockchain = new ElectrumBlockchain({ url: "ssl://electrum.blockstream.info:60002" });
console.log("\nSynchronizing with testnet...");
wallet.sync(blockchain);
const balance = wallet.getBalance();
console.log(`Balance: ${balance.total} satoshis (${balance.total / 100000000} BTC)`);
Re-run the script after adding this code.
Initial balance (likely 0 satoshis
until funded).
Note: Uses SPV for lightweight synchronization; no full node required.
Simulate real-world usage by funding your wallet with testnet BTC.
- Select one of your generated addresses.
- Request testnet coins from a faucet (e.g., testnet-faucet.com) or a peer.
- Wait briefly (1-2 minutes) for the transaction to propagate.
- Re-run the script to update the balance.
Updated balance (e.g., “Balance: 50000 satoshis”).
Note: Testnet faucets may have rate limits; coordinate with peers if needed.
Add logic to confirm the wallet has been funded.
Append a status check after the balance:
if balance.total > 0:
print("Funding confirmed—wallet is active.")
else:
print("No funds detected—continue funding.")
if (balance.total > 0) {
console.log("Funding confirmed—wallet is active.");
} else {
console.log("No funds detected—continue funding.");
}
Re-run after funding.
Confirmation message based on balance.
- Transaction Building: Implement a send function using
wallet.buildTx()
to transfer funds. - Alternative Derivation: Experiment with BIP44 (P2PKH) by modifying the descriptor (e.g.,
pkh(...)
). - Transaction History: Retrieve transaction details with
wallet.listTransactions()
.
- Commit your work:
git add . && git commit -m "Completed BDK wallet exercise" && git push
- Verify functionality in class or via a pull request.
- BDK Documentation: BDK
- Testnet Faucet: testnet-faucet.com
- Support: Consult the instructor or reference BDK’s API docs.