Skip to content

Commit 1437e1e

Browse files
Add the hardware_signer example
1 parent 1a71eb1 commit 1437e1e

File tree

2 files changed

+108
-0
lines changed

2 files changed

+108
-0
lines changed

Cargo.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,11 @@ name = "psbt_signer"
126126
path = "examples/psbt_signer.rs"
127127
required-features = ["electrum"]
128128

129+
[[example]]
130+
name = "hardware_signer"
131+
path = "examples/hardware_signer.rs"
132+
required-features = ["electrum", "hardware-signer"]
133+
129134
[workspace]
130135
members = ["macros"]
131136
[package.metadata.docs.rs]

examples/hardware_signer.rs

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
use bdk::bitcoin::{Address, Network};
2+
use bdk::blockchain::{Blockchain, ElectrumBlockchain};
3+
use bdk::database::MemoryDatabase;
4+
use bdk::hwi::{types::HWIChain, HWIClient};
5+
use bdk::signer::SignerOrdering;
6+
use bdk::wallet::{hardwaresigner::HWISigner, AddressIndex};
7+
use bdk::{FeeRate, KeychainKind, SignOptions, SyncOptions, Wallet};
8+
use electrum_client::Client;
9+
use std::str::FromStr;
10+
use std::sync::Arc;
11+
12+
// This example shows how to sync a wallet, create a transaction, sign it
13+
// and broadcast it using an external hardware wallet.
14+
// The hardware wallet must be connected to the computer and unlocked before
15+
// running the example. Also, the `hwi` python package should be installed
16+
// and available in the environment.
17+
//
18+
// To avoid loss of funds, consider using an hardware wallet simulator:
19+
// * Coldcard: https://github.com/Coldcard/firmware
20+
// * Ledger: https://github.com/LedgerHQ/speculos
21+
// * Trezor: https://docs.trezor.io/trezor-firmware/core/emulator/index.html
22+
fn main() -> Result<(), Box<dyn std::error::Error>> {
23+
println!("Hold tight, I'm connecting to your hardware wallet...");
24+
25+
// Listing all the available hardware wallet devices...
26+
let devices = HWIClient::enumerate()?;
27+
let first_device = devices
28+
.first()
29+
.expect("No devices found. Either plug in a hardware wallet, or start a simulator.");
30+
// ...and creating a client out of the first one
31+
let client = HWIClient::get_client(first_device, true, HWIChain::Test)?;
32+
println!("Look what I found, a {}!", first_device.model);
33+
34+
// Getting the HW's public descriptors
35+
let descriptors = client.get_descriptors(None)?;
36+
println!(
37+
"The hardware wallet's descriptor is: {}",
38+
descriptors.receive[0]
39+
);
40+
41+
// Creating a custom signer from the device
42+
let custom_signer = HWISigner::from_device(first_device, HWIChain::Test)?;
43+
let mut wallet = Wallet::new(
44+
&descriptors.receive[0],
45+
Some(&descriptors.internal[0]),
46+
Network::Testnet,
47+
MemoryDatabase::default(),
48+
)?;
49+
50+
// Adding the hardware signer to the BDK wallet
51+
wallet.add_signer(
52+
KeychainKind::External,
53+
SignerOrdering(200),
54+
Arc::new(custom_signer),
55+
);
56+
57+
// create client for Blockstream's testnet electrum server
58+
let blockchain =
59+
ElectrumBlockchain::from(Client::new("ssl://electrum.blockstream.info:60002")?);
60+
61+
println!("Syncing the wallet...");
62+
wallet.sync(&blockchain, SyncOptions::default())?;
63+
64+
// get deposit address
65+
let deposit_address = wallet.get_address(AddressIndex::New)?;
66+
67+
let balance = wallet.get_balance()?;
68+
println!("Wallet balances in SATs: {}", balance);
69+
70+
if balance.get_total() < 10000 {
71+
println!(
72+
"Send some sats from the u01.net testnet faucet to address '{addr}'.\nFaucet URL: https://bitcoinfaucet.uo1.net/?to={addr}",
73+
addr = deposit_address.address
74+
);
75+
return Ok(());
76+
}
77+
78+
let return_address = Address::from_str("tb1ql7w62elx9ucw4pj5lgw4l028hmuw80sndtntxt")?;
79+
let (mut psbt, _details) = {
80+
let mut builder = wallet.build_tx();
81+
builder
82+
.drain_wallet()
83+
.drain_to(return_address.script_pubkey())
84+
.enable_rbf()
85+
.fee_rate(FeeRate::from_sat_per_vb(5.0));
86+
builder.finish()?
87+
};
88+
89+
// `sign` will call the hardware wallet asking for a signature
90+
assert!(
91+
wallet.sign(&mut psbt, SignOptions::default())?,
92+
"The hardware wallet couldn't finalize the transaction :("
93+
);
94+
95+
println!("Let's broadcast your tx...");
96+
let raw_transaction = psbt.extract_tx();
97+
let txid = raw_transaction.txid();
98+
99+
blockchain.broadcast(&raw_transaction)?;
100+
println!("Transaction broadcasted! TXID: {txid}.\nExplorer URL: https://mempool.space/testnet/tx/{txid}", txid = txid);
101+
102+
Ok(())
103+
}

0 commit comments

Comments
 (0)