Skip to content

Commit 0aa3d65

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 fdb272e commit 0aa3d65

File tree

2 files changed

+233
-0
lines changed

2 files changed

+233
-0
lines changed

Cargo.toml

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

110+
[[example]]
111+
name = "rpcwallet"
112+
path = "example/rpcwallet"
113+
required-features = ["all-keys", "key-value-db", "rpc"]
114+
110115
[workspace]
111116
members = ["macros"]
112117
[package.metadata.docs.rs]

examples/rpwallet.rs

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

0 commit comments

Comments
 (0)