From 308fce9e6a545c4f9f455a56299855e0f15e7954 Mon Sep 17 00:00:00 2001 From: River Kanies Date: Wed, 30 Oct 2024 13:31:25 -0400 Subject: [PATCH] feat: use sqlite in full-wallet ex --- .gitignore | 2 +- docs/cookbook/full-wallet.md | 16 ++++++++-------- examples/rust/full-wallet/Cargo.toml | 2 +- examples/rust/full-wallet/src/main.rs | 19 ++++++++----------- 4 files changed, 18 insertions(+), 21 deletions(-) diff --git a/.gitignore b/.gitignore index 5d3790e..51e9da7 100644 --- a/.gitignore +++ b/.gitignore @@ -7,7 +7,7 @@ dist/ drafts/ target/ *Cargo.lock -*.db +*.sqlite3 # MacOS *.DS_Store diff --git a/docs/cookbook/full-wallet.md b/docs/cookbook/full-wallet.md index 5fdcf6f..dedcf64 100644 --- a/docs/cookbook/full-wallet.md +++ b/docs/cookbook/full-wallet.md @@ -8,7 +8,7 @@ This page illustrates core wallet functionality, including: - Creating and broadcasting a transaction !!! tip - The logic for this page is split between 2 separate examples in the [examples source code](https://github.com/bitcoindevkit/book-of-bdk/tree/master/examples/rust). One to create descriptors and a second for everything else.If you are following along with the code examples you will need to copy and paste your private key descriptors you get from the first example into the second. We leave descriptor creation in a separate example because bdk does not handle private key descriptor storage, that is up to the wallet developer. + The logic for this page is split between 2 separate examples in the [examples source code](https://github.com/bitcoindevkit/book-of-bdk/tree/master/examples/rust). One to create descriptors and a second for everything else.If you are following along with the code examples you will need to copy and paste your private descriptors you get from the first example into the second. We leave descriptor creation in a separate example because bdk does not handle private descriptor (or private key) storage, that is up to the wallet developer. ## Generating Descriptors @@ -20,28 +20,28 @@ First we [create signet descriptors](keys-descriptors/descriptors.md) for our wa Notice we are creating private descriptors here in order to sign transactions later on. -## Full Scan and Invoice Address Generation (First Run) +## Full Scan and Address Generation (First Run) -Next, lets use those descriptors to load up our wallet. Replace the placeholder descriptors in the `full-wallet` example with your private key descriptors: +Next, lets use those descriptors to load up our wallet. Replace the placeholder descriptors in the `full-wallet` example with your private descriptors: ```rust title="examples/rust/full-wallet/src/main.rs" --8<-- "examples/rust/full-wallet/src/main.rs:descriptors" ``` -We are going to run this example twice. On the first run it will do a full scan for your wallet, persist that chain data, generate a new invoice address for you, and display your current wallet balance, it will then attempt to build a transaction, but will fail becuase we don't have any funds yet. We will use the invoice address from the first run to request funds from the [Mutinynet faucet](https://faucet.mutinynet.com/) so we can build a transaction on the second run. On the second run it will load the data from the previous run, do a light weight sync to check for updates (no need to repeat the full scan), and then build and broadcast a transaction. Let's go through this step by step. +We are going to run this example twice. On the first run it will do a full scan for your wallet, persist that chain data, generate a new address for you, and display your current wallet balance, it will then attempt to build a transaction, but will fail becuase we don't have any funds yet. We will use the new address (from the first run) to request funds from the [Mutinynet faucet](https://faucet.mutinynet.com/) so we can build a transaction on the second run. On the second run it will load the data from the previous run, do a light weight sync to check for updates (no need to repeat the full scan), and then build and broadcast a transaction. Let's go through this step by step. ```rust title="examples/rust/full-wallet/src/main.rs" --8<-- "examples/rust/full-wallet/src/main.rs:persist" ``` -In the quickstart example we simply used an in-memory wallet, with no persistence. But here we are saving wallet data to a file. Notice that we are providing our private key descriptors during wallet load. This is because bdk never stores private keys, that responsibility is on the wallet developer (you). The data we are loading here does not include the private keys, but we want our wallet to have signing capabilities, so we need to provide our private key descriptors during wallet load. If we get a wallet back from the load attempt, we'll use that, otherwise we'll create a new one. Since this is our first run nothing will be loaded and a new wallet will be created. +In the quickstart example we simply used an in-memory wallet, with no persistence. But here we are saving wallet data to a file. Notice that we are providing our private descriptors during wallet load. This is because bdk never stores private keys, that responsibility is on the wallet developer (you). The data we are loading here does not include the private keys, but we want our wallet to have signing capabilities, so we need to provide our private descriptors during wallet load. If we get a wallet back from the load attempt, we'll use that, otherwise we'll create a new one. Since this is our first run nothing will be loaded and a new wallet will be created. ```rust title="examples/rust/full-wallet/src/main.rs" --8<-- "examples/rust/full-wallet/src/main.rs:scan" ``` Next we'll fetch data from our blockchain client. On the first run, we don't yet have any data, so we need to do a full scan. We then persist the data from the scan. -Finally, we'll print out an invoice that we can use to request funds. You should also see the current balance printed out, it should be 0 since this is a brand new wallet. Note that we persist the wallet after generating the invoice address. This is to avoid re-using the same address as that would compromise our privacy (on subsequent runs you'll notice the address index incremented). +Finally, we'll print out an address that we can use to request funds. You should also see the current balance printed out, it should be 0 since this is a brand new wallet. Note that we persist the wallet after generating the new address; this is to avoid re-using the same address as that would compromise our privacy (on subsequent runs you'll notice the address index incremented). ```rust title="examples/rust/full-wallet/src/main.rs" --8<-- "examples/rust/full-wallet/src/main.rs:address" @@ -55,7 +55,7 @@ We can now use our new address to request some sats from the [Mutinynet faucet]( ## Load, Sync, and Send a Transaction (Second Run) -Now that we have some funds, we can re-run the `full-wallet` example. Since we persisted data from the previous run, this time our wallet will be loaded. You do not need to provide descriptors to load wallet data, however, if you don't you will not have signing capabilities, so here we do provide our private key descriptors in the loading process: +Now that we have some funds, we can re-run the `full-wallet` example. Since we persisted data from the previous run, this time our wallet will be loaded. You do not need to provide descriptors to load wallet data, however, if you don't you will not have signing capabilities, so here we do provide our private descriptors in the loading process: ```rust title="examples/rust/full-wallet/src/main.rs" --8<-- "examples/rust/full-wallet/src/main.rs:persist" @@ -73,7 +73,7 @@ Now that we have funds, let's prepare to send a transaction. We need to decide w --8<-- "examples/rust/full-wallet/src/main.rs:faucet" ``` -Here we are preparing to send 5000 sats back to the mutiny faucet. When you're done testing things out with this wallet you can uncomment the bottom line here to send all the rest of your remaining funds back (we subtract 100 sats from the total wallet balance as that is the minimum transaction fee). +Here we are preparing to send 5000 sats back to the mutiny faucet (it's good practice to send test sats back to the faucet when you're done using them). Finally we are ready to build, sign, and broadcast the transaction: diff --git a/examples/rust/full-wallet/Cargo.toml b/examples/rust/full-wallet/Cargo.toml index d149508..7ecb986 100644 --- a/examples/rust/full-wallet/Cargo.toml +++ b/examples/rust/full-wallet/Cargo.toml @@ -4,6 +4,6 @@ version = "0.1.0" edition = "2021" [dependencies] -bdk_wallet = { version = "=1.0.0-beta.5", features = ["keys-bip39", "file_store"] } +bdk_wallet = { version = "=1.0.0-beta.5", features = ["keys-bip39", "rusqlite"] } bdk_esplora = { version = "=0.19.0", features = ["blocking"] } anyhow = "1" diff --git a/examples/rust/full-wallet/src/main.rs b/examples/rust/full-wallet/src/main.rs index 9cef1a9..800e77c 100644 --- a/examples/rust/full-wallet/src/main.rs +++ b/examples/rust/full-wallet/src/main.rs @@ -8,39 +8,38 @@ use bdk_esplora::EsploraExt; use bdk_esplora::esplora_client::Builder; use bdk_esplora::esplora_client; use bdk_wallet::chain::spk_client::{FullScanRequestBuilder, FullScanResult, SyncRequestBuilder, SyncResult}; -use bdk_wallet::file_store::Store; use std::str::FromStr; +use bdk_wallet::rusqlite::Connection; -const DB_MAGIC: &str = "full_wallet_example"; -const DB_PATH: &str = "full-wallet.db"; +const DB_PATH: &str = "full-wallet.sqlite3"; const STOP_GAP: usize = 50; const PARALLEL_REQUESTS: usize = 1; // --8<-- [start:descriptors] const DESCRIPTOR_PRIVATE_EXTERNAL: &str = "[your private external descriptor here ...]"; const DESCRIPTOR_PRIVATE_INTERNAL: &str = "[your private internal descriptor here ...]"; -// Example private key descriptors +// Example private descriptors // const DESCRIPTOR_PRIVATE_EXTERNAL: &str = "tr(tprv8ZgxMBicQKsPdJuLWWArdBsWjqDA3W5WoREnfdgKEcCQB1FMKfSoaFz9JHZU71HwXAqTsjHripkLM62kUQar14SDD8brsmhFKqVUPXGrZLc/86'/1'/0'/0/*)#fv8tutn2"; // const DESCRIPTOR_PRIVATE_INTERNAL: &str = "tr(tprv8ZgxMBicQKsPdJuLWWArdBsWjqDA3W5WoREnfdgKEcCQB1FMKfSoaFz9JHZU71HwXAqTsjHripkLM62kUQar14SDD8brsmhFKqVUPXGrZLc/86'/1'/0'/1/*)#ccz2p7rj"; // --8<-- [end:descriptors] fn main() -> Result<(), anyhow::Error> { // --8<-- [start:persist] - let mut db = Store::::open_or_create_new(DB_MAGIC.as_bytes(), DB_PATH)?; + let mut conn = Connection::open(DB_PATH)?; let wallet_opt = Wallet::load() .descriptor(KeychainKind::External, Some(DESCRIPTOR_PRIVATE_EXTERNAL)) .descriptor(KeychainKind::Internal, Some(DESCRIPTOR_PRIVATE_INTERNAL)) .extract_keys() .check_network(Network::Signet) - .load_wallet(&mut db)?; + .load_wallet(&mut conn)?; let (mut wallet, is_new_wallet) = if let Some(loaded_wallet) = wallet_opt { (loaded_wallet, false) } else { (Wallet::create(DESCRIPTOR_PRIVATE_EXTERNAL, DESCRIPTOR_PRIVATE_INTERNAL) .network(Network::Signet) - .create_wallet(&mut db)?, true) + .create_wallet(&mut conn)?, true) }; // --8<-- [end:persist] @@ -60,14 +59,14 @@ fn main() -> Result<(), anyhow::Error> { let update: SyncResult = client.sync(sync_request, PARALLEL_REQUESTS)?; wallet.apply_update(update).unwrap(); }; - wallet.persist(&mut db)?; + wallet.persist(&mut conn)?; // --8<-- [end:scan] // --8<-- [start:address] // Reveal a new address from your external keychain let address: AddressInfo = wallet.reveal_next_address(KeychainKind::External); println!("Generated address {} at index {}", address.address, address.index); - wallet.persist(&mut db)?; + wallet.persist(&mut conn)?; // --8<-- [end:address] let balance = wallet.balance(); @@ -81,8 +80,6 @@ fn main() -> Result<(), anyhow::Error> { .unwrap(); let send_amount: Amount = Amount::from_sat(5000); - // return all sats to mutinynet faucet - // let send_amount: Amount = Amount::from_sat(balance.total().to_sat() - 100);// min fee is 100 sat // --8<-- [end:faucet] // --8<-- [start:transaction]