Skip to content

Commit 8a98e69

Browse files
Add rpc wallet creation example
This adds an example wallet creation code with sled DB and RPC Blockchain. The backend RPC is managed using electrsd::bitcoind
1 parent 3334c8d commit 8a98e69

File tree

2 files changed

+238
-0
lines changed

2 files changed

+238
-0
lines changed

Cargo.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,11 @@ name = "miniscriptc"
109109
path = "examples/compiler.rs"
110110
required-features = ["compiler"]
111111

112+
[[example]]
113+
name = "rpcwallet"
114+
path = "examples/rpcwallet.rs"
115+
required-features = ["keys-bip39", "key-value-db", "rpc"]
116+
112117
[workspace]
113118
members = ["macros"]
114119
[package.metadata.docs.rs]

examples/rpcwallet.rs

Lines changed: 233 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
1+
// Copyright (c) 2020-2021 Bitcoin Dev Kit Developers
2+
//
3+
// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
4+
// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
5+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
6+
// You may not use this file except in accordance with one or both of these
7+
// licenses.
8+
9+
use bdk::bitcoin::secp256k1::Secp256k1;
10+
use bdk::bitcoin::util::bip32::ExtendedPrivKey;
11+
use bdk::bitcoin::Amount;
12+
use bdk::bitcoin::Network;
13+
use bdk::bitcoincore_rpc::RpcApi;
14+
15+
use bdk::blockchain::rpc::{Auth, RpcBlockchain, RpcConfig};
16+
use bdk::blockchain::ConfigurableBlockchain;
17+
18+
use bdk::keys::bip39::{Language, Mnemonic, WordCount};
19+
use bdk::keys::{DerivableKey, ExtendedKey, GeneratableKey, GeneratedKey};
20+
21+
use bdk::miniscript::miniscript::Segwitv0;
22+
23+
use bdk::sled;
24+
use bdk::template::Bip84;
25+
use bdk::wallet::{signer::SignOptions, wallet_name_from_descriptor, AddressIndex, SyncOptions};
26+
use bdk::KeychainKind;
27+
use bdk::Wallet;
28+
29+
use bdk::blockchain::Blockchain;
30+
31+
use electrsd;
32+
33+
use std::error::Error;
34+
use std::path::PathBuf;
35+
use std::str::FromStr;
36+
37+
/// This example demonstrates a typical way to create a wallet and work with bdk.
38+
///
39+
/// This example bdk wallet is connected to a bitcoin core rpc regtest node,
40+
/// and will attempt to receive, create and broadcast transactions.
41+
///
42+
/// To start a bitcoind regtest node programmatically, this example uses
43+
/// `electrsd` library, which is also a bdk dev-dependency.
44+
///
45+
/// But you can start your own bitcoind backend, and the rest of the example should work fine.
46+
47+
fn main() -> Result<(), Box<dyn Error>> {
48+
// -- Setting up background bitcoind process
49+
50+
println!(">> Setting up bitcoind");
51+
52+
// Start the bitcoind process
53+
let bitcoind_conf = electrsd::bitcoind::Conf::default();
54+
55+
// electrsd will automatically download the bitcoin core binaries
56+
let bitcoind_exe =
57+
electrsd::bitcoind::downloaded_exe_path().expect("We should always have downloaded path");
58+
59+
// Launch bitcoind and gather authentication access
60+
let bitcoind = electrsd::bitcoind::BitcoinD::with_conf(bitcoind_exe, &bitcoind_conf).unwrap();
61+
let bitcoind_auth = Auth::Cookie {
62+
file: bitcoind.params.cookie_file.clone(),
63+
};
64+
65+
// Get a new core address
66+
let core_address = bitcoind.client.get_new_address(None, None)?;
67+
68+
// Generate 101 blocks and use the above address as coinbase
69+
bitcoind.client.generate_to_address(101, &core_address)?;
70+
71+
println!(">> bitcoind setup complete");
72+
println!(
73+
"Available coins in Core wallet : {}",
74+
bitcoind.client.get_balance(None, None)?
75+
);
76+
77+
// -- Setting up the Wallet
78+
79+
println!("\n>> Setting up BDK wallet");
80+
81+
// Get a random private key
82+
let xprv = generate_random_ext_privkey()?;
83+
84+
// Use the derived descriptors from the privatekey to
85+
// create unique wallet name.
86+
// This is a special utility function exposed via `bdk::wallet_name_from_descriptor()`
87+
let wallet_name = wallet_name_from_descriptor(
88+
Bip84(xprv, KeychainKind::External),
89+
Some(Bip84(xprv, KeychainKind::Internal)),
90+
Network::Regtest,
91+
&Secp256k1::new(),
92+
)?;
93+
94+
// Create a database (using default sled type) to store wallet data
95+
let mut datadir = PathBuf::from_str("/tmp/")?;
96+
datadir.push(".bdk-example");
97+
let database = sled::open(datadir)?;
98+
let database = database.open_tree(wallet_name.clone())?;
99+
100+
// Create a RPC configuration of the running bitcoind backend we created in last step
101+
// Note: If you are using custom regtest node, use the appropriate url and auth
102+
let rpc_config = RpcConfig {
103+
url: bitcoind.params.rpc_socket.to_string(),
104+
auth: bitcoind_auth,
105+
network: Network::Regtest,
106+
wallet_name,
107+
skip_blocks: None,
108+
};
109+
110+
// Use the above configuration to create a RPC blockchain backend
111+
let blockchain = RpcBlockchain::from_config(&rpc_config)?;
112+
113+
// Combine Database + Descriptor to create the final wallet
114+
let wallet = Wallet::new(
115+
Bip84(xprv, KeychainKind::External),
116+
Some(Bip84(xprv, KeychainKind::Internal)),
117+
Network::Regtest,
118+
database,
119+
)?;
120+
121+
// The `wallet` and the `blockchain` are independent structs.
122+
// The wallet will be used to do all wallet level actions
123+
// The blockchain can be used to do all blockchain level actions.
124+
// For certain actions (like sync) the wallet will ask for a blockchain.
125+
126+
// Sync the wallet
127+
// The first sync is important as this will instantiate the
128+
// wallet files.
129+
wallet.sync(&blockchain, SyncOptions::default())?;
130+
131+
println!(">> BDK wallet setup complete.");
132+
println!(
133+
"Available initial coins in BDK wallet : {} sats",
134+
wallet.get_balance()?
135+
);
136+
137+
// -- Wallet transaction demonstration
138+
139+
println!("\n>> Sending coins: Core --> BDK, 10 BTC");
140+
// Get a new address to receive coins
141+
let bdk_new_addr = wallet.get_address(AddressIndex::New)?.address;
142+
143+
// Send 10 BTC from core wallet to bdk wallet
144+
bitcoind.client.send_to_address(
145+
&bdk_new_addr,
146+
Amount::from_btc(10.0)?,
147+
None,
148+
None,
149+
None,
150+
None,
151+
None,
152+
None,
153+
)?;
154+
155+
// Confirm transaction by generating 1 block
156+
bitcoind.client.generate_to_address(1, &core_address)?;
157+
158+
// Sync the BDK wallet
159+
// This time the sync will fetch the new transaction and update it in
160+
// wallet database
161+
wallet.sync(&blockchain, SyncOptions::default())?;
162+
163+
println!(">> Received coins in BDK wallet");
164+
println!(
165+
"Available balance in BDK wallet: {} sats",
166+
wallet.get_balance()?
167+
);
168+
169+
println!("\n>> Sending coins: BDK --> Core, 5 BTC");
170+
// Attempt to send back 5.0 BTC to core address by creating a transaction
171+
//
172+
// Transactions are created using a `TxBuilder`.
173+
// This helps us to systematically build a transaction with all
174+
// required customization.
175+
// A full list of APIs offered by `TxBuilder` can be found at
176+
// https://docs.rs/bdk/latest/bdk/wallet/tx_builder/struct.TxBuilder.html
177+
let mut tx_builder = wallet.build_tx();
178+
179+
// For a regular transaction, just set the recipient and amount
180+
tx_builder.set_recipients(vec![(core_address.script_pubkey(), 500000000)]);
181+
182+
// Finalize the transaction and extract the PSBT
183+
let (mut psbt, _) = tx_builder.finish()?;
184+
185+
// Set signing option
186+
let signopt = SignOptions {
187+
assume_height: None,
188+
..Default::default()
189+
};
190+
191+
// Sign the psbt
192+
wallet.sign(&mut psbt, signopt)?;
193+
194+
// Extract the signed transaction
195+
let tx = psbt.extract_tx();
196+
197+
// Broadcast the transaction
198+
blockchain.broadcast(&tx)?;
199+
200+
// Confirm transaction by generating some blocks
201+
bitcoind.client.generate_to_address(1, &core_address)?;
202+
203+
// Sync the BDK wallet
204+
wallet.sync(&blockchain, SyncOptions::default())?;
205+
206+
println!(">> Coins sent to Core wallet");
207+
println!(
208+
"Remaining BDK wallet balance: {} sats",
209+
wallet.get_balance()?
210+
);
211+
println!("\nCongrats!! you made your first test transaction with bdk and bitcoin core.");
212+
213+
Ok(())
214+
}
215+
216+
// Helper function demonstrating privatekey extraction using bip39 mnemonic
217+
// The mnemonic can be shown to user to safekeeping and the same wallet
218+
// private descriptors can be recreated from it.
219+
fn generate_random_ext_privkey() -> Result<ExtendedPrivKey, Box<dyn Error>> {
220+
// a Bip39 passphrase can be set optionally
221+
let password = Some("random password".to_string());
222+
223+
// Generate a random mnemonic, and use that to create an Extended PrivateKey
224+
let mnemonic: GeneratedKey<_, Segwitv0> =
225+
Mnemonic::generate((WordCount::Words12, Language::English))
226+
.map_err(|e| e.expect("Unknown Error"))?;
227+
let mnemonic = mnemonic.into_key();
228+
let xkey: ExtendedKey = (mnemonic, password).into_extended_key()?;
229+
let xprv = xkey
230+
.into_xprv(Network::Regtest)
231+
.expect("Expected Private Key");
232+
Ok(xprv)
233+
}

0 commit comments

Comments
 (0)