Skip to content

Commit 7120eaf

Browse files
feat: implement RPC wallet example
1 parent b69c61f commit 7120eaf

File tree

2 files changed

+135
-2
lines changed

2 files changed

+135
-2
lines changed

example-crates/wallet_rpc/Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,7 @@ edition = "2021"
66
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
77

88
[dependencies]
9+
bdk = { path = "../../crates/bdk" }
10+
bdk_bitcoind_rpc = { path = "../../crates/bitcoind_rpc" }
11+
bdk_file_store = { path = "../../crates/file_store" }
12+
anyhow = "1"
Lines changed: 131 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,132 @@
1-
fn main() {
2-
println!("Hello, world!");
1+
use bdk::{
2+
bitcoin::{ Network, Address },
3+
chain::{indexed_tx_graph, local_chain, Append},
4+
wallet::{AddressIndex, WalletChangeSet},
5+
Wallet,
6+
SignOptions,
7+
};
8+
use bdk_bitcoind_rpc::{
9+
bitcoincore_rpc::{Auth, Client, RpcApi},
10+
EmittedUpdate, Emitter,
11+
};
12+
use bdk_file_store::Store;
13+
use std::sync::mpsc::sync_channel;
14+
use std::str::FromStr;
15+
16+
const DB_MAGIC: &str = "bdk-rpc-example";
17+
const FALLBACK_HEIGHT: u32 = 2476300;
18+
const CHANNEL_BOUND: usize = 100;
19+
const SEND_AMOUNT: u64 = 5000;
20+
21+
fn main() -> Result<(), Box<dyn std::error::Error>> {
22+
let db_path = std::env::temp_dir().join("bdk-rpc-example");
23+
let db = Store::<bdk::wallet::ChangeSet>::new_from_path(DB_MAGIC.as_bytes(), db_path)?;
24+
let external_descriptor = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/0/*)";
25+
let internal_descriptor = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/1/*)";
26+
27+
let mut wallet = Wallet::new(
28+
external_descriptor,
29+
Some(internal_descriptor),
30+
db,
31+
Network::Testnet,
32+
)?;
33+
34+
let address = wallet.get_address(AddressIndex::New);
35+
println!("Generated Address: {}", address);
36+
37+
let balance = wallet.get_balance();
38+
println!("Wallet balance before syncing: {} sats", balance.total());
39+
40+
41+
print!("Syncing...");
42+
43+
let client = Client::new(
44+
"127.0.0.1:18332",
45+
Auth::UserPass("bitcoin".to_string(), "password".to_string()),
46+
)?;
47+
48+
println!("Connected to Bitcoin Core RPC at {:?}", client.get_blockchain_info().unwrap());
49+
50+
wallet.mut_indexed_tx_graph().index.set_lookahead_for_all(20);
51+
52+
let (chan, recv) = sync_channel::<(EmittedUpdate, u32)>(CHANNEL_BOUND);
53+
let prev_cp = wallet.latest_checkpoint();
54+
55+
let join_handle = std::thread::spawn(move || -> anyhow::Result<()> {
56+
let mut tip_height = Option::<u32>::None;
57+
58+
let mut emitter = Emitter::new(&client, FALLBACK_HEIGHT, prev_cp);
59+
loop {
60+
let item = emitter.emit_update()?;
61+
let is_mempool = item.is_mempool();
62+
63+
if tip_height.is_none() || is_mempool {
64+
tip_height = Some(client.get_block_count()? as u32);
65+
}
66+
chan.send((item, tip_height.expect("must have tip height")))?;
67+
68+
if !is_mempool {
69+
continue;
70+
} else {
71+
break;
72+
}
73+
}
74+
75+
Ok(())
76+
});
77+
78+
for (item, _) in recv {
79+
let chain_update = item.chain_update();
80+
81+
let mut indexed_changeset = indexed_tx_graph::ChangeSet::default();
82+
83+
let graph_update = item.indexed_tx_graph_update(bdk_bitcoind_rpc::confirmation_time_anchor);
84+
indexed_changeset.append(wallet.mut_indexed_tx_graph().insert_relevant_txs(graph_update));
85+
86+
let chain_changeset = match chain_update {
87+
Some(update) => wallet.mut_local_chain().apply_update(update)?,
88+
None => local_chain::ChangeSet::default(),
89+
};
90+
91+
let mut wallet_changeset = WalletChangeSet::from(chain_changeset);
92+
wallet_changeset.append(WalletChangeSet::from(indexed_changeset));
93+
wallet.stage(wallet_changeset);
94+
println!("scanning ...");
95+
wallet.commit()?;
96+
}
97+
98+
let _ = join_handle.join().expect("failed to join chain source thread");
99+
100+
let balance = wallet.get_balance();
101+
println!("Wallet balance after syncing: {} sats", balance.total());
102+
103+
if balance.total() < SEND_AMOUNT {
104+
println!(
105+
"Please send at least {} sats to the receiving address",
106+
SEND_AMOUNT
107+
);
108+
std::process::exit(0);
109+
}
110+
111+
let faucet_address = Address::from_str("tb1qw2c3lxufxqe2x9s4rdzh65tpf4d7fssjgh8nv6")?
112+
.require_network(Network::Testnet)?;
113+
114+
let mut tx_builder = wallet.build_tx();
115+
tx_builder
116+
.add_recipient(faucet_address.script_pubkey(), SEND_AMOUNT)
117+
.enable_rbf();
118+
119+
let (mut psbt, _) = tx_builder.finish()?;
120+
let finalized = wallet.sign(&mut psbt, SignOptions::default())?;
121+
assert!(finalized);
122+
123+
let tx = psbt.extract_tx();
124+
let client = Client::new(
125+
"127.0.0.1:18332",
126+
Auth::UserPass("bitcoin".to_string(), "password".to_string()),
127+
)?;
128+
client.send_raw_transaction(&tx)?;
129+
println!("Tx broadcasted! Txid: {}", tx.txid());
130+
131+
Ok(())
3132
}

0 commit comments

Comments
 (0)