Skip to content

Commit a722c52

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 a722c52

File tree

2 files changed

+241
-0
lines changed

2 files changed

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

0 commit comments

Comments
 (0)