Skip to content

Commit 14cb771

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 14cb771

File tree

2 files changed

+235
-0
lines changed

2 files changed

+235
-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: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
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!("Available initial coins in BDK wallet : {} sats", wallet.get_balance()?);
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!("Available balance in BDK wallet: {} sats", wallet.get_balance()?);
165+
166+
println!("\n>> Sending coins: BDK --> Core, 5 BTC");
167+
// Attempt to send back 5.0 BTC to core address by creating a transaction
168+
//
169+
// Transactions are created using a `TxBuilder`.
170+
// This helps us to systematically build a transaction with all
171+
// required customization.
172+
// A full list of APIs offered by `TxBuilder` can be found at
173+
// https://docs.rs/bdk/latest/bdk/wallet/tx_builder/struct.TxBuilder.html
174+
let mut tx_builder = wallet.build_tx();
175+
176+
// For a regular transaction, just set the recipient and amount
177+
tx_builder.set_recipients(vec![(core_address.script_pubkey(), 500000000)]);
178+
179+
// Finalize the transaction and extract the PSBT
180+
let (mut psbt, _) = tx_builder.finish()?;
181+
182+
// Set signing option
183+
let signopt = SignOptions {
184+
assume_height: None,
185+
..Default::default()
186+
};
187+
188+
// Sign the psbt
189+
wallet.sign(&mut psbt, signopt)?;
190+
191+
// Extract the signed transaction
192+
let tx = psbt.extract_tx();
193+
194+
// Broadcast the transaction
195+
blockchain.broadcast(&tx)?;
196+
197+
// Confirm transaction by generating some blocks
198+
bitcoind.client.generate_to_address(1, &core_address)?;
199+
200+
// Sync the BDK wallet
201+
wallet.sync(&blockchain, SyncOptions::default())?;
202+
203+
println!(">> Coins sent to Core wallet");
204+
println!(
205+
"Remaining BDK wallet balance: {} sats",
206+
wallet.get_balance()?
207+
);
208+
println!("\nCongrats!! you made your first test transaction with bdk and bitcoin core.");
209+
210+
Ok(())
211+
}
212+
213+
// Helper function demonstrating privatekey extraction using bip39 mnemonic
214+
// The mnemonic can be shown to user to safekeeping and the same wallet
215+
// private descriptors can be recreated from it.
216+
fn generate_random_ext_privkey() -> Result<ExtendedPrivKey, Box<dyn Error>> {
217+
// a Bip39 passphrase can be set optionally
218+
let password = Some("random password".to_string());
219+
220+
// Generate a random mnemonic, and use that to create an Extended PrivateKey
221+
let mnemonic: GeneratedKey<_, Segwitv0> =
222+
Mnemonic::generate((WordCount::Words12, Language::English))
223+
.map_err(|e| e.expect("Unknown Error"))?;
224+
let mnemonic = mnemonic.into_key();
225+
let xkey: ExtendedKey = (mnemonic, password).into_extended_key()?;
226+
let xprv = xkey
227+
.into_xprv(Network::Regtest)
228+
.expect("Expected Private Key");
229+
Ok(xprv)
230+
}

0 commit comments

Comments
 (0)