Skip to content

Commit

Permalink
feat: use sqlite in full-wallet ex
Browse files Browse the repository at this point in the history
  • Loading branch information
riverKanies committed Oct 30, 2024
1 parent 297234f commit 308fce9
Show file tree
Hide file tree
Showing 4 changed files with 18 additions and 21 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ dist/
drafts/
target/
*Cargo.lock
*.db
*.sqlite3

# MacOS
*.DS_Store
Expand Down
16 changes: 8 additions & 8 deletions docs/cookbook/full-wallet.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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"
Expand All @@ -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"
Expand All @@ -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:

Expand Down
2 changes: 1 addition & 1 deletion examples/rust/full-wallet/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
19 changes: 8 additions & 11 deletions examples/rust/full-wallet/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::<bdk_wallet::ChangeSet>::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]

Expand All @@ -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();
Expand All @@ -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]
Expand Down

0 comments on commit 308fce9

Please sign in to comment.