2022-10-12 14:31:17 +01:00
|
|
|
use bdk::bitcoin::{Address, Network};
|
|
|
|
use bdk::blockchain::{Blockchain, ElectrumBlockchain};
|
|
|
|
use bdk::database::MemoryDatabase;
|
|
|
|
use bdk::hwi::{types::HWIChain, HWIClient};
|
2022-12-19 10:50:59 +01:00
|
|
|
use bdk::miniscript::{Descriptor, DescriptorPublicKey};
|
2022-10-12 14:31:17 +01:00
|
|
|
use bdk::signer::SignerOrdering;
|
|
|
|
use bdk::wallet::{hardwaresigner::HWISigner, AddressIndex};
|
|
|
|
use bdk::{FeeRate, KeychainKind, SignOptions, SyncOptions, Wallet};
|
|
|
|
use electrum_client::Client;
|
|
|
|
use std::str::FromStr;
|
|
|
|
use std::sync::Arc;
|
|
|
|
|
|
|
|
// This example shows how to sync a wallet, create a transaction, sign it
|
|
|
|
// and broadcast it using an external hardware wallet.
|
|
|
|
// The hardware wallet must be connected to the computer and unlocked before
|
|
|
|
// running the example. Also, the `hwi` python package should be installed
|
|
|
|
// and available in the environment.
|
|
|
|
//
|
|
|
|
// To avoid loss of funds, consider using an hardware wallet simulator:
|
|
|
|
// * Coldcard: https://github.com/Coldcard/firmware
|
|
|
|
// * Ledger: https://github.com/LedgerHQ/speculos
|
|
|
|
// * Trezor: https://docs.trezor.io/trezor-firmware/core/emulator/index.html
|
|
|
|
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
|
|
println!("Hold tight, I'm connecting to your hardware wallet...");
|
|
|
|
|
|
|
|
// Listing all the available hardware wallet devices...
|
2022-12-19 10:50:59 +01:00
|
|
|
let mut devices = HWIClient::enumerate()?;
|
|
|
|
if devices.is_empty() {
|
|
|
|
panic!("No devices found. Either plug in a hardware wallet, or start a simulator.");
|
|
|
|
}
|
|
|
|
let first_device = devices.remove(0)?;
|
2022-10-12 14:31:17 +01:00
|
|
|
// ...and creating a client out of the first one
|
2022-12-19 10:50:59 +01:00
|
|
|
let client = HWIClient::get_client(&first_device, true, HWIChain::Test)?;
|
2022-10-12 14:31:17 +01:00
|
|
|
println!("Look what I found, a {}!", first_device.model);
|
|
|
|
|
|
|
|
// Getting the HW's public descriptors
|
2022-12-19 10:50:59 +01:00
|
|
|
let descriptors = client.get_descriptors::<Descriptor<DescriptorPublicKey>>(None)?;
|
2022-10-12 14:31:17 +01:00
|
|
|
println!(
|
|
|
|
"The hardware wallet's descriptor is: {}",
|
|
|
|
descriptors.receive[0]
|
|
|
|
);
|
|
|
|
|
|
|
|
// Creating a custom signer from the device
|
2022-12-19 10:50:59 +01:00
|
|
|
let custom_signer = HWISigner::from_device(&first_device, HWIChain::Test)?;
|
2022-10-12 14:31:17 +01:00
|
|
|
let mut wallet = Wallet::new(
|
2022-12-19 10:50:59 +01:00
|
|
|
descriptors.receive[0].clone(),
|
|
|
|
Some(descriptors.internal[0].clone()),
|
2022-10-12 14:31:17 +01:00
|
|
|
Network::Testnet,
|
|
|
|
MemoryDatabase::default(),
|
|
|
|
)?;
|
|
|
|
|
|
|
|
// Adding the hardware signer to the BDK wallet
|
|
|
|
wallet.add_signer(
|
|
|
|
KeychainKind::External,
|
|
|
|
SignerOrdering(200),
|
|
|
|
Arc::new(custom_signer),
|
|
|
|
);
|
|
|
|
|
|
|
|
// create client for Blockstream's testnet electrum server
|
|
|
|
let blockchain =
|
|
|
|
ElectrumBlockchain::from(Client::new("ssl://electrum.blockstream.info:60002")?);
|
|
|
|
|
|
|
|
println!("Syncing the wallet...");
|
|
|
|
wallet.sync(&blockchain, SyncOptions::default())?;
|
|
|
|
|
|
|
|
// get deposit address
|
|
|
|
let deposit_address = wallet.get_address(AddressIndex::New)?;
|
|
|
|
|
|
|
|
let balance = wallet.get_balance()?;
|
|
|
|
println!("Wallet balances in SATs: {}", balance);
|
|
|
|
|
|
|
|
if balance.get_total() < 10000 {
|
|
|
|
println!(
|
|
|
|
"Send some sats from the u01.net testnet faucet to address '{addr}'.\nFaucet URL: https://bitcoinfaucet.uo1.net/?to={addr}",
|
|
|
|
addr = deposit_address.address
|
|
|
|
);
|
|
|
|
return Ok(());
|
|
|
|
}
|
|
|
|
|
|
|
|
let return_address = Address::from_str("tb1ql7w62elx9ucw4pj5lgw4l028hmuw80sndtntxt")?;
|
|
|
|
let (mut psbt, _details) = {
|
|
|
|
let mut builder = wallet.build_tx();
|
|
|
|
builder
|
|
|
|
.drain_wallet()
|
|
|
|
.drain_to(return_address.script_pubkey())
|
|
|
|
.enable_rbf()
|
|
|
|
.fee_rate(FeeRate::from_sat_per_vb(5.0));
|
|
|
|
builder.finish()?
|
|
|
|
};
|
|
|
|
|
|
|
|
// `sign` will call the hardware wallet asking for a signature
|
|
|
|
assert!(
|
|
|
|
wallet.sign(&mut psbt, SignOptions::default())?,
|
|
|
|
"The hardware wallet couldn't finalize the transaction :("
|
|
|
|
);
|
|
|
|
|
|
|
|
println!("Let's broadcast your tx...");
|
|
|
|
let raw_transaction = psbt.extract_tx();
|
|
|
|
let txid = raw_transaction.txid();
|
|
|
|
|
|
|
|
blockchain.broadcast(&raw_transaction)?;
|
|
|
|
println!("Transaction broadcasted! TXID: {txid}.\nExplorer URL: https://mempool.space/testnet/tx/{txid}", txid = txid);
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|