Compare commits
25 Commits
v0.2.0
...
release/0.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1420cf8d0f | ||
|
|
bddd418c8e | ||
|
|
49db898acb | ||
|
|
01585227c5 | ||
|
|
52b45c5b89 | ||
|
|
733355a6ae | ||
|
|
6955a7776d | ||
|
|
bf04a2cf69 | ||
|
|
2b669afd3e | ||
|
|
8510b2b86e | ||
|
|
a95a9f754c | ||
|
|
3980b90bff | ||
|
|
b2bd1b5831 | ||
|
|
aa31c96821 | ||
|
|
f74bfdd493 | ||
|
|
5034ca2267 | ||
|
|
8094263028 | ||
|
|
0c9c0716a4 | ||
|
|
c2b2da7601 | ||
|
|
407f14add9 | ||
|
|
656c9c9da8 | ||
|
|
a578d20282 | ||
|
|
2e222c7ad9 | ||
|
|
7d6cd6d4f5 | ||
|
|
e31bd812ed |
2
.github/workflows/code_coverage.yml
vendored
2
.github/workflows/code_coverage.yml
vendored
@@ -18,7 +18,7 @@ jobs:
|
||||
- name: Install tarpaulin
|
||||
run: cargo install cargo-tarpaulin
|
||||
- name: Tarpaulin
|
||||
run: cargo tarpaulin --features all-keys,cli-utils,compiler,esplora,compact_filters --run-types Tests,Doctests --exclude-files "testutils/*" --out Xml
|
||||
run: cargo tarpaulin --features all-keys,compiler,esplora,compact_filters --run-types Tests,Doctests --exclude-files "testutils/*" --out Xml
|
||||
|
||||
- name: Publish to codecov.io
|
||||
uses: codecov/codecov-action@v1.0.15
|
||||
|
||||
6
.github/workflows/cont_integration.yml
vendored
6
.github/workflows/cont_integration.yml
vendored
@@ -20,7 +20,7 @@ jobs:
|
||||
- key-value-db
|
||||
- electrum
|
||||
- compact_filters
|
||||
- cli-utils,esplora,key-value-db,electrum
|
||||
- esplora,key-value-db,electrum
|
||||
- compiler
|
||||
steps:
|
||||
- name: checkout
|
||||
@@ -44,7 +44,7 @@ jobs:
|
||||
- name: Build
|
||||
run: cargo build --features ${{ matrix.features }} --no-default-features
|
||||
- name: Clippy
|
||||
run: cargo clippy -- -D warnings
|
||||
run: cargo clippy --features ${{ matrix.features }} --no-default-features -- -D warnings
|
||||
- name: Test
|
||||
run: cargo test --features ${{ matrix.features }} --no-default-features
|
||||
|
||||
@@ -130,7 +130,7 @@ jobs:
|
||||
- name: Add target wasm32
|
||||
run: rustup target add wasm32-unknown-unknown
|
||||
- name: Check
|
||||
run: cargo check --target wasm32-unknown-unknown --features cli-utils,esplora --no-default-features
|
||||
run: cargo check --target wasm32-unknown-unknown --features esplora --no-default-features
|
||||
|
||||
fmt:
|
||||
name: Rust fmt
|
||||
|
||||
2
.github/workflows/nightly_docs.yml
vendored
2
.github/workflows/nightly_docs.yml
vendored
@@ -27,7 +27,7 @@ jobs:
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: rustdoc
|
||||
args: --verbose --features=compiler,electrum,esplora,compact_filters,key-value-db,all-keys -- --cfg docsrs
|
||||
args: --verbose --features=compiler,electrum,esplora,compact_filters,key-value-db,all-keys -- --cfg docsrs -Dwarnings
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
|
||||
24
CHANGELOG.md
24
CHANGELOG.md
@@ -6,6 +6,26 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [v0.3.0] - [v0.2.0]
|
||||
|
||||
### Descriptor
|
||||
#### Changed
|
||||
- Added an alias `DescriptorError` for `descriptor::error::Error`
|
||||
- Changed the error returned by `descriptor!()` and `fragment!()` to `DescriptorError`
|
||||
- Changed the error type in `ToWalletDescriptor` to `DescriptorError`
|
||||
- Improved checks on descriptors built using the macros
|
||||
|
||||
### Blockchain
|
||||
#### Changed
|
||||
- Remove `BlockchainMarker`, `OfflineClient` and `OfflineWallet` in favor of just using the unit
|
||||
type to mark for a missing client.
|
||||
|
||||
### CLI
|
||||
#### Changed
|
||||
- Remove `cli.rs` module, `cli-utils` feature and `repl.rs` example; moved to new [`bdk-cli`](https://github.com/bitcoindevkit/bdk-cli) repository
|
||||
|
||||
## [v0.2.0] - [0.1.0-beta.1]
|
||||
|
||||
### Project
|
||||
#### Added
|
||||
- Add CONTRIBUTING.md
|
||||
@@ -209,5 +229,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- Use `MemoryDatabase` in the compiler example
|
||||
- Make the REPL return JSON
|
||||
|
||||
[unreleased]: https://github.com/bitcoindevkit/bdk/compare/0.1.0-beta.1...HEAD
|
||||
[unreleased]: https://github.com/bitcoindevkit/bdk/compare/v0.2.0...HEAD
|
||||
[0.1.0-beta.1]: https://github.com/bitcoindevkit/bdk/compare/96c87ea5...0.1.0-beta.1
|
||||
[v0.2.0]: https://github.com/bitcoindevkit/bdk/compare/0.1.0-beta.1...v0.2.0
|
||||
[v0.3.0]: https://github.com/bitcoindevkit/bdk/compare/v0.2.0...v0.3.0
|
||||
|
||||
21
Cargo.toml
21
Cargo.toml
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "bdk"
|
||||
version = "0.2.0"
|
||||
version = "0.3.1-dev"
|
||||
edition = "2018"
|
||||
authors = ["Alekos Filini <alekos.filini@gmail.com>", "Riccardo Casatta <riccardo@casatta.it>"]
|
||||
homepage = "https://bitcoindevkit.org"
|
||||
@@ -9,7 +9,7 @@ documentation = "https://docs.rs/bdk"
|
||||
description = "A modern, lightweight, descriptor-based wallet library"
|
||||
keywords = ["bitcoin", "wallet", "descriptor", "psbt"]
|
||||
readme = "README.md"
|
||||
license-file = "LICENSE"
|
||||
license = "MIT"
|
||||
|
||||
[dependencies]
|
||||
bdk-macros = "0.2"
|
||||
@@ -22,11 +22,9 @@ rand = "^0.7"
|
||||
|
||||
# Optional dependencies
|
||||
sled = { version = "0.34", optional = true }
|
||||
electrum-client = { version = "0.4.0-beta.1", optional = true }
|
||||
electrum-client = { version = "0.5.0-beta.1", optional = true }
|
||||
reqwest = { version = "0.10", optional = true, features = ["json"] }
|
||||
futures = { version = "0.3", optional = true }
|
||||
clap = { version = "2.33", optional = true }
|
||||
base64 = { version = "^0.11", optional = true }
|
||||
async-trait = { version = "0.1", optional = true }
|
||||
rocksdb = { version = "0.14", optional = true }
|
||||
# pin cc version to 1.0.62 because 1.0.63 break rocksdb build
|
||||
@@ -34,7 +32,6 @@ cc = { version = "=1.0.62", optional = true }
|
||||
socks = { version = "0.3", optional = true }
|
||||
lazy_static = { version = "1.4", optional = true }
|
||||
tiny-bip39 = { version = "^0.8", optional = true }
|
||||
structopt = { version = "^0.3", optional = true }
|
||||
|
||||
# Platform-specific dependencies
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||
@@ -47,13 +44,12 @@ rand = { version = "^0.7", features = ["wasm-bindgen"] }
|
||||
|
||||
[features]
|
||||
minimal = []
|
||||
compiler = ["clap", "miniscript/compiler"]
|
||||
compiler = ["miniscript/compiler"]
|
||||
default = ["key-value-db", "electrum"]
|
||||
electrum = ["electrum-client"]
|
||||
esplora = ["reqwest", "futures"]
|
||||
compact_filters = ["rocksdb", "socks", "lazy_static", "cc"]
|
||||
key-value-db = ["sled"]
|
||||
cli-utils = ["clap", "base64", "structopt"]
|
||||
async-interface = ["async-trait"]
|
||||
all-keys = ["keys-bip39"]
|
||||
keys-bip39 = ["tiny-bip39"]
|
||||
@@ -61,20 +57,17 @@ keys-bip39 = ["tiny-bip39"]
|
||||
# Debug/Test features
|
||||
debug-proc-macros = ["bdk-macros/debug", "bdk-testutils-macros/debug"]
|
||||
test-electrum = ["electrum"]
|
||||
test-md-docs = ["base64", "electrum"]
|
||||
test-md-docs = ["electrum"]
|
||||
|
||||
[dev-dependencies]
|
||||
bdk-testutils = "0.2"
|
||||
bdk-testutils-macros = "0.2"
|
||||
serial_test = "0.4"
|
||||
lazy_static = "1.4"
|
||||
rustyline = "6.0"
|
||||
dirs-next = "2.0"
|
||||
env_logger = "0.7"
|
||||
base64 = "^0.11"
|
||||
clap = "2.33"
|
||||
|
||||
[[example]]
|
||||
name = "repl"
|
||||
required-features = ["cli-utils"]
|
||||
[[example]]
|
||||
name = "parse_descriptor"
|
||||
[[example]]
|
||||
|
||||
@@ -35,12 +35,14 @@ Pre-`v1.0.0` our "major" releases only affect the "minor" semver value. Accordin
|
||||
5. If a bug is found:
|
||||
- If it's a minor issue you can just fix it in the release branch, since it will be merged back to `master` eventually
|
||||
- For bigger issues you can fix them on `master` and then *cherry-pick* the commit to the release branch
|
||||
6. On release day, make a commit on the release branch to bump the version to `x.y.z`. The message should be "Bump version to x.y.z".
|
||||
7. Add a tag to this commit. The tag name should be `vx.y.z` (for example `v0.5.0`), and the message "Release x.y.z". Make sure the tag is signed, for extra safety use the explicit `--sign` flag.
|
||||
8. Push the new commits to the upstream release branch, wait for the CI to finish one last time.
|
||||
9. Publish **all** the updated crates to crates.io.
|
||||
10. Make a new commit to bump the version value to `x.y.(z+1)-dev`. The message should be "Bump version to x.y.(z+1)-dev".
|
||||
11. Merge the release branch back into `master`.
|
||||
12. Make sure the new release shows up on crates.io and that the docs are built correctly on docs.rs.
|
||||
13. Announce the release on Twitter, Discord and Telegram.
|
||||
14. Celebrate :tada:
|
||||
6. Update the changelog with the new release version
|
||||
7. On release day, make a commit on the release branch to bump the version to `x.y.z`. The message should be "Bump version to x.y.z".
|
||||
8. Add a tag to this commit. The tag name should be `vx.y.z` (for example `v0.5.0`), and the message "Release x.y.z". Make sure the tag is signed, for extra safety use the explicit `--sign` flag.
|
||||
9. Push the new commits to the upstream release branch, wait for the CI to finish one last time.
|
||||
10. Publish **all** the updated crates to crates.io.
|
||||
11. Make a new commit to bump the version value to `x.y.(z+1)-dev`. The message should be "Bump version to x.y.(z+1)-dev".
|
||||
12. Merge the release branch back into `master`.
|
||||
13. Create the release on GitHub: go to "tags", click on the dots on the right and select "Create Release". Then set the title to `vx.y.z` and write down some brief release notes.
|
||||
14. Make sure the new release shows up on crates.io and that the docs are built correctly on docs.rs.
|
||||
15. Announce the release on Twitter, Discord and Telegram.
|
||||
16. Celebrate :tada:
|
||||
|
||||
17
README.md
17
README.md
@@ -8,10 +8,11 @@
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<!-- <a href="https://crates.io/crates/magical"><img alt="Crate Info" src="https://img.shields.io/crates/v/magical.svg"/></a> -->
|
||||
<a href="https://crates.io/crates/bdk"><img alt="Crate Info" src="https://img.shields.io/crates/v/bdk.svg"/></a>
|
||||
<a href="https://github.com/bitcoindevkit/bdk/blob/master/LICENSE"><img alt="MIT Licensed" src="https://img.shields.io/badge/license-MIT-blue.svg"/></a>
|
||||
<a href="https://github.com/bitcoindevkit/bdk/actions?query=workflow%3ACI"><img alt="CI Status" src="https://github.com/bitcoindevkit/bdk/workflows/CI/badge.svg"></a>
|
||||
<a href="https://codecov.io/gh/bitcoindevkit/bdk"><img src="https://codecov.io/gh/bitcoindevkit/bdk/branch/master/graph/badge.svg"/></a>
|
||||
<a href="https://bitcoindevkit.org/docs-rs/bdk"><img alt="API Docs" src="https://img.shields.io/badge/docs.rs-bdk-green"/></a>
|
||||
<a href="https://docs.rs/bdk"><img alt="API Docs" src="https://img.shields.io/badge/docs.rs-bdk-green"/></a>
|
||||
<a href="https://blog.rust-lang.org/2020/07/16/Rust-1.45.0.html"><img alt="Rustc Version 1.45+" src="https://img.shields.io/badge/rustc-1.45%2B-lightgrey.svg"/></a>
|
||||
<a href="https://discord.gg/d7NkDKm"><img alt="Chat on Discord" src="https://img.shields.io/discord/753336465005608961?logo=discord"></a>
|
||||
</p>
|
||||
@@ -19,7 +20,7 @@
|
||||
<h4>
|
||||
<a href="https://bitcoindevkit.org">Project Homepage</a>
|
||||
<span> | </span>
|
||||
<a href="https://bitcoindevkit.org/docs-rs/bdk">Documentation</a>
|
||||
<a href="https://docs.rs/bdk">Documentation</a>
|
||||
</h4>
|
||||
</div>
|
||||
|
||||
@@ -65,11 +66,10 @@ fn main() -> Result<(), bdk::Error> {
|
||||
### Generate a few addresses
|
||||
|
||||
```rust
|
||||
use bdk::{Wallet, OfflineWallet};
|
||||
use bdk::database::MemoryDatabase;
|
||||
use bdk::{Wallet, database::MemoryDatabase};
|
||||
|
||||
fn main() -> Result<(), bdk::Error> {
|
||||
let wallet: OfflineWallet<_> = Wallet::new_offline(
|
||||
let wallet = Wallet::new_offline(
|
||||
"wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)",
|
||||
Some("wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)"),
|
||||
bitcoin::Network::Testnet,
|
||||
@@ -125,13 +125,12 @@ fn main() -> Result<(), bdk::Error> {
|
||||
### Sign a transaction
|
||||
|
||||
```rust,no_run
|
||||
use bdk::{Wallet, OfflineWallet};
|
||||
use bdk::database::MemoryDatabase;
|
||||
use bdk::{Wallet, database::MemoryDatabase};
|
||||
|
||||
use bitcoin::consensus::deserialize;
|
||||
|
||||
fn main() -> Result<(), bdk::Error> {
|
||||
let wallet: OfflineWallet<_> = Wallet::new_offline(
|
||||
let wallet = Wallet::new_offline(
|
||||
"wpkh([c258d2e4/84h/1h/0h]tprv8griRPhA7342zfRyB6CqeKF8CJDXYu5pgnj1cjL1u2ngKcJha5jjTRimG82ABzJQ4MQe71CV54xfn25BbhCNfEGGJZnxvCDQCd6JkbvxW6h/0/*)",
|
||||
Some("wpkh([c258d2e4/84h/1h/0h]tprv8griRPhA7342zfRyB6CqeKF8CJDXYu5pgnj1cjL1u2ngKcJha5jjTRimG82ABzJQ4MQe71CV54xfn25BbhCNfEGGJZnxvCDQCd6JkbvxW6h/1/*)"),
|
||||
bitcoin::Network::Testnet,
|
||||
|
||||
@@ -29,7 +29,7 @@ use bdk::database::MemoryDatabase;
|
||||
use bdk::descriptor::HDKeyPaths;
|
||||
use bdk::wallet::address_validator::{AddressValidator, AddressValidatorError};
|
||||
use bdk::KeychainKind;
|
||||
use bdk::{OfflineWallet, Wallet};
|
||||
use bdk::Wallet;
|
||||
|
||||
use bitcoin::hashes::hex::FromHex;
|
||||
use bitcoin::util::bip32::Fingerprint;
|
||||
@@ -59,7 +59,7 @@ impl AddressValidator for DummyValidator {
|
||||
|
||||
fn main() -> Result<(), bdk::Error> {
|
||||
let descriptor = "sh(and_v(v:pk(tpubDDpWvmUrPZrhSPmUzCMBHffvC3HyMAPnWDSAQNBTnj1iZeJa7BZQEttFiP4DS4GCcXQHezdXhn86Hj6LHX5EDstXPWrMaSneRWM8yUf6NFd/*),after(630000)))";
|
||||
let mut wallet: OfflineWallet<_> =
|
||||
let mut wallet =
|
||||
Wallet::new_offline(descriptor, None, Network::Regtest, MemoryDatabase::new())?;
|
||||
|
||||
wallet.add_address_validator(Arc::new(DummyValidator));
|
||||
|
||||
@@ -40,7 +40,7 @@ use miniscript::policy::Concrete;
|
||||
use miniscript::Descriptor;
|
||||
|
||||
use bdk::database::memory::MemoryDatabase;
|
||||
use bdk::{KeychainKind, OfflineWallet, Wallet};
|
||||
use bdk::{KeychainKind, Wallet};
|
||||
|
||||
fn main() {
|
||||
env_logger::init_from_env(
|
||||
@@ -98,8 +98,7 @@ fn main() {
|
||||
Some("regtest") => Network::Regtest,
|
||||
Some("testnet") | _ => Network::Testnet,
|
||||
};
|
||||
let wallet: OfflineWallet<_> =
|
||||
Wallet::new_offline(&format!("{}", descriptor), None, network, database).unwrap();
|
||||
let wallet = Wallet::new_offline(&format!("{}", descriptor), None, network, database).unwrap();
|
||||
|
||||
info!("... First address: {}", wallet.get_new_address().unwrap());
|
||||
|
||||
|
||||
174
examples/repl.rs
174
examples/repl.rs
@@ -1,174 +0,0 @@
|
||||
// Magical Bitcoin Library
|
||||
// Written in 2020 by
|
||||
// Alekos Filini <alekos.filini@gmail.com>
|
||||
//
|
||||
// Copyright (c) 2020 Magical Bitcoin
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
use std::str::FromStr;
|
||||
use std::sync::Arc;
|
||||
|
||||
use bitcoin::Network;
|
||||
use clap::AppSettings;
|
||||
use log::{debug, info, warn, LevelFilter};
|
||||
use rustyline::error::ReadlineError;
|
||||
use rustyline::Editor;
|
||||
use structopt::StructOpt;
|
||||
|
||||
use bdk::bitcoin;
|
||||
#[cfg(feature = "esplora")]
|
||||
use bdk::blockchain::esplora::EsploraBlockchainConfig;
|
||||
use bdk::blockchain::{
|
||||
AnyBlockchain, AnyBlockchainConfig, ConfigurableBlockchain, ElectrumBlockchainConfig,
|
||||
};
|
||||
use bdk::cli::{self, WalletOpt, WalletSubCommand};
|
||||
use bdk::sled;
|
||||
use bdk::Wallet;
|
||||
|
||||
#[derive(Debug, StructOpt, Clone, PartialEq)]
|
||||
#[structopt(name = "BDK Wallet", setting = AppSettings::NoBinaryName,
|
||||
version = option_env ! ("CARGO_PKG_VERSION").unwrap_or("unknown"),
|
||||
author = option_env ! ("CARGO_PKG_AUTHORS").unwrap_or(""))]
|
||||
struct ReplOpt {
|
||||
/// Wallet sub-command
|
||||
#[structopt(subcommand)]
|
||||
pub subcommand: WalletSubCommand,
|
||||
}
|
||||
|
||||
fn prepare_home_dir() -> PathBuf {
|
||||
let mut dir = PathBuf::new();
|
||||
dir.push(&dirs_next::home_dir().unwrap());
|
||||
dir.push(".bdk-bitcoin");
|
||||
|
||||
if !dir.exists() {
|
||||
info!("Creating home directory {}", dir.as_path().display());
|
||||
fs::create_dir(&dir).unwrap();
|
||||
}
|
||||
|
||||
dir.push("database.sled");
|
||||
dir
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let cli_opt: WalletOpt = WalletOpt::from_args();
|
||||
|
||||
let level = LevelFilter::from_str(cli_opt.log_level.as_str()).unwrap_or(LevelFilter::Info);
|
||||
env_logger::builder().filter_level(level).init();
|
||||
|
||||
let network = Network::from_str(cli_opt.network.as_str()).unwrap_or(Network::Testnet);
|
||||
debug!("network: {:?}", network);
|
||||
if network == Network::Bitcoin {
|
||||
warn!("This is experimental software and not currently recommended for use on Bitcoin mainnet, proceed with caution.")
|
||||
}
|
||||
|
||||
let descriptor = cli_opt.descriptor.as_str();
|
||||
let change_descriptor = cli_opt.change_descriptor.as_deref();
|
||||
debug!("descriptors: {:?} {:?}", descriptor, change_descriptor);
|
||||
|
||||
let database = sled::open(prepare_home_dir().to_str().unwrap()).unwrap();
|
||||
let tree = database.open_tree(cli_opt.wallet).unwrap();
|
||||
debug!("database opened successfully");
|
||||
|
||||
// Try to use Esplora config if "esplora" feature is enabled
|
||||
#[cfg(feature = "esplora")]
|
||||
let config_esplora: Option<AnyBlockchainConfig> = {
|
||||
let esplora_concurrency = cli_opt.esplora_concurrency;
|
||||
cli_opt.esplora.map(|base_url| {
|
||||
AnyBlockchainConfig::Esplora(EsploraBlockchainConfig {
|
||||
base_url: base_url.to_string(),
|
||||
concurrency: Some(esplora_concurrency),
|
||||
})
|
||||
})
|
||||
};
|
||||
#[cfg(not(feature = "esplora"))]
|
||||
let config_esplora = None;
|
||||
|
||||
// Fall back to Electrum config if Esplora config isn't provided
|
||||
let config =
|
||||
config_esplora.unwrap_or(AnyBlockchainConfig::Electrum(ElectrumBlockchainConfig {
|
||||
url: cli_opt.electrum,
|
||||
socks5: cli_opt.proxy,
|
||||
retry: 10,
|
||||
timeout: 10,
|
||||
}));
|
||||
|
||||
let wallet = Wallet::new(
|
||||
descriptor,
|
||||
change_descriptor,
|
||||
network,
|
||||
tree,
|
||||
AnyBlockchain::from_config(&config).unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let wallet = Arc::new(wallet);
|
||||
|
||||
match cli_opt.subcommand {
|
||||
WalletSubCommand::Other(external) if external.contains(&"repl".to_string()) => {
|
||||
let mut rl = Editor::<()>::new();
|
||||
|
||||
// if rl.load_history("history.txt").is_err() {
|
||||
// println!("No previous history.");
|
||||
// }
|
||||
|
||||
loop {
|
||||
let readline = rl.readline(">> ");
|
||||
match readline {
|
||||
Ok(line) => {
|
||||
if line.trim() == "" {
|
||||
continue;
|
||||
}
|
||||
rl.add_history_entry(line.as_str());
|
||||
let split_line: Vec<&str> = line.split(" ").collect();
|
||||
let repl_subcommand: Result<ReplOpt, clap::Error> =
|
||||
ReplOpt::from_iter_safe(split_line);
|
||||
debug!("repl_subcommand = {:?}", repl_subcommand);
|
||||
|
||||
if let Err(err) = repl_subcommand {
|
||||
println!("{}", err.message);
|
||||
continue;
|
||||
}
|
||||
|
||||
let result = cli::handle_wallet_subcommand(
|
||||
&Arc::clone(&wallet),
|
||||
repl_subcommand.unwrap().subcommand,
|
||||
)
|
||||
.unwrap();
|
||||
println!("{}", serde_json::to_string_pretty(&result).unwrap());
|
||||
}
|
||||
Err(ReadlineError::Interrupted) => continue,
|
||||
Err(ReadlineError::Eof) => break,
|
||||
Err(err) => {
|
||||
println!("{:?}", err);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// rl.save_history("history.txt").unwrap();
|
||||
}
|
||||
_ => {
|
||||
let result = cli::handle_wallet_subcommand(&wallet, cli_opt.subcommand).unwrap();
|
||||
println!("{}", serde_json::to_string_pretty(&result).unwrap());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@ repository = "https://github.com/bitcoindevkit/bdk"
|
||||
documentation = "https://docs.rs/bdk-macros"
|
||||
description = "Supporting macros for `bdk`"
|
||||
keywords = ["bdk"]
|
||||
license-file = "../LICENSE"
|
||||
license = "MIT"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
|
||||
@@ -131,7 +131,7 @@ impl CompactFiltersBlockchain {
|
||||
|
||||
let network = peers[0].get_network();
|
||||
|
||||
let cfs = DB::list_cf(&opts, &storage_dir).unwrap_or(vec!["default".to_string()]);
|
||||
let cfs = DB::list_cf(&opts, &storage_dir).unwrap_or_else(|_| vec!["default".to_string()]);
|
||||
let db = DB::open_cf(&opts, &storage_dir, &cfs)?;
|
||||
let headers = Arc::new(ChainStore::new(db, network)?);
|
||||
|
||||
@@ -221,7 +221,7 @@ impl CompactFiltersBlockchain {
|
||||
sent: outgoing,
|
||||
height,
|
||||
timestamp,
|
||||
fees: inputs_sum.checked_sub(outputs_sum).unwrap_or(0),
|
||||
fees: inputs_sum.saturating_sub(outputs_sum),
|
||||
};
|
||||
|
||||
info!("Saving tx {}", tx.txid);
|
||||
@@ -257,13 +257,10 @@ impl Blockchain for CompactFiltersBlockchain {
|
||||
.map(|x| x / 1000)
|
||||
.unwrap_or(0)
|
||||
+ 1;
|
||||
let expected_bundles_to_sync = total_bundles
|
||||
.checked_sub(cf_sync.pruned_bundles()?)
|
||||
.unwrap_or(0);
|
||||
let expected_bundles_to_sync = total_bundles.saturating_sub(cf_sync.pruned_bundles()?);
|
||||
|
||||
let headers_cost = (first_peer.get_version().start_height as usize)
|
||||
.checked_sub(initial_height)
|
||||
.unwrap_or(0) as f32
|
||||
.saturating_sub(initial_height) as f32
|
||||
* SYNC_HEADERS_COST;
|
||||
let filters_cost = expected_bundles_to_sync as f32 * SYNC_FILTERS_COST;
|
||||
|
||||
@@ -274,7 +271,7 @@ impl Blockchain for CompactFiltersBlockchain {
|
||||
Arc::clone(&self.headers),
|
||||
|new_height| {
|
||||
let local_headers_cost =
|
||||
new_height.checked_sub(initial_height).unwrap_or(0) as f32 * SYNC_HEADERS_COST;
|
||||
new_height.saturating_sub(initial_height) as f32 * SYNC_HEADERS_COST;
|
||||
progress_update.update(
|
||||
local_headers_cost / total_cost * 100.0,
|
||||
Some(format!("Synced headers to {}", new_height)),
|
||||
@@ -288,9 +285,7 @@ impl Blockchain for CompactFiltersBlockchain {
|
||||
}
|
||||
|
||||
let synced_height = self.headers.get_height()?;
|
||||
let buried_height = synced_height
|
||||
.checked_sub(sync::BURIED_CONFIRMATIONS)
|
||||
.unwrap_or(0);
|
||||
let buried_height = synced_height.saturating_sub(sync::BURIED_CONFIRMATIONS);
|
||||
info!("Synced headers to height: {}", synced_height);
|
||||
|
||||
cf_sync.prepare_sync(Arc::clone(&first_peer))?;
|
||||
@@ -303,7 +298,9 @@ impl Blockchain for CompactFiltersBlockchain {
|
||||
.collect::<Vec<_>>(),
|
||||
);
|
||||
|
||||
#[allow(clippy::mutex_atomic)]
|
||||
let last_synced_block = Arc::new(Mutex::new(synced_height));
|
||||
|
||||
let synced_bundles = Arc::new(AtomicUsize::new(0));
|
||||
let progress_update = Arc::new(Mutex::new(progress_update));
|
||||
|
||||
@@ -328,10 +325,7 @@ impl Blockchain for CompactFiltersBlockchain {
|
||||
}
|
||||
|
||||
let block_height = headers.get_height_for(block_hash)?.unwrap_or(0);
|
||||
let saved_correct_block = match headers.get_full_block(block_height)? {
|
||||
Some(block) if &block.block_hash() == block_hash => true,
|
||||
_ => false,
|
||||
};
|
||||
let saved_correct_block = matches!(headers.get_full_block(block_height)?, Some(block) if &block.block_hash() == block_hash);
|
||||
|
||||
if saved_correct_block {
|
||||
Ok(false)
|
||||
|
||||
@@ -333,7 +333,7 @@ impl Peer {
|
||||
NetworkMessage::Alert(_) => continue,
|
||||
NetworkMessage::GetData(ref inv) => {
|
||||
let (found, not_found): (Vec<_>, Vec<_>) = inv
|
||||
.into_iter()
|
||||
.iter()
|
||||
.map(|item| (*item, reader_thread_mempool.get_tx(item)))
|
||||
.partition(|(_, d)| d.is_some());
|
||||
for (_, found_tx) in found {
|
||||
@@ -518,10 +518,9 @@ impl InvPeer for Peer {
|
||||
let getdata = inv
|
||||
.iter()
|
||||
.cloned()
|
||||
.filter(|item| match item {
|
||||
Inventory::Transaction(txid) if !self.mempool.has_tx(txid) => true,
|
||||
_ => false,
|
||||
})
|
||||
.filter(
|
||||
|item| matches!(item, Inventory::Transaction(txid) if !self.mempool.has_tx(txid)),
|
||||
)
|
||||
.collect::<Vec<_>>();
|
||||
let num_txs = getdata.len();
|
||||
self.send(NetworkMessage::GetData(getdata))?;
|
||||
|
||||
@@ -375,7 +375,7 @@ impl ChainStore<Full> {
|
||||
let min_height = match iterator
|
||||
.next()
|
||||
.and_then(|(k, _)| k[1..].try_into().ok())
|
||||
.map(|bytes| usize::from_be_bytes(bytes))
|
||||
.map(usize::from_be_bytes)
|
||||
{
|
||||
None => {
|
||||
std::mem::drop(iterator);
|
||||
@@ -444,9 +444,6 @@ impl ChainStore<Full> {
|
||||
}
|
||||
|
||||
read_store.write(batch)?;
|
||||
|
||||
std::mem::drop(snapshot_cf_handle);
|
||||
std::mem::drop(cf_handle);
|
||||
std::mem::drop(read_store);
|
||||
|
||||
self.store.write().unwrap().drop_cf(&snaphost.cf_name)?;
|
||||
@@ -461,7 +458,7 @@ impl ChainStore<Full> {
|
||||
let read_store = self.store.read().unwrap();
|
||||
let cf_handle = read_store.cf_handle(&self.cf_name).unwrap();
|
||||
|
||||
let key = StoreEntry::BlockHeaderIndex(Some(block_hash.clone())).get_key();
|
||||
let key = StoreEntry::BlockHeaderIndex(Some(*block_hash)).get_key();
|
||||
let data = read_store.get_pinned_cf(cf_handle, key)?;
|
||||
Ok(data
|
||||
.map(|data| {
|
||||
@@ -642,7 +639,6 @@ impl<T: StoreType> ChainStore<T> {
|
||||
);
|
||||
}
|
||||
|
||||
std::mem::drop(cf_handle);
|
||||
std::mem::drop(read_store);
|
||||
|
||||
self.store.write().unwrap().write(batch)?;
|
||||
|
||||
@@ -204,9 +204,8 @@ impl CFSync {
|
||||
if let BundleStatus::CFilters { cf_filters } = status {
|
||||
log::trace!("status: CFilters");
|
||||
|
||||
let last_sync_buried_height = (start_height + already_processed)
|
||||
.checked_sub(BURIED_CONFIRMATIONS)
|
||||
.unwrap_or(0);
|
||||
let last_sync_buried_height =
|
||||
(start_height + already_processed).saturating_sub(BURIED_CONFIRMATIONS);
|
||||
|
||||
for (filter_index, filter) in cf_filters.iter().enumerate() {
|
||||
let height = filter_index + start_height;
|
||||
@@ -280,10 +279,7 @@ where
|
||||
|
||||
match locators_map.get(&headers[0].prev_blockhash) {
|
||||
None => return Err(CompactFiltersError::InvalidHeaders),
|
||||
Some(from) => (
|
||||
store.start_snapshot(*from)?,
|
||||
headers[0].prev_blockhash.clone(),
|
||||
),
|
||||
Some(from) => (store.start_snapshot(*from)?, headers[0].prev_blockhash),
|
||||
}
|
||||
} else {
|
||||
return Err(CompactFiltersError::InvalidResponse);
|
||||
|
||||
@@ -168,7 +168,7 @@ pub struct ElectrumBlockchainConfig {
|
||||
/// Request retry count
|
||||
pub retry: u8,
|
||||
/// Request timeout (seconds)
|
||||
pub timeout: u8,
|
||||
pub timeout: Option<u8>,
|
||||
}
|
||||
|
||||
impl ConfigurableBlockchain for ElectrumBlockchain {
|
||||
@@ -178,8 +178,8 @@ impl ConfigurableBlockchain for ElectrumBlockchain {
|
||||
let socks5 = config.socks5.as_ref().map(Socks5Config::new);
|
||||
let electrum_config = ConfigBuilder::new()
|
||||
.retry(config.retry)
|
||||
.socks5(socks5)?
|
||||
.timeout(config.timeout)?
|
||||
.socks5(socks5)?
|
||||
.build();
|
||||
|
||||
Ok(ElectrumBlockchain(Client::from_config(
|
||||
|
||||
@@ -79,29 +79,9 @@ pub enum Capability {
|
||||
AccurateFees,
|
||||
}
|
||||
|
||||
/// Marker trait for a blockchain backend
|
||||
///
|
||||
/// This is a marker trait for blockchain types. It is automatically implemented for types that
|
||||
/// implement [`Blockchain`], so as a user of the library you won't have to implement this
|
||||
/// manually.
|
||||
///
|
||||
/// Users of the library will probably never have to implement this trait manually, but they
|
||||
/// could still need to import it to define types and structs with generics;
|
||||
/// Implementing only the marker trait is pointless, since [`OfflineBlockchain`]
|
||||
/// already does that, and whenever [`Blockchain`] is implemented, the marker trait is also
|
||||
/// automatically implemented by the library.
|
||||
pub trait BlockchainMarker {}
|
||||
|
||||
/// The [`BlockchainMarker`] marker trait is automatically implemented for [`Blockchain`] types
|
||||
impl<T: Blockchain> BlockchainMarker for T {}
|
||||
|
||||
/// Type that only implements [`BlockchainMarker`] and is always "offline"
|
||||
pub struct OfflineBlockchain;
|
||||
impl BlockchainMarker for OfflineBlockchain {}
|
||||
|
||||
/// Trait that defines the actions that must be supported by a blockchain backend
|
||||
#[maybe_async]
|
||||
pub trait Blockchain: BlockchainMarker {
|
||||
pub trait Blockchain {
|
||||
/// Return the set of [`Capability`] supported by this backend
|
||||
fn get_capabilities(&self) -> HashSet<Capability>;
|
||||
|
||||
@@ -189,7 +169,7 @@ pub fn progress() -> (Sender<ProgressData>, Receiver<ProgressData>) {
|
||||
|
||||
impl Progress for Sender<ProgressData> {
|
||||
fn update(&self, progress: f32, message: Option<String>) -> Result<(), Error> {
|
||||
if progress < 0.0 || progress > 100.0 {
|
||||
if !(0.0..=100.0).contains(&progress) {
|
||||
return Err(Error::InvalidProgressValue(progress));
|
||||
}
|
||||
|
||||
|
||||
750
src/cli.rs
750
src/cli.rs
@@ -1,750 +0,0 @@
|
||||
// Magical Bitcoin Library
|
||||
// Written in 2020 by
|
||||
// Alekos Filini <alekos.filini@gmail.com>
|
||||
//
|
||||
// Copyright (c) 2020 Magical Bitcoin
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
//! Command line interface
|
||||
//!
|
||||
//! This module provides a [structopt](https://docs.rs/crate/structopt) `struct` and `enum` that
|
||||
//! parse global wallet options and wallet subcommand options needed for a wallet command line
|
||||
//! interface.
|
||||
//!
|
||||
//! See the `repl.rs` example for how to use this module to create a simple command line REPL
|
||||
//! wallet application.
|
||||
//!
|
||||
//! See [`WalletOpt`] for global wallet options and [`WalletSubCommand`] for supported sub-commands.
|
||||
//!
|
||||
//! # Example
|
||||
//!
|
||||
//! ```
|
||||
//! # use bdk::bitcoin::Network;
|
||||
//! # use bdk::blockchain::esplora::EsploraBlockchainConfig;
|
||||
//! # use bdk::blockchain::{AnyBlockchain, ConfigurableBlockchain};
|
||||
//! # use bdk::blockchain::{AnyBlockchainConfig, ElectrumBlockchainConfig};
|
||||
//! # use bdk::cli::{self, WalletOpt, WalletSubCommand};
|
||||
//! # use bdk::database::MemoryDatabase;
|
||||
//! # use bdk::Wallet;
|
||||
//! # use bitcoin::hashes::core::str::FromStr;
|
||||
//! # use std::sync::Arc;
|
||||
//! # use structopt::StructOpt;
|
||||
//!
|
||||
//! // to get args from cli use:
|
||||
//! // let cli_opt = WalletOpt::from_args();
|
||||
//!
|
||||
//! let cli_args = vec!["repl", "--network", "testnet", "--descriptor",
|
||||
//! "wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)",
|
||||
//! "sync", "--max_addresses", "50"];
|
||||
//! let cli_opt = WalletOpt::from_iter(&cli_args);
|
||||
//!
|
||||
//! let network = Network::from_str(cli_opt.network.as_str()).unwrap_or(Network::Testnet);
|
||||
//!
|
||||
//! let descriptor = cli_opt.descriptor.as_str();
|
||||
//! let change_descriptor = cli_opt.change_descriptor.as_deref();
|
||||
//!
|
||||
//! let database = MemoryDatabase::new();
|
||||
//!
|
||||
//! let config = match cli_opt.esplora {
|
||||
//! Some(base_url) => AnyBlockchainConfig::Esplora(EsploraBlockchainConfig {
|
||||
//! base_url: base_url.to_string(),
|
||||
//! concurrency: Some(cli_opt.esplora_concurrency),
|
||||
//! }),
|
||||
//! None => AnyBlockchainConfig::Electrum(ElectrumBlockchainConfig {
|
||||
//! url: cli_opt.electrum,
|
||||
//! socks5: cli_opt.proxy,
|
||||
//! retry: 3,
|
||||
//! timeout: 5,
|
||||
//! }),
|
||||
//! };
|
||||
//!
|
||||
//! let wallet = Wallet::new(
|
||||
//! descriptor,
|
||||
//! change_descriptor,
|
||||
//! network,
|
||||
//! database,
|
||||
//! AnyBlockchain::from_config(&config).unwrap(),
|
||||
//! ).unwrap();
|
||||
//!
|
||||
//! let wallet = Arc::new(wallet);
|
||||
//!
|
||||
//! let result = cli::handle_wallet_subcommand(&wallet, cli_opt.subcommand).unwrap();
|
||||
//! println!("{}", serde_json::to_string_pretty(&result).unwrap());
|
||||
//! ```
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use std::str::FromStr;
|
||||
|
||||
use structopt::StructOpt;
|
||||
|
||||
#[allow(unused_imports)]
|
||||
use log::{debug, error, info, trace, LevelFilter};
|
||||
|
||||
use bitcoin::consensus::encode::{deserialize, serialize, serialize_hex};
|
||||
use bitcoin::hashes::hex::FromHex;
|
||||
use bitcoin::util::psbt::PartiallySignedTransaction;
|
||||
use bitcoin::{Address, OutPoint, Script, Txid};
|
||||
|
||||
use crate::blockchain::log_progress;
|
||||
use crate::error::Error;
|
||||
use crate::types::KeychainKind;
|
||||
use crate::{FeeRate, TxBuilder, Wallet};
|
||||
|
||||
/// Wallet global options and sub-command
|
||||
///
|
||||
/// A [structopt](https://docs.rs/crate/structopt) `struct` that parses wallet global options and
|
||||
/// sub-command from the command line or from a `String` vector. See [`WalletSubCommand`] for details
|
||||
/// on parsing sub-commands.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # use bdk::cli::{WalletOpt, WalletSubCommand};
|
||||
/// # use structopt::StructOpt;
|
||||
///
|
||||
/// let cli_args = vec!["repl", "--network", "testnet",
|
||||
/// "--descriptor", "wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/44'/1'/0'/0/*)",
|
||||
/// "sync", "--max_addresses", "50"];
|
||||
///
|
||||
/// // to get WalletOpt from OS command line args use:
|
||||
/// // let wallet_opt = WalletOpt::from_args();
|
||||
///
|
||||
/// let wallet_opt = WalletOpt::from_iter(&cli_args);
|
||||
///
|
||||
/// let expected_wallet_opt = WalletOpt {
|
||||
/// network: "testnet".to_string(),
|
||||
/// wallet: "main".to_string(),
|
||||
/// proxy: None,
|
||||
/// descriptor: "wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/44'/1'/0'/0/*)".to_string(),
|
||||
/// change_descriptor: None,
|
||||
/// log_level: "info".to_string(),
|
||||
/// #[cfg(feature = "esplora")]
|
||||
/// esplora: None,
|
||||
/// #[cfg(feature = "esplora")]
|
||||
/// esplora_concurrency: 4,
|
||||
/// electrum: "ssl://electrum.blockstream.info:60002".to_string(),
|
||||
/// subcommand: WalletSubCommand::Sync {
|
||||
/// max_addresses: Some(50)
|
||||
/// },
|
||||
/// };
|
||||
///
|
||||
/// assert_eq!(expected_wallet_opt, wallet_opt);
|
||||
/// ```
|
||||
|
||||
#[derive(Debug, StructOpt, Clone, PartialEq)]
|
||||
#[structopt(name = "BDK Wallet",
|
||||
version = option_env ! ("CARGO_PKG_VERSION").unwrap_or("unknown"),
|
||||
author = option_env ! ("CARGO_PKG_AUTHORS").unwrap_or(""))]
|
||||
pub struct WalletOpt {
|
||||
/// Sets the network
|
||||
#[structopt(
|
||||
name = "NETWORK",
|
||||
short = "n",
|
||||
long = "network",
|
||||
default_value = "testnet"
|
||||
)]
|
||||
pub network: String,
|
||||
/// Selects the wallet to use
|
||||
#[structopt(
|
||||
name = "WALLET_NAME",
|
||||
short = "w",
|
||||
long = "wallet",
|
||||
default_value = "main"
|
||||
)]
|
||||
pub wallet: String,
|
||||
#[cfg(feature = "electrum")]
|
||||
/// Sets the SOCKS5 proxy for the Electrum client
|
||||
#[structopt(name = "PROXY_SERVER:PORT", short = "p", long = "proxy")]
|
||||
pub proxy: Option<String>,
|
||||
/// Sets the descriptor to use for the external addresses
|
||||
#[structopt(name = "DESCRIPTOR", short = "d", long = "descriptor", required = true)]
|
||||
pub descriptor: String,
|
||||
/// Sets the descriptor to use for internal addresses
|
||||
#[structopt(name = "CHANGE_DESCRIPTOR", short = "c", long = "change_descriptor")]
|
||||
pub change_descriptor: Option<String>,
|
||||
/// Sets the logging level filter (off, error, warn, info, debug, trace)
|
||||
#[structopt(long = "log_level", short = "l", default_value = "info")]
|
||||
pub log_level: String,
|
||||
#[cfg(feature = "esplora")]
|
||||
/// Use the esplora server if given as parameter
|
||||
#[structopt(name = "ESPLORA_URL", short = "e", long = "esplora")]
|
||||
pub esplora: Option<String>,
|
||||
#[cfg(feature = "esplora")]
|
||||
/// Concurrency of requests made to the esplora server
|
||||
#[structopt(
|
||||
name = "ESPLORA_CONCURRENCY",
|
||||
long = "esplora_concurrency",
|
||||
default_value = "4"
|
||||
)]
|
||||
pub esplora_concurrency: u8,
|
||||
#[cfg(feature = "electrum")]
|
||||
/// Sets the Electrum server to use
|
||||
#[structopt(
|
||||
name = "SERVER:PORT",
|
||||
short = "s",
|
||||
long = "server",
|
||||
default_value = "ssl://electrum.blockstream.info:60002"
|
||||
)]
|
||||
pub electrum: String,
|
||||
/// Wallet sub-command
|
||||
#[structopt(subcommand)]
|
||||
pub subcommand: WalletSubCommand,
|
||||
}
|
||||
|
||||
/// Wallet sub-command
|
||||
///
|
||||
/// A [structopt](https://docs.rs/crate/structopt) enum that parses wallet sub-command arguments from
|
||||
/// the command line or from a `String` vector, such as in the [`repl`](https://github.com/bitcoindevkit/bdk/blob/master/examples/repl.rs)
|
||||
/// example app.
|
||||
///
|
||||
/// Additional "external" sub-commands can be captured via the [`WalletSubCommand::Other`] enum and passed to a
|
||||
/// custom `structopt` or another parser. See [structopt "External subcommands"](https://docs.rs/structopt/0.3.21/structopt/index.html#external-subcommands)
|
||||
/// for more information.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # use bdk::cli::WalletSubCommand;
|
||||
/// # use structopt::StructOpt;
|
||||
///
|
||||
/// let sync_sub_command = WalletSubCommand::from_iter(&["repl", "sync", "--max_addresses", "50"]);
|
||||
/// assert!(matches!(
|
||||
/// sync_sub_command,
|
||||
/// WalletSubCommand::Sync {
|
||||
/// max_addresses: Some(50)
|
||||
/// }
|
||||
/// ));
|
||||
///
|
||||
/// let other_sub_command = WalletSubCommand::from_iter(&["repl", "custom", "--param1", "20"]);
|
||||
/// let external_args: Vec<String> = vec!["custom".to_string(), "--param1".to_string(), "20".to_string()];
|
||||
/// assert!(matches!(
|
||||
/// other_sub_command,
|
||||
/// WalletSubCommand::Other(v) if v == external_args
|
||||
/// ));
|
||||
/// ```
|
||||
///
|
||||
/// To capture wallet sub-commands from a string vector without a preceeding binary name you can
|
||||
/// create a custom struct the includes the `NoBinaryName` clap setting and wraps the WalletSubCommand
|
||||
/// enum. See also the [`repl`](https://github.com/bitcoindevkit/bdk/blob/master/examples/repl.rs)
|
||||
/// example app.
|
||||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
/// # use bdk::cli::WalletSubCommand;
|
||||
/// # use structopt::StructOpt;
|
||||
/// # use clap::AppSettings;
|
||||
///
|
||||
/// #[derive(Debug, StructOpt, Clone, PartialEq)]
|
||||
/// #[structopt(name = "BDK Wallet", setting = AppSettings::NoBinaryName,
|
||||
/// version = option_env ! ("CARGO_PKG_VERSION").unwrap_or("unknown"),
|
||||
/// author = option_env ! ("CARGO_PKG_AUTHORS").unwrap_or(""))]
|
||||
/// struct ReplOpt {
|
||||
/// /// Wallet sub-command
|
||||
/// #[structopt(subcommand)]
|
||||
/// pub subcommand: WalletSubCommand,
|
||||
/// }
|
||||
/// ```
|
||||
#[derive(Debug, StructOpt, Clone, PartialEq)]
|
||||
#[structopt(
|
||||
rename_all = "snake",
|
||||
long_about = "A modern, lightweight, descriptor-based wallet"
|
||||
)]
|
||||
pub enum WalletSubCommand {
|
||||
/// Generates a new external address
|
||||
GetNewAddress,
|
||||
/// Syncs with the chosen blockchain server
|
||||
Sync {
|
||||
/// max addresses to consider
|
||||
#[structopt(short = "v", long = "max_addresses")]
|
||||
max_addresses: Option<u32>,
|
||||
},
|
||||
/// Lists the available spendable UTXOs
|
||||
ListUnspent,
|
||||
/// Lists all the incoming and outgoing transactions of the wallet
|
||||
ListTransactions,
|
||||
/// Returns the current wallet balance
|
||||
GetBalance,
|
||||
/// Creates a new unsigned transaction
|
||||
CreateTx {
|
||||
/// Adds a recipient to the transaction
|
||||
#[structopt(name = "ADDRESS:SAT", long = "to", required = true, parse(try_from_str = parse_recipient))]
|
||||
recipients: Vec<(Script, u64)>,
|
||||
/// Sends all the funds (or all the selected utxos). Requires only one recipients of value 0
|
||||
#[structopt(short = "all", long = "send_all")]
|
||||
send_all: bool,
|
||||
/// Enables Replace-By-Fee (BIP125)
|
||||
#[structopt(short = "rbf", long = "enable_rbf")]
|
||||
enable_rbf: bool,
|
||||
/// Make a PSBT that can be signed by offline signers and hardware wallets. Forces the addition of `non_witness_utxo` and more details to let the signer identify the change output.
|
||||
#[structopt(long = "offline_signer")]
|
||||
offline_signer: bool,
|
||||
/// Selects which utxos *must* be spent
|
||||
#[structopt(name = "MUST_SPEND_TXID:VOUT", long = "utxos", parse(try_from_str = parse_outpoint))]
|
||||
utxos: Option<Vec<OutPoint>>,
|
||||
/// Marks a utxo as unspendable
|
||||
#[structopt(name = "CANT_SPEND_TXID:VOUT", long = "unspendable", parse(try_from_str = parse_outpoint))]
|
||||
unspendable: Option<Vec<OutPoint>>,
|
||||
/// Fee rate to use in sat/vbyte
|
||||
#[structopt(name = "SATS_VBYTE", short = "fee", long = "fee_rate")]
|
||||
fee_rate: Option<f32>,
|
||||
/// Selects which policy should be used to satisfy the external descriptor
|
||||
#[structopt(name = "EXT_POLICY", long = "external_policy")]
|
||||
external_policy: Option<String>,
|
||||
/// Selects which policy should be used to satisfy the internal descriptor
|
||||
#[structopt(name = "INT_POLICY", long = "internal_policy")]
|
||||
internal_policy: Option<String>,
|
||||
},
|
||||
/// Bumps the fees of an RBF transaction
|
||||
BumpFee {
|
||||
/// TXID of the transaction to update
|
||||
#[structopt(name = "TXID", short = "txid", long = "txid")]
|
||||
txid: String,
|
||||
/// Allows the wallet to reduce the amount of the only output in order to increase fees. This is generally the expected behavior for transactions originally created with `send_all`
|
||||
#[structopt(short = "all", long = "send_all")]
|
||||
send_all: bool,
|
||||
/// Make a PSBT that can be signed by offline signers and hardware wallets. Forces the addition of `non_witness_utxo` and more details to let the signer identify the change output.
|
||||
#[structopt(long = "offline_signer")]
|
||||
offline_signer: bool,
|
||||
/// Selects which utxos *must* be added to the tx. Unconfirmed utxos cannot be used
|
||||
#[structopt(name = "MUST_SPEND_TXID:VOUT", long = "utxos", parse(try_from_str = parse_outpoint))]
|
||||
utxos: Option<Vec<OutPoint>>,
|
||||
/// Marks an utxo as unspendable, in case more inputs are needed to cover the extra fees
|
||||
#[structopt(name = "CANT_SPEND_TXID:VOUT", long = "unspendable", parse(try_from_str = parse_outpoint))]
|
||||
unspendable: Option<Vec<OutPoint>>,
|
||||
/// The new targeted fee rate in sat/vbyte
|
||||
#[structopt(name = "SATS_VBYTE", short = "fee", long = "fee_rate")]
|
||||
fee_rate: f32,
|
||||
},
|
||||
/// Returns the available spending policies for the descriptor
|
||||
Policies,
|
||||
/// Returns the public version of the wallet's descriptor(s)
|
||||
PublicDescriptor,
|
||||
/// Signs and tries to finalize a PSBT
|
||||
Sign {
|
||||
/// Sets the PSBT to sign
|
||||
#[structopt(name = "BASE64_PSBT", long = "psbt")]
|
||||
psbt: String,
|
||||
/// Assume the blockchain has reached a specific height. This affects the transaction finalization, if there are timelocks in the descriptor
|
||||
#[structopt(name = "HEIGHT", long = "assume_height")]
|
||||
assume_height: Option<u32>,
|
||||
},
|
||||
/// Broadcasts a transaction to the network. Takes either a raw transaction or a PSBT to extract
|
||||
Broadcast {
|
||||
/// Sets the PSBT to sign
|
||||
#[structopt(
|
||||
name = "BASE64_PSBT",
|
||||
long = "psbt",
|
||||
required_unless = "RAWTX",
|
||||
conflicts_with = "RAWTX"
|
||||
)]
|
||||
psbt: Option<String>,
|
||||
/// Sets the raw transaction to broadcast
|
||||
#[structopt(
|
||||
name = "RAWTX",
|
||||
long = "tx",
|
||||
required_unless = "BASE64_PSBT",
|
||||
conflicts_with = "BASE64_PSBT"
|
||||
)]
|
||||
tx: Option<String>,
|
||||
},
|
||||
/// Extracts a raw transaction from a PSBT
|
||||
ExtractPsbt {
|
||||
/// Sets the PSBT to extract
|
||||
#[structopt(name = "BASE64_PSBT", long = "psbt")]
|
||||
psbt: String,
|
||||
},
|
||||
/// Finalizes a PSBT
|
||||
FinalizePsbt {
|
||||
/// Sets the PSBT to finalize
|
||||
#[structopt(name = "BASE64_PSBT", long = "psbt")]
|
||||
psbt: String,
|
||||
/// Assume the blockchain has reached a specific height
|
||||
#[structopt(name = "HEIGHT", long = "assume_height")]
|
||||
assume_height: Option<u32>,
|
||||
},
|
||||
/// Combines multiple PSBTs into one
|
||||
CombinePsbt {
|
||||
/// Add one PSBT to combine. This option can be repeated multiple times, one for each PSBT
|
||||
#[structopt(name = "BASE64_PSBT", long = "psbt", required = true)]
|
||||
psbt: Vec<String>,
|
||||
},
|
||||
/// Put any extra arguments into this Vec
|
||||
#[structopt(external_subcommand)]
|
||||
Other(Vec<String>),
|
||||
}
|
||||
|
||||
fn parse_recipient(s: &str) -> Result<(Script, u64), String> {
|
||||
let parts: Vec<_> = s.split(':').collect();
|
||||
if parts.len() != 2 {
|
||||
return Err("Invalid format".to_string());
|
||||
}
|
||||
|
||||
let addr = Address::from_str(&parts[0]);
|
||||
if let Err(e) = addr {
|
||||
return Err(format!("{:?}", e));
|
||||
}
|
||||
let val = u64::from_str(&parts[1]);
|
||||
if let Err(e) = val {
|
||||
return Err(format!("{:?}", e));
|
||||
}
|
||||
|
||||
Ok((addr.unwrap().script_pubkey(), val.unwrap()))
|
||||
}
|
||||
|
||||
fn parse_outpoint(s: &str) -> Result<OutPoint, String> {
|
||||
OutPoint::from_str(s).map_err(|e| format!("{:?}", e))
|
||||
}
|
||||
|
||||
/// Execute a wallet sub-command with a given [`Wallet`].
|
||||
///
|
||||
/// Wallet sub-commands are described in [`WalletSubCommand`]. See [`super::cli`] for example usage.
|
||||
#[maybe_async]
|
||||
pub fn handle_wallet_subcommand<C, D>(
|
||||
wallet: &Wallet<C, D>,
|
||||
wallet_subcommand: WalletSubCommand,
|
||||
) -> Result<serde_json::Value, Error>
|
||||
where
|
||||
C: crate::blockchain::Blockchain,
|
||||
D: crate::database::BatchDatabase,
|
||||
{
|
||||
match wallet_subcommand {
|
||||
WalletSubCommand::GetNewAddress => Ok(json!({"address": wallet.get_new_address()?})),
|
||||
WalletSubCommand::Sync { max_addresses } => {
|
||||
maybe_await!(wallet.sync(log_progress(), max_addresses))?;
|
||||
Ok(json!({}))
|
||||
}
|
||||
WalletSubCommand::ListUnspent => Ok(serde_json::to_value(&wallet.list_unspent()?)?),
|
||||
WalletSubCommand::ListTransactions => {
|
||||
Ok(serde_json::to_value(&wallet.list_transactions(false)?)?)
|
||||
}
|
||||
WalletSubCommand::GetBalance => Ok(json!({"satoshi": wallet.get_balance()?})),
|
||||
WalletSubCommand::CreateTx {
|
||||
recipients,
|
||||
send_all,
|
||||
enable_rbf,
|
||||
offline_signer,
|
||||
utxos,
|
||||
unspendable,
|
||||
fee_rate,
|
||||
external_policy,
|
||||
internal_policy,
|
||||
} => {
|
||||
let mut tx_builder = TxBuilder::new();
|
||||
|
||||
if send_all {
|
||||
tx_builder = tx_builder
|
||||
.drain_wallet()
|
||||
.set_single_recipient(recipients[0].0.clone());
|
||||
} else {
|
||||
tx_builder = tx_builder.set_recipients(recipients);
|
||||
}
|
||||
|
||||
if enable_rbf {
|
||||
tx_builder = tx_builder.enable_rbf();
|
||||
}
|
||||
|
||||
if offline_signer {
|
||||
tx_builder = tx_builder
|
||||
.force_non_witness_utxo()
|
||||
.include_output_redeem_witness_script();
|
||||
}
|
||||
|
||||
if let Some(fee_rate) = fee_rate {
|
||||
tx_builder = tx_builder.fee_rate(FeeRate::from_sat_per_vb(fee_rate));
|
||||
}
|
||||
|
||||
if let Some(utxos) = utxos {
|
||||
tx_builder = tx_builder.utxos(utxos).manually_selected_only();
|
||||
}
|
||||
|
||||
if let Some(unspendable) = unspendable {
|
||||
tx_builder = tx_builder.unspendable(unspendable);
|
||||
}
|
||||
|
||||
let policies = vec![
|
||||
external_policy.map(|p| (p, KeychainKind::External)),
|
||||
internal_policy.map(|p| (p, KeychainKind::Internal)),
|
||||
];
|
||||
|
||||
for (policy, keychain) in policies.into_iter().filter_map(|x| x) {
|
||||
let policy = serde_json::from_str::<BTreeMap<String, Vec<usize>>>(&policy)
|
||||
.map_err(|s| Error::Generic(s.to_string()))?;
|
||||
tx_builder = tx_builder.policy_path(policy, keychain);
|
||||
}
|
||||
|
||||
let (psbt, details) = wallet.create_tx(tx_builder)?;
|
||||
Ok(json!({"psbt": base64::encode(&serialize(&psbt)),"details": details,}))
|
||||
}
|
||||
WalletSubCommand::BumpFee {
|
||||
txid,
|
||||
send_all,
|
||||
offline_signer,
|
||||
utxos,
|
||||
unspendable,
|
||||
fee_rate,
|
||||
} => {
|
||||
let txid = Txid::from_str(txid.as_str()).map_err(|s| Error::Generic(s.to_string()))?;
|
||||
|
||||
let mut tx_builder = TxBuilder::new().fee_rate(FeeRate::from_sat_per_vb(fee_rate));
|
||||
|
||||
if send_all {
|
||||
tx_builder = tx_builder.maintain_single_recipient();
|
||||
}
|
||||
|
||||
if offline_signer {
|
||||
tx_builder = tx_builder
|
||||
.force_non_witness_utxo()
|
||||
.include_output_redeem_witness_script();
|
||||
}
|
||||
|
||||
if let Some(utxos) = utxos {
|
||||
tx_builder = tx_builder.utxos(utxos);
|
||||
}
|
||||
|
||||
if let Some(unspendable) = unspendable {
|
||||
tx_builder = tx_builder.unspendable(unspendable);
|
||||
}
|
||||
|
||||
let (psbt, details) = wallet.bump_fee(&txid, tx_builder)?;
|
||||
Ok(json!({"psbt": base64::encode(&serialize(&psbt)),"details": details,}))
|
||||
}
|
||||
WalletSubCommand::Policies => Ok(json!({
|
||||
"external": wallet.policies(KeychainKind::External)?,
|
||||
"internal": wallet.policies(KeychainKind::Internal)?,
|
||||
})),
|
||||
WalletSubCommand::PublicDescriptor => Ok(json!({
|
||||
"external": wallet.public_descriptor(KeychainKind::External)?.map(|d| d.to_string()),
|
||||
"internal": wallet.public_descriptor(KeychainKind::Internal)?.map(|d| d.to_string()),
|
||||
})),
|
||||
WalletSubCommand::Sign {
|
||||
psbt,
|
||||
assume_height,
|
||||
} => {
|
||||
let psbt = base64::decode(&psbt).unwrap();
|
||||
let psbt: PartiallySignedTransaction = deserialize(&psbt).unwrap();
|
||||
let (psbt, finalized) = wallet.sign(psbt, assume_height)?;
|
||||
Ok(json!({"psbt": base64::encode(&serialize(&psbt)),"is_finalized": finalized,}))
|
||||
}
|
||||
WalletSubCommand::Broadcast { psbt, tx } => {
|
||||
let tx = match (psbt, tx) {
|
||||
(Some(psbt), None) => {
|
||||
let psbt = base64::decode(&psbt).unwrap();
|
||||
let psbt: PartiallySignedTransaction = deserialize(&psbt).unwrap();
|
||||
psbt.extract_tx()
|
||||
}
|
||||
(None, Some(tx)) => deserialize(&Vec::<u8>::from_hex(&tx).unwrap()).unwrap(),
|
||||
(Some(_), Some(_)) => panic!("Both `psbt` and `tx` options not allowed"),
|
||||
(None, None) => panic!("Missing `psbt` and `tx` option"),
|
||||
};
|
||||
|
||||
let txid = maybe_await!(wallet.broadcast(tx))?;
|
||||
Ok(json!({ "txid": txid }))
|
||||
}
|
||||
WalletSubCommand::ExtractPsbt { psbt } => {
|
||||
let psbt = base64::decode(&psbt).unwrap();
|
||||
let psbt: PartiallySignedTransaction = deserialize(&psbt).unwrap();
|
||||
Ok(json!({"raw_tx": serialize_hex(&psbt.extract_tx()),}))
|
||||
}
|
||||
WalletSubCommand::FinalizePsbt {
|
||||
psbt,
|
||||
assume_height,
|
||||
} => {
|
||||
let psbt = base64::decode(&psbt).unwrap();
|
||||
let psbt: PartiallySignedTransaction = deserialize(&psbt).unwrap();
|
||||
|
||||
let (psbt, finalized) = wallet.finalize_psbt(psbt, assume_height)?;
|
||||
Ok(json!({ "psbt": base64::encode(&serialize(&psbt)),"is_finalized": finalized,}))
|
||||
}
|
||||
WalletSubCommand::CombinePsbt { psbt } => {
|
||||
let mut psbts = psbt
|
||||
.iter()
|
||||
.map(|s| {
|
||||
let psbt = base64::decode(&s).unwrap();
|
||||
let psbt: PartiallySignedTransaction = deserialize(&psbt).unwrap();
|
||||
psbt
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let init_psbt = psbts.pop().unwrap();
|
||||
let final_psbt = psbts
|
||||
.into_iter()
|
||||
.try_fold::<_, _, Result<PartiallySignedTransaction, Error>>(
|
||||
init_psbt,
|
||||
|mut acc, x| {
|
||||
acc.merge(x)?;
|
||||
Ok(acc)
|
||||
},
|
||||
)?;
|
||||
|
||||
Ok(json!({ "psbt": base64::encode(&serialize(&final_psbt)) }))
|
||||
}
|
||||
WalletSubCommand::Other(_) => Ok(json!({})),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::{WalletOpt, WalletSubCommand};
|
||||
use bitcoin::hashes::core::str::FromStr;
|
||||
use bitcoin::{Address, OutPoint};
|
||||
use structopt::StructOpt;
|
||||
|
||||
#[test]
|
||||
fn test_get_new_address() {
|
||||
let cli_args = vec!["repl", "--network", "bitcoin",
|
||||
"--descriptor", "wpkh(xpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/0/*)",
|
||||
"--change_descriptor", "wpkh(xpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/1/*)",
|
||||
"--esplora", "https://blockstream.info/api/",
|
||||
"--esplora_concurrency", "5",
|
||||
"get_new_address"];
|
||||
|
||||
let wallet_opt = WalletOpt::from_iter(&cli_args);
|
||||
|
||||
let expected_wallet_opt = WalletOpt {
|
||||
network: "bitcoin".to_string(),
|
||||
wallet: "main".to_string(),
|
||||
proxy: None,
|
||||
descriptor: "wpkh(xpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/0/*)".to_string(),
|
||||
change_descriptor: Some("wpkh(xpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/1/*)".to_string()),
|
||||
log_level: "info".to_string(),
|
||||
#[cfg(feature = "esplora")]
|
||||
esplora: Some("https://blockstream.info/api/".to_string()),
|
||||
#[cfg(feature = "esplora")]
|
||||
esplora_concurrency: 5,
|
||||
electrum: "ssl://electrum.blockstream.info:60002".to_string(),
|
||||
subcommand: WalletSubCommand::GetNewAddress,
|
||||
};
|
||||
|
||||
assert_eq!(expected_wallet_opt, wallet_opt);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sync() {
|
||||
let cli_args = vec!["repl", "--network", "testnet",
|
||||
"--descriptor", "wpkh(tpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/0/*)",
|
||||
"sync", "--max_addresses", "50"];
|
||||
|
||||
let wallet_opt = WalletOpt::from_iter(&cli_args);
|
||||
|
||||
let expected_wallet_opt = WalletOpt {
|
||||
network: "testnet".to_string(),
|
||||
wallet: "main".to_string(),
|
||||
proxy: None,
|
||||
descriptor: "wpkh(tpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/0/*)".to_string(),
|
||||
change_descriptor: None,
|
||||
log_level: "info".to_string(),
|
||||
#[cfg(feature = "esplora")]
|
||||
esplora: None,
|
||||
#[cfg(feature = "esplora")]
|
||||
esplora_concurrency: 4,
|
||||
electrum: "ssl://electrum.blockstream.info:60002".to_string(),
|
||||
subcommand: WalletSubCommand::Sync {
|
||||
max_addresses: Some(50)
|
||||
},
|
||||
};
|
||||
|
||||
assert_eq!(expected_wallet_opt, wallet_opt);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_create_tx() {
|
||||
let cli_args = vec!["repl", "--network", "testnet", "--proxy", "127.0.0.1:9150",
|
||||
"--descriptor", "wpkh(tpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/0/*)",
|
||||
"--change_descriptor", "wpkh(tpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/1/*)",
|
||||
"--server","ssl://electrum.blockstream.info:50002",
|
||||
"create_tx", "--to", "n2Z3YNXtceeJhFkTknVaNjT1mnCGWesykJ:123456","mjDZ34icH4V2k9GmC8niCrhzVuR3z8Mgkf:78910",
|
||||
"--utxos","87345e46bfd702d24d54890cc094d08a005f773b27c8f965dfe0eb1e23eef88e:1",
|
||||
"--utxos","87345e46bfd702d24d54890cc094d08a005f773b27c8f965dfe0eb1e23eef88e:2"];
|
||||
|
||||
let wallet_opt = WalletOpt::from_iter(&cli_args);
|
||||
|
||||
let script1 = Address::from_str("n2Z3YNXtceeJhFkTknVaNjT1mnCGWesykJ")
|
||||
.unwrap()
|
||||
.script_pubkey();
|
||||
let script2 = Address::from_str("mjDZ34icH4V2k9GmC8niCrhzVuR3z8Mgkf")
|
||||
.unwrap()
|
||||
.script_pubkey();
|
||||
let outpoint1 = OutPoint::from_str(
|
||||
"87345e46bfd702d24d54890cc094d08a005f773b27c8f965dfe0eb1e23eef88e:1",
|
||||
)
|
||||
.unwrap();
|
||||
let outpoint2 = OutPoint::from_str(
|
||||
"87345e46bfd702d24d54890cc094d08a005f773b27c8f965dfe0eb1e23eef88e:2",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let expected_wallet_opt = WalletOpt {
|
||||
network: "testnet".to_string(),
|
||||
wallet: "main".to_string(),
|
||||
proxy: Some("127.0.0.1:9150".to_string()),
|
||||
descriptor: "wpkh(tpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/0/*)".to_string(),
|
||||
change_descriptor: Some("wpkh(tpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/1/*)".to_string()),
|
||||
log_level: "info".to_string(),
|
||||
#[cfg(feature = "esplora")]
|
||||
esplora: None,
|
||||
#[cfg(feature = "esplora")]
|
||||
esplora_concurrency: 4,
|
||||
electrum: "ssl://electrum.blockstream.info:50002".to_string(),
|
||||
subcommand: WalletSubCommand::CreateTx {
|
||||
recipients: vec![(script1, 123456), (script2, 78910)],
|
||||
send_all: false,
|
||||
enable_rbf: false,
|
||||
offline_signer: false,
|
||||
utxos: Some(vec!(outpoint1, outpoint2)),
|
||||
unspendable: None,
|
||||
fee_rate: None,
|
||||
external_policy: None,
|
||||
internal_policy: None,
|
||||
},
|
||||
};
|
||||
|
||||
assert_eq!(expected_wallet_opt, wallet_opt);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_broadcast() {
|
||||
let cli_args = vec!["repl", "--network", "testnet",
|
||||
"--descriptor", "wpkh(tpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/0/*)",
|
||||
"broadcast",
|
||||
"--psbt", "cHNidP8BAEICAAAAASWhGE1AhvtO+2GjJHopssFmgfbq+WweHd8zN/DeaqmDAAAAAAD/////AQAAAAAAAAAABmoEAAECAwAAAAAAAAA="];
|
||||
|
||||
let wallet_opt = WalletOpt::from_iter(&cli_args);
|
||||
|
||||
let expected_wallet_opt = WalletOpt {
|
||||
network: "testnet".to_string(),
|
||||
wallet: "main".to_string(),
|
||||
proxy: None,
|
||||
descriptor: "wpkh(tpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/0/*)".to_string(),
|
||||
change_descriptor: None,
|
||||
log_level: "info".to_string(),
|
||||
#[cfg(feature = "esplora")]
|
||||
esplora: None,
|
||||
#[cfg(feature = "esplora")]
|
||||
esplora_concurrency: 4,
|
||||
electrum: "ssl://electrum.blockstream.info:60002".to_string(),
|
||||
subcommand: WalletSubCommand::Broadcast {
|
||||
psbt: Some("cHNidP8BAEICAAAAASWhGE1AhvtO+2GjJHopssFmgfbq+WweHd8zN/DeaqmDAAAAAAD/////AQAAAAAAAAAABmoEAAECAwAAAAAAAAA=".to_string()),
|
||||
tx: None
|
||||
},
|
||||
};
|
||||
|
||||
assert_eq!(expected_wallet_opt, wallet_opt);
|
||||
}
|
||||
}
|
||||
@@ -29,21 +29,19 @@
|
||||
//!
|
||||
//! ## Example
|
||||
//!
|
||||
//! In this example, `wallet_memory` and `wallet_sled` have the same type of `Wallet<OfflineBlockchain, AnyDatabase>`.
|
||||
//! In this example, `wallet_memory` and `wallet_sled` have the same type of `Wallet<(), AnyDatabase>`.
|
||||
//!
|
||||
//! ```no_run
|
||||
//! # use bitcoin::Network;
|
||||
//! # use bdk::database::{AnyDatabase, MemoryDatabase};
|
||||
//! # use bdk::{Wallet, OfflineWallet};
|
||||
//! let memory = MemoryDatabase::default().into();
|
||||
//! let wallet_memory: OfflineWallet<AnyDatabase> =
|
||||
//! Wallet::new_offline("...", None, Network::Testnet, memory)?;
|
||||
//! # use bdk::{Wallet};
|
||||
//! let memory = MemoryDatabase::default();
|
||||
//! let wallet_memory = Wallet::new_offline("...", None, Network::Testnet, memory)?;
|
||||
//!
|
||||
//! # #[cfg(feature = "key-value-db")]
|
||||
//! # {
|
||||
//! let sled = sled::open("my-database")?.open_tree("default_tree")?.into();
|
||||
//! let wallet_sled: OfflineWallet<AnyDatabase> =
|
||||
//! Wallet::new_offline("...", None, Network::Testnet, sled)?;
|
||||
//! let sled = sled::open("my-database")?.open_tree("default_tree")?;
|
||||
//! let wallet_sled = Wallet::new_offline("...", None, Network::Testnet, sled)?;
|
||||
//! # }
|
||||
//! # Ok::<(), bdk::Error>(())
|
||||
//! ```
|
||||
@@ -54,10 +52,10 @@
|
||||
//! ```no_run
|
||||
//! # use bitcoin::Network;
|
||||
//! # use bdk::database::*;
|
||||
//! # use bdk::{Wallet, OfflineWallet};
|
||||
//! # use bdk::{Wallet};
|
||||
//! let config = serde_json::from_str("...")?;
|
||||
//! let database = AnyDatabase::from_config(&config)?;
|
||||
//! let wallet: OfflineWallet<_> = Wallet::new_offline("...", None, Network::Testnet, database)?;
|
||||
//! let wallet = Wallet::new_offline("...", None, Network::Testnet, database)?;
|
||||
//! # Ok::<(), bdk::Error>(())
|
||||
//! ```
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
|
||||
use std::iter::FromIterator;
|
||||
|
||||
use crate::descriptor::Error;
|
||||
use crate::descriptor::DescriptorError;
|
||||
|
||||
const INPUT_CHARSET: &str = "0123456789()[],'/*abcdefgh@:$%{}IJKLMNOPQRSTUVWXYZ&+-.;<=>?!^_|~ijklmnopqrstuvwxyzABCDEFGH`#\"\\ ";
|
||||
const CHECKSUM_CHARSET: &str = "qpzry9x8gf2tvdw0s3jn54khce6mua7l";
|
||||
@@ -57,14 +57,14 @@ fn poly_mod(mut c: u64, val: u64) -> u64 {
|
||||
}
|
||||
|
||||
/// Compute the checksum of a descriptor
|
||||
pub fn get_checksum(desc: &str) -> Result<String, Error> {
|
||||
pub fn get_checksum(desc: &str) -> Result<String, DescriptorError> {
|
||||
let mut c = 1;
|
||||
let mut cls = 0;
|
||||
let mut clscount = 0;
|
||||
for ch in desc.chars() {
|
||||
let pos = INPUT_CHARSET
|
||||
.find(ch)
|
||||
.ok_or(Error::InvalidDescriptorCharacter(ch))? as u64;
|
||||
.ok_or(DescriptorError::InvalidDescriptorCharacter(ch))? as u64;
|
||||
c = poly_mod(c, pos & 31);
|
||||
cls = cls * 3 + (pos >> 5);
|
||||
clscount += 1;
|
||||
@@ -120,7 +120,7 @@ mod test {
|
||||
|
||||
assert!(matches!(
|
||||
get_checksum(&invalid_desc).err(),
|
||||
Some(Error::InvalidDescriptorCharacter(invalid_char)) if invalid_char == sparkle_heart
|
||||
Some(DescriptorError::InvalidDescriptorCharacter(invalid_char)) if invalid_char == sparkle_heart
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,6 +60,7 @@ macro_rules! impl_top_level_pk {
|
||||
|
||||
$key.to_descriptor_key()
|
||||
.and_then(|key: DescriptorKey<$ctx>| key.extract(&secp))
|
||||
.map_err($crate::descriptor::DescriptorError::Key)
|
||||
.map(|(pk, key_map, valid_networks)| {
|
||||
(
|
||||
$crate::miniscript::Descriptor::<
|
||||
@@ -75,11 +76,17 @@ macro_rules! impl_top_level_pk {
|
||||
#[doc(hidden)]
|
||||
#[macro_export]
|
||||
macro_rules! impl_leaf_opcode {
|
||||
( $terminal_variant:ident ) => {
|
||||
( $terminal_variant:ident ) => {{
|
||||
use $crate::descriptor::CheckMiniscript;
|
||||
|
||||
$crate::miniscript::Miniscript::from_ast(
|
||||
$crate::miniscript::miniscript::decode::Terminal::$terminal_variant,
|
||||
)
|
||||
.map_err($crate::Error::Miniscript)
|
||||
.map_err($crate::descriptor::DescriptorError::Miniscript)
|
||||
.and_then(|minisc| {
|
||||
minisc.check_minsicript()?;
|
||||
Ok(minisc)
|
||||
})
|
||||
.map(|minisc| {
|
||||
(
|
||||
minisc,
|
||||
@@ -87,17 +94,23 @@ macro_rules! impl_leaf_opcode {
|
||||
$crate::keys::any_network(),
|
||||
)
|
||||
})
|
||||
};
|
||||
}};
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[macro_export]
|
||||
macro_rules! impl_leaf_opcode_value {
|
||||
( $terminal_variant:ident, $value:expr ) => {
|
||||
( $terminal_variant:ident, $value:expr ) => {{
|
||||
use $crate::descriptor::CheckMiniscript;
|
||||
|
||||
$crate::miniscript::Miniscript::from_ast(
|
||||
$crate::miniscript::miniscript::decode::Terminal::$terminal_variant($value),
|
||||
)
|
||||
.map_err($crate::Error::Miniscript)
|
||||
.map_err($crate::descriptor::DescriptorError::Miniscript)
|
||||
.and_then(|minisc| {
|
||||
minisc.check_minsicript()?;
|
||||
Ok(minisc)
|
||||
})
|
||||
.map(|minisc| {
|
||||
(
|
||||
minisc,
|
||||
@@ -105,17 +118,23 @@ macro_rules! impl_leaf_opcode_value {
|
||||
$crate::keys::any_network(),
|
||||
)
|
||||
})
|
||||
};
|
||||
}};
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[macro_export]
|
||||
macro_rules! impl_leaf_opcode_value_two {
|
||||
( $terminal_variant:ident, $one:expr, $two:expr ) => {
|
||||
( $terminal_variant:ident, $one:expr, $two:expr ) => {{
|
||||
use $crate::descriptor::CheckMiniscript;
|
||||
|
||||
$crate::miniscript::Miniscript::from_ast(
|
||||
$crate::miniscript::miniscript::decode::Terminal::$terminal_variant($one, $two),
|
||||
)
|
||||
.map_err($crate::Error::Miniscript)
|
||||
.map_err($crate::descriptor::DescriptorError::Miniscript)
|
||||
.and_then(|minisc| {
|
||||
minisc.check_minsicript()?;
|
||||
Ok(minisc)
|
||||
})
|
||||
.map(|minisc| {
|
||||
(
|
||||
minisc,
|
||||
@@ -123,13 +142,15 @@ macro_rules! impl_leaf_opcode_value_two {
|
||||
$crate::keys::any_network(),
|
||||
)
|
||||
})
|
||||
};
|
||||
}};
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[macro_export]
|
||||
macro_rules! impl_node_opcode_two {
|
||||
( $terminal_variant:ident, $( $inner:tt )* ) => ({
|
||||
use $crate::descriptor::CheckMiniscript;
|
||||
|
||||
let inner = $crate::fragment_internal!( @t $( $inner )* );
|
||||
let (a, b) = $crate::descriptor::dsl::TupleTwo::from(inner).flattened();
|
||||
|
||||
@@ -139,10 +160,14 @@ macro_rules! impl_node_opcode_two {
|
||||
// join key_maps
|
||||
a_keymap.extend(b_keymap.into_iter());
|
||||
|
||||
Ok(($crate::miniscript::Miniscript::from_ast($crate::miniscript::miniscript::decode::Terminal::$terminal_variant(
|
||||
let minisc = $crate::miniscript::Miniscript::from_ast($crate::miniscript::miniscript::decode::Terminal::$terminal_variant(
|
||||
std::sync::Arc::new(a_minisc),
|
||||
std::sync::Arc::new(b_minisc),
|
||||
))?, a_keymap, $crate::keys::merge_networks(&a_networks, &b_networks)))
|
||||
))?;
|
||||
|
||||
minisc.check_minsicript()?;
|
||||
|
||||
Ok((minisc, a_keymap, $crate::keys::merge_networks(&a_networks, &b_networks)))
|
||||
})
|
||||
});
|
||||
}
|
||||
@@ -151,6 +176,8 @@ macro_rules! impl_node_opcode_two {
|
||||
#[macro_export]
|
||||
macro_rules! impl_node_opcode_three {
|
||||
( $terminal_variant:ident, $( $inner:tt )* ) => {
|
||||
use $crate::descriptor::CheckMiniscript;
|
||||
|
||||
let inner = $crate::fragment_internal!( @t $( $inner )* );
|
||||
let (a, b, c) = $crate::descriptor::dsl::TupleThree::from(inner).flattened();
|
||||
|
||||
@@ -164,11 +191,15 @@ macro_rules! impl_node_opcode_three {
|
||||
let networks = $crate::keys::merge_networks(&a_networks, &b_networks);
|
||||
let networks = $crate::keys::merge_networks(&networks, &c_networks);
|
||||
|
||||
Ok(($crate::miniscript::Miniscript::from_ast($crate::miniscript::miniscript::decode::Terminal::$terminal_variant(
|
||||
let minisc = $crate::miniscript::Miniscript::from_ast($crate::miniscript::miniscript::decode::Terminal::$terminal_variant(
|
||||
std::sync::Arc::new(a_minisc),
|
||||
std::sync::Arc::new(b_minisc),
|
||||
std::sync::Arc::new(c_minisc),
|
||||
))?, a_keymap, networks))
|
||||
))?;
|
||||
|
||||
minisc.check_minsicript()?;
|
||||
|
||||
Ok((minisc, a_keymap, networks))
|
||||
})
|
||||
};
|
||||
}
|
||||
@@ -190,6 +221,7 @@ macro_rules! impl_sortedmulti {
|
||||
)*
|
||||
|
||||
keys.into_iter().collect::<Result<Vec<_>, _>>()
|
||||
.map_err($crate::descriptor::DescriptorError::Key)
|
||||
.and_then(|keys| $crate::keys::make_sortedmulti_inner($thresh, keys, &secp))
|
||||
});
|
||||
|
||||
@@ -199,18 +231,20 @@ macro_rules! impl_sortedmulti {
|
||||
#[macro_export]
|
||||
macro_rules! apply_modifier {
|
||||
( $terminal_variant:ident, $inner:expr ) => {{
|
||||
use $crate::descriptor::CheckMiniscript;
|
||||
|
||||
$inner
|
||||
.map_err(|e| -> $crate::Error { e.into() })
|
||||
.map_err(|e| -> $crate::descriptor::DescriptorError { e.into() })
|
||||
.and_then(|(minisc, keymap, networks)| {
|
||||
Ok((
|
||||
$crate::miniscript::Miniscript::from_ast(
|
||||
$crate::miniscript::miniscript::decode::Terminal::$terminal_variant(
|
||||
std::sync::Arc::new(minisc),
|
||||
),
|
||||
)?,
|
||||
keymap,
|
||||
networks,
|
||||
))
|
||||
let minisc = $crate::miniscript::Miniscript::from_ast(
|
||||
$crate::miniscript::miniscript::decode::Terminal::$terminal_variant(
|
||||
std::sync::Arc::new(minisc),
|
||||
),
|
||||
)?;
|
||||
|
||||
minisc.check_minsicript()?;
|
||||
|
||||
Ok((minisc, keymap, networks))
|
||||
})
|
||||
}};
|
||||
|
||||
@@ -272,7 +306,7 @@ macro_rules! apply_modifier {
|
||||
/// Macro to write full descriptors with code
|
||||
///
|
||||
/// This macro expands to a `Result` of
|
||||
/// [`DescriptorTemplateOut`](super::template::DescriptorTemplateOut) and [`Error`](crate::Error)
|
||||
/// [`DescriptorTemplateOut`](super::template::DescriptorTemplateOut) and [`DescriptorError`](crate::descriptor::DescriptorError)
|
||||
///
|
||||
/// The syntax is very similar to the normal descriptor syntax, with the exception that modifiers
|
||||
/// cannot be grouped together. For instance, a descriptor fragment like `sdv:older(144)` has to be
|
||||
@@ -305,8 +339,11 @@ macro_rules! apply_modifier {
|
||||
///
|
||||
/// ```
|
||||
/// # use std::str::FromStr;
|
||||
/// let my_key_1 = bitcoin::PublicKey::from_str("02e96fe52ef0e22d2f131dd425ce1893073a3c6ad20e8cac36726393dfb4856a4c")?;
|
||||
/// let my_key_2 = bitcoin::PrivateKey::from_wif("cVt4o7BGAig1UXywgGSmARhxMdzP5qvQsxKkSsc1XEkw3tDTQFpy")?;
|
||||
/// let my_key_1 = bitcoin::PublicKey::from_str(
|
||||
/// "02e96fe52ef0e22d2f131dd425ce1893073a3c6ad20e8cac36726393dfb4856a4c",
|
||||
/// )?;
|
||||
/// let my_key_2 =
|
||||
/// bitcoin::PrivateKey::from_wif("cVt4o7BGAig1UXywgGSmARhxMdzP5qvQsxKkSsc1XEkw3tDTQFpy")?;
|
||||
/// let my_timelock = 50;
|
||||
///
|
||||
/// let (descriptor_a, key_map_a, networks) = bdk::descriptor! {
|
||||
@@ -315,12 +352,13 @@ macro_rules! apply_modifier {
|
||||
/// )
|
||||
/// }?;
|
||||
///
|
||||
/// #[rustfmt::skip]
|
||||
/// let b_items = vec![
|
||||
/// bdk::fragment!(pk(my_key_1))?,
|
||||
/// bdk::fragment!(s:pk(my_key_2))?,
|
||||
/// bdk::fragment!(s:d:v:older(my_timelock))?,
|
||||
/// ];
|
||||
/// let (descriptor_b, mut key_map_b, networks) = bdk::descriptor!(wsh(thresh_vec(2,b_items)))?;
|
||||
/// let (descriptor_b, mut key_map_b, networks) = bdk::descriptor!(wsh(thresh_vec(2, b_items)))?;
|
||||
///
|
||||
/// assert_eq!(descriptor_a, descriptor_b);
|
||||
/// assert_eq!(key_map_a.len(), key_map_b.len());
|
||||
@@ -496,7 +534,7 @@ macro_rules! fragment_internal {
|
||||
|
||||
/// Macro to write descriptor fragments with code
|
||||
///
|
||||
/// This macro will be expanded to an object of type `Result<(Miniscript<DescriptorPublicKey, _>, KeyMap, ValidNetworks), Error>`. It allows writing
|
||||
/// This macro will be expanded to an object of type `Result<(Miniscript<DescriptorPublicKey, _>, KeyMap, ValidNetworks), DescriptorError>`. It allows writing
|
||||
/// fragments of larger descriptors that can be pieced together using `fragment!(thresh_vec(m, ...))`.
|
||||
///
|
||||
/// The syntax to write macro fragment is the same as documented for the [`descriptor`] macro.
|
||||
@@ -599,6 +637,7 @@ macro_rules! fragment {
|
||||
)*
|
||||
|
||||
keys.into_iter().collect::<Result<Vec<_>, _>>()
|
||||
.map_err($crate::descriptor::DescriptorError::Key)
|
||||
.and_then(|keys| $crate::keys::make_multi($thresh, keys, &secp))
|
||||
});
|
||||
|
||||
@@ -620,8 +659,8 @@ mod test {
|
||||
|
||||
use std::str::FromStr;
|
||||
|
||||
use crate::descriptor::DescriptorMeta;
|
||||
use crate::keys::{DescriptorKey, KeyError, ToDescriptorKey, ValidNetworks};
|
||||
use crate::descriptor::{DescriptorError, DescriptorMeta};
|
||||
use crate::keys::{DescriptorKey, ToDescriptorKey, ValidNetworks};
|
||||
use bitcoin::network::constants::Network::{Bitcoin, Regtest, Testnet};
|
||||
use bitcoin::util::bip32;
|
||||
use bitcoin::util::bip32::ChildNumber;
|
||||
@@ -631,7 +670,7 @@ mod test {
|
||||
|
||||
// verify descriptor generates expected script(s) (if bare or pk) or address(es)
|
||||
fn check(
|
||||
desc: Result<(Descriptor<DescriptorPublicKey>, KeyMap, ValidNetworks), KeyError>,
|
||||
desc: Result<(Descriptor<DescriptorPublicKey>, KeyMap, ValidNetworks), DescriptorError>,
|
||||
is_witness: bool,
|
||||
is_fixed: bool,
|
||||
expected: &[&str],
|
||||
@@ -978,4 +1017,15 @@ mod test {
|
||||
|
||||
assert_eq!(descriptor.to_string(), "wsh(thresh(2,dv:older(1),s:pk(02e96fe52ef0e22d2f131dd425ce1893073a3c6ad20e8cac36726393dfb4856a4c),s:pk(02e96fe52ef0e22d2f131dd425ce1893073a3c6ad20e8cac36726393dfb4856a4c)))")
|
||||
}
|
||||
|
||||
// TODO: uncomment once https://github.com/rust-bitcoin/rust-miniscript/pull/221 is released
|
||||
//
|
||||
// #[test]
|
||||
// #[should_panic(expected = "Miniscript(ContextError(CompressedOnly))")]
|
||||
// fn test_dsl_miniscript_checks() {
|
||||
// let mut uncompressed_pk = PrivateKey::from_wif("L5EZftvrYaSudiozVRzTqLcHLNDoVn7H5HSfM9BAN6tMJX8oTWz6").unwrap();
|
||||
// uncompressed_pk.compressed = false;
|
||||
|
||||
// descriptor!(wsh(v:pk(uncompressed_pk))).unwrap();
|
||||
// }
|
||||
}
|
||||
|
||||
@@ -27,26 +27,19 @@
|
||||
/// Errors related to the parsing and usage of descriptors
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
//InternalError,
|
||||
//InvalidPrefix(Vec<u8>),
|
||||
//HardenedDerivationOnXpub,
|
||||
//MalformedInput,
|
||||
/// Invalid HD Key path, such as having a wildcard but a length != 1
|
||||
InvalidHDKeyPath,
|
||||
/// The provided descriptor doesn't match its checksum
|
||||
InvalidDescriptorChecksum,
|
||||
|
||||
//KeyParsingError(String),
|
||||
/// Error thrown while working with [`keys`](crate::keys)
|
||||
Key(crate::keys::KeyError),
|
||||
/// Error while extracting and manipulating policies
|
||||
Policy(crate::descriptor::policy::PolicyError),
|
||||
|
||||
//InputIndexDoesntExist,
|
||||
//MissingPublicKey,
|
||||
//MissingDetails,
|
||||
/// Invalid character found in the descriptor checksum
|
||||
InvalidDescriptorCharacter(char),
|
||||
|
||||
//CantDeriveWithMiniscript,
|
||||
/// BIP32 error
|
||||
BIP32(bitcoin::util::bip32::Error),
|
||||
/// Error during base58 decoding
|
||||
|
||||
@@ -49,7 +49,7 @@ pub mod policy;
|
||||
pub mod template;
|
||||
|
||||
pub use self::checksum::get_checksum;
|
||||
use self::error::Error;
|
||||
pub use self::error::Error as DescriptorError;
|
||||
pub use self::policy::Policy;
|
||||
use self::template::DescriptorTemplateOut;
|
||||
use crate::keys::{KeyError, ToDescriptorKey};
|
||||
@@ -72,14 +72,14 @@ pub trait ToWalletDescriptor {
|
||||
fn to_wallet_descriptor(
|
||||
self,
|
||||
network: Network,
|
||||
) -> Result<(ExtendedDescriptor, KeyMap), KeyError>;
|
||||
) -> Result<(ExtendedDescriptor, KeyMap), DescriptorError>;
|
||||
}
|
||||
|
||||
impl ToWalletDescriptor for &str {
|
||||
fn to_wallet_descriptor(
|
||||
self,
|
||||
network: Network,
|
||||
) -> Result<(ExtendedDescriptor, KeyMap), KeyError> {
|
||||
) -> Result<(ExtendedDescriptor, KeyMap), DescriptorError> {
|
||||
let descriptor = if self.contains('#') {
|
||||
let parts: Vec<&str> = self.splitn(2, '#').collect();
|
||||
if !get_checksum(parts[0])
|
||||
@@ -87,7 +87,7 @@ impl ToWalletDescriptor for &str {
|
||||
.map(|computed| computed == parts[1])
|
||||
.unwrap_or(false)
|
||||
{
|
||||
return Err(KeyError::InvalidChecksum);
|
||||
return Err(DescriptorError::InvalidDescriptorChecksum);
|
||||
}
|
||||
|
||||
parts[0]
|
||||
@@ -103,7 +103,7 @@ impl ToWalletDescriptor for &String {
|
||||
fn to_wallet_descriptor(
|
||||
self,
|
||||
network: Network,
|
||||
) -> Result<(ExtendedDescriptor, KeyMap), KeyError> {
|
||||
) -> Result<(ExtendedDescriptor, KeyMap), DescriptorError> {
|
||||
self.as_str().to_wallet_descriptor(network)
|
||||
}
|
||||
}
|
||||
@@ -112,7 +112,7 @@ impl ToWalletDescriptor for ExtendedDescriptor {
|
||||
fn to_wallet_descriptor(
|
||||
self,
|
||||
network: Network,
|
||||
) -> Result<(ExtendedDescriptor, KeyMap), KeyError> {
|
||||
) -> Result<(ExtendedDescriptor, KeyMap), DescriptorError> {
|
||||
(self, KeyMap::default()).to_wallet_descriptor(network)
|
||||
}
|
||||
}
|
||||
@@ -121,7 +121,7 @@ impl ToWalletDescriptor for (ExtendedDescriptor, KeyMap) {
|
||||
fn to_wallet_descriptor(
|
||||
self,
|
||||
network: Network,
|
||||
) -> Result<(ExtendedDescriptor, KeyMap), KeyError> {
|
||||
) -> Result<(ExtendedDescriptor, KeyMap), DescriptorError> {
|
||||
use crate::keys::DescriptorKey;
|
||||
|
||||
let secp = Secp256k1::new();
|
||||
@@ -140,7 +140,7 @@ impl ToWalletDescriptor for (ExtendedDescriptor, KeyMap) {
|
||||
if networks.contains(&network) {
|
||||
Ok(pk)
|
||||
} else {
|
||||
Err(KeyError::InvalidNetwork)
|
||||
Err(DescriptorError::Key(KeyError::InvalidNetwork))
|
||||
}
|
||||
};
|
||||
|
||||
@@ -155,7 +155,7 @@ impl ToWalletDescriptor for DescriptorTemplateOut {
|
||||
fn to_wallet_descriptor(
|
||||
self,
|
||||
network: Network,
|
||||
) -> Result<(ExtendedDescriptor, KeyMap), KeyError> {
|
||||
) -> Result<(ExtendedDescriptor, KeyMap), DescriptorError> {
|
||||
let valid_networks = &self.2;
|
||||
|
||||
let fix_key = |pk: &DescriptorPublicKey| {
|
||||
@@ -177,7 +177,7 @@ impl ToWalletDescriptor for DescriptorTemplateOut {
|
||||
|
||||
Ok(pk)
|
||||
} else {
|
||||
Err(KeyError::InvalidNetwork)
|
||||
Err(DescriptorError::Key(KeyError::InvalidNetwork))
|
||||
}
|
||||
};
|
||||
|
||||
@@ -188,6 +188,22 @@ impl ToWalletDescriptor for DescriptorTemplateOut {
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
/// Used internally mainly by the `descriptor!()` and `fragment!()` macros
|
||||
pub trait CheckMiniscript<Ctx: miniscript::ScriptContext> {
|
||||
fn check_minsicript(&self) -> Result<(), miniscript::Error>;
|
||||
}
|
||||
|
||||
impl<Ctx: miniscript::ScriptContext, Pk: miniscript::MiniscriptKey> CheckMiniscript<Ctx>
|
||||
for miniscript::Miniscript<Pk, Ctx>
|
||||
{
|
||||
fn check_minsicript(&self) -> Result<(), miniscript::Error> {
|
||||
Ctx::check_global_validity(self)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Trait implemented on [`Descriptor`]s to add a method to extract the spending [`policy`]
|
||||
pub trait ExtractPolicy {
|
||||
/// Extract the spending [`policy`]
|
||||
@@ -195,7 +211,7 @@ pub trait ExtractPolicy {
|
||||
&self,
|
||||
signers: &SignersContainer,
|
||||
secp: &SecpCtx,
|
||||
) -> Result<Option<Policy>, Error>;
|
||||
) -> Result<Option<Policy>, DescriptorError>;
|
||||
}
|
||||
|
||||
pub(crate) trait XKeyUtils {
|
||||
@@ -235,8 +251,8 @@ impl<K: InnerXKey> XKeyUtils for DescriptorXKey<K> {
|
||||
|
||||
pub(crate) trait DescriptorMeta: Sized {
|
||||
fn is_witness(&self) -> bool;
|
||||
fn get_hd_keypaths(&self, index: u32, secp: &SecpCtx) -> Result<HDKeyPaths, Error>;
|
||||
fn get_extended_keys(&self) -> Result<Vec<DescriptorXKey<ExtendedPubKey>>, Error>;
|
||||
fn get_hd_keypaths(&self, index: u32, secp: &SecpCtx) -> Result<HDKeyPaths, DescriptorError>;
|
||||
fn get_extended_keys(&self) -> Result<Vec<DescriptorXKey<ExtendedPubKey>>, DescriptorError>;
|
||||
fn is_fixed(&self) -> bool;
|
||||
fn derive_from_hd_keypaths(&self, hd_keypaths: &HDKeyPaths, secp: &SecpCtx) -> Option<Self>;
|
||||
fn derive_from_psbt_input(
|
||||
@@ -297,11 +313,11 @@ impl DescriptorMeta for Descriptor<DescriptorPublicKey> {
|
||||
}
|
||||
}
|
||||
|
||||
fn get_hd_keypaths(&self, index: u32, secp: &SecpCtx) -> Result<HDKeyPaths, Error> {
|
||||
fn get_hd_keypaths(&self, index: u32, secp: &SecpCtx) -> Result<HDKeyPaths, DescriptorError> {
|
||||
let translate_key = |key: &DescriptorPublicKey,
|
||||
index: u32,
|
||||
paths: &mut HDKeyPaths|
|
||||
-> Result<DummyKey, Error> {
|
||||
-> Result<DummyKey, DescriptorError> {
|
||||
match key {
|
||||
DescriptorPublicKey::SinglePub(_) => {}
|
||||
DescriptorPublicKey::XPub(xpub) => {
|
||||
@@ -344,10 +360,10 @@ impl DescriptorMeta for Descriptor<DescriptorPublicKey> {
|
||||
Ok(answer_pk)
|
||||
}
|
||||
|
||||
fn get_extended_keys(&self) -> Result<Vec<DescriptorXKey<ExtendedPubKey>>, Error> {
|
||||
fn get_extended_keys(&self) -> Result<Vec<DescriptorXKey<ExtendedPubKey>>, DescriptorError> {
|
||||
let get_key = |key: &DescriptorPublicKey,
|
||||
keys: &mut Vec<DescriptorXKey<ExtendedPubKey>>|
|
||||
-> Result<DummyKey, Error> {
|
||||
-> Result<DummyKey, DescriptorError> {
|
||||
if let DescriptorPublicKey::XPub(xpub) = key {
|
||||
keys.push(xpub.clone())
|
||||
}
|
||||
@@ -369,7 +385,10 @@ impl DescriptorMeta for Descriptor<DescriptorPublicKey> {
|
||||
}
|
||||
|
||||
fn is_fixed(&self) -> bool {
|
||||
fn check_key(key: &DescriptorPublicKey, flag: &mut bool) -> Result<DummyKey, Error> {
|
||||
fn check_key(
|
||||
key: &DescriptorPublicKey,
|
||||
flag: &mut bool,
|
||||
) -> Result<DummyKey, DescriptorError> {
|
||||
match key {
|
||||
DescriptorPublicKey::SinglePub(_) => {}
|
||||
DescriptorPublicKey::XPub(xpub) => {
|
||||
@@ -398,7 +417,7 @@ impl DescriptorMeta for Descriptor<DescriptorPublicKey> {
|
||||
let try_key = |key: &DescriptorPublicKey,
|
||||
index: &HashMap<Fingerprint, DerivationPath>,
|
||||
found_path: &mut Option<ChildNumber>|
|
||||
-> Result<DummyKey, Error> {
|
||||
-> Result<DummyKey, DescriptorError> {
|
||||
if found_path.is_some() {
|
||||
// already found a matching path, we are done
|
||||
return Ok(DummyKey::default());
|
||||
@@ -432,7 +451,7 @@ impl DescriptorMeta for Descriptor<DescriptorPublicKey> {
|
||||
Some(path) if !xpub.is_wildcard && path.is_empty() => {
|
||||
*found_path = Some(ChildNumber::Normal { index: 0 })
|
||||
}
|
||||
Some(_) => return Err(Error::InvalidHDKeyPath),
|
||||
Some(_) => return Err(DescriptorError::InvalidHDKeyPath),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
@@ -713,11 +732,17 @@ mod test {
|
||||
|
||||
let desc = "wpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcLNfjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/2/*)#67ju93jw"
|
||||
.to_wallet_descriptor(Network::Testnet);
|
||||
assert!(matches!(desc.err(), Some(KeyError::InvalidChecksum)));
|
||||
assert!(matches!(
|
||||
desc.err(),
|
||||
Some(DescriptorError::InvalidDescriptorChecksum)
|
||||
));
|
||||
|
||||
let desc = "wpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcLNfjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/2/*)#67ju93jw"
|
||||
.to_wallet_descriptor(Network::Testnet);
|
||||
assert!(matches!(desc.err(), Some(KeyError::InvalidChecksum)));
|
||||
assert!(matches!(
|
||||
desc.err(),
|
||||
Some(DescriptorError::InvalidDescriptorChecksum)
|
||||
));
|
||||
}
|
||||
|
||||
// test ToWalletDescriptor trait from &str with keys from right and wrong network
|
||||
@@ -749,11 +774,17 @@ mod test {
|
||||
|
||||
let desc = "wpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcLNfjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/2/*)"
|
||||
.to_wallet_descriptor(Network::Bitcoin);
|
||||
assert!(matches!(desc.err(), Some(KeyError::InvalidNetwork)));
|
||||
assert!(matches!(
|
||||
desc.err(),
|
||||
Some(DescriptorError::Key(KeyError::InvalidNetwork))
|
||||
));
|
||||
|
||||
let desc = "wpkh(tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/1/2/*)"
|
||||
.to_wallet_descriptor(Network::Bitcoin);
|
||||
assert!(matches!(desc.err(), Some(KeyError::InvalidNetwork)));
|
||||
assert!(matches!(
|
||||
desc.err(),
|
||||
Some(DescriptorError::Key(KeyError::InvalidNetwork))
|
||||
));
|
||||
}
|
||||
|
||||
// test ToWalletDescriptor trait from the output of the descriptor!() macro
|
||||
|
||||
@@ -165,11 +165,13 @@ pub enum SatisfiableItem {
|
||||
impl SatisfiableItem {
|
||||
/// Returns whether the [`SatisfiableItem`] is a leaf item
|
||||
pub fn is_leaf(&self) -> bool {
|
||||
!matches!(self,
|
||||
SatisfiableItem::Thresh {
|
||||
items: _,
|
||||
threshold: _,
|
||||
})
|
||||
!matches!(
|
||||
self,
|
||||
SatisfiableItem::Thresh {
|
||||
items: _,
|
||||
threshold: _,
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns a unique id for the [`SatisfiableItem`]
|
||||
@@ -1143,11 +1145,12 @@ mod test {
|
||||
let (prvkey0, _pubkey0, _fingerprint0) = setup_keys(TPRV0_STR);
|
||||
let (_prvkey1, pubkey1, _fingerprint1) = setup_keys(TPRV1_STR);
|
||||
let sequence = 50;
|
||||
#[rustfmt::skip]
|
||||
let desc = descriptor!(wsh(thresh(
|
||||
2,
|
||||
pk(prvkey0),
|
||||
s: pk(pubkey1),
|
||||
s: d: v: older(sequence)
|
||||
s:pk(pubkey1),
|
||||
s:d:v:older(sequence)
|
||||
)))
|
||||
.unwrap();
|
||||
|
||||
|
||||
@@ -33,7 +33,8 @@ use bitcoin::Network;
|
||||
use miniscript::{Legacy, Segwitv0};
|
||||
|
||||
use super::{ExtendedDescriptor, KeyMap, ToWalletDescriptor};
|
||||
use crate::keys::{DerivableKey, KeyError, ToDescriptorKey, ValidNetworks};
|
||||
use crate::descriptor::DescriptorError;
|
||||
use crate::keys::{DerivableKey, ToDescriptorKey, ValidNetworks};
|
||||
use crate::{descriptor, KeychainKind};
|
||||
|
||||
/// Type alias for the return type of [`DescriptorTemplate`], [`descriptor!`](crate::descriptor!) and others
|
||||
@@ -47,6 +48,7 @@ pub type DescriptorTemplateOut = (ExtendedDescriptor, KeyMap, ValidNetworks);
|
||||
/// ## Example
|
||||
///
|
||||
/// ```
|
||||
/// use bdk::descriptor::error::Error as DescriptorError;
|
||||
/// use bdk::keys::{KeyError, ToDescriptorKey};
|
||||
/// use bdk::miniscript::Legacy;
|
||||
/// use bdk::template::{DescriptorTemplate, DescriptorTemplateOut};
|
||||
@@ -54,14 +56,14 @@ pub type DescriptorTemplateOut = (ExtendedDescriptor, KeyMap, ValidNetworks);
|
||||
/// struct MyP2PKH<K: ToDescriptorKey<Legacy>>(K);
|
||||
///
|
||||
/// impl<K: ToDescriptorKey<Legacy>> DescriptorTemplate for MyP2PKH<K> {
|
||||
/// fn build(self) -> Result<DescriptorTemplateOut, KeyError> {
|
||||
/// fn build(self) -> Result<DescriptorTemplateOut, DescriptorError> {
|
||||
/// Ok(bdk::descriptor!(pkh(self.0))?)
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
pub trait DescriptorTemplate {
|
||||
/// Build the complete descriptor
|
||||
fn build(self) -> Result<DescriptorTemplateOut, KeyError>;
|
||||
fn build(self) -> Result<DescriptorTemplateOut, DescriptorError>;
|
||||
}
|
||||
|
||||
/// Turns a [`DescriptorTemplate`] into a valid wallet descriptor by calling its
|
||||
@@ -70,7 +72,7 @@ impl<T: DescriptorTemplate> ToWalletDescriptor for T {
|
||||
fn to_wallet_descriptor(
|
||||
self,
|
||||
network: Network,
|
||||
) -> Result<(ExtendedDescriptor, KeyMap), KeyError> {
|
||||
) -> Result<(ExtendedDescriptor, KeyMap), DescriptorError> {
|
||||
Ok(self.build()?.to_wallet_descriptor(network)?)
|
||||
}
|
||||
}
|
||||
@@ -81,13 +83,13 @@ impl<T: DescriptorTemplate> ToWalletDescriptor for T {
|
||||
///
|
||||
/// ```
|
||||
/// # use bdk::bitcoin::{PrivateKey, Network};
|
||||
/// # use bdk::{Wallet, OfflineWallet};
|
||||
/// # use bdk::{Wallet};
|
||||
/// # use bdk::database::MemoryDatabase;
|
||||
/// use bdk::template::P2PKH;
|
||||
///
|
||||
/// let key =
|
||||
/// bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")?;
|
||||
/// let wallet: OfflineWallet<_> = Wallet::new_offline(
|
||||
/// let wallet = Wallet::new_offline(
|
||||
/// P2PKH(key),
|
||||
/// None,
|
||||
/// Network::Testnet,
|
||||
@@ -103,7 +105,7 @@ impl<T: DescriptorTemplate> ToWalletDescriptor for T {
|
||||
pub struct P2PKH<K: ToDescriptorKey<Legacy>>(pub K);
|
||||
|
||||
impl<K: ToDescriptorKey<Legacy>> DescriptorTemplate for P2PKH<K> {
|
||||
fn build(self) -> Result<DescriptorTemplateOut, KeyError> {
|
||||
fn build(self) -> Result<DescriptorTemplateOut, DescriptorError> {
|
||||
Ok(descriptor!(pkh(self.0))?)
|
||||
}
|
||||
}
|
||||
@@ -114,13 +116,13 @@ impl<K: ToDescriptorKey<Legacy>> DescriptorTemplate for P2PKH<K> {
|
||||
///
|
||||
/// ```
|
||||
/// # use bdk::bitcoin::{PrivateKey, Network};
|
||||
/// # use bdk::{Wallet, OfflineWallet};
|
||||
/// # use bdk::{Wallet};
|
||||
/// # use bdk::database::MemoryDatabase;
|
||||
/// use bdk::template::P2WPKH_P2SH;
|
||||
///
|
||||
/// let key =
|
||||
/// bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")?;
|
||||
/// let wallet: OfflineWallet<_> = Wallet::new_offline(
|
||||
/// let wallet = Wallet::new_offline(
|
||||
/// P2WPKH_P2SH(key),
|
||||
/// None,
|
||||
/// Network::Testnet,
|
||||
@@ -137,7 +139,7 @@ impl<K: ToDescriptorKey<Legacy>> DescriptorTemplate for P2PKH<K> {
|
||||
pub struct P2WPKH_P2SH<K: ToDescriptorKey<Segwitv0>>(pub K);
|
||||
|
||||
impl<K: ToDescriptorKey<Segwitv0>> DescriptorTemplate for P2WPKH_P2SH<K> {
|
||||
fn build(self) -> Result<DescriptorTemplateOut, KeyError> {
|
||||
fn build(self) -> Result<DescriptorTemplateOut, DescriptorError> {
|
||||
Ok(descriptor!(sh(wpkh(self.0)))?)
|
||||
}
|
||||
}
|
||||
@@ -148,13 +150,13 @@ impl<K: ToDescriptorKey<Segwitv0>> DescriptorTemplate for P2WPKH_P2SH<K> {
|
||||
///
|
||||
/// ```
|
||||
/// # use bdk::bitcoin::{PrivateKey, Network};
|
||||
/// # use bdk::{Wallet, OfflineWallet};
|
||||
/// # use bdk::{Wallet};
|
||||
/// # use bdk::database::MemoryDatabase;
|
||||
/// use bdk::template::P2WPKH;
|
||||
///
|
||||
/// let key =
|
||||
/// bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")?;
|
||||
/// let wallet: OfflineWallet<_> = Wallet::new_offline(
|
||||
/// let wallet = Wallet::new_offline(
|
||||
/// P2WPKH(key),
|
||||
/// None,
|
||||
/// Network::Testnet,
|
||||
@@ -170,7 +172,7 @@ impl<K: ToDescriptorKey<Segwitv0>> DescriptorTemplate for P2WPKH_P2SH<K> {
|
||||
pub struct P2WPKH<K: ToDescriptorKey<Segwitv0>>(pub K);
|
||||
|
||||
impl<K: ToDescriptorKey<Segwitv0>> DescriptorTemplate for P2WPKH<K> {
|
||||
fn build(self) -> Result<DescriptorTemplateOut, KeyError> {
|
||||
fn build(self) -> Result<DescriptorTemplateOut, DescriptorError> {
|
||||
Ok(descriptor!(wpkh(self.0))?)
|
||||
}
|
||||
}
|
||||
@@ -186,12 +188,12 @@ impl<K: ToDescriptorKey<Segwitv0>> DescriptorTemplate for P2WPKH<K> {
|
||||
/// ```
|
||||
/// # use std::str::FromStr;
|
||||
/// # use bdk::bitcoin::{PrivateKey, Network};
|
||||
/// # use bdk::{Wallet, OfflineWallet, KeychainKind};
|
||||
/// # use bdk::{Wallet, KeychainKind};
|
||||
/// # use bdk::database::MemoryDatabase;
|
||||
/// use bdk::template::BIP44;
|
||||
///
|
||||
/// let key = bitcoin::util::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?;
|
||||
/// let wallet: OfflineWallet<_> = Wallet::new_offline(
|
||||
/// let wallet = Wallet::new_offline(
|
||||
/// BIP44(key.clone(), KeychainKind::External),
|
||||
/// Some(BIP44(key, KeychainKind::Internal)),
|
||||
/// Network::Testnet,
|
||||
@@ -205,7 +207,7 @@ impl<K: ToDescriptorKey<Segwitv0>> DescriptorTemplate for P2WPKH<K> {
|
||||
pub struct BIP44<K: DerivableKey<Legacy>>(pub K, pub KeychainKind);
|
||||
|
||||
impl<K: DerivableKey<Legacy>> DescriptorTemplate for BIP44<K> {
|
||||
fn build(self) -> Result<DescriptorTemplateOut, KeyError> {
|
||||
fn build(self) -> Result<DescriptorTemplateOut, DescriptorError> {
|
||||
Ok(P2PKH(legacy::make_bipxx_private(44, self.0, self.1)?).build()?)
|
||||
}
|
||||
}
|
||||
@@ -224,13 +226,13 @@ impl<K: DerivableKey<Legacy>> DescriptorTemplate for BIP44<K> {
|
||||
/// ```
|
||||
/// # use std::str::FromStr;
|
||||
/// # use bdk::bitcoin::{PrivateKey, Network};
|
||||
/// # use bdk::{Wallet, OfflineWallet, KeychainKind};
|
||||
/// # use bdk::{Wallet, KeychainKind};
|
||||
/// # use bdk::database::MemoryDatabase;
|
||||
/// use bdk::template::BIP44Public;
|
||||
///
|
||||
/// let key = bitcoin::util::bip32::ExtendedPubKey::from_str("tpubDDDzQ31JkZB7VxUr9bjvBivDdqoFLrDPyLWtLapArAi51ftfmCb2DPxwLQzX65iNcXz1DGaVvyvo6JQ6rTU73r2gqdEo8uov9QKRb7nKCSU")?;
|
||||
/// let fingerprint = bitcoin::util::bip32::Fingerprint::from_str("c55b303f")?;
|
||||
/// let wallet: OfflineWallet<_> = Wallet::new_offline(
|
||||
/// let wallet = Wallet::new_offline(
|
||||
/// BIP44Public(key.clone(), fingerprint, KeychainKind::External),
|
||||
/// Some(BIP44Public(key, fingerprint, KeychainKind::Internal)),
|
||||
/// Network::Testnet,
|
||||
@@ -244,7 +246,7 @@ impl<K: DerivableKey<Legacy>> DescriptorTemplate for BIP44<K> {
|
||||
pub struct BIP44Public<K: DerivableKey<Legacy>>(pub K, pub bip32::Fingerprint, pub KeychainKind);
|
||||
|
||||
impl<K: DerivableKey<Legacy>> DescriptorTemplate for BIP44Public<K> {
|
||||
fn build(self) -> Result<DescriptorTemplateOut, KeyError> {
|
||||
fn build(self) -> Result<DescriptorTemplateOut, DescriptorError> {
|
||||
Ok(P2PKH(legacy::make_bipxx_public(44, self.0, self.1, self.2)?).build()?)
|
||||
}
|
||||
}
|
||||
@@ -260,12 +262,12 @@ impl<K: DerivableKey<Legacy>> DescriptorTemplate for BIP44Public<K> {
|
||||
/// ```
|
||||
/// # use std::str::FromStr;
|
||||
/// # use bdk::bitcoin::{PrivateKey, Network};
|
||||
/// # use bdk::{Wallet, OfflineWallet, KeychainKind};
|
||||
/// # use bdk::{Wallet, KeychainKind};
|
||||
/// # use bdk::database::MemoryDatabase;
|
||||
/// use bdk::template::BIP49;
|
||||
///
|
||||
/// let key = bitcoin::util::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?;
|
||||
/// let wallet: OfflineWallet<_> = Wallet::new_offline(
|
||||
/// let wallet = Wallet::new_offline(
|
||||
/// BIP49(key.clone(), KeychainKind::External),
|
||||
/// Some(BIP49(key, KeychainKind::Internal)),
|
||||
/// Network::Testnet,
|
||||
@@ -279,7 +281,7 @@ impl<K: DerivableKey<Legacy>> DescriptorTemplate for BIP44Public<K> {
|
||||
pub struct BIP49<K: DerivableKey<Segwitv0>>(pub K, pub KeychainKind);
|
||||
|
||||
impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for BIP49<K> {
|
||||
fn build(self) -> Result<DescriptorTemplateOut, KeyError> {
|
||||
fn build(self) -> Result<DescriptorTemplateOut, DescriptorError> {
|
||||
Ok(P2WPKH_P2SH(segwit_v0::make_bipxx_private(49, self.0, self.1)?).build()?)
|
||||
}
|
||||
}
|
||||
@@ -298,13 +300,13 @@ impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for BIP49<K> {
|
||||
/// ```
|
||||
/// # use std::str::FromStr;
|
||||
/// # use bdk::bitcoin::{PrivateKey, Network};
|
||||
/// # use bdk::{Wallet, OfflineWallet, KeychainKind};
|
||||
/// # use bdk::{Wallet, KeychainKind};
|
||||
/// # use bdk::database::MemoryDatabase;
|
||||
/// use bdk::template::BIP49Public;
|
||||
///
|
||||
/// let key = bitcoin::util::bip32::ExtendedPubKey::from_str("tpubDC49r947KGK52X5rBWS4BLs5m9SRY3pYHnvRrm7HcybZ3BfdEsGFyzCMzayi1u58eT82ZeyFZwH7DD6Q83E3fM9CpfMtmnTygnLfP59jL9L")?;
|
||||
/// let fingerprint = bitcoin::util::bip32::Fingerprint::from_str("c55b303f")?;
|
||||
/// let wallet: OfflineWallet<_> = Wallet::new_offline(
|
||||
/// let wallet = Wallet::new_offline(
|
||||
/// BIP49Public(key.clone(), fingerprint, KeychainKind::External),
|
||||
/// Some(BIP49Public(key, fingerprint, KeychainKind::Internal)),
|
||||
/// Network::Testnet,
|
||||
@@ -318,7 +320,7 @@ impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for BIP49<K> {
|
||||
pub struct BIP49Public<K: DerivableKey<Segwitv0>>(pub K, pub bip32::Fingerprint, pub KeychainKind);
|
||||
|
||||
impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for BIP49Public<K> {
|
||||
fn build(self) -> Result<DescriptorTemplateOut, KeyError> {
|
||||
fn build(self) -> Result<DescriptorTemplateOut, DescriptorError> {
|
||||
Ok(P2WPKH_P2SH(segwit_v0::make_bipxx_public(49, self.0, self.1, self.2)?).build()?)
|
||||
}
|
||||
}
|
||||
@@ -334,12 +336,12 @@ impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for BIP49Public<K> {
|
||||
/// ```
|
||||
/// # use std::str::FromStr;
|
||||
/// # use bdk::bitcoin::{PrivateKey, Network};
|
||||
/// # use bdk::{Wallet, OfflineWallet, KeychainKind};
|
||||
/// # use bdk::{Wallet, KeychainKind};
|
||||
/// # use bdk::database::MemoryDatabase;
|
||||
/// use bdk::template::BIP84;
|
||||
///
|
||||
/// let key = bitcoin::util::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?;
|
||||
/// let wallet: OfflineWallet<_> = Wallet::new_offline(
|
||||
/// let wallet = Wallet::new_offline(
|
||||
/// BIP84(key.clone(), KeychainKind::External),
|
||||
/// Some(BIP84(key, KeychainKind::Internal)),
|
||||
/// Network::Testnet,
|
||||
@@ -353,7 +355,7 @@ impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for BIP49Public<K> {
|
||||
pub struct BIP84<K: DerivableKey<Segwitv0>>(pub K, pub KeychainKind);
|
||||
|
||||
impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for BIP84<K> {
|
||||
fn build(self) -> Result<DescriptorTemplateOut, KeyError> {
|
||||
fn build(self) -> Result<DescriptorTemplateOut, DescriptorError> {
|
||||
Ok(P2WPKH(segwit_v0::make_bipxx_private(84, self.0, self.1)?).build()?)
|
||||
}
|
||||
}
|
||||
@@ -372,13 +374,13 @@ impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for BIP84<K> {
|
||||
/// ```
|
||||
/// # use std::str::FromStr;
|
||||
/// # use bdk::bitcoin::{PrivateKey, Network};
|
||||
/// # use bdk::{Wallet, OfflineWallet, KeychainKind};
|
||||
/// # use bdk::{Wallet, KeychainKind};
|
||||
/// # use bdk::database::MemoryDatabase;
|
||||
/// use bdk::template::BIP84Public;
|
||||
///
|
||||
/// let key = bitcoin::util::bip32::ExtendedPubKey::from_str("tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q")?;
|
||||
/// let fingerprint = bitcoin::util::bip32::Fingerprint::from_str("c55b303f")?;
|
||||
/// let wallet: OfflineWallet<_> = Wallet::new_offline(
|
||||
/// let wallet = Wallet::new_offline(
|
||||
/// BIP84Public(key.clone(), fingerprint, KeychainKind::External),
|
||||
/// Some(BIP84Public(key, fingerprint, KeychainKind::Internal)),
|
||||
/// Network::Testnet,
|
||||
@@ -392,7 +394,7 @@ impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for BIP84<K> {
|
||||
pub struct BIP84Public<K: DerivableKey<Segwitv0>>(pub K, pub bip32::Fingerprint, pub KeychainKind);
|
||||
|
||||
impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for BIP84Public<K> {
|
||||
fn build(self) -> Result<DescriptorTemplateOut, KeyError> {
|
||||
fn build(self) -> Result<DescriptorTemplateOut, DescriptorError> {
|
||||
Ok(P2WPKH(segwit_v0::make_bipxx_public(84, self.0, self.1, self.2)?).build()?)
|
||||
}
|
||||
}
|
||||
@@ -406,7 +408,7 @@ macro_rules! expand_make_bipxx {
|
||||
bip: u32,
|
||||
key: K,
|
||||
keychain: KeychainKind,
|
||||
) -> Result<impl ToDescriptorKey<$ctx>, KeyError> {
|
||||
) -> Result<impl ToDescriptorKey<$ctx>, DescriptorError> {
|
||||
let mut derivation_path = Vec::with_capacity(4);
|
||||
derivation_path.push(bip32::ChildNumber::from_hardened_idx(bip)?);
|
||||
derivation_path.push(bip32::ChildNumber::from_hardened_idx(0)?);
|
||||
@@ -430,7 +432,7 @@ macro_rules! expand_make_bipxx {
|
||||
key: K,
|
||||
parent_fingerprint: bip32::Fingerprint,
|
||||
keychain: KeychainKind,
|
||||
) -> Result<impl ToDescriptorKey<$ctx>, KeyError> {
|
||||
) -> Result<impl ToDescriptorKey<$ctx>, DescriptorError> {
|
||||
let derivation_path: bip32::DerivationPath = match keychain {
|
||||
KeychainKind::External => vec![bip32::ChildNumber::from_normal_idx(0)?].into(),
|
||||
KeychainKind::Internal => vec![bip32::ChildNumber::from_normal_idx(1)?].into(),
|
||||
@@ -456,8 +458,8 @@ mod test {
|
||||
// test existing descriptor templates, make sure they are expanded to the right descriptors
|
||||
|
||||
use super::*;
|
||||
use crate::descriptor::DescriptorMeta;
|
||||
use crate::keys::{KeyError, ValidNetworks};
|
||||
use crate::descriptor::{DescriptorError, DescriptorMeta};
|
||||
use crate::keys::ValidNetworks;
|
||||
use bitcoin::hashes::core::str::FromStr;
|
||||
use bitcoin::network::constants::Network::Regtest;
|
||||
use bitcoin::secp256k1::Secp256k1;
|
||||
@@ -467,7 +469,7 @@ mod test {
|
||||
|
||||
// verify template descriptor generates expected address(es)
|
||||
fn check(
|
||||
desc: Result<(Descriptor<DescriptorPublicKey>, KeyMap, ValidNetworks), KeyError>,
|
||||
desc: Result<(Descriptor<DescriptorPublicKey>, KeyMap, ValidNetworks), DescriptorError>,
|
||||
is_witness: bool,
|
||||
is_fixed: bool,
|
||||
expected: &[&str],
|
||||
|
||||
14
src/error.rs
14
src/error.rs
@@ -47,7 +47,12 @@ pub enum Error {
|
||||
/// Output created is under the dust limit, 546 satoshis
|
||||
OutputBelowDustLimit(usize),
|
||||
/// Wallet's UTXO set is not enough to cover recipient's requested plus fee
|
||||
InsufficientFunds,
|
||||
InsufficientFunds {
|
||||
/// Sats needed for some transaction
|
||||
needed: u64,
|
||||
/// Sats available for spending
|
||||
available: u64,
|
||||
},
|
||||
/// Branch and bound coin selection possible attempts with sufficiently big UTXO set could grow
|
||||
/// exponentially, thus a limit is set, and when hit, this error is thrown
|
||||
BnBTotalTriesExceeded,
|
||||
@@ -89,11 +94,6 @@ pub enum Error {
|
||||
/// Signing error
|
||||
Signer(crate::wallet::signer::SignerError),
|
||||
|
||||
// Blockchain interface errors
|
||||
/// Thrown when trying to call a method that requires a network connection, [`Wallet::sync`](crate::Wallet::sync) and [`Wallet::broadcast`](crate::Wallet::broadcast)
|
||||
/// This error is thrown when creating the Client for the first time, while recovery attempts are tried
|
||||
/// during the sync
|
||||
OfflineClient,
|
||||
/// Progress value must be between `0.0` (included) and `100.0` (included)
|
||||
InvalidProgressValue(f32),
|
||||
/// Progress update error (maybe the channel has been closed)
|
||||
@@ -198,7 +198,7 @@ impl From<crate::blockchain::compact_filters::CompactFiltersError> for Error {
|
||||
fn from(other: crate::blockchain::compact_filters::CompactFiltersError) -> Self {
|
||||
match other {
|
||||
crate::blockchain::compact_filters::CompactFiltersError::Global(e) => *e,
|
||||
err @ _ => Error::CompactFilters(err),
|
||||
err => Error::CompactFilters(err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,6 +43,7 @@ use miniscript::descriptor::{DescriptorXKey, KeyMap};
|
||||
pub use miniscript::ScriptContext;
|
||||
use miniscript::{Miniscript, Terminal};
|
||||
|
||||
use crate::descriptor::{CheckMiniscript, DescriptorError};
|
||||
use crate::wallet::utils::SecpCtx;
|
||||
|
||||
#[cfg(feature = "keys-bip39")]
|
||||
@@ -420,7 +421,7 @@ where
|
||||
/// [`ToDescriptorKey`]: the generated keys can be directly used in descriptors if `Self` is also
|
||||
/// [`ToDescriptorKey`].
|
||||
pub trait GeneratableKey<Ctx: ScriptContext>: Sized {
|
||||
/// Type specifying the amount of entropy required e.g. [u8;32]
|
||||
/// Type specifying the amount of entropy required e.g. `[u8;32]`
|
||||
type Entropy: AsMut<[u8]> + Default;
|
||||
|
||||
/// Extra options required by the `generate_with_entropy`
|
||||
@@ -572,14 +573,13 @@ fn expand_multi_keys<Pk: ToDescriptorKey<Ctx>, Ctx: ScriptContext>(
|
||||
pub fn make_pk<Pk: ToDescriptorKey<Ctx>, Ctx: ScriptContext>(
|
||||
descriptor_key: Pk,
|
||||
secp: &SecpCtx,
|
||||
) -> Result<(Miniscript<DescriptorPublicKey, Ctx>, KeyMap, ValidNetworks), KeyError> {
|
||||
) -> Result<(Miniscript<DescriptorPublicKey, Ctx>, KeyMap, ValidNetworks), DescriptorError> {
|
||||
let (key, key_map, valid_networks) = descriptor_key.to_descriptor_key()?.extract(secp)?;
|
||||
let minisc = Miniscript::from_ast(Terminal::PkK(key))?;
|
||||
|
||||
Ok((
|
||||
Miniscript::from_ast(Terminal::PkK(key))?,
|
||||
key_map,
|
||||
valid_networks,
|
||||
))
|
||||
minisc.check_minsicript()?;
|
||||
|
||||
Ok((minisc, key_map, valid_networks))
|
||||
}
|
||||
|
||||
// Used internally by `bdk::fragment!` to build `multi()` fragments
|
||||
@@ -588,14 +588,13 @@ pub fn make_multi<Pk: ToDescriptorKey<Ctx>, Ctx: ScriptContext>(
|
||||
thresh: usize,
|
||||
pks: Vec<Pk>,
|
||||
secp: &SecpCtx,
|
||||
) -> Result<(Miniscript<DescriptorPublicKey, Ctx>, KeyMap, ValidNetworks), KeyError> {
|
||||
) -> Result<(Miniscript<DescriptorPublicKey, Ctx>, KeyMap, ValidNetworks), DescriptorError> {
|
||||
let (pks, key_map, valid_networks) = expand_multi_keys(pks, secp)?;
|
||||
let minisc = Miniscript::from_ast(Terminal::Multi(thresh, pks))?;
|
||||
|
||||
Ok((
|
||||
Miniscript::from_ast(Terminal::Multi(thresh, pks))?,
|
||||
key_map,
|
||||
valid_networks,
|
||||
))
|
||||
minisc.check_minsicript()?;
|
||||
|
||||
Ok((minisc, key_map, valid_networks))
|
||||
}
|
||||
|
||||
// Used internally by `bdk::descriptor!` to build `sortedmulti()` fragments
|
||||
@@ -610,11 +609,14 @@ pub fn make_sortedmulti_inner<Pk: ToDescriptorKey<Ctx>, Ctx: ScriptContext>(
|
||||
KeyMap,
|
||||
ValidNetworks,
|
||||
),
|
||||
KeyError,
|
||||
DescriptorError,
|
||||
> {
|
||||
let (pks, key_map, valid_networks) = expand_multi_keys(pks, secp)?;
|
||||
let minisc = SortedMultiVec::new(thresh, pks)?;
|
||||
|
||||
Ok((SortedMultiVec::new(thresh, pks)?, key_map, valid_networks))
|
||||
// TODO: should we apply the checks here as well?
|
||||
|
||||
Ok((minisc, key_map, valid_networks))
|
||||
}
|
||||
|
||||
/// The "identity" conversion is used internally by some `bdk::fragment`s
|
||||
|
||||
14
src/lib.rs
14
src/lib.rs
@@ -91,11 +91,11 @@
|
||||
//!
|
||||
//! ### Example
|
||||
//! ```
|
||||
//! use bdk::{Wallet, OfflineWallet};
|
||||
//! use bdk::{Wallet};
|
||||
//! use bdk::database::MemoryDatabase;
|
||||
//!
|
||||
//! fn main() -> Result<(), bdk::Error> {
|
||||
//! let wallet: OfflineWallet<_> = Wallet::new_offline(
|
||||
//! let wallet = Wallet::new_offline(
|
||||
//! "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)",
|
||||
//! Some("wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)"),
|
||||
//! bitcoin::Network::Testnet,
|
||||
@@ -155,13 +155,13 @@
|
||||
//! ### Example
|
||||
//! ```ignore
|
||||
//! use base64::decode;
|
||||
//! use bdk::{Wallet, OfflineWallet};
|
||||
//! use bdk::{Wallet};
|
||||
//! use bdk::database::MemoryDatabase;
|
||||
//!
|
||||
//! use bitcoin::consensus::deserialize;
|
||||
//!
|
||||
//! fn main() -> Result<(), bdk::Error> {
|
||||
//! let wallet: OfflineWallet<_> = Wallet::new_offline(
|
||||
//! let wallet = Wallet::new_offline(
|
||||
//! "wpkh([c258d2e4/84h/1h/0h]tprv8griRPhA7342zfRyB6CqeKF8CJDXYu5pgnj1cjL1u2ngKcJha5jjTRimG82ABzJQ4MQe71CV54xfn25BbhCNfEGGJZnxvCDQCd6JkbvxW6h/0/*)",
|
||||
//! Some("wpkh([c258d2e4/84h/1h/0h]tprv8griRPhA7342zfRyB6CqeKF8CJDXYu5pgnj1cjL1u2ngKcJha5jjTRimG82ABzJQ4MQe71CV54xfn25BbhCNfEGGJZnxvCDQCd6JkbvxW6h/1/*)"),
|
||||
//! bitcoin::Network::Testnet,
|
||||
@@ -191,7 +191,6 @@
|
||||
//!
|
||||
//! * `all-keys`: all features for working with bitcoin keys
|
||||
//! * `async-interface`: async functions in bdk traits
|
||||
//! * `cli-utils`: utilities for creating a command line interface wallet
|
||||
//! * `keys-bip39`: [BIP-39](https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki) mnemonic codes for generating deterministic keys
|
||||
//!
|
||||
//! ## Internal features
|
||||
@@ -233,9 +232,6 @@ pub extern crate reqwest;
|
||||
#[cfg(feature = "key-value-db")]
|
||||
pub extern crate sled;
|
||||
|
||||
#[cfg(feature = "cli-utils")]
|
||||
pub mod cli;
|
||||
|
||||
#[allow(unused_imports)]
|
||||
#[cfg(test)]
|
||||
#[macro_use]
|
||||
@@ -268,4 +264,4 @@ pub use types::*;
|
||||
pub use wallet::address_validator;
|
||||
pub use wallet::signer;
|
||||
pub use wallet::tx_builder::TxBuilder;
|
||||
pub use wallet::{OfflineWallet, Wallet};
|
||||
pub use wallet::Wallet;
|
||||
|
||||
@@ -66,7 +66,7 @@
|
||||
//! }
|
||||
//!
|
||||
//! let descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/*)";
|
||||
//! let mut wallet: OfflineWallet<_> = Wallet::new_offline(descriptor, None, Network::Testnet, MemoryDatabase::default())?;
|
||||
//! let mut wallet = Wallet::new_offline(descriptor, None, Network::Testnet, MemoryDatabase::default())?;
|
||||
//! wallet.add_address_validator(Arc::new(PrintAddressAndContinue));
|
||||
//!
|
||||
//! let address = wallet.get_new_address()?;
|
||||
|
||||
@@ -71,9 +71,9 @@
|
||||
//! })
|
||||
//! .collect::<Vec<_>>();
|
||||
//! let additional_fees = additional_weight as f32 * fee_rate.as_sat_vb() / 4.0;
|
||||
//!
|
||||
//! if (fee_amount + additional_fees).ceil() as u64 + amount_needed > selected_amount {
|
||||
//! return Err(bdk::Error::InsufficientFunds);
|
||||
//! let amount_needed_with_fees = (fee_amount + additional_fees).ceil() as u64 + amount_needed;
|
||||
//! if amount_needed_with_fees > selected_amount {
|
||||
//! return Err(bdk::Error::InsufficientFunds{ needed: amount_needed_with_fees, available: selected_amount });
|
||||
//! }
|
||||
//!
|
||||
//! Ok(CoinSelectionResult {
|
||||
@@ -84,7 +84,7 @@
|
||||
//! }
|
||||
//! }
|
||||
//!
|
||||
//! # let wallet: OfflineWallet<_> = Wallet::new_offline("", None, Network::Testnet, bdk::database::MemoryDatabase::default())?;
|
||||
//! # let wallet = Wallet::new_offline("", None, Network::Testnet, bdk::database::MemoryDatabase::default())?;
|
||||
//! // create wallet, sync, ...
|
||||
//!
|
||||
//! let to_address = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap();
|
||||
@@ -221,8 +221,12 @@ impl<D: Database> CoinSelectionAlgorithm<D> for LargestFirstCoinSelection {
|
||||
)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if selected_amount < amount_needed + (fee_amount.ceil() as u64) {
|
||||
return Err(Error::InsufficientFunds);
|
||||
let amount_needed_with_fees = amount_needed + (fee_amount.ceil() as u64);
|
||||
if selected_amount < amount_needed_with_fees {
|
||||
return Err(Error::InsufficientFunds {
|
||||
needed: amount_needed_with_fees,
|
||||
available: selected_amount,
|
||||
});
|
||||
}
|
||||
|
||||
Ok(CoinSelectionResult {
|
||||
@@ -321,7 +325,10 @@ impl<D: Database> CoinSelectionAlgorithm<D> for BranchAndBoundCoinSelection {
|
||||
let cost_of_change = self.size_of_change as f32 * fee_rate.as_sat_vb();
|
||||
|
||||
if curr_available_value + curr_value < actual_target {
|
||||
return Err(Error::InsufficientFunds);
|
||||
return Err(Error::InsufficientFunds {
|
||||
needed: actual_target,
|
||||
available: curr_available_value + curr_value,
|
||||
});
|
||||
}
|
||||
|
||||
Ok(self
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
//! }"#;
|
||||
//!
|
||||
//! let import = WalletExport::from_str(import)?;
|
||||
//! let wallet: OfflineWallet<_> = Wallet::new_offline(
|
||||
//! let wallet = Wallet::new_offline(
|
||||
//! &import.descriptor(),
|
||||
//! import.change_descriptor().as_ref(),
|
||||
//! Network::Testnet,
|
||||
@@ -58,7 +58,7 @@
|
||||
//! # use bdk::database::*;
|
||||
//! # use bdk::wallet::export::*;
|
||||
//! # use bdk::*;
|
||||
//! let wallet: OfflineWallet<_> = Wallet::new_offline(
|
||||
//! let wallet = Wallet::new_offline(
|
||||
//! "wpkh([c258d2e4/84h/1h/0h]tpubDD3ynpHgJQW8VvWRzQ5WFDCrs4jqVFGHB3vLC3r49XHJSqP8bHKdK4AriuUKLccK68zfzowx7YhmDN8SiSkgCDENUFx9qVw65YyqM78vyVe/0/*)",
|
||||
//! Some("wpkh([c258d2e4/84h/1h/0h]tpubDD3ynpHgJQW8VvWRzQ5WFDCrs4jqVFGHB3vLC3r49XHJSqP8bHKdK4AriuUKLccK68zfzowx7YhmDN8SiSkgCDENUFx9qVw65YyqM78vyVe/1/*)"),
|
||||
//! Network::Testnet,
|
||||
@@ -78,7 +78,6 @@ use serde::{Deserialize, Serialize};
|
||||
|
||||
use miniscript::{Descriptor, DescriptorPublicKey, ScriptContext, Terminal};
|
||||
|
||||
use crate::blockchain::BlockchainMarker;
|
||||
use crate::database::BatchDatabase;
|
||||
use crate::wallet::Wallet;
|
||||
|
||||
@@ -120,7 +119,7 @@ impl WalletExport {
|
||||
///
|
||||
/// If the database is empty or `include_blockheight` is false, the `blockheight` field
|
||||
/// returned will be `0`.
|
||||
pub fn export_wallet<B: BlockchainMarker, D: BatchDatabase>(
|
||||
pub fn export_wallet<B, D: BatchDatabase>(
|
||||
wallet: &Wallet<B, D>,
|
||||
label: &str,
|
||||
include_blockheight: bool,
|
||||
@@ -208,7 +207,7 @@ mod test {
|
||||
use super::*;
|
||||
use crate::database::{memory::MemoryDatabase, BatchOperations};
|
||||
use crate::types::TransactionDetails;
|
||||
use crate::wallet::{OfflineWallet, Wallet};
|
||||
use crate::wallet::Wallet;
|
||||
|
||||
fn get_test_db() -> MemoryDatabase {
|
||||
let mut db = MemoryDatabase::new();
|
||||
@@ -234,7 +233,7 @@ mod test {
|
||||
let descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/0/*)";
|
||||
let change_descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/1/*)";
|
||||
|
||||
let wallet: OfflineWallet<_> = Wallet::new_offline(
|
||||
let wallet = Wallet::new_offline(
|
||||
descriptor,
|
||||
Some(change_descriptor),
|
||||
Network::Bitcoin,
|
||||
@@ -258,7 +257,7 @@ mod test {
|
||||
|
||||
let descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/0/*)";
|
||||
|
||||
let wallet: OfflineWallet<_> =
|
||||
let wallet =
|
||||
Wallet::new_offline(descriptor, None, Network::Bitcoin, get_test_db()).unwrap();
|
||||
WalletExport::export_wallet(&wallet, "Test Label", true).unwrap();
|
||||
}
|
||||
@@ -272,7 +271,7 @@ mod test {
|
||||
let descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/0/*)";
|
||||
let change_descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/50'/0'/1/*)";
|
||||
|
||||
let wallet: OfflineWallet<_> = Wallet::new_offline(
|
||||
let wallet = Wallet::new_offline(
|
||||
descriptor,
|
||||
Some(change_descriptor),
|
||||
Network::Bitcoin,
|
||||
@@ -295,7 +294,7 @@ mod test {
|
||||
[c98b1535/48'/0'/0'/2']tpubDCDi5W4sP6zSnzJeowy8rQDVhBdRARaPhK1axABi8V1661wEPeanpEXj4ZLAUEoikVtoWcyK26TKKJSecSfeKxwHCcRrge9k1ybuiL71z4a/1/*\
|
||||
))";
|
||||
|
||||
let wallet: OfflineWallet<_> = Wallet::new_offline(
|
||||
let wallet = Wallet::new_offline(
|
||||
descriptor,
|
||||
Some(change_descriptor),
|
||||
Network::Testnet,
|
||||
@@ -315,7 +314,7 @@ mod test {
|
||||
let descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/0/*)";
|
||||
let change_descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/1/*)";
|
||||
|
||||
let wallet: OfflineWallet<_> = Wallet::new_offline(
|
||||
let wallet = Wallet::new_offline(
|
||||
descriptor,
|
||||
Some(change_descriptor),
|
||||
Network::Bitcoin,
|
||||
|
||||
@@ -59,9 +59,12 @@ pub use utils::IsDust;
|
||||
use address_validator::AddressValidator;
|
||||
use signer::{Signer, SignerId, SignerOrdering, SignersContainer};
|
||||
use tx_builder::{BumpFee, CreateTx, FeePolicy, TxBuilder, TxBuilderContext};
|
||||
use utils::{check_nlocktime, check_nsequence_rbf, descriptor_to_pk_ctx, After, Older, SecpCtx};
|
||||
use utils::{
|
||||
check_nlocktime, check_nsequence_rbf, descriptor_to_pk_ctx, After, Older, SecpCtx,
|
||||
DUST_LIMIT_SATOSHI,
|
||||
};
|
||||
|
||||
use crate::blockchain::{Blockchain, BlockchainMarker, OfflineBlockchain, Progress};
|
||||
use crate::blockchain::{Blockchain, Progress};
|
||||
use crate::database::{BatchDatabase, BatchOperations, DatabaseUtils};
|
||||
use crate::descriptor::{
|
||||
get_checksum, DescriptorMeta, DescriptorScripts, ExtendedDescriptor, ExtractPolicy, Policy,
|
||||
@@ -73,9 +76,6 @@ use crate::types::*;
|
||||
|
||||
const CACHE_ADDR_BATCH_SIZE: u32 = 100;
|
||||
|
||||
/// Type alias for a [`Wallet`] that uses [`OfflineBlockchain`]
|
||||
pub type OfflineWallet<D> = Wallet<OfflineBlockchain, D>;
|
||||
|
||||
/// A Bitcoin wallet
|
||||
///
|
||||
/// A wallet takes descriptors, a [`database`](trait@crate::database::Database) and a
|
||||
@@ -84,7 +84,7 @@ pub type OfflineWallet<D> = Wallet<OfflineBlockchain, D>;
|
||||
/// [creating transactions](Wallet::create_tx), etc.
|
||||
///
|
||||
/// A wallet can be either "online" if the [`blockchain`](crate::blockchain) type provided
|
||||
/// implements [`Blockchain`], or "offline" [`OfflineBlockchain`] is used. Offline wallets only expose
|
||||
/// implements [`Blockchain`], or "offline" if it is the unit type `()`. Offline wallets only expose
|
||||
/// methods that don't need any interaction with the blockchain to work.
|
||||
pub struct Wallet<B, D> {
|
||||
descriptor: ExtendedDescriptor,
|
||||
@@ -99,24 +99,38 @@ pub struct Wallet<B, D> {
|
||||
|
||||
current_height: Option<u32>,
|
||||
|
||||
client: Option<B>,
|
||||
client: B,
|
||||
database: RefCell<D>,
|
||||
|
||||
secp: SecpCtx,
|
||||
}
|
||||
|
||||
// offline actions, always available
|
||||
impl<B, D> Wallet<B, D>
|
||||
impl<D> Wallet<(), D>
|
||||
where
|
||||
B: BlockchainMarker,
|
||||
D: BatchDatabase,
|
||||
{
|
||||
/// Create a new "offline" wallet
|
||||
pub fn new_offline<E: ToWalletDescriptor>(
|
||||
descriptor: E,
|
||||
change_descriptor: Option<E>,
|
||||
network: Network,
|
||||
database: D,
|
||||
) -> Result<Self, Error> {
|
||||
Self::_new(descriptor, change_descriptor, network, database, (), None)
|
||||
}
|
||||
}
|
||||
|
||||
impl<B, D> Wallet<B, D>
|
||||
where
|
||||
D: BatchDatabase,
|
||||
{
|
||||
fn _new<E: ToWalletDescriptor>(
|
||||
descriptor: E,
|
||||
change_descriptor: Option<E>,
|
||||
network: Network,
|
||||
mut database: D,
|
||||
client: B,
|
||||
current_height: Option<u32>,
|
||||
) -> Result<Self, Error> {
|
||||
let (descriptor, keymap) = descriptor.to_wallet_descriptor(network)?;
|
||||
database.check_descriptor_checksum(
|
||||
@@ -148,18 +162,20 @@ where
|
||||
signers,
|
||||
change_signers,
|
||||
address_validators: Vec::new(),
|
||||
|
||||
network,
|
||||
|
||||
current_height: None,
|
||||
|
||||
client: None,
|
||||
current_height,
|
||||
client,
|
||||
database: RefCell::new(database),
|
||||
|
||||
secp: Secp256k1::new(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// offline actions, always available
|
||||
impl<B, D> Wallet<B, D>
|
||||
where
|
||||
D: BatchDatabase,
|
||||
{
|
||||
/// Return a newly generated address using the external descriptor
|
||||
pub fn get_new_address(&self) -> Result<Address, Error> {
|
||||
let index = self.fetch_and_increment_index(KeychainKind::External)?;
|
||||
@@ -241,7 +257,7 @@ where
|
||||
/// # use bdk::*;
|
||||
/// # use bdk::database::*;
|
||||
/// # let descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/*)";
|
||||
/// # let wallet: OfflineWallet<_> = Wallet::new_offline(descriptor, None, Network::Testnet, MemoryDatabase::default())?;
|
||||
/// # let wallet = Wallet::new_offline(descriptor, None, Network::Testnet, MemoryDatabase::default())?;
|
||||
/// # let to_address = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap();
|
||||
/// let (psbt, details) = wallet.create_tx(
|
||||
/// TxBuilder::with_recipients(vec![(to_address.script_pubkey(), 50_000)])
|
||||
@@ -495,11 +511,15 @@ where
|
||||
match change_output {
|
||||
None if change_val.is_dust() => {
|
||||
// single recipient, but the only output would be below dust limit
|
||||
return Err(Error::InsufficientFunds); // TODO: or OutputBelowDustLimit?
|
||||
// TODO: or OutputBelowDustLimit?
|
||||
return Err(Error::InsufficientFunds {
|
||||
needed: DUST_LIMIT_SATOSHI,
|
||||
available: change_val,
|
||||
});
|
||||
}
|
||||
Some(_) if change_val.is_dust() => {
|
||||
// skip the change output because it's dust, this adds up to the fees
|
||||
fee_amount += selected_amount - outgoing;
|
||||
// skip the change output because it's dust -- just include it in the fee.
|
||||
fee_amount += change_val;
|
||||
}
|
||||
Some(mut change_output) => {
|
||||
change_output.value = change_val;
|
||||
@@ -556,7 +576,7 @@ where
|
||||
/// # use bdk::*;
|
||||
/// # use bdk::database::*;
|
||||
/// # let descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/*)";
|
||||
/// # let wallet: OfflineWallet<_> = Wallet::new_offline(descriptor, None, Network::Testnet, MemoryDatabase::default())?;
|
||||
/// # let wallet = Wallet::new_offline(descriptor, None, Network::Testnet, MemoryDatabase::default())?;
|
||||
/// let txid = Txid::from_str("faff0a466b70f5d5f92bd757a92c1371d4838bdd5bc53a06764e2488e51ce8f8").unwrap();
|
||||
/// let (psbt, details) = wallet.bump_fee(
|
||||
/// &txid,
|
||||
@@ -773,7 +793,11 @@ where
|
||||
}
|
||||
Some(_) if change_val_after_add.is_dust() => {
|
||||
// single_recipient but the only output would be below dust limit
|
||||
return Err(Error::InsufficientFunds); // TODO: or OutputBelowDustLimit?
|
||||
// TODO: or OutputBelowDustLimit?
|
||||
return Err(Error::InsufficientFunds {
|
||||
needed: DUST_LIMIT_SATOSHI,
|
||||
available: change_val_after_add,
|
||||
});
|
||||
}
|
||||
None => {
|
||||
removed_updatable_output.value = change_val_after_add;
|
||||
@@ -820,7 +844,7 @@ where
|
||||
/// # use bdk::*;
|
||||
/// # use bdk::database::*;
|
||||
/// # let descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/*)";
|
||||
/// # let wallet: OfflineWallet<_> = Wallet::new_offline(descriptor, None, Network::Testnet, MemoryDatabase::default())?;
|
||||
/// # let wallet = Wallet::new_offline(descriptor, None, Network::Testnet, MemoryDatabase::default())?;
|
||||
/// # let (psbt, _) = wallet.create_tx(TxBuilder::new())?;
|
||||
/// let (signed_psbt, finalized) = wallet.sign(psbt, None)?;
|
||||
/// # Ok::<(), bdk::Error>(())
|
||||
@@ -1319,12 +1343,15 @@ where
|
||||
database: D,
|
||||
client: B,
|
||||
) -> Result<Self, Error> {
|
||||
let mut wallet = Self::new_offline(descriptor, change_descriptor, network, database)?;
|
||||
|
||||
wallet.current_height = Some(maybe_await!(client.get_height())? as u32);
|
||||
wallet.client = Some(client);
|
||||
|
||||
Ok(wallet)
|
||||
let current_height = Some(maybe_await!(client.get_height())? as u32);
|
||||
Self::_new(
|
||||
descriptor,
|
||||
change_descriptor,
|
||||
network,
|
||||
database,
|
||||
client,
|
||||
current_height,
|
||||
)
|
||||
}
|
||||
|
||||
/// Sync the internal database with the blockchain
|
||||
@@ -1372,13 +1399,13 @@ where
|
||||
// TODO: what if i generate an address first and cache some addresses?
|
||||
// TODO: we should sync if generating an address triggers a new batch to be stored
|
||||
if run_setup {
|
||||
maybe_await!(self.client.as_ref().ok_or(Error::OfflineClient)?.setup(
|
||||
maybe_await!(self.client.setup(
|
||||
None,
|
||||
self.database.borrow_mut().deref_mut(),
|
||||
progress_update,
|
||||
))
|
||||
} else {
|
||||
maybe_await!(self.client.as_ref().ok_or(Error::OfflineClient)?.sync(
|
||||
maybe_await!(self.client.sync(
|
||||
None,
|
||||
self.database.borrow_mut().deref_mut(),
|
||||
progress_update,
|
||||
@@ -1387,8 +1414,8 @@ where
|
||||
}
|
||||
|
||||
/// Return a reference to the internal blockchain client
|
||||
pub fn client(&self) -> Option<&B> {
|
||||
self.client.as_ref()
|
||||
pub fn client(&self) -> &B {
|
||||
&self.client
|
||||
}
|
||||
|
||||
/// Get the Bitcoin network the wallet is using.
|
||||
@@ -1399,11 +1426,7 @@ where
|
||||
/// Broadcast a transaction to the network
|
||||
#[maybe_async]
|
||||
pub fn broadcast(&self, tx: Transaction) -> Result<Txid, Error> {
|
||||
maybe_await!(self
|
||||
.client
|
||||
.as_ref()
|
||||
.ok_or(Error::OfflineClient)?
|
||||
.broadcast(&tx))?;
|
||||
maybe_await!(self.client.broadcast(&tx))?;
|
||||
|
||||
Ok(tx.txid())
|
||||
}
|
||||
@@ -1424,7 +1447,7 @@ mod test {
|
||||
#[test]
|
||||
fn test_cache_addresses_fixed() {
|
||||
let db = MemoryDatabase::new();
|
||||
let wallet: OfflineWallet<_> = Wallet::new_offline(
|
||||
let wallet = Wallet::new_offline(
|
||||
"wpkh(L5EZftvrYaSudiozVRzTqLcHLNDoVn7H5HSfM9BAN6tMJX8oTWz6)",
|
||||
None,
|
||||
Network::Testnet,
|
||||
@@ -1458,7 +1481,7 @@ mod test {
|
||||
#[test]
|
||||
fn test_cache_addresses() {
|
||||
let db = MemoryDatabase::new();
|
||||
let wallet: OfflineWallet<_> = Wallet::new_offline("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)", None, Network::Testnet, db).unwrap();
|
||||
let wallet = Wallet::new_offline("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)", None, Network::Testnet, db).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
wallet.get_new_address().unwrap().to_string(),
|
||||
@@ -1486,7 +1509,7 @@ mod test {
|
||||
#[test]
|
||||
fn test_cache_addresses_refill() {
|
||||
let db = MemoryDatabase::new();
|
||||
let wallet: OfflineWallet<_> = Wallet::new_offline("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)", None, Network::Testnet, db).unwrap();
|
||||
let wallet = Wallet::new_offline("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)", None, Network::Testnet, db).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
wallet.get_new_address().unwrap().to_string(),
|
||||
@@ -1533,12 +1556,12 @@ mod test {
|
||||
pub(crate) fn get_funded_wallet(
|
||||
descriptor: &str,
|
||||
) -> (
|
||||
OfflineWallet<MemoryDatabase>,
|
||||
Wallet<(), MemoryDatabase>,
|
||||
(String, Option<String>),
|
||||
bitcoin::Txid,
|
||||
) {
|
||||
let descriptors = testutils!(@descriptors (descriptor));
|
||||
let wallet: OfflineWallet<_> = Wallet::new_offline(
|
||||
let wallet = Wallet::new_offline(
|
||||
&descriptors.0,
|
||||
None,
|
||||
Network::Regtest,
|
||||
@@ -1952,7 +1975,7 @@ mod test {
|
||||
fn test_create_tx_skip_change_dust() {
|
||||
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
|
||||
let addr = wallet.get_new_address().unwrap();
|
||||
let (psbt, _) = wallet
|
||||
let (psbt, details) = wallet
|
||||
.create_tx(TxBuilder::with_recipients(vec![(
|
||||
addr.script_pubkey(),
|
||||
49_800,
|
||||
@@ -1961,6 +1984,7 @@ mod test {
|
||||
|
||||
assert_eq!(psbt.global.unsigned_tx.output.len(), 1);
|
||||
assert_eq!(psbt.global.unsigned_tx.output[0].value, 49_800);
|
||||
assert_eq!(details.fees, 200);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -79,7 +79,7 @@
|
||||
//! let custom_signer = CustomSigner::connect();
|
||||
//!
|
||||
//! let descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/*)";
|
||||
//! let mut wallet: OfflineWallet<_> = Wallet::new_offline(descriptor, None, Network::Testnet, MemoryDatabase::default())?;
|
||||
//! let mut wallet = Wallet::new_offline(descriptor, None, Network::Testnet, MemoryDatabase::default())?;
|
||||
//! wallet.add_signer(
|
||||
//! KeychainKind::External,
|
||||
//! Fingerprint::from_str("e30f11b8").unwrap().into(),
|
||||
|
||||
@@ -29,7 +29,7 @@ use miniscript::descriptor::DescriptorPublicKeyCtx;
|
||||
use miniscript::{MiniscriptKey, Satisfier, ToPublicKey};
|
||||
|
||||
// De-facto standard "dust limit" (even though it should change based on the output type)
|
||||
const DUST_LIMIT_SATOSHI: u64 = 546;
|
||||
pub const DUST_LIMIT_SATOSHI: u64 = 546;
|
||||
|
||||
// MSB of the nSequence. If set there's no consensus-constraint, so it must be disabled when
|
||||
// spending using CSV in order to enforce CSV rules
|
||||
|
||||
@@ -8,7 +8,7 @@ repository = "https://github.com/bitcoindevkit/bdk"
|
||||
documentation = "https://docs.rs/bdk-testutils-macros"
|
||||
description = "Supporting testing macros for `bdk`"
|
||||
keywords = ["bdk"]
|
||||
license-file = "../LICENSE"
|
||||
license = "MIT"
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
||||
@@ -8,7 +8,7 @@ repository = "https://github.com/bitcoindevkit/bdk"
|
||||
documentation = "https://docs.rs/bdk-testutils"
|
||||
description = "Supporting testing utilities for `bdk`"
|
||||
keywords = ["bdk"]
|
||||
license-file = "../LICENSE"
|
||||
license = "MIT"
|
||||
|
||||
[lib]
|
||||
name = "testutils"
|
||||
|
||||
Reference in New Issue
Block a user