diff --git a/Cargo.toml b/Cargo.toml index 49fc8a225..9763e98a9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -109,6 +109,11 @@ name = "miniscriptc" path = "examples/compiler.rs" required-features = ["compiler"] +[[example]] +name = "rpcwallet" +path = "examples/rpcwallet.rs" +required-features = ["keys-bip39", "key-value-db", "rpc"] + [workspace] members = ["macros"] [package.metadata.docs.rs] diff --git a/examples/rpcwallet.rs b/examples/rpcwallet.rs new file mode 100644 index 000000000..0263ef78b --- /dev/null +++ b/examples/rpcwallet.rs @@ -0,0 +1,233 @@ +// Copyright (c) 2020-2021 Bitcoin Dev Kit Developers +// +// This file is licensed under the Apache License, Version 2.0 or the MIT license +// , at your option. +// You may not use this file except in accordance with one or both of these +// licenses. + +use bdk::bitcoin::secp256k1::Secp256k1; +use bdk::bitcoin::util::bip32::ExtendedPrivKey; +use bdk::bitcoin::Amount; +use bdk::bitcoin::Network; +use bdk::bitcoincore_rpc::RpcApi; + +use bdk::blockchain::rpc::{Auth, RpcBlockchain, RpcConfig}; +use bdk::blockchain::ConfigurableBlockchain; + +use bdk::keys::bip39::{Language, Mnemonic, WordCount}; +use bdk::keys::{DerivableKey, ExtendedKey, GeneratableKey, GeneratedKey}; + +use bdk::miniscript::miniscript::Segwitv0; + +use bdk::sled; +use bdk::template::Bip84; +use bdk::wallet::{signer::SignOptions, wallet_name_from_descriptor, AddressIndex, SyncOptions}; +use bdk::KeychainKind; +use bdk::Wallet; + +use bdk::blockchain::Blockchain; + +use electrsd; + +use std::error::Error; +use std::path::PathBuf; +use std::str::FromStr; + +/// This example demonstrates a typical way to create a wallet and work with bdk. +/// +/// This example bdk wallet is connected to a bitcoin core rpc regtest node, +/// and will attempt to receive, create and broadcast transactions. +/// +/// To start a bitcoind regtest node programmatically, this example uses +/// `electrsd` library, which is also a bdk dev-dependency. +/// +/// But you can start your own bitcoind backend, and the rest of the example should work fine. + +fn main() -> Result<(), Box> { + // -- Setting up background bitcoind process + + println!(">> Setting up bitcoind"); + + // Start the bitcoind process + let bitcoind_conf = electrsd::bitcoind::Conf::default(); + + // electrsd will automatically download the bitcoin core binaries + let bitcoind_exe = + electrsd::bitcoind::downloaded_exe_path().expect("We should always have downloaded path"); + + // Launch bitcoind and gather authentication access + let bitcoind = electrsd::bitcoind::BitcoinD::with_conf(bitcoind_exe, &bitcoind_conf).unwrap(); + let bitcoind_auth = Auth::Cookie { + file: bitcoind.params.cookie_file.clone(), + }; + + // Get a new core address + let core_address = bitcoind.client.get_new_address(None, None)?; + + // Generate 101 blocks and use the above address as coinbase + bitcoind.client.generate_to_address(101, &core_address)?; + + println!(">> bitcoind setup complete"); + println!( + "Available coins in Core wallet : {}", + bitcoind.client.get_balance(None, None)? + ); + + // -- Setting up the Wallet + + println!("\n>> Setting up BDK wallet"); + + // Get a random private key + let xprv = generate_random_ext_privkey()?; + + // Use the derived descriptors from the privatekey to + // create unique wallet name. + // This is a special utility function exposed via `bdk::wallet_name_from_descriptor()` + let wallet_name = wallet_name_from_descriptor( + Bip84(xprv, KeychainKind::External), + Some(Bip84(xprv, KeychainKind::Internal)), + Network::Regtest, + &Secp256k1::new(), + )?; + + // Create a database (using default sled type) to store wallet data + let mut datadir = PathBuf::from_str("/tmp/")?; + datadir.push(".bdk-example"); + let database = sled::open(datadir)?; + let database = database.open_tree(wallet_name.clone())?; + + // Create a RPC configuration of the running bitcoind backend we created in last step + // Note: If you are using custom regtest node, use the appropriate url and auth + let rpc_config = RpcConfig { + url: bitcoind.params.rpc_socket.to_string(), + auth: bitcoind_auth, + network: Network::Regtest, + wallet_name, + skip_blocks: None, + }; + + // Use the above configuration to create a RPC blockchain backend + let blockchain = RpcBlockchain::from_config(&rpc_config)?; + + // Combine Database + Descriptor to create the final wallet + let wallet = Wallet::new( + Bip84(xprv, KeychainKind::External), + Some(Bip84(xprv, KeychainKind::Internal)), + Network::Regtest, + database, + )?; + + // The `wallet` and the `blockchain` are independent structs. + // The wallet will be used to do all wallet level actions + // The blockchain can be used to do all blockchain level actions. + // For certain actions (like sync) the wallet will ask for a blockchain. + + // Sync the wallet + // The first sync is important as this will instantiate the + // wallet files. + wallet.sync(&blockchain, SyncOptions::default())?; + + println!(">> BDK wallet setup complete."); + println!( + "Available initial coins in BDK wallet : {} sats", + wallet.get_balance()? + ); + + // -- Wallet transaction demonstration + + println!("\n>> Sending coins: Core --> BDK, 10 BTC"); + // Get a new address to receive coins + let bdk_new_addr = wallet.get_address(AddressIndex::New)?.address; + + // Send 10 BTC from core wallet to bdk wallet + bitcoind.client.send_to_address( + &bdk_new_addr, + Amount::from_btc(10.0)?, + None, + None, + None, + None, + None, + None, + )?; + + // Confirm transaction by generating 1 block + bitcoind.client.generate_to_address(1, &core_address)?; + + // Sync the BDK wallet + // This time the sync will fetch the new transaction and update it in + // wallet database + wallet.sync(&blockchain, SyncOptions::default())?; + + println!(">> Received coins in BDK wallet"); + println!( + "Available balance in BDK wallet: {} sats", + wallet.get_balance()? + ); + + println!("\n>> Sending coins: BDK --> Core, 5 BTC"); + // Attempt to send back 5.0 BTC to core address by creating a transaction + // + // Transactions are created using a `TxBuilder`. + // This helps us to systematically build a transaction with all + // required customization. + // A full list of APIs offered by `TxBuilder` can be found at + // https://docs.rs/bdk/latest/bdk/wallet/tx_builder/struct.TxBuilder.html + let mut tx_builder = wallet.build_tx(); + + // For a regular transaction, just set the recipient and amount + tx_builder.set_recipients(vec![(core_address.script_pubkey(), 500000000)]); + + // Finalize the transaction and extract the PSBT + let (mut psbt, _) = tx_builder.finish()?; + + // Set signing option + let signopt = SignOptions { + assume_height: None, + ..Default::default() + }; + + // Sign the psbt + wallet.sign(&mut psbt, signopt)?; + + // Extract the signed transaction + let tx = psbt.extract_tx(); + + // Broadcast the transaction + blockchain.broadcast(&tx)?; + + // Confirm transaction by generating some blocks + bitcoind.client.generate_to_address(1, &core_address)?; + + // Sync the BDK wallet + wallet.sync(&blockchain, SyncOptions::default())?; + + println!(">> Coins sent to Core wallet"); + println!( + "Remaining BDK wallet balance: {} sats", + wallet.get_balance()? + ); + println!("\nCongrats!! you made your first test transaction with bdk and bitcoin core."); + + Ok(()) +} + +// Helper function demonstrating privatekey extraction using bip39 mnemonic +// The mnemonic can be shown to user to safekeeping and the same wallet +// private descriptors can be recreated from it. +fn generate_random_ext_privkey() -> Result> { + // a Bip39 passphrase can be set optionally + let password = Some("random password".to_string()); + + // Generate a random mnemonic, and use that to create an Extended PrivateKey + let mnemonic: GeneratedKey<_, Segwitv0> = + Mnemonic::generate((WordCount::Words12, Language::English)) + .map_err(|e| e.expect("Unknown Error"))?; + let mnemonic = mnemonic.into_key(); + let xkey: ExtendedKey = (mnemonic, password).into_extended_key()?; + let xprv = xkey + .into_xprv(Network::Regtest) + .expect("Expected Private Key"); + Ok(xprv) +}