Compare commits
24 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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
|
- name: Install tarpaulin
|
||||||
run: cargo install cargo-tarpaulin
|
run: cargo install cargo-tarpaulin
|
||||||
- name: 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
|
- name: Publish to codecov.io
|
||||||
uses: codecov/codecov-action@v1.0.15
|
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
|
- key-value-db
|
||||||
- electrum
|
- electrum
|
||||||
- compact_filters
|
- compact_filters
|
||||||
- cli-utils,esplora,key-value-db,electrum
|
- esplora,key-value-db,electrum
|
||||||
- compiler
|
- compiler
|
||||||
steps:
|
steps:
|
||||||
- name: checkout
|
- name: checkout
|
||||||
@@ -44,7 +44,7 @@ jobs:
|
|||||||
- name: Build
|
- name: Build
|
||||||
run: cargo build --features ${{ matrix.features }} --no-default-features
|
run: cargo build --features ${{ matrix.features }} --no-default-features
|
||||||
- name: Clippy
|
- name: Clippy
|
||||||
run: cargo clippy -- -D warnings
|
run: cargo clippy --features ${{ matrix.features }} --no-default-features -- -D warnings
|
||||||
- name: Test
|
- name: Test
|
||||||
run: cargo test --features ${{ matrix.features }} --no-default-features
|
run: cargo test --features ${{ matrix.features }} --no-default-features
|
||||||
|
|
||||||
@@ -130,7 +130,7 @@ jobs:
|
|||||||
- name: Add target wasm32
|
- name: Add target wasm32
|
||||||
run: rustup target add wasm32-unknown-unknown
|
run: rustup target add wasm32-unknown-unknown
|
||||||
- name: Check
|
- 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:
|
fmt:
|
||||||
name: Rust 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
|
uses: actions-rs/cargo@v1
|
||||||
with:
|
with:
|
||||||
command: rustdoc
|
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
|
- name: Upload artifact
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v2
|
||||||
with:
|
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]
|
## [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
|
### Project
|
||||||
#### Added
|
#### Added
|
||||||
- Add CONTRIBUTING.md
|
- 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
|
- Use `MemoryDatabase` in the compiler example
|
||||||
- Make the REPL return JSON
|
- 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
|
[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]
|
[package]
|
||||||
name = "bdk"
|
name = "bdk"
|
||||||
version = "0.2.0"
|
version = "0.3.0"
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
authors = ["Alekos Filini <alekos.filini@gmail.com>", "Riccardo Casatta <riccardo@casatta.it>"]
|
authors = ["Alekos Filini <alekos.filini@gmail.com>", "Riccardo Casatta <riccardo@casatta.it>"]
|
||||||
homepage = "https://bitcoindevkit.org"
|
homepage = "https://bitcoindevkit.org"
|
||||||
@@ -9,7 +9,7 @@ documentation = "https://docs.rs/bdk"
|
|||||||
description = "A modern, lightweight, descriptor-based wallet library"
|
description = "A modern, lightweight, descriptor-based wallet library"
|
||||||
keywords = ["bitcoin", "wallet", "descriptor", "psbt"]
|
keywords = ["bitcoin", "wallet", "descriptor", "psbt"]
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
license-file = "LICENSE"
|
license = "MIT"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
bdk-macros = "0.2"
|
bdk-macros = "0.2"
|
||||||
@@ -22,11 +22,9 @@ rand = "^0.7"
|
|||||||
|
|
||||||
# Optional dependencies
|
# Optional dependencies
|
||||||
sled = { version = "0.34", optional = true }
|
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"] }
|
reqwest = { version = "0.10", optional = true, features = ["json"] }
|
||||||
futures = { version = "0.3", optional = true }
|
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 }
|
async-trait = { version = "0.1", optional = true }
|
||||||
rocksdb = { version = "0.14", optional = true }
|
rocksdb = { version = "0.14", optional = true }
|
||||||
# pin cc version to 1.0.62 because 1.0.63 break rocksdb build
|
# 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 }
|
socks = { version = "0.3", optional = true }
|
||||||
lazy_static = { version = "1.4", optional = true }
|
lazy_static = { version = "1.4", optional = true }
|
||||||
tiny-bip39 = { version = "^0.8", optional = true }
|
tiny-bip39 = { version = "^0.8", optional = true }
|
||||||
structopt = { version = "^0.3", optional = true }
|
|
||||||
|
|
||||||
# Platform-specific dependencies
|
# Platform-specific dependencies
|
||||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||||
@@ -47,13 +44,12 @@ rand = { version = "^0.7", features = ["wasm-bindgen"] }
|
|||||||
|
|
||||||
[features]
|
[features]
|
||||||
minimal = []
|
minimal = []
|
||||||
compiler = ["clap", "miniscript/compiler"]
|
compiler = ["miniscript/compiler"]
|
||||||
default = ["key-value-db", "electrum"]
|
default = ["key-value-db", "electrum"]
|
||||||
electrum = ["electrum-client"]
|
electrum = ["electrum-client"]
|
||||||
esplora = ["reqwest", "futures"]
|
esplora = ["reqwest", "futures"]
|
||||||
compact_filters = ["rocksdb", "socks", "lazy_static", "cc"]
|
compact_filters = ["rocksdb", "socks", "lazy_static", "cc"]
|
||||||
key-value-db = ["sled"]
|
key-value-db = ["sled"]
|
||||||
cli-utils = ["clap", "base64", "structopt"]
|
|
||||||
async-interface = ["async-trait"]
|
async-interface = ["async-trait"]
|
||||||
all-keys = ["keys-bip39"]
|
all-keys = ["keys-bip39"]
|
||||||
keys-bip39 = ["tiny-bip39"]
|
keys-bip39 = ["tiny-bip39"]
|
||||||
@@ -61,20 +57,17 @@ keys-bip39 = ["tiny-bip39"]
|
|||||||
# Debug/Test features
|
# Debug/Test features
|
||||||
debug-proc-macros = ["bdk-macros/debug", "bdk-testutils-macros/debug"]
|
debug-proc-macros = ["bdk-macros/debug", "bdk-testutils-macros/debug"]
|
||||||
test-electrum = ["electrum"]
|
test-electrum = ["electrum"]
|
||||||
test-md-docs = ["base64", "electrum"]
|
test-md-docs = ["electrum"]
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
bdk-testutils = "0.2"
|
bdk-testutils = "0.2"
|
||||||
bdk-testutils-macros = "0.2"
|
bdk-testutils-macros = "0.2"
|
||||||
serial_test = "0.4"
|
serial_test = "0.4"
|
||||||
lazy_static = "1.4"
|
lazy_static = "1.4"
|
||||||
rustyline = "6.0"
|
|
||||||
dirs-next = "2.0"
|
|
||||||
env_logger = "0.7"
|
env_logger = "0.7"
|
||||||
|
base64 = "^0.11"
|
||||||
|
clap = "2.33"
|
||||||
|
|
||||||
[[example]]
|
|
||||||
name = "repl"
|
|
||||||
required-features = ["cli-utils"]
|
|
||||||
[[example]]
|
[[example]]
|
||||||
name = "parse_descriptor"
|
name = "parse_descriptor"
|
||||||
[[example]]
|
[[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:
|
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
|
- 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
|
- 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".
|
6. Update the changelog with the new release version
|
||||||
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.
|
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. Push the new commits to the upstream release branch, wait for the CI to finish one last time.
|
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. Publish **all** the updated crates to crates.io.
|
9. Push the new commits to the upstream release branch, wait for the CI to finish one last time.
|
||||||
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".
|
10. Publish **all** the updated crates to crates.io.
|
||||||
11. Merge the release branch back into `master`.
|
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. Make sure the new release shows up on crates.io and that the docs are built correctly on docs.rs.
|
12. Merge the release branch back into `master`.
|
||||||
13. Announce the release on Twitter, Discord and Telegram.
|
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. Celebrate :tada:
|
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>
|
||||||
|
|
||||||
<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://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://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://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>
|
<a href="https://discord.gg/d7NkDKm"><img alt="Chat on Discord" src="https://img.shields.io/discord/753336465005608961?logo=discord"></a>
|
||||||
</p>
|
</p>
|
||||||
@@ -19,7 +20,7 @@
|
|||||||
<h4>
|
<h4>
|
||||||
<a href="https://bitcoindevkit.org">Project Homepage</a>
|
<a href="https://bitcoindevkit.org">Project Homepage</a>
|
||||||
<span> | </span>
|
<span> | </span>
|
||||||
<a href="https://bitcoindevkit.org/docs-rs/bdk">Documentation</a>
|
<a href="https://docs.rs/bdk">Documentation</a>
|
||||||
</h4>
|
</h4>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -65,11 +66,10 @@ fn main() -> Result<(), bdk::Error> {
|
|||||||
### Generate a few addresses
|
### Generate a few addresses
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
use bdk::{Wallet, OfflineWallet};
|
use bdk::{Wallet, database::MemoryDatabase};
|
||||||
use bdk::database::MemoryDatabase;
|
|
||||||
|
|
||||||
fn main() -> Result<(), bdk::Error> {
|
fn main() -> Result<(), bdk::Error> {
|
||||||
let wallet: OfflineWallet<_> = Wallet::new_offline(
|
let wallet = Wallet::new_offline(
|
||||||
"wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)",
|
"wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)",
|
||||||
Some("wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)"),
|
Some("wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)"),
|
||||||
bitcoin::Network::Testnet,
|
bitcoin::Network::Testnet,
|
||||||
@@ -125,13 +125,12 @@ fn main() -> Result<(), bdk::Error> {
|
|||||||
### Sign a transaction
|
### Sign a transaction
|
||||||
|
|
||||||
```rust,no_run
|
```rust,no_run
|
||||||
use bdk::{Wallet, OfflineWallet};
|
use bdk::{Wallet, database::MemoryDatabase};
|
||||||
use bdk::database::MemoryDatabase;
|
|
||||||
|
|
||||||
use bitcoin::consensus::deserialize;
|
use bitcoin::consensus::deserialize;
|
||||||
|
|
||||||
fn main() -> Result<(), bdk::Error> {
|
fn main() -> Result<(), bdk::Error> {
|
||||||
let wallet: OfflineWallet<_> = Wallet::new_offline(
|
let wallet = Wallet::new_offline(
|
||||||
"wpkh([c258d2e4/84h/1h/0h]tprv8griRPhA7342zfRyB6CqeKF8CJDXYu5pgnj1cjL1u2ngKcJha5jjTRimG82ABzJQ4MQe71CV54xfn25BbhCNfEGGJZnxvCDQCd6JkbvxW6h/0/*)",
|
"wpkh([c258d2e4/84h/1h/0h]tprv8griRPhA7342zfRyB6CqeKF8CJDXYu5pgnj1cjL1u2ngKcJha5jjTRimG82ABzJQ4MQe71CV54xfn25BbhCNfEGGJZnxvCDQCd6JkbvxW6h/0/*)",
|
||||||
Some("wpkh([c258d2e4/84h/1h/0h]tprv8griRPhA7342zfRyB6CqeKF8CJDXYu5pgnj1cjL1u2ngKcJha5jjTRimG82ABzJQ4MQe71CV54xfn25BbhCNfEGGJZnxvCDQCd6JkbvxW6h/1/*)"),
|
Some("wpkh([c258d2e4/84h/1h/0h]tprv8griRPhA7342zfRyB6CqeKF8CJDXYu5pgnj1cjL1u2ngKcJha5jjTRimG82ABzJQ4MQe71CV54xfn25BbhCNfEGGJZnxvCDQCd6JkbvxW6h/1/*)"),
|
||||||
bitcoin::Network::Testnet,
|
bitcoin::Network::Testnet,
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ use bdk::database::MemoryDatabase;
|
|||||||
use bdk::descriptor::HDKeyPaths;
|
use bdk::descriptor::HDKeyPaths;
|
||||||
use bdk::wallet::address_validator::{AddressValidator, AddressValidatorError};
|
use bdk::wallet::address_validator::{AddressValidator, AddressValidatorError};
|
||||||
use bdk::KeychainKind;
|
use bdk::KeychainKind;
|
||||||
use bdk::{OfflineWallet, Wallet};
|
use bdk::Wallet;
|
||||||
|
|
||||||
use bitcoin::hashes::hex::FromHex;
|
use bitcoin::hashes::hex::FromHex;
|
||||||
use bitcoin::util::bip32::Fingerprint;
|
use bitcoin::util::bip32::Fingerprint;
|
||||||
@@ -59,7 +59,7 @@ impl AddressValidator for DummyValidator {
|
|||||||
|
|
||||||
fn main() -> Result<(), bdk::Error> {
|
fn main() -> Result<(), bdk::Error> {
|
||||||
let descriptor = "sh(and_v(v:pk(tpubDDpWvmUrPZrhSPmUzCMBHffvC3HyMAPnWDSAQNBTnj1iZeJa7BZQEttFiP4DS4GCcXQHezdXhn86Hj6LHX5EDstXPWrMaSneRWM8yUf6NFd/*),after(630000)))";
|
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::new_offline(descriptor, None, Network::Regtest, MemoryDatabase::new())?;
|
||||||
|
|
||||||
wallet.add_address_validator(Arc::new(DummyValidator));
|
wallet.add_address_validator(Arc::new(DummyValidator));
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ use miniscript::policy::Concrete;
|
|||||||
use miniscript::Descriptor;
|
use miniscript::Descriptor;
|
||||||
|
|
||||||
use bdk::database::memory::MemoryDatabase;
|
use bdk::database::memory::MemoryDatabase;
|
||||||
use bdk::{KeychainKind, OfflineWallet, Wallet};
|
use bdk::{KeychainKind, Wallet};
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
env_logger::init_from_env(
|
env_logger::init_from_env(
|
||||||
@@ -98,8 +98,7 @@ fn main() {
|
|||||||
Some("regtest") => Network::Regtest,
|
Some("regtest") => Network::Regtest,
|
||||||
Some("testnet") | _ => Network::Testnet,
|
Some("testnet") | _ => Network::Testnet,
|
||||||
};
|
};
|
||||||
let wallet: OfflineWallet<_> =
|
let wallet = Wallet::new_offline(&format!("{}", descriptor), None, network, database).unwrap();
|
||||||
Wallet::new_offline(&format!("{}", descriptor), None, network, database).unwrap();
|
|
||||||
|
|
||||||
info!("... First address: {}", wallet.get_new_address().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"
|
documentation = "https://docs.rs/bdk-macros"
|
||||||
description = "Supporting macros for `bdk`"
|
description = "Supporting macros for `bdk`"
|
||||||
keywords = ["bdk"]
|
keywords = ["bdk"]
|
||||||
license-file = "../LICENSE"
|
license = "MIT"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# 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 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 db = DB::open_cf(&opts, &storage_dir, &cfs)?;
|
||||||
let headers = Arc::new(ChainStore::new(db, network)?);
|
let headers = Arc::new(ChainStore::new(db, network)?);
|
||||||
|
|
||||||
@@ -221,7 +221,7 @@ impl CompactFiltersBlockchain {
|
|||||||
sent: outgoing,
|
sent: outgoing,
|
||||||
height,
|
height,
|
||||||
timestamp,
|
timestamp,
|
||||||
fees: inputs_sum.checked_sub(outputs_sum).unwrap_or(0),
|
fees: inputs_sum.saturating_sub(outputs_sum),
|
||||||
};
|
};
|
||||||
|
|
||||||
info!("Saving tx {}", tx.txid);
|
info!("Saving tx {}", tx.txid);
|
||||||
@@ -257,13 +257,10 @@ impl Blockchain for CompactFiltersBlockchain {
|
|||||||
.map(|x| x / 1000)
|
.map(|x| x / 1000)
|
||||||
.unwrap_or(0)
|
.unwrap_or(0)
|
||||||
+ 1;
|
+ 1;
|
||||||
let expected_bundles_to_sync = total_bundles
|
let expected_bundles_to_sync = total_bundles.saturating_sub(cf_sync.pruned_bundles()?);
|
||||||
.checked_sub(cf_sync.pruned_bundles()?)
|
|
||||||
.unwrap_or(0);
|
|
||||||
|
|
||||||
let headers_cost = (first_peer.get_version().start_height as usize)
|
let headers_cost = (first_peer.get_version().start_height as usize)
|
||||||
.checked_sub(initial_height)
|
.saturating_sub(initial_height) as f32
|
||||||
.unwrap_or(0) as f32
|
|
||||||
* SYNC_HEADERS_COST;
|
* SYNC_HEADERS_COST;
|
||||||
let filters_cost = expected_bundles_to_sync as f32 * SYNC_FILTERS_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),
|
Arc::clone(&self.headers),
|
||||||
|new_height| {
|
|new_height| {
|
||||||
let local_headers_cost =
|
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(
|
progress_update.update(
|
||||||
local_headers_cost / total_cost * 100.0,
|
local_headers_cost / total_cost * 100.0,
|
||||||
Some(format!("Synced headers to {}", new_height)),
|
Some(format!("Synced headers to {}", new_height)),
|
||||||
@@ -288,9 +285,7 @@ impl Blockchain for CompactFiltersBlockchain {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let synced_height = self.headers.get_height()?;
|
let synced_height = self.headers.get_height()?;
|
||||||
let buried_height = synced_height
|
let buried_height = synced_height.saturating_sub(sync::BURIED_CONFIRMATIONS);
|
||||||
.checked_sub(sync::BURIED_CONFIRMATIONS)
|
|
||||||
.unwrap_or(0);
|
|
||||||
info!("Synced headers to height: {}", synced_height);
|
info!("Synced headers to height: {}", synced_height);
|
||||||
|
|
||||||
cf_sync.prepare_sync(Arc::clone(&first_peer))?;
|
cf_sync.prepare_sync(Arc::clone(&first_peer))?;
|
||||||
@@ -303,7 +298,9 @@ impl Blockchain for CompactFiltersBlockchain {
|
|||||||
.collect::<Vec<_>>(),
|
.collect::<Vec<_>>(),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
#[allow(clippy::mutex_atomic)]
|
||||||
let last_synced_block = Arc::new(Mutex::new(synced_height));
|
let last_synced_block = Arc::new(Mutex::new(synced_height));
|
||||||
|
|
||||||
let synced_bundles = Arc::new(AtomicUsize::new(0));
|
let synced_bundles = Arc::new(AtomicUsize::new(0));
|
||||||
let progress_update = Arc::new(Mutex::new(progress_update));
|
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 block_height = headers.get_height_for(block_hash)?.unwrap_or(0);
|
||||||
let saved_correct_block = match headers.get_full_block(block_height)? {
|
let saved_correct_block = matches!(headers.get_full_block(block_height)?, Some(block) if &block.block_hash() == block_hash);
|
||||||
Some(block) if &block.block_hash() == block_hash => true,
|
|
||||||
_ => false,
|
|
||||||
};
|
|
||||||
|
|
||||||
if saved_correct_block {
|
if saved_correct_block {
|
||||||
Ok(false)
|
Ok(false)
|
||||||
|
|||||||
@@ -333,7 +333,7 @@ impl Peer {
|
|||||||
NetworkMessage::Alert(_) => continue,
|
NetworkMessage::Alert(_) => continue,
|
||||||
NetworkMessage::GetData(ref inv) => {
|
NetworkMessage::GetData(ref inv) => {
|
||||||
let (found, not_found): (Vec<_>, Vec<_>) = inv
|
let (found, not_found): (Vec<_>, Vec<_>) = inv
|
||||||
.into_iter()
|
.iter()
|
||||||
.map(|item| (*item, reader_thread_mempool.get_tx(item)))
|
.map(|item| (*item, reader_thread_mempool.get_tx(item)))
|
||||||
.partition(|(_, d)| d.is_some());
|
.partition(|(_, d)| d.is_some());
|
||||||
for (_, found_tx) in found {
|
for (_, found_tx) in found {
|
||||||
@@ -518,10 +518,9 @@ impl InvPeer for Peer {
|
|||||||
let getdata = inv
|
let getdata = inv
|
||||||
.iter()
|
.iter()
|
||||||
.cloned()
|
.cloned()
|
||||||
.filter(|item| match item {
|
.filter(
|
||||||
Inventory::Transaction(txid) if !self.mempool.has_tx(txid) => true,
|
|item| matches!(item, Inventory::Transaction(txid) if !self.mempool.has_tx(txid)),
|
||||||
_ => false,
|
)
|
||||||
})
|
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
let num_txs = getdata.len();
|
let num_txs = getdata.len();
|
||||||
self.send(NetworkMessage::GetData(getdata))?;
|
self.send(NetworkMessage::GetData(getdata))?;
|
||||||
|
|||||||
@@ -375,7 +375,7 @@ impl ChainStore<Full> {
|
|||||||
let min_height = match iterator
|
let min_height = match iterator
|
||||||
.next()
|
.next()
|
||||||
.and_then(|(k, _)| k[1..].try_into().ok())
|
.and_then(|(k, _)| k[1..].try_into().ok())
|
||||||
.map(|bytes| usize::from_be_bytes(bytes))
|
.map(usize::from_be_bytes)
|
||||||
{
|
{
|
||||||
None => {
|
None => {
|
||||||
std::mem::drop(iterator);
|
std::mem::drop(iterator);
|
||||||
@@ -444,9 +444,6 @@ impl ChainStore<Full> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
read_store.write(batch)?;
|
read_store.write(batch)?;
|
||||||
|
|
||||||
std::mem::drop(snapshot_cf_handle);
|
|
||||||
std::mem::drop(cf_handle);
|
|
||||||
std::mem::drop(read_store);
|
std::mem::drop(read_store);
|
||||||
|
|
||||||
self.store.write().unwrap().drop_cf(&snaphost.cf_name)?;
|
self.store.write().unwrap().drop_cf(&snaphost.cf_name)?;
|
||||||
@@ -461,7 +458,7 @@ impl ChainStore<Full> {
|
|||||||
let read_store = self.store.read().unwrap();
|
let read_store = self.store.read().unwrap();
|
||||||
let cf_handle = read_store.cf_handle(&self.cf_name).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)?;
|
let data = read_store.get_pinned_cf(cf_handle, key)?;
|
||||||
Ok(data
|
Ok(data
|
||||||
.map(|data| {
|
.map(|data| {
|
||||||
@@ -642,7 +639,6 @@ impl<T: StoreType> ChainStore<T> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::mem::drop(cf_handle);
|
|
||||||
std::mem::drop(read_store);
|
std::mem::drop(read_store);
|
||||||
|
|
||||||
self.store.write().unwrap().write(batch)?;
|
self.store.write().unwrap().write(batch)?;
|
||||||
|
|||||||
@@ -204,9 +204,8 @@ impl CFSync {
|
|||||||
if let BundleStatus::CFilters { cf_filters } = status {
|
if let BundleStatus::CFilters { cf_filters } = status {
|
||||||
log::trace!("status: CFilters");
|
log::trace!("status: CFilters");
|
||||||
|
|
||||||
let last_sync_buried_height = (start_height + already_processed)
|
let last_sync_buried_height =
|
||||||
.checked_sub(BURIED_CONFIRMATIONS)
|
(start_height + already_processed).saturating_sub(BURIED_CONFIRMATIONS);
|
||||||
.unwrap_or(0);
|
|
||||||
|
|
||||||
for (filter_index, filter) in cf_filters.iter().enumerate() {
|
for (filter_index, filter) in cf_filters.iter().enumerate() {
|
||||||
let height = filter_index + start_height;
|
let height = filter_index + start_height;
|
||||||
@@ -280,10 +279,7 @@ where
|
|||||||
|
|
||||||
match locators_map.get(&headers[0].prev_blockhash) {
|
match locators_map.get(&headers[0].prev_blockhash) {
|
||||||
None => return Err(CompactFiltersError::InvalidHeaders),
|
None => return Err(CompactFiltersError::InvalidHeaders),
|
||||||
Some(from) => (
|
Some(from) => (store.start_snapshot(*from)?, headers[0].prev_blockhash),
|
||||||
store.start_snapshot(*from)?,
|
|
||||||
headers[0].prev_blockhash.clone(),
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return Err(CompactFiltersError::InvalidResponse);
|
return Err(CompactFiltersError::InvalidResponse);
|
||||||
|
|||||||
@@ -168,7 +168,7 @@ pub struct ElectrumBlockchainConfig {
|
|||||||
/// Request retry count
|
/// Request retry count
|
||||||
pub retry: u8,
|
pub retry: u8,
|
||||||
/// Request timeout (seconds)
|
/// Request timeout (seconds)
|
||||||
pub timeout: u8,
|
pub timeout: Option<u8>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ConfigurableBlockchain for ElectrumBlockchain {
|
impl ConfigurableBlockchain for ElectrumBlockchain {
|
||||||
@@ -178,8 +178,8 @@ impl ConfigurableBlockchain for ElectrumBlockchain {
|
|||||||
let socks5 = config.socks5.as_ref().map(Socks5Config::new);
|
let socks5 = config.socks5.as_ref().map(Socks5Config::new);
|
||||||
let electrum_config = ConfigBuilder::new()
|
let electrum_config = ConfigBuilder::new()
|
||||||
.retry(config.retry)
|
.retry(config.retry)
|
||||||
.socks5(socks5)?
|
|
||||||
.timeout(config.timeout)?
|
.timeout(config.timeout)?
|
||||||
|
.socks5(socks5)?
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
Ok(ElectrumBlockchain(Client::from_config(
|
Ok(ElectrumBlockchain(Client::from_config(
|
||||||
|
|||||||
@@ -79,29 +79,9 @@ pub enum Capability {
|
|||||||
AccurateFees,
|
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
|
/// Trait that defines the actions that must be supported by a blockchain backend
|
||||||
#[maybe_async]
|
#[maybe_async]
|
||||||
pub trait Blockchain: BlockchainMarker {
|
pub trait Blockchain {
|
||||||
/// Return the set of [`Capability`] supported by this backend
|
/// Return the set of [`Capability`] supported by this backend
|
||||||
fn get_capabilities(&self) -> HashSet<Capability>;
|
fn get_capabilities(&self) -> HashSet<Capability>;
|
||||||
|
|
||||||
@@ -189,7 +169,7 @@ pub fn progress() -> (Sender<ProgressData>, Receiver<ProgressData>) {
|
|||||||
|
|
||||||
impl Progress for Sender<ProgressData> {
|
impl Progress for Sender<ProgressData> {
|
||||||
fn update(&self, progress: f32, message: Option<String>) -> Result<(), Error> {
|
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));
|
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
|
//! ## 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
|
//! ```no_run
|
||||||
//! # use bitcoin::Network;
|
//! # use bitcoin::Network;
|
||||||
//! # use bdk::database::{AnyDatabase, MemoryDatabase};
|
//! # use bdk::database::{AnyDatabase, MemoryDatabase};
|
||||||
//! # use bdk::{Wallet, OfflineWallet};
|
//! # use bdk::{Wallet};
|
||||||
//! let memory = MemoryDatabase::default().into();
|
//! let memory = MemoryDatabase::default();
|
||||||
//! let wallet_memory: OfflineWallet<AnyDatabase> =
|
//! let wallet_memory = Wallet::new_offline("...", None, Network::Testnet, memory)?;
|
||||||
//! Wallet::new_offline("...", None, Network::Testnet, memory)?;
|
|
||||||
//!
|
//!
|
||||||
//! # #[cfg(feature = "key-value-db")]
|
//! # #[cfg(feature = "key-value-db")]
|
||||||
//! # {
|
//! # {
|
||||||
//! let sled = sled::open("my-database")?.open_tree("default_tree")?.into();
|
//! let sled = sled::open("my-database")?.open_tree("default_tree")?;
|
||||||
//! let wallet_sled: OfflineWallet<AnyDatabase> =
|
//! let wallet_sled = Wallet::new_offline("...", None, Network::Testnet, sled)?;
|
||||||
//! Wallet::new_offline("...", None, Network::Testnet, sled)?;
|
|
||||||
//! # }
|
//! # }
|
||||||
//! # Ok::<(), bdk::Error>(())
|
//! # Ok::<(), bdk::Error>(())
|
||||||
//! ```
|
//! ```
|
||||||
@@ -54,10 +52,10 @@
|
|||||||
//! ```no_run
|
//! ```no_run
|
||||||
//! # use bitcoin::Network;
|
//! # use bitcoin::Network;
|
||||||
//! # use bdk::database::*;
|
//! # use bdk::database::*;
|
||||||
//! # use bdk::{Wallet, OfflineWallet};
|
//! # use bdk::{Wallet};
|
||||||
//! let config = serde_json::from_str("...")?;
|
//! let config = serde_json::from_str("...")?;
|
||||||
//! let database = AnyDatabase::from_config(&config)?;
|
//! 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>(())
|
//! # Ok::<(), bdk::Error>(())
|
||||||
//! ```
|
//! ```
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,7 @@
|
|||||||
|
|
||||||
use std::iter::FromIterator;
|
use std::iter::FromIterator;
|
||||||
|
|
||||||
use crate::descriptor::Error;
|
use crate::descriptor::DescriptorError;
|
||||||
|
|
||||||
const INPUT_CHARSET: &str = "0123456789()[],'/*abcdefgh@:$%{}IJKLMNOPQRSTUVWXYZ&+-.;<=>?!^_|~ijklmnopqrstuvwxyzABCDEFGH`#\"\\ ";
|
const INPUT_CHARSET: &str = "0123456789()[],'/*abcdefgh@:$%{}IJKLMNOPQRSTUVWXYZ&+-.;<=>?!^_|~ijklmnopqrstuvwxyzABCDEFGH`#\"\\ ";
|
||||||
const CHECKSUM_CHARSET: &str = "qpzry9x8gf2tvdw0s3jn54khce6mua7l";
|
const CHECKSUM_CHARSET: &str = "qpzry9x8gf2tvdw0s3jn54khce6mua7l";
|
||||||
@@ -57,14 +57,14 @@ fn poly_mod(mut c: u64, val: u64) -> u64 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Compute the checksum of a descriptor
|
/// 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 c = 1;
|
||||||
let mut cls = 0;
|
let mut cls = 0;
|
||||||
let mut clscount = 0;
|
let mut clscount = 0;
|
||||||
for ch in desc.chars() {
|
for ch in desc.chars() {
|
||||||
let pos = INPUT_CHARSET
|
let pos = INPUT_CHARSET
|
||||||
.find(ch)
|
.find(ch)
|
||||||
.ok_or(Error::InvalidDescriptorCharacter(ch))? as u64;
|
.ok_or(DescriptorError::InvalidDescriptorCharacter(ch))? as u64;
|
||||||
c = poly_mod(c, pos & 31);
|
c = poly_mod(c, pos & 31);
|
||||||
cls = cls * 3 + (pos >> 5);
|
cls = cls * 3 + (pos >> 5);
|
||||||
clscount += 1;
|
clscount += 1;
|
||||||
@@ -120,7 +120,7 @@ mod test {
|
|||||||
|
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
get_checksum(&invalid_desc).err(),
|
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()
|
$key.to_descriptor_key()
|
||||||
.and_then(|key: DescriptorKey<$ctx>| key.extract(&secp))
|
.and_then(|key: DescriptorKey<$ctx>| key.extract(&secp))
|
||||||
|
.map_err($crate::descriptor::DescriptorError::Key)
|
||||||
.map(|(pk, key_map, valid_networks)| {
|
.map(|(pk, key_map, valid_networks)| {
|
||||||
(
|
(
|
||||||
$crate::miniscript::Descriptor::<
|
$crate::miniscript::Descriptor::<
|
||||||
@@ -75,11 +76,17 @@ macro_rules! impl_top_level_pk {
|
|||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! impl_leaf_opcode {
|
macro_rules! impl_leaf_opcode {
|
||||||
( $terminal_variant:ident ) => {
|
( $terminal_variant:ident ) => {{
|
||||||
|
use $crate::descriptor::CheckMiniscript;
|
||||||
|
|
||||||
$crate::miniscript::Miniscript::from_ast(
|
$crate::miniscript::Miniscript::from_ast(
|
||||||
$crate::miniscript::miniscript::decode::Terminal::$terminal_variant,
|
$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| {
|
.map(|minisc| {
|
||||||
(
|
(
|
||||||
minisc,
|
minisc,
|
||||||
@@ -87,17 +94,23 @@ macro_rules! impl_leaf_opcode {
|
|||||||
$crate::keys::any_network(),
|
$crate::keys::any_network(),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
};
|
}};
|
||||||
}
|
}
|
||||||
|
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! impl_leaf_opcode_value {
|
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::from_ast(
|
||||||
$crate::miniscript::miniscript::decode::Terminal::$terminal_variant($value),
|
$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| {
|
.map(|minisc| {
|
||||||
(
|
(
|
||||||
minisc,
|
minisc,
|
||||||
@@ -105,17 +118,23 @@ macro_rules! impl_leaf_opcode_value {
|
|||||||
$crate::keys::any_network(),
|
$crate::keys::any_network(),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
};
|
}};
|
||||||
}
|
}
|
||||||
|
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! impl_leaf_opcode_value_two {
|
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::from_ast(
|
||||||
$crate::miniscript::miniscript::decode::Terminal::$terminal_variant($one, $two),
|
$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| {
|
.map(|minisc| {
|
||||||
(
|
(
|
||||||
minisc,
|
minisc,
|
||||||
@@ -123,13 +142,15 @@ macro_rules! impl_leaf_opcode_value_two {
|
|||||||
$crate::keys::any_network(),
|
$crate::keys::any_network(),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
};
|
}};
|
||||||
}
|
}
|
||||||
|
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! impl_node_opcode_two {
|
macro_rules! impl_node_opcode_two {
|
||||||
( $terminal_variant:ident, $( $inner:tt )* ) => ({
|
( $terminal_variant:ident, $( $inner:tt )* ) => ({
|
||||||
|
use $crate::descriptor::CheckMiniscript;
|
||||||
|
|
||||||
let inner = $crate::fragment_internal!( @t $( $inner )* );
|
let inner = $crate::fragment_internal!( @t $( $inner )* );
|
||||||
let (a, b) = $crate::descriptor::dsl::TupleTwo::from(inner).flattened();
|
let (a, b) = $crate::descriptor::dsl::TupleTwo::from(inner).flattened();
|
||||||
|
|
||||||
@@ -139,10 +160,14 @@ macro_rules! impl_node_opcode_two {
|
|||||||
// join key_maps
|
// join key_maps
|
||||||
a_keymap.extend(b_keymap.into_iter());
|
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(a_minisc),
|
||||||
std::sync::Arc::new(b_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_export]
|
||||||
macro_rules! impl_node_opcode_three {
|
macro_rules! impl_node_opcode_three {
|
||||||
( $terminal_variant:ident, $( $inner:tt )* ) => {
|
( $terminal_variant:ident, $( $inner:tt )* ) => {
|
||||||
|
use $crate::descriptor::CheckMiniscript;
|
||||||
|
|
||||||
let inner = $crate::fragment_internal!( @t $( $inner )* );
|
let inner = $crate::fragment_internal!( @t $( $inner )* );
|
||||||
let (a, b, c) = $crate::descriptor::dsl::TupleThree::from(inner).flattened();
|
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(&a_networks, &b_networks);
|
||||||
let networks = $crate::keys::merge_networks(&networks, &c_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(a_minisc),
|
||||||
std::sync::Arc::new(b_minisc),
|
std::sync::Arc::new(b_minisc),
|
||||||
std::sync::Arc::new(c_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<_>, _>>()
|
keys.into_iter().collect::<Result<Vec<_>, _>>()
|
||||||
|
.map_err($crate::descriptor::DescriptorError::Key)
|
||||||
.and_then(|keys| $crate::keys::make_sortedmulti_inner($thresh, keys, &secp))
|
.and_then(|keys| $crate::keys::make_sortedmulti_inner($thresh, keys, &secp))
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -199,18 +231,20 @@ macro_rules! impl_sortedmulti {
|
|||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! apply_modifier {
|
macro_rules! apply_modifier {
|
||||||
( $terminal_variant:ident, $inner:expr ) => {{
|
( $terminal_variant:ident, $inner:expr ) => {{
|
||||||
|
use $crate::descriptor::CheckMiniscript;
|
||||||
|
|
||||||
$inner
|
$inner
|
||||||
.map_err(|e| -> $crate::Error { e.into() })
|
.map_err(|e| -> $crate::descriptor::DescriptorError { e.into() })
|
||||||
.and_then(|(minisc, keymap, networks)| {
|
.and_then(|(minisc, keymap, networks)| {
|
||||||
Ok((
|
let minisc = $crate::miniscript::Miniscript::from_ast(
|
||||||
$crate::miniscript::Miniscript::from_ast(
|
$crate::miniscript::miniscript::decode::Terminal::$terminal_variant(
|
||||||
$crate::miniscript::miniscript::decode::Terminal::$terminal_variant(
|
std::sync::Arc::new(minisc),
|
||||||
std::sync::Arc::new(minisc),
|
),
|
||||||
),
|
)?;
|
||||||
)?,
|
|
||||||
keymap,
|
minisc.check_minsicript()?;
|
||||||
networks,
|
|
||||||
))
|
Ok((minisc, keymap, networks))
|
||||||
})
|
})
|
||||||
}};
|
}};
|
||||||
|
|
||||||
@@ -272,7 +306,7 @@ macro_rules! apply_modifier {
|
|||||||
/// Macro to write full descriptors with code
|
/// Macro to write full descriptors with code
|
||||||
///
|
///
|
||||||
/// This macro expands to a `Result` of
|
/// 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
|
/// 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
|
/// 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;
|
/// # use std::str::FromStr;
|
||||||
/// let my_key_1 = bitcoin::PublicKey::from_str("02e96fe52ef0e22d2f131dd425ce1893073a3c6ad20e8cac36726393dfb4856a4c")?;
|
/// let my_key_1 = bitcoin::PublicKey::from_str(
|
||||||
/// let my_key_2 = bitcoin::PrivateKey::from_wif("cVt4o7BGAig1UXywgGSmARhxMdzP5qvQsxKkSsc1XEkw3tDTQFpy")?;
|
/// "02e96fe52ef0e22d2f131dd425ce1893073a3c6ad20e8cac36726393dfb4856a4c",
|
||||||
|
/// )?;
|
||||||
|
/// let my_key_2 =
|
||||||
|
/// bitcoin::PrivateKey::from_wif("cVt4o7BGAig1UXywgGSmARhxMdzP5qvQsxKkSsc1XEkw3tDTQFpy")?;
|
||||||
/// let my_timelock = 50;
|
/// let my_timelock = 50;
|
||||||
///
|
///
|
||||||
/// let (descriptor_a, key_map_a, networks) = bdk::descriptor! {
|
/// let (descriptor_a, key_map_a, networks) = bdk::descriptor! {
|
||||||
@@ -315,12 +352,13 @@ macro_rules! apply_modifier {
|
|||||||
/// )
|
/// )
|
||||||
/// }?;
|
/// }?;
|
||||||
///
|
///
|
||||||
|
/// #[rustfmt::skip]
|
||||||
/// let b_items = vec![
|
/// let b_items = vec![
|
||||||
/// bdk::fragment!(pk(my_key_1))?,
|
/// bdk::fragment!(pk(my_key_1))?,
|
||||||
/// bdk::fragment!(s:pk(my_key_2))?,
|
/// bdk::fragment!(s:pk(my_key_2))?,
|
||||||
/// bdk::fragment!(s:d:v:older(my_timelock))?,
|
/// 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!(descriptor_a, descriptor_b);
|
||||||
/// assert_eq!(key_map_a.len(), key_map_b.len());
|
/// 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
|
/// 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, ...))`.
|
/// 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.
|
/// 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<_>, _>>()
|
keys.into_iter().collect::<Result<Vec<_>, _>>()
|
||||||
|
.map_err($crate::descriptor::DescriptorError::Key)
|
||||||
.and_then(|keys| $crate::keys::make_multi($thresh, keys, &secp))
|
.and_then(|keys| $crate::keys::make_multi($thresh, keys, &secp))
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -620,8 +659,8 @@ mod test {
|
|||||||
|
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use crate::descriptor::DescriptorMeta;
|
use crate::descriptor::{DescriptorError, DescriptorMeta};
|
||||||
use crate::keys::{DescriptorKey, KeyError, ToDescriptorKey, ValidNetworks};
|
use crate::keys::{DescriptorKey, ToDescriptorKey, ValidNetworks};
|
||||||
use bitcoin::network::constants::Network::{Bitcoin, Regtest, Testnet};
|
use bitcoin::network::constants::Network::{Bitcoin, Regtest, Testnet};
|
||||||
use bitcoin::util::bip32;
|
use bitcoin::util::bip32;
|
||||||
use bitcoin::util::bip32::ChildNumber;
|
use bitcoin::util::bip32::ChildNumber;
|
||||||
@@ -631,7 +670,7 @@ mod test {
|
|||||||
|
|
||||||
// verify descriptor generates expected script(s) (if bare or pk) or address(es)
|
// verify descriptor generates expected script(s) (if bare or pk) or address(es)
|
||||||
fn check(
|
fn check(
|
||||||
desc: Result<(Descriptor<DescriptorPublicKey>, KeyMap, ValidNetworks), KeyError>,
|
desc: Result<(Descriptor<DescriptorPublicKey>, KeyMap, ValidNetworks), DescriptorError>,
|
||||||
is_witness: bool,
|
is_witness: bool,
|
||||||
is_fixed: bool,
|
is_fixed: bool,
|
||||||
expected: &[&str],
|
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)))")
|
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
|
/// Errors related to the parsing and usage of descriptors
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
//InternalError,
|
|
||||||
//InvalidPrefix(Vec<u8>),
|
|
||||||
//HardenedDerivationOnXpub,
|
|
||||||
//MalformedInput,
|
|
||||||
/// Invalid HD Key path, such as having a wildcard but a length != 1
|
/// Invalid HD Key path, such as having a wildcard but a length != 1
|
||||||
InvalidHDKeyPath,
|
InvalidHDKeyPath,
|
||||||
|
/// The provided descriptor doesn't match its checksum
|
||||||
|
InvalidDescriptorChecksum,
|
||||||
|
|
||||||
//KeyParsingError(String),
|
|
||||||
/// Error thrown while working with [`keys`](crate::keys)
|
/// Error thrown while working with [`keys`](crate::keys)
|
||||||
Key(crate::keys::KeyError),
|
Key(crate::keys::KeyError),
|
||||||
/// Error while extracting and manipulating policies
|
/// Error while extracting and manipulating policies
|
||||||
Policy(crate::descriptor::policy::PolicyError),
|
Policy(crate::descriptor::policy::PolicyError),
|
||||||
|
|
||||||
//InputIndexDoesntExist,
|
|
||||||
//MissingPublicKey,
|
|
||||||
//MissingDetails,
|
|
||||||
/// Invalid character found in the descriptor checksum
|
/// Invalid character found in the descriptor checksum
|
||||||
InvalidDescriptorCharacter(char),
|
InvalidDescriptorCharacter(char),
|
||||||
|
|
||||||
//CantDeriveWithMiniscript,
|
|
||||||
/// BIP32 error
|
/// BIP32 error
|
||||||
BIP32(bitcoin::util::bip32::Error),
|
BIP32(bitcoin::util::bip32::Error),
|
||||||
/// Error during base58 decoding
|
/// Error during base58 decoding
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ pub mod policy;
|
|||||||
pub mod template;
|
pub mod template;
|
||||||
|
|
||||||
pub use self::checksum::get_checksum;
|
pub use self::checksum::get_checksum;
|
||||||
use self::error::Error;
|
pub use self::error::Error as DescriptorError;
|
||||||
pub use self::policy::Policy;
|
pub use self::policy::Policy;
|
||||||
use self::template::DescriptorTemplateOut;
|
use self::template::DescriptorTemplateOut;
|
||||||
use crate::keys::{KeyError, ToDescriptorKey};
|
use crate::keys::{KeyError, ToDescriptorKey};
|
||||||
@@ -72,14 +72,14 @@ pub trait ToWalletDescriptor {
|
|||||||
fn to_wallet_descriptor(
|
fn to_wallet_descriptor(
|
||||||
self,
|
self,
|
||||||
network: Network,
|
network: Network,
|
||||||
) -> Result<(ExtendedDescriptor, KeyMap), KeyError>;
|
) -> Result<(ExtendedDescriptor, KeyMap), DescriptorError>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToWalletDescriptor for &str {
|
impl ToWalletDescriptor for &str {
|
||||||
fn to_wallet_descriptor(
|
fn to_wallet_descriptor(
|
||||||
self,
|
self,
|
||||||
network: Network,
|
network: Network,
|
||||||
) -> Result<(ExtendedDescriptor, KeyMap), KeyError> {
|
) -> Result<(ExtendedDescriptor, KeyMap), DescriptorError> {
|
||||||
let descriptor = if self.contains('#') {
|
let descriptor = if self.contains('#') {
|
||||||
let parts: Vec<&str> = self.splitn(2, '#').collect();
|
let parts: Vec<&str> = self.splitn(2, '#').collect();
|
||||||
if !get_checksum(parts[0])
|
if !get_checksum(parts[0])
|
||||||
@@ -87,7 +87,7 @@ impl ToWalletDescriptor for &str {
|
|||||||
.map(|computed| computed == parts[1])
|
.map(|computed| computed == parts[1])
|
||||||
.unwrap_or(false)
|
.unwrap_or(false)
|
||||||
{
|
{
|
||||||
return Err(KeyError::InvalidChecksum);
|
return Err(DescriptorError::InvalidDescriptorChecksum);
|
||||||
}
|
}
|
||||||
|
|
||||||
parts[0]
|
parts[0]
|
||||||
@@ -103,7 +103,7 @@ impl ToWalletDescriptor for &String {
|
|||||||
fn to_wallet_descriptor(
|
fn to_wallet_descriptor(
|
||||||
self,
|
self,
|
||||||
network: Network,
|
network: Network,
|
||||||
) -> Result<(ExtendedDescriptor, KeyMap), KeyError> {
|
) -> Result<(ExtendedDescriptor, KeyMap), DescriptorError> {
|
||||||
self.as_str().to_wallet_descriptor(network)
|
self.as_str().to_wallet_descriptor(network)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -112,7 +112,7 @@ impl ToWalletDescriptor for ExtendedDescriptor {
|
|||||||
fn to_wallet_descriptor(
|
fn to_wallet_descriptor(
|
||||||
self,
|
self,
|
||||||
network: Network,
|
network: Network,
|
||||||
) -> Result<(ExtendedDescriptor, KeyMap), KeyError> {
|
) -> Result<(ExtendedDescriptor, KeyMap), DescriptorError> {
|
||||||
(self, KeyMap::default()).to_wallet_descriptor(network)
|
(self, KeyMap::default()).to_wallet_descriptor(network)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -121,7 +121,7 @@ impl ToWalletDescriptor for (ExtendedDescriptor, KeyMap) {
|
|||||||
fn to_wallet_descriptor(
|
fn to_wallet_descriptor(
|
||||||
self,
|
self,
|
||||||
network: Network,
|
network: Network,
|
||||||
) -> Result<(ExtendedDescriptor, KeyMap), KeyError> {
|
) -> Result<(ExtendedDescriptor, KeyMap), DescriptorError> {
|
||||||
use crate::keys::DescriptorKey;
|
use crate::keys::DescriptorKey;
|
||||||
|
|
||||||
let secp = Secp256k1::new();
|
let secp = Secp256k1::new();
|
||||||
@@ -140,7 +140,7 @@ impl ToWalletDescriptor for (ExtendedDescriptor, KeyMap) {
|
|||||||
if networks.contains(&network) {
|
if networks.contains(&network) {
|
||||||
Ok(pk)
|
Ok(pk)
|
||||||
} else {
|
} else {
|
||||||
Err(KeyError::InvalidNetwork)
|
Err(DescriptorError::Key(KeyError::InvalidNetwork))
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -155,7 +155,7 @@ impl ToWalletDescriptor for DescriptorTemplateOut {
|
|||||||
fn to_wallet_descriptor(
|
fn to_wallet_descriptor(
|
||||||
self,
|
self,
|
||||||
network: Network,
|
network: Network,
|
||||||
) -> Result<(ExtendedDescriptor, KeyMap), KeyError> {
|
) -> Result<(ExtendedDescriptor, KeyMap), DescriptorError> {
|
||||||
let valid_networks = &self.2;
|
let valid_networks = &self.2;
|
||||||
|
|
||||||
let fix_key = |pk: &DescriptorPublicKey| {
|
let fix_key = |pk: &DescriptorPublicKey| {
|
||||||
@@ -177,7 +177,7 @@ impl ToWalletDescriptor for DescriptorTemplateOut {
|
|||||||
|
|
||||||
Ok(pk)
|
Ok(pk)
|
||||||
} else {
|
} 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`]
|
/// Trait implemented on [`Descriptor`]s to add a method to extract the spending [`policy`]
|
||||||
pub trait ExtractPolicy {
|
pub trait ExtractPolicy {
|
||||||
/// Extract the spending [`policy`]
|
/// Extract the spending [`policy`]
|
||||||
@@ -195,7 +211,7 @@ pub trait ExtractPolicy {
|
|||||||
&self,
|
&self,
|
||||||
signers: &SignersContainer,
|
signers: &SignersContainer,
|
||||||
secp: &SecpCtx,
|
secp: &SecpCtx,
|
||||||
) -> Result<Option<Policy>, Error>;
|
) -> Result<Option<Policy>, DescriptorError>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) trait XKeyUtils {
|
pub(crate) trait XKeyUtils {
|
||||||
@@ -235,8 +251,8 @@ impl<K: InnerXKey> XKeyUtils for DescriptorXKey<K> {
|
|||||||
|
|
||||||
pub(crate) trait DescriptorMeta: Sized {
|
pub(crate) trait DescriptorMeta: Sized {
|
||||||
fn is_witness(&self) -> bool;
|
fn is_witness(&self) -> bool;
|
||||||
fn get_hd_keypaths(&self, index: u32, secp: &SecpCtx) -> Result<HDKeyPaths, Error>;
|
fn get_hd_keypaths(&self, index: u32, secp: &SecpCtx) -> Result<HDKeyPaths, DescriptorError>;
|
||||||
fn get_extended_keys(&self) -> Result<Vec<DescriptorXKey<ExtendedPubKey>>, Error>;
|
fn get_extended_keys(&self) -> Result<Vec<DescriptorXKey<ExtendedPubKey>>, DescriptorError>;
|
||||||
fn is_fixed(&self) -> bool;
|
fn is_fixed(&self) -> bool;
|
||||||
fn derive_from_hd_keypaths(&self, hd_keypaths: &HDKeyPaths, secp: &SecpCtx) -> Option<Self>;
|
fn derive_from_hd_keypaths(&self, hd_keypaths: &HDKeyPaths, secp: &SecpCtx) -> Option<Self>;
|
||||||
fn derive_from_psbt_input(
|
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,
|
let translate_key = |key: &DescriptorPublicKey,
|
||||||
index: u32,
|
index: u32,
|
||||||
paths: &mut HDKeyPaths|
|
paths: &mut HDKeyPaths|
|
||||||
-> Result<DummyKey, Error> {
|
-> Result<DummyKey, DescriptorError> {
|
||||||
match key {
|
match key {
|
||||||
DescriptorPublicKey::SinglePub(_) => {}
|
DescriptorPublicKey::SinglePub(_) => {}
|
||||||
DescriptorPublicKey::XPub(xpub) => {
|
DescriptorPublicKey::XPub(xpub) => {
|
||||||
@@ -344,10 +360,10 @@ impl DescriptorMeta for Descriptor<DescriptorPublicKey> {
|
|||||||
Ok(answer_pk)
|
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,
|
let get_key = |key: &DescriptorPublicKey,
|
||||||
keys: &mut Vec<DescriptorXKey<ExtendedPubKey>>|
|
keys: &mut Vec<DescriptorXKey<ExtendedPubKey>>|
|
||||||
-> Result<DummyKey, Error> {
|
-> Result<DummyKey, DescriptorError> {
|
||||||
if let DescriptorPublicKey::XPub(xpub) = key {
|
if let DescriptorPublicKey::XPub(xpub) = key {
|
||||||
keys.push(xpub.clone())
|
keys.push(xpub.clone())
|
||||||
}
|
}
|
||||||
@@ -369,7 +385,10 @@ impl DescriptorMeta for Descriptor<DescriptorPublicKey> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn is_fixed(&self) -> bool {
|
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 {
|
match key {
|
||||||
DescriptorPublicKey::SinglePub(_) => {}
|
DescriptorPublicKey::SinglePub(_) => {}
|
||||||
DescriptorPublicKey::XPub(xpub) => {
|
DescriptorPublicKey::XPub(xpub) => {
|
||||||
@@ -398,7 +417,7 @@ impl DescriptorMeta for Descriptor<DescriptorPublicKey> {
|
|||||||
let try_key = |key: &DescriptorPublicKey,
|
let try_key = |key: &DescriptorPublicKey,
|
||||||
index: &HashMap<Fingerprint, DerivationPath>,
|
index: &HashMap<Fingerprint, DerivationPath>,
|
||||||
found_path: &mut Option<ChildNumber>|
|
found_path: &mut Option<ChildNumber>|
|
||||||
-> Result<DummyKey, Error> {
|
-> Result<DummyKey, DescriptorError> {
|
||||||
if found_path.is_some() {
|
if found_path.is_some() {
|
||||||
// already found a matching path, we are done
|
// already found a matching path, we are done
|
||||||
return Ok(DummyKey::default());
|
return Ok(DummyKey::default());
|
||||||
@@ -432,7 +451,7 @@ impl DescriptorMeta for Descriptor<DescriptorPublicKey> {
|
|||||||
Some(path) if !xpub.is_wildcard && path.is_empty() => {
|
Some(path) if !xpub.is_wildcard && path.is_empty() => {
|
||||||
*found_path = Some(ChildNumber::Normal { index: 0 })
|
*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"
|
let desc = "wpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcLNfjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/2/*)#67ju93jw"
|
||||||
.to_wallet_descriptor(Network::Testnet);
|
.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"
|
let desc = "wpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcLNfjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/2/*)#67ju93jw"
|
||||||
.to_wallet_descriptor(Network::Testnet);
|
.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
|
// test ToWalletDescriptor trait from &str with keys from right and wrong network
|
||||||
@@ -749,11 +774,17 @@ mod test {
|
|||||||
|
|
||||||
let desc = "wpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcLNfjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/2/*)"
|
let desc = "wpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcLNfjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/2/*)"
|
||||||
.to_wallet_descriptor(Network::Bitcoin);
|
.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/*)"
|
let desc = "wpkh(tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/1/2/*)"
|
||||||
.to_wallet_descriptor(Network::Bitcoin);
|
.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
|
// test ToWalletDescriptor trait from the output of the descriptor!() macro
|
||||||
|
|||||||
@@ -165,11 +165,13 @@ pub enum SatisfiableItem {
|
|||||||
impl SatisfiableItem {
|
impl SatisfiableItem {
|
||||||
/// Returns whether the [`SatisfiableItem`] is a leaf item
|
/// Returns whether the [`SatisfiableItem`] is a leaf item
|
||||||
pub fn is_leaf(&self) -> bool {
|
pub fn is_leaf(&self) -> bool {
|
||||||
!matches!(self,
|
!matches!(
|
||||||
SatisfiableItem::Thresh {
|
self,
|
||||||
items: _,
|
SatisfiableItem::Thresh {
|
||||||
threshold: _,
|
items: _,
|
||||||
})
|
threshold: _,
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a unique id for the [`SatisfiableItem`]
|
/// Returns a unique id for the [`SatisfiableItem`]
|
||||||
@@ -1143,11 +1145,12 @@ mod test {
|
|||||||
let (prvkey0, _pubkey0, _fingerprint0) = setup_keys(TPRV0_STR);
|
let (prvkey0, _pubkey0, _fingerprint0) = setup_keys(TPRV0_STR);
|
||||||
let (_prvkey1, pubkey1, _fingerprint1) = setup_keys(TPRV1_STR);
|
let (_prvkey1, pubkey1, _fingerprint1) = setup_keys(TPRV1_STR);
|
||||||
let sequence = 50;
|
let sequence = 50;
|
||||||
|
#[rustfmt::skip]
|
||||||
let desc = descriptor!(wsh(thresh(
|
let desc = descriptor!(wsh(thresh(
|
||||||
2,
|
2,
|
||||||
pk(prvkey0),
|
pk(prvkey0),
|
||||||
s: pk(pubkey1),
|
s:pk(pubkey1),
|
||||||
s: d: v: older(sequence)
|
s:d:v:older(sequence)
|
||||||
)))
|
)))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
|||||||
@@ -33,7 +33,8 @@ use bitcoin::Network;
|
|||||||
use miniscript::{Legacy, Segwitv0};
|
use miniscript::{Legacy, Segwitv0};
|
||||||
|
|
||||||
use super::{ExtendedDescriptor, KeyMap, ToWalletDescriptor};
|
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};
|
use crate::{descriptor, KeychainKind};
|
||||||
|
|
||||||
/// Type alias for the return type of [`DescriptorTemplate`], [`descriptor!`](crate::descriptor!) and others
|
/// Type alias for the return type of [`DescriptorTemplate`], [`descriptor!`](crate::descriptor!) and others
|
||||||
@@ -47,6 +48,7 @@ pub type DescriptorTemplateOut = (ExtendedDescriptor, KeyMap, ValidNetworks);
|
|||||||
/// ## Example
|
/// ## Example
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
|
/// use bdk::descriptor::error::Error as DescriptorError;
|
||||||
/// use bdk::keys::{KeyError, ToDescriptorKey};
|
/// use bdk::keys::{KeyError, ToDescriptorKey};
|
||||||
/// use bdk::miniscript::Legacy;
|
/// use bdk::miniscript::Legacy;
|
||||||
/// use bdk::template::{DescriptorTemplate, DescriptorTemplateOut};
|
/// use bdk::template::{DescriptorTemplate, DescriptorTemplateOut};
|
||||||
@@ -54,14 +56,14 @@ pub type DescriptorTemplateOut = (ExtendedDescriptor, KeyMap, ValidNetworks);
|
|||||||
/// struct MyP2PKH<K: ToDescriptorKey<Legacy>>(K);
|
/// struct MyP2PKH<K: ToDescriptorKey<Legacy>>(K);
|
||||||
///
|
///
|
||||||
/// impl<K: ToDescriptorKey<Legacy>> DescriptorTemplate for MyP2PKH<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))?)
|
/// Ok(bdk::descriptor!(pkh(self.0))?)
|
||||||
/// }
|
/// }
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
pub trait DescriptorTemplate {
|
pub trait DescriptorTemplate {
|
||||||
/// Build the complete descriptor
|
/// 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
|
/// 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(
|
fn to_wallet_descriptor(
|
||||||
self,
|
self,
|
||||||
network: Network,
|
network: Network,
|
||||||
) -> Result<(ExtendedDescriptor, KeyMap), KeyError> {
|
) -> Result<(ExtendedDescriptor, KeyMap), DescriptorError> {
|
||||||
Ok(self.build()?.to_wallet_descriptor(network)?)
|
Ok(self.build()?.to_wallet_descriptor(network)?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -81,13 +83,13 @@ impl<T: DescriptorTemplate> ToWalletDescriptor for T {
|
|||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// # use bdk::bitcoin::{PrivateKey, Network};
|
/// # use bdk::bitcoin::{PrivateKey, Network};
|
||||||
/// # use bdk::{Wallet, OfflineWallet};
|
/// # use bdk::{Wallet};
|
||||||
/// # use bdk::database::MemoryDatabase;
|
/// # use bdk::database::MemoryDatabase;
|
||||||
/// use bdk::template::P2PKH;
|
/// use bdk::template::P2PKH;
|
||||||
///
|
///
|
||||||
/// let key =
|
/// let key =
|
||||||
/// bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")?;
|
/// bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")?;
|
||||||
/// let wallet: OfflineWallet<_> = Wallet::new_offline(
|
/// let wallet = Wallet::new_offline(
|
||||||
/// P2PKH(key),
|
/// P2PKH(key),
|
||||||
/// None,
|
/// None,
|
||||||
/// Network::Testnet,
|
/// Network::Testnet,
|
||||||
@@ -103,7 +105,7 @@ impl<T: DescriptorTemplate> ToWalletDescriptor for T {
|
|||||||
pub struct P2PKH<K: ToDescriptorKey<Legacy>>(pub K);
|
pub struct P2PKH<K: ToDescriptorKey<Legacy>>(pub K);
|
||||||
|
|
||||||
impl<K: ToDescriptorKey<Legacy>> DescriptorTemplate for P2PKH<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))?)
|
Ok(descriptor!(pkh(self.0))?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -114,13 +116,13 @@ impl<K: ToDescriptorKey<Legacy>> DescriptorTemplate for P2PKH<K> {
|
|||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// # use bdk::bitcoin::{PrivateKey, Network};
|
/// # use bdk::bitcoin::{PrivateKey, Network};
|
||||||
/// # use bdk::{Wallet, OfflineWallet};
|
/// # use bdk::{Wallet};
|
||||||
/// # use bdk::database::MemoryDatabase;
|
/// # use bdk::database::MemoryDatabase;
|
||||||
/// use bdk::template::P2WPKH_P2SH;
|
/// use bdk::template::P2WPKH_P2SH;
|
||||||
///
|
///
|
||||||
/// let key =
|
/// let key =
|
||||||
/// bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")?;
|
/// bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")?;
|
||||||
/// let wallet: OfflineWallet<_> = Wallet::new_offline(
|
/// let wallet = Wallet::new_offline(
|
||||||
/// P2WPKH_P2SH(key),
|
/// P2WPKH_P2SH(key),
|
||||||
/// None,
|
/// None,
|
||||||
/// Network::Testnet,
|
/// Network::Testnet,
|
||||||
@@ -137,7 +139,7 @@ impl<K: ToDescriptorKey<Legacy>> DescriptorTemplate for P2PKH<K> {
|
|||||||
pub struct P2WPKH_P2SH<K: ToDescriptorKey<Segwitv0>>(pub K);
|
pub struct P2WPKH_P2SH<K: ToDescriptorKey<Segwitv0>>(pub K);
|
||||||
|
|
||||||
impl<K: ToDescriptorKey<Segwitv0>> DescriptorTemplate for P2WPKH_P2SH<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)))?)
|
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::bitcoin::{PrivateKey, Network};
|
||||||
/// # use bdk::{Wallet, OfflineWallet};
|
/// # use bdk::{Wallet};
|
||||||
/// # use bdk::database::MemoryDatabase;
|
/// # use bdk::database::MemoryDatabase;
|
||||||
/// use bdk::template::P2WPKH;
|
/// use bdk::template::P2WPKH;
|
||||||
///
|
///
|
||||||
/// let key =
|
/// let key =
|
||||||
/// bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")?;
|
/// bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")?;
|
||||||
/// let wallet: OfflineWallet<_> = Wallet::new_offline(
|
/// let wallet = Wallet::new_offline(
|
||||||
/// P2WPKH(key),
|
/// P2WPKH(key),
|
||||||
/// None,
|
/// None,
|
||||||
/// Network::Testnet,
|
/// Network::Testnet,
|
||||||
@@ -170,7 +172,7 @@ impl<K: ToDescriptorKey<Segwitv0>> DescriptorTemplate for P2WPKH_P2SH<K> {
|
|||||||
pub struct P2WPKH<K: ToDescriptorKey<Segwitv0>>(pub K);
|
pub struct P2WPKH<K: ToDescriptorKey<Segwitv0>>(pub K);
|
||||||
|
|
||||||
impl<K: ToDescriptorKey<Segwitv0>> DescriptorTemplate for P2WPKH<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))?)
|
Ok(descriptor!(wpkh(self.0))?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -186,12 +188,12 @@ impl<K: ToDescriptorKey<Segwitv0>> DescriptorTemplate for P2WPKH<K> {
|
|||||||
/// ```
|
/// ```
|
||||||
/// # use std::str::FromStr;
|
/// # use std::str::FromStr;
|
||||||
/// # use bdk::bitcoin::{PrivateKey, Network};
|
/// # use bdk::bitcoin::{PrivateKey, Network};
|
||||||
/// # use bdk::{Wallet, OfflineWallet, KeychainKind};
|
/// # use bdk::{Wallet, KeychainKind};
|
||||||
/// # use bdk::database::MemoryDatabase;
|
/// # use bdk::database::MemoryDatabase;
|
||||||
/// use bdk::template::BIP44;
|
/// use bdk::template::BIP44;
|
||||||
///
|
///
|
||||||
/// let key = bitcoin::util::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?;
|
/// 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),
|
/// BIP44(key.clone(), KeychainKind::External),
|
||||||
/// Some(BIP44(key, KeychainKind::Internal)),
|
/// Some(BIP44(key, KeychainKind::Internal)),
|
||||||
/// Network::Testnet,
|
/// Network::Testnet,
|
||||||
@@ -205,7 +207,7 @@ impl<K: ToDescriptorKey<Segwitv0>> DescriptorTemplate for P2WPKH<K> {
|
|||||||
pub struct BIP44<K: DerivableKey<Legacy>>(pub K, pub KeychainKind);
|
pub struct BIP44<K: DerivableKey<Legacy>>(pub K, pub KeychainKind);
|
||||||
|
|
||||||
impl<K: DerivableKey<Legacy>> DescriptorTemplate for BIP44<K> {
|
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()?)
|
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 std::str::FromStr;
|
||||||
/// # use bdk::bitcoin::{PrivateKey, Network};
|
/// # use bdk::bitcoin::{PrivateKey, Network};
|
||||||
/// # use bdk::{Wallet, OfflineWallet, KeychainKind};
|
/// # use bdk::{Wallet, KeychainKind};
|
||||||
/// # use bdk::database::MemoryDatabase;
|
/// # use bdk::database::MemoryDatabase;
|
||||||
/// use bdk::template::BIP44Public;
|
/// use bdk::template::BIP44Public;
|
||||||
///
|
///
|
||||||
/// let key = bitcoin::util::bip32::ExtendedPubKey::from_str("tpubDDDzQ31JkZB7VxUr9bjvBivDdqoFLrDPyLWtLapArAi51ftfmCb2DPxwLQzX65iNcXz1DGaVvyvo6JQ6rTU73r2gqdEo8uov9QKRb7nKCSU")?;
|
/// let key = bitcoin::util::bip32::ExtendedPubKey::from_str("tpubDDDzQ31JkZB7VxUr9bjvBivDdqoFLrDPyLWtLapArAi51ftfmCb2DPxwLQzX65iNcXz1DGaVvyvo6JQ6rTU73r2gqdEo8uov9QKRb7nKCSU")?;
|
||||||
/// let fingerprint = bitcoin::util::bip32::Fingerprint::from_str("c55b303f")?;
|
/// 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),
|
/// BIP44Public(key.clone(), fingerprint, KeychainKind::External),
|
||||||
/// Some(BIP44Public(key, fingerprint, KeychainKind::Internal)),
|
/// Some(BIP44Public(key, fingerprint, KeychainKind::Internal)),
|
||||||
/// Network::Testnet,
|
/// 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);
|
pub struct BIP44Public<K: DerivableKey<Legacy>>(pub K, pub bip32::Fingerprint, pub KeychainKind);
|
||||||
|
|
||||||
impl<K: DerivableKey<Legacy>> DescriptorTemplate for BIP44Public<K> {
|
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()?)
|
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 std::str::FromStr;
|
||||||
/// # use bdk::bitcoin::{PrivateKey, Network};
|
/// # use bdk::bitcoin::{PrivateKey, Network};
|
||||||
/// # use bdk::{Wallet, OfflineWallet, KeychainKind};
|
/// # use bdk::{Wallet, KeychainKind};
|
||||||
/// # use bdk::database::MemoryDatabase;
|
/// # use bdk::database::MemoryDatabase;
|
||||||
/// use bdk::template::BIP49;
|
/// use bdk::template::BIP49;
|
||||||
///
|
///
|
||||||
/// let key = bitcoin::util::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?;
|
/// 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),
|
/// BIP49(key.clone(), KeychainKind::External),
|
||||||
/// Some(BIP49(key, KeychainKind::Internal)),
|
/// Some(BIP49(key, KeychainKind::Internal)),
|
||||||
/// Network::Testnet,
|
/// Network::Testnet,
|
||||||
@@ -279,7 +281,7 @@ impl<K: DerivableKey<Legacy>> DescriptorTemplate for BIP44Public<K> {
|
|||||||
pub struct BIP49<K: DerivableKey<Segwitv0>>(pub K, pub KeychainKind);
|
pub struct BIP49<K: DerivableKey<Segwitv0>>(pub K, pub KeychainKind);
|
||||||
|
|
||||||
impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for BIP49<K> {
|
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()?)
|
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 std::str::FromStr;
|
||||||
/// # use bdk::bitcoin::{PrivateKey, Network};
|
/// # use bdk::bitcoin::{PrivateKey, Network};
|
||||||
/// # use bdk::{Wallet, OfflineWallet, KeychainKind};
|
/// # use bdk::{Wallet, KeychainKind};
|
||||||
/// # use bdk::database::MemoryDatabase;
|
/// # use bdk::database::MemoryDatabase;
|
||||||
/// use bdk::template::BIP49Public;
|
/// use bdk::template::BIP49Public;
|
||||||
///
|
///
|
||||||
/// let key = bitcoin::util::bip32::ExtendedPubKey::from_str("tpubDC49r947KGK52X5rBWS4BLs5m9SRY3pYHnvRrm7HcybZ3BfdEsGFyzCMzayi1u58eT82ZeyFZwH7DD6Q83E3fM9CpfMtmnTygnLfP59jL9L")?;
|
/// let key = bitcoin::util::bip32::ExtendedPubKey::from_str("tpubDC49r947KGK52X5rBWS4BLs5m9SRY3pYHnvRrm7HcybZ3BfdEsGFyzCMzayi1u58eT82ZeyFZwH7DD6Q83E3fM9CpfMtmnTygnLfP59jL9L")?;
|
||||||
/// let fingerprint = bitcoin::util::bip32::Fingerprint::from_str("c55b303f")?;
|
/// 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),
|
/// BIP49Public(key.clone(), fingerprint, KeychainKind::External),
|
||||||
/// Some(BIP49Public(key, fingerprint, KeychainKind::Internal)),
|
/// Some(BIP49Public(key, fingerprint, KeychainKind::Internal)),
|
||||||
/// Network::Testnet,
|
/// 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);
|
pub struct BIP49Public<K: DerivableKey<Segwitv0>>(pub K, pub bip32::Fingerprint, pub KeychainKind);
|
||||||
|
|
||||||
impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for BIP49Public<K> {
|
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()?)
|
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 std::str::FromStr;
|
||||||
/// # use bdk::bitcoin::{PrivateKey, Network};
|
/// # use bdk::bitcoin::{PrivateKey, Network};
|
||||||
/// # use bdk::{Wallet, OfflineWallet, KeychainKind};
|
/// # use bdk::{Wallet, KeychainKind};
|
||||||
/// # use bdk::database::MemoryDatabase;
|
/// # use bdk::database::MemoryDatabase;
|
||||||
/// use bdk::template::BIP84;
|
/// use bdk::template::BIP84;
|
||||||
///
|
///
|
||||||
/// let key = bitcoin::util::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?;
|
/// 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),
|
/// BIP84(key.clone(), KeychainKind::External),
|
||||||
/// Some(BIP84(key, KeychainKind::Internal)),
|
/// Some(BIP84(key, KeychainKind::Internal)),
|
||||||
/// Network::Testnet,
|
/// Network::Testnet,
|
||||||
@@ -353,7 +355,7 @@ impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for BIP49Public<K> {
|
|||||||
pub struct BIP84<K: DerivableKey<Segwitv0>>(pub K, pub KeychainKind);
|
pub struct BIP84<K: DerivableKey<Segwitv0>>(pub K, pub KeychainKind);
|
||||||
|
|
||||||
impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for BIP84<K> {
|
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()?)
|
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 std::str::FromStr;
|
||||||
/// # use bdk::bitcoin::{PrivateKey, Network};
|
/// # use bdk::bitcoin::{PrivateKey, Network};
|
||||||
/// # use bdk::{Wallet, OfflineWallet, KeychainKind};
|
/// # use bdk::{Wallet, KeychainKind};
|
||||||
/// # use bdk::database::MemoryDatabase;
|
/// # use bdk::database::MemoryDatabase;
|
||||||
/// use bdk::template::BIP84Public;
|
/// use bdk::template::BIP84Public;
|
||||||
///
|
///
|
||||||
/// let key = bitcoin::util::bip32::ExtendedPubKey::from_str("tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q")?;
|
/// let key = bitcoin::util::bip32::ExtendedPubKey::from_str("tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q")?;
|
||||||
/// let fingerprint = bitcoin::util::bip32::Fingerprint::from_str("c55b303f")?;
|
/// 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),
|
/// BIP84Public(key.clone(), fingerprint, KeychainKind::External),
|
||||||
/// Some(BIP84Public(key, fingerprint, KeychainKind::Internal)),
|
/// Some(BIP84Public(key, fingerprint, KeychainKind::Internal)),
|
||||||
/// Network::Testnet,
|
/// 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);
|
pub struct BIP84Public<K: DerivableKey<Segwitv0>>(pub K, pub bip32::Fingerprint, pub KeychainKind);
|
||||||
|
|
||||||
impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for BIP84Public<K> {
|
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()?)
|
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,
|
bip: u32,
|
||||||
key: K,
|
key: K,
|
||||||
keychain: KeychainKind,
|
keychain: KeychainKind,
|
||||||
) -> Result<impl ToDescriptorKey<$ctx>, KeyError> {
|
) -> Result<impl ToDescriptorKey<$ctx>, DescriptorError> {
|
||||||
let mut derivation_path = Vec::with_capacity(4);
|
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(bip)?);
|
||||||
derivation_path.push(bip32::ChildNumber::from_hardened_idx(0)?);
|
derivation_path.push(bip32::ChildNumber::from_hardened_idx(0)?);
|
||||||
@@ -430,7 +432,7 @@ macro_rules! expand_make_bipxx {
|
|||||||
key: K,
|
key: K,
|
||||||
parent_fingerprint: bip32::Fingerprint,
|
parent_fingerprint: bip32::Fingerprint,
|
||||||
keychain: KeychainKind,
|
keychain: KeychainKind,
|
||||||
) -> Result<impl ToDescriptorKey<$ctx>, KeyError> {
|
) -> Result<impl ToDescriptorKey<$ctx>, DescriptorError> {
|
||||||
let derivation_path: bip32::DerivationPath = match keychain {
|
let derivation_path: bip32::DerivationPath = match keychain {
|
||||||
KeychainKind::External => vec![bip32::ChildNumber::from_normal_idx(0)?].into(),
|
KeychainKind::External => vec![bip32::ChildNumber::from_normal_idx(0)?].into(),
|
||||||
KeychainKind::Internal => vec![bip32::ChildNumber::from_normal_idx(1)?].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
|
// test existing descriptor templates, make sure they are expanded to the right descriptors
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::descriptor::DescriptorMeta;
|
use crate::descriptor::{DescriptorError, DescriptorMeta};
|
||||||
use crate::keys::{KeyError, ValidNetworks};
|
use crate::keys::ValidNetworks;
|
||||||
use bitcoin::hashes::core::str::FromStr;
|
use bitcoin::hashes::core::str::FromStr;
|
||||||
use bitcoin::network::constants::Network::Regtest;
|
use bitcoin::network::constants::Network::Regtest;
|
||||||
use bitcoin::secp256k1::Secp256k1;
|
use bitcoin::secp256k1::Secp256k1;
|
||||||
@@ -467,7 +469,7 @@ mod test {
|
|||||||
|
|
||||||
// verify template descriptor generates expected address(es)
|
// verify template descriptor generates expected address(es)
|
||||||
fn check(
|
fn check(
|
||||||
desc: Result<(Descriptor<DescriptorPublicKey>, KeyMap, ValidNetworks), KeyError>,
|
desc: Result<(Descriptor<DescriptorPublicKey>, KeyMap, ValidNetworks), DescriptorError>,
|
||||||
is_witness: bool,
|
is_witness: bool,
|
||||||
is_fixed: bool,
|
is_fixed: bool,
|
||||||
expected: &[&str],
|
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
|
/// Output created is under the dust limit, 546 satoshis
|
||||||
OutputBelowDustLimit(usize),
|
OutputBelowDustLimit(usize),
|
||||||
/// Wallet's UTXO set is not enough to cover recipient's requested plus fee
|
/// 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
|
/// 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
|
/// exponentially, thus a limit is set, and when hit, this error is thrown
|
||||||
BnBTotalTriesExceeded,
|
BnBTotalTriesExceeded,
|
||||||
@@ -89,11 +94,6 @@ pub enum Error {
|
|||||||
/// Signing error
|
/// Signing error
|
||||||
Signer(crate::wallet::signer::SignerError),
|
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)
|
/// Progress value must be between `0.0` (included) and `100.0` (included)
|
||||||
InvalidProgressValue(f32),
|
InvalidProgressValue(f32),
|
||||||
/// Progress update error (maybe the channel has been closed)
|
/// 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 {
|
fn from(other: crate::blockchain::compact_filters::CompactFiltersError) -> Self {
|
||||||
match other {
|
match other {
|
||||||
crate::blockchain::compact_filters::CompactFiltersError::Global(e) => *e,
|
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;
|
pub use miniscript::ScriptContext;
|
||||||
use miniscript::{Miniscript, Terminal};
|
use miniscript::{Miniscript, Terminal};
|
||||||
|
|
||||||
|
use crate::descriptor::{CheckMiniscript, DescriptorError};
|
||||||
use crate::wallet::utils::SecpCtx;
|
use crate::wallet::utils::SecpCtx;
|
||||||
|
|
||||||
#[cfg(feature = "keys-bip39")]
|
#[cfg(feature = "keys-bip39")]
|
||||||
@@ -420,7 +421,7 @@ where
|
|||||||
/// [`ToDescriptorKey`]: the generated keys can be directly used in descriptors if `Self` is also
|
/// [`ToDescriptorKey`]: the generated keys can be directly used in descriptors if `Self` is also
|
||||||
/// [`ToDescriptorKey`].
|
/// [`ToDescriptorKey`].
|
||||||
pub trait GeneratableKey<Ctx: ScriptContext>: Sized {
|
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;
|
type Entropy: AsMut<[u8]> + Default;
|
||||||
|
|
||||||
/// Extra options required by the `generate_with_entropy`
|
/// 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>(
|
pub fn make_pk<Pk: ToDescriptorKey<Ctx>, Ctx: ScriptContext>(
|
||||||
descriptor_key: Pk,
|
descriptor_key: Pk,
|
||||||
secp: &SecpCtx,
|
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 (key, key_map, valid_networks) = descriptor_key.to_descriptor_key()?.extract(secp)?;
|
||||||
|
let minisc = Miniscript::from_ast(Terminal::PkK(key))?;
|
||||||
|
|
||||||
Ok((
|
minisc.check_minsicript()?;
|
||||||
Miniscript::from_ast(Terminal::PkK(key))?,
|
|
||||||
key_map,
|
Ok((minisc, key_map, valid_networks))
|
||||||
valid_networks,
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Used internally by `bdk::fragment!` to build `multi()` fragments
|
// Used internally by `bdk::fragment!` to build `multi()` fragments
|
||||||
@@ -588,14 +588,13 @@ pub fn make_multi<Pk: ToDescriptorKey<Ctx>, Ctx: ScriptContext>(
|
|||||||
thresh: usize,
|
thresh: usize,
|
||||||
pks: Vec<Pk>,
|
pks: Vec<Pk>,
|
||||||
secp: &SecpCtx,
|
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 (pks, key_map, valid_networks) = expand_multi_keys(pks, secp)?;
|
||||||
|
let minisc = Miniscript::from_ast(Terminal::Multi(thresh, pks))?;
|
||||||
|
|
||||||
Ok((
|
minisc.check_minsicript()?;
|
||||||
Miniscript::from_ast(Terminal::Multi(thresh, pks))?,
|
|
||||||
key_map,
|
Ok((minisc, key_map, valid_networks))
|
||||||
valid_networks,
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Used internally by `bdk::descriptor!` to build `sortedmulti()` fragments
|
// Used internally by `bdk::descriptor!` to build `sortedmulti()` fragments
|
||||||
@@ -610,11 +609,14 @@ pub fn make_sortedmulti_inner<Pk: ToDescriptorKey<Ctx>, Ctx: ScriptContext>(
|
|||||||
KeyMap,
|
KeyMap,
|
||||||
ValidNetworks,
|
ValidNetworks,
|
||||||
),
|
),
|
||||||
KeyError,
|
DescriptorError,
|
||||||
> {
|
> {
|
||||||
let (pks, key_map, valid_networks) = expand_multi_keys(pks, secp)?;
|
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
|
/// The "identity" conversion is used internally by some `bdk::fragment`s
|
||||||
|
|||||||
14
src/lib.rs
14
src/lib.rs
@@ -91,11 +91,11 @@
|
|||||||
//!
|
//!
|
||||||
//! ### Example
|
//! ### Example
|
||||||
//! ```
|
//! ```
|
||||||
//! use bdk::{Wallet, OfflineWallet};
|
//! use bdk::{Wallet};
|
||||||
//! use bdk::database::MemoryDatabase;
|
//! use bdk::database::MemoryDatabase;
|
||||||
//!
|
//!
|
||||||
//! fn main() -> Result<(), bdk::Error> {
|
//! fn main() -> Result<(), bdk::Error> {
|
||||||
//! let wallet: OfflineWallet<_> = Wallet::new_offline(
|
//! let wallet = Wallet::new_offline(
|
||||||
//! "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)",
|
//! "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)",
|
||||||
//! Some("wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)"),
|
//! Some("wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)"),
|
||||||
//! bitcoin::Network::Testnet,
|
//! bitcoin::Network::Testnet,
|
||||||
@@ -155,13 +155,13 @@
|
|||||||
//! ### Example
|
//! ### Example
|
||||||
//! ```ignore
|
//! ```ignore
|
||||||
//! use base64::decode;
|
//! use base64::decode;
|
||||||
//! use bdk::{Wallet, OfflineWallet};
|
//! use bdk::{Wallet};
|
||||||
//! use bdk::database::MemoryDatabase;
|
//! use bdk::database::MemoryDatabase;
|
||||||
//!
|
//!
|
||||||
//! use bitcoin::consensus::deserialize;
|
//! use bitcoin::consensus::deserialize;
|
||||||
//!
|
//!
|
||||||
//! fn main() -> Result<(), bdk::Error> {
|
//! fn main() -> Result<(), bdk::Error> {
|
||||||
//! let wallet: OfflineWallet<_> = Wallet::new_offline(
|
//! let wallet = Wallet::new_offline(
|
||||||
//! "wpkh([c258d2e4/84h/1h/0h]tprv8griRPhA7342zfRyB6CqeKF8CJDXYu5pgnj1cjL1u2ngKcJha5jjTRimG82ABzJQ4MQe71CV54xfn25BbhCNfEGGJZnxvCDQCd6JkbvxW6h/0/*)",
|
//! "wpkh([c258d2e4/84h/1h/0h]tprv8griRPhA7342zfRyB6CqeKF8CJDXYu5pgnj1cjL1u2ngKcJha5jjTRimG82ABzJQ4MQe71CV54xfn25BbhCNfEGGJZnxvCDQCd6JkbvxW6h/0/*)",
|
||||||
//! Some("wpkh([c258d2e4/84h/1h/0h]tprv8griRPhA7342zfRyB6CqeKF8CJDXYu5pgnj1cjL1u2ngKcJha5jjTRimG82ABzJQ4MQe71CV54xfn25BbhCNfEGGJZnxvCDQCd6JkbvxW6h/1/*)"),
|
//! Some("wpkh([c258d2e4/84h/1h/0h]tprv8griRPhA7342zfRyB6CqeKF8CJDXYu5pgnj1cjL1u2ngKcJha5jjTRimG82ABzJQ4MQe71CV54xfn25BbhCNfEGGJZnxvCDQCd6JkbvxW6h/1/*)"),
|
||||||
//! bitcoin::Network::Testnet,
|
//! bitcoin::Network::Testnet,
|
||||||
@@ -191,7 +191,6 @@
|
|||||||
//!
|
//!
|
||||||
//! * `all-keys`: all features for working with bitcoin keys
|
//! * `all-keys`: all features for working with bitcoin keys
|
||||||
//! * `async-interface`: async functions in bdk traits
|
//! * `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
|
//! * `keys-bip39`: [BIP-39](https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki) mnemonic codes for generating deterministic keys
|
||||||
//!
|
//!
|
||||||
//! ## Internal features
|
//! ## Internal features
|
||||||
@@ -233,9 +232,6 @@ pub extern crate reqwest;
|
|||||||
#[cfg(feature = "key-value-db")]
|
#[cfg(feature = "key-value-db")]
|
||||||
pub extern crate sled;
|
pub extern crate sled;
|
||||||
|
|
||||||
#[cfg(feature = "cli-utils")]
|
|
||||||
pub mod cli;
|
|
||||||
|
|
||||||
#[allow(unused_imports)]
|
#[allow(unused_imports)]
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
@@ -268,4 +264,4 @@ pub use types::*;
|
|||||||
pub use wallet::address_validator;
|
pub use wallet::address_validator;
|
||||||
pub use wallet::signer;
|
pub use wallet::signer;
|
||||||
pub use wallet::tx_builder::TxBuilder;
|
pub use wallet::tx_builder::TxBuilder;
|
||||||
pub use wallet::{OfflineWallet, Wallet};
|
pub use wallet::Wallet;
|
||||||
|
|||||||
@@ -66,7 +66,7 @@
|
|||||||
//! }
|
//! }
|
||||||
//!
|
//!
|
||||||
//! let descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/*)";
|
//! 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));
|
//! wallet.add_address_validator(Arc::new(PrintAddressAndContinue));
|
||||||
//!
|
//!
|
||||||
//! let address = wallet.get_new_address()?;
|
//! let address = wallet.get_new_address()?;
|
||||||
|
|||||||
@@ -71,9 +71,9 @@
|
|||||||
//! })
|
//! })
|
||||||
//! .collect::<Vec<_>>();
|
//! .collect::<Vec<_>>();
|
||||||
//! let additional_fees = additional_weight as f32 * fee_rate.as_sat_vb() / 4.0;
|
//! let additional_fees = additional_weight as f32 * fee_rate.as_sat_vb() / 4.0;
|
||||||
//!
|
//! let amount_needed_with_fees = (fee_amount + additional_fees).ceil() as u64 + amount_needed;
|
||||||
//! if (fee_amount + additional_fees).ceil() as u64 + amount_needed > selected_amount {
|
//! if amount_needed_with_fees > selected_amount {
|
||||||
//! return Err(bdk::Error::InsufficientFunds);
|
//! return Err(bdk::Error::InsufficientFunds{ needed: amount_needed_with_fees, available: selected_amount });
|
||||||
//! }
|
//! }
|
||||||
//!
|
//!
|
||||||
//! Ok(CoinSelectionResult {
|
//! 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, ...
|
//! // create wallet, sync, ...
|
||||||
//!
|
//!
|
||||||
//! let to_address = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap();
|
//! let to_address = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap();
|
||||||
@@ -221,8 +221,12 @@ impl<D: Database> CoinSelectionAlgorithm<D> for LargestFirstCoinSelection {
|
|||||||
)
|
)
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
if selected_amount < amount_needed + (fee_amount.ceil() as u64) {
|
let amount_needed_with_fees = amount_needed + (fee_amount.ceil() as u64);
|
||||||
return Err(Error::InsufficientFunds);
|
if selected_amount < amount_needed_with_fees {
|
||||||
|
return Err(Error::InsufficientFunds {
|
||||||
|
needed: amount_needed_with_fees,
|
||||||
|
available: selected_amount,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(CoinSelectionResult {
|
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();
|
let cost_of_change = self.size_of_change as f32 * fee_rate.as_sat_vb();
|
||||||
|
|
||||||
if curr_available_value + curr_value < actual_target {
|
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
|
Ok(self
|
||||||
|
|||||||
@@ -43,7 +43,7 @@
|
|||||||
//! }"#;
|
//! }"#;
|
||||||
//!
|
//!
|
||||||
//! let import = WalletExport::from_str(import)?;
|
//! let import = WalletExport::from_str(import)?;
|
||||||
//! let wallet: OfflineWallet<_> = Wallet::new_offline(
|
//! let wallet = Wallet::new_offline(
|
||||||
//! &import.descriptor(),
|
//! &import.descriptor(),
|
||||||
//! import.change_descriptor().as_ref(),
|
//! import.change_descriptor().as_ref(),
|
||||||
//! Network::Testnet,
|
//! Network::Testnet,
|
||||||
@@ -58,7 +58,7 @@
|
|||||||
//! # use bdk::database::*;
|
//! # use bdk::database::*;
|
||||||
//! # use bdk::wallet::export::*;
|
//! # use bdk::wallet::export::*;
|
||||||
//! # use bdk::*;
|
//! # use bdk::*;
|
||||||
//! let wallet: OfflineWallet<_> = Wallet::new_offline(
|
//! let wallet = Wallet::new_offline(
|
||||||
//! "wpkh([c258d2e4/84h/1h/0h]tpubDD3ynpHgJQW8VvWRzQ5WFDCrs4jqVFGHB3vLC3r49XHJSqP8bHKdK4AriuUKLccK68zfzowx7YhmDN8SiSkgCDENUFx9qVw65YyqM78vyVe/0/*)",
|
//! "wpkh([c258d2e4/84h/1h/0h]tpubDD3ynpHgJQW8VvWRzQ5WFDCrs4jqVFGHB3vLC3r49XHJSqP8bHKdK4AriuUKLccK68zfzowx7YhmDN8SiSkgCDENUFx9qVw65YyqM78vyVe/0/*)",
|
||||||
//! Some("wpkh([c258d2e4/84h/1h/0h]tpubDD3ynpHgJQW8VvWRzQ5WFDCrs4jqVFGHB3vLC3r49XHJSqP8bHKdK4AriuUKLccK68zfzowx7YhmDN8SiSkgCDENUFx9qVw65YyqM78vyVe/1/*)"),
|
//! Some("wpkh([c258d2e4/84h/1h/0h]tpubDD3ynpHgJQW8VvWRzQ5WFDCrs4jqVFGHB3vLC3r49XHJSqP8bHKdK4AriuUKLccK68zfzowx7YhmDN8SiSkgCDENUFx9qVw65YyqM78vyVe/1/*)"),
|
||||||
//! Network::Testnet,
|
//! Network::Testnet,
|
||||||
@@ -78,7 +78,6 @@ use serde::{Deserialize, Serialize};
|
|||||||
|
|
||||||
use miniscript::{Descriptor, DescriptorPublicKey, ScriptContext, Terminal};
|
use miniscript::{Descriptor, DescriptorPublicKey, ScriptContext, Terminal};
|
||||||
|
|
||||||
use crate::blockchain::BlockchainMarker;
|
|
||||||
use crate::database::BatchDatabase;
|
use crate::database::BatchDatabase;
|
||||||
use crate::wallet::Wallet;
|
use crate::wallet::Wallet;
|
||||||
|
|
||||||
@@ -120,7 +119,7 @@ impl WalletExport {
|
|||||||
///
|
///
|
||||||
/// If the database is empty or `include_blockheight` is false, the `blockheight` field
|
/// If the database is empty or `include_blockheight` is false, the `blockheight` field
|
||||||
/// returned will be `0`.
|
/// returned will be `0`.
|
||||||
pub fn export_wallet<B: BlockchainMarker, D: BatchDatabase>(
|
pub fn export_wallet<B, D: BatchDatabase>(
|
||||||
wallet: &Wallet<B, D>,
|
wallet: &Wallet<B, D>,
|
||||||
label: &str,
|
label: &str,
|
||||||
include_blockheight: bool,
|
include_blockheight: bool,
|
||||||
@@ -208,7 +207,7 @@ mod test {
|
|||||||
use super::*;
|
use super::*;
|
||||||
use crate::database::{memory::MemoryDatabase, BatchOperations};
|
use crate::database::{memory::MemoryDatabase, BatchOperations};
|
||||||
use crate::types::TransactionDetails;
|
use crate::types::TransactionDetails;
|
||||||
use crate::wallet::{OfflineWallet, Wallet};
|
use crate::wallet::Wallet;
|
||||||
|
|
||||||
fn get_test_db() -> MemoryDatabase {
|
fn get_test_db() -> MemoryDatabase {
|
||||||
let mut db = MemoryDatabase::new();
|
let mut db = MemoryDatabase::new();
|
||||||
@@ -234,7 +233,7 @@ mod test {
|
|||||||
let descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/0/*)";
|
let descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/0/*)";
|
||||||
let change_descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/1/*)";
|
let change_descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/1/*)";
|
||||||
|
|
||||||
let wallet: OfflineWallet<_> = Wallet::new_offline(
|
let wallet = Wallet::new_offline(
|
||||||
descriptor,
|
descriptor,
|
||||||
Some(change_descriptor),
|
Some(change_descriptor),
|
||||||
Network::Bitcoin,
|
Network::Bitcoin,
|
||||||
@@ -258,7 +257,7 @@ mod test {
|
|||||||
|
|
||||||
let descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/0/*)";
|
let descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/0/*)";
|
||||||
|
|
||||||
let wallet: OfflineWallet<_> =
|
let wallet =
|
||||||
Wallet::new_offline(descriptor, None, Network::Bitcoin, get_test_db()).unwrap();
|
Wallet::new_offline(descriptor, None, Network::Bitcoin, get_test_db()).unwrap();
|
||||||
WalletExport::export_wallet(&wallet, "Test Label", true).unwrap();
|
WalletExport::export_wallet(&wallet, "Test Label", true).unwrap();
|
||||||
}
|
}
|
||||||
@@ -272,7 +271,7 @@ mod test {
|
|||||||
let descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/0/*)";
|
let descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/0/*)";
|
||||||
let change_descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/50'/0'/1/*)";
|
let change_descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/50'/0'/1/*)";
|
||||||
|
|
||||||
let wallet: OfflineWallet<_> = Wallet::new_offline(
|
let wallet = Wallet::new_offline(
|
||||||
descriptor,
|
descriptor,
|
||||||
Some(change_descriptor),
|
Some(change_descriptor),
|
||||||
Network::Bitcoin,
|
Network::Bitcoin,
|
||||||
@@ -295,7 +294,7 @@ mod test {
|
|||||||
[c98b1535/48'/0'/0'/2']tpubDCDi5W4sP6zSnzJeowy8rQDVhBdRARaPhK1axABi8V1661wEPeanpEXj4ZLAUEoikVtoWcyK26TKKJSecSfeKxwHCcRrge9k1ybuiL71z4a/1/*\
|
[c98b1535/48'/0'/0'/2']tpubDCDi5W4sP6zSnzJeowy8rQDVhBdRARaPhK1axABi8V1661wEPeanpEXj4ZLAUEoikVtoWcyK26TKKJSecSfeKxwHCcRrge9k1ybuiL71z4a/1/*\
|
||||||
))";
|
))";
|
||||||
|
|
||||||
let wallet: OfflineWallet<_> = Wallet::new_offline(
|
let wallet = Wallet::new_offline(
|
||||||
descriptor,
|
descriptor,
|
||||||
Some(change_descriptor),
|
Some(change_descriptor),
|
||||||
Network::Testnet,
|
Network::Testnet,
|
||||||
@@ -315,7 +314,7 @@ mod test {
|
|||||||
let descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/0/*)";
|
let descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/0/*)";
|
||||||
let change_descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/1/*)";
|
let change_descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/1/*)";
|
||||||
|
|
||||||
let wallet: OfflineWallet<_> = Wallet::new_offline(
|
let wallet = Wallet::new_offline(
|
||||||
descriptor,
|
descriptor,
|
||||||
Some(change_descriptor),
|
Some(change_descriptor),
|
||||||
Network::Bitcoin,
|
Network::Bitcoin,
|
||||||
|
|||||||
@@ -59,9 +59,12 @@ pub use utils::IsDust;
|
|||||||
use address_validator::AddressValidator;
|
use address_validator::AddressValidator;
|
||||||
use signer::{Signer, SignerId, SignerOrdering, SignersContainer};
|
use signer::{Signer, SignerId, SignerOrdering, SignersContainer};
|
||||||
use tx_builder::{BumpFee, CreateTx, FeePolicy, TxBuilder, TxBuilderContext};
|
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::database::{BatchDatabase, BatchOperations, DatabaseUtils};
|
||||||
use crate::descriptor::{
|
use crate::descriptor::{
|
||||||
get_checksum, DescriptorMeta, DescriptorScripts, ExtendedDescriptor, ExtractPolicy, Policy,
|
get_checksum, DescriptorMeta, DescriptorScripts, ExtendedDescriptor, ExtractPolicy, Policy,
|
||||||
@@ -73,9 +76,6 @@ use crate::types::*;
|
|||||||
|
|
||||||
const CACHE_ADDR_BATCH_SIZE: u32 = 100;
|
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 Bitcoin wallet
|
||||||
///
|
///
|
||||||
/// A wallet takes descriptors, a [`database`](trait@crate::database::Database) and a
|
/// 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.
|
/// [creating transactions](Wallet::create_tx), etc.
|
||||||
///
|
///
|
||||||
/// A wallet can be either "online" if the [`blockchain`](crate::blockchain) type provided
|
/// 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.
|
/// methods that don't need any interaction with the blockchain to work.
|
||||||
pub struct Wallet<B, D> {
|
pub struct Wallet<B, D> {
|
||||||
descriptor: ExtendedDescriptor,
|
descriptor: ExtendedDescriptor,
|
||||||
@@ -99,24 +99,38 @@ pub struct Wallet<B, D> {
|
|||||||
|
|
||||||
current_height: Option<u32>,
|
current_height: Option<u32>,
|
||||||
|
|
||||||
client: Option<B>,
|
client: B,
|
||||||
database: RefCell<D>,
|
database: RefCell<D>,
|
||||||
|
|
||||||
secp: SecpCtx,
|
secp: SecpCtx,
|
||||||
}
|
}
|
||||||
|
|
||||||
// offline actions, always available
|
impl<D> Wallet<(), D>
|
||||||
impl<B, D> Wallet<B, D>
|
|
||||||
where
|
where
|
||||||
B: BlockchainMarker,
|
|
||||||
D: BatchDatabase,
|
D: BatchDatabase,
|
||||||
{
|
{
|
||||||
/// Create a new "offline" wallet
|
/// Create a new "offline" wallet
|
||||||
pub fn new_offline<E: ToWalletDescriptor>(
|
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,
|
descriptor: E,
|
||||||
change_descriptor: Option<E>,
|
change_descriptor: Option<E>,
|
||||||
network: Network,
|
network: Network,
|
||||||
mut database: D,
|
mut database: D,
|
||||||
|
client: B,
|
||||||
|
current_height: Option<u32>,
|
||||||
) -> Result<Self, Error> {
|
) -> Result<Self, Error> {
|
||||||
let (descriptor, keymap) = descriptor.to_wallet_descriptor(network)?;
|
let (descriptor, keymap) = descriptor.to_wallet_descriptor(network)?;
|
||||||
database.check_descriptor_checksum(
|
database.check_descriptor_checksum(
|
||||||
@@ -148,18 +162,20 @@ where
|
|||||||
signers,
|
signers,
|
||||||
change_signers,
|
change_signers,
|
||||||
address_validators: Vec::new(),
|
address_validators: Vec::new(),
|
||||||
|
|
||||||
network,
|
network,
|
||||||
|
current_height,
|
||||||
current_height: None,
|
client,
|
||||||
|
|
||||||
client: None,
|
|
||||||
database: RefCell::new(database),
|
database: RefCell::new(database),
|
||||||
|
|
||||||
secp: Secp256k1::new(),
|
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
|
/// Return a newly generated address using the external descriptor
|
||||||
pub fn get_new_address(&self) -> Result<Address, Error> {
|
pub fn get_new_address(&self) -> Result<Address, Error> {
|
||||||
let index = self.fetch_and_increment_index(KeychainKind::External)?;
|
let index = self.fetch_and_increment_index(KeychainKind::External)?;
|
||||||
@@ -241,7 +257,7 @@ where
|
|||||||
/// # use bdk::*;
|
/// # use bdk::*;
|
||||||
/// # use bdk::database::*;
|
/// # use bdk::database::*;
|
||||||
/// # let descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/*)";
|
/// # 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 to_address = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap();
|
||||||
/// let (psbt, details) = wallet.create_tx(
|
/// let (psbt, details) = wallet.create_tx(
|
||||||
/// TxBuilder::with_recipients(vec![(to_address.script_pubkey(), 50_000)])
|
/// TxBuilder::with_recipients(vec![(to_address.script_pubkey(), 50_000)])
|
||||||
@@ -495,11 +511,15 @@ where
|
|||||||
match change_output {
|
match change_output {
|
||||||
None if change_val.is_dust() => {
|
None if change_val.is_dust() => {
|
||||||
// single recipient, but the only output would be below dust limit
|
// 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() => {
|
Some(_) if change_val.is_dust() => {
|
||||||
// skip the change output because it's dust, this adds up to the fees
|
// skip the change output because it's dust -- just include it in the fee.
|
||||||
fee_amount += selected_amount - outgoing;
|
fee_amount += change_val;
|
||||||
}
|
}
|
||||||
Some(mut change_output) => {
|
Some(mut change_output) => {
|
||||||
change_output.value = change_val;
|
change_output.value = change_val;
|
||||||
@@ -556,7 +576,7 @@ where
|
|||||||
/// # use bdk::*;
|
/// # use bdk::*;
|
||||||
/// # use bdk::database::*;
|
/// # use bdk::database::*;
|
||||||
/// # let descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/*)";
|
/// # 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 txid = Txid::from_str("faff0a466b70f5d5f92bd757a92c1371d4838bdd5bc53a06764e2488e51ce8f8").unwrap();
|
||||||
/// let (psbt, details) = wallet.bump_fee(
|
/// let (psbt, details) = wallet.bump_fee(
|
||||||
/// &txid,
|
/// &txid,
|
||||||
@@ -773,7 +793,11 @@ where
|
|||||||
}
|
}
|
||||||
Some(_) if change_val_after_add.is_dust() => {
|
Some(_) if change_val_after_add.is_dust() => {
|
||||||
// single_recipient but the only output would be below dust limit
|
// 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 => {
|
None => {
|
||||||
removed_updatable_output.value = change_val_after_add;
|
removed_updatable_output.value = change_val_after_add;
|
||||||
@@ -820,7 +844,7 @@ where
|
|||||||
/// # use bdk::*;
|
/// # use bdk::*;
|
||||||
/// # use bdk::database::*;
|
/// # use bdk::database::*;
|
||||||
/// # let descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/*)";
|
/// # 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 (psbt, _) = wallet.create_tx(TxBuilder::new())?;
|
||||||
/// let (signed_psbt, finalized) = wallet.sign(psbt, None)?;
|
/// let (signed_psbt, finalized) = wallet.sign(psbt, None)?;
|
||||||
/// # Ok::<(), bdk::Error>(())
|
/// # Ok::<(), bdk::Error>(())
|
||||||
@@ -1319,12 +1343,15 @@ where
|
|||||||
database: D,
|
database: D,
|
||||||
client: B,
|
client: B,
|
||||||
) -> Result<Self, Error> {
|
) -> Result<Self, Error> {
|
||||||
let mut wallet = Self::new_offline(descriptor, change_descriptor, network, database)?;
|
let current_height = Some(maybe_await!(client.get_height())? as u32);
|
||||||
|
Self::_new(
|
||||||
wallet.current_height = Some(maybe_await!(client.get_height())? as u32);
|
descriptor,
|
||||||
wallet.client = Some(client);
|
change_descriptor,
|
||||||
|
network,
|
||||||
Ok(wallet)
|
database,
|
||||||
|
client,
|
||||||
|
current_height,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sync the internal database with the blockchain
|
/// 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: 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
|
// TODO: we should sync if generating an address triggers a new batch to be stored
|
||||||
if run_setup {
|
if run_setup {
|
||||||
maybe_await!(self.client.as_ref().ok_or(Error::OfflineClient)?.setup(
|
maybe_await!(self.client.setup(
|
||||||
None,
|
None,
|
||||||
self.database.borrow_mut().deref_mut(),
|
self.database.borrow_mut().deref_mut(),
|
||||||
progress_update,
|
progress_update,
|
||||||
))
|
))
|
||||||
} else {
|
} else {
|
||||||
maybe_await!(self.client.as_ref().ok_or(Error::OfflineClient)?.sync(
|
maybe_await!(self.client.sync(
|
||||||
None,
|
None,
|
||||||
self.database.borrow_mut().deref_mut(),
|
self.database.borrow_mut().deref_mut(),
|
||||||
progress_update,
|
progress_update,
|
||||||
@@ -1387,8 +1414,8 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Return a reference to the internal blockchain client
|
/// Return a reference to the internal blockchain client
|
||||||
pub fn client(&self) -> Option<&B> {
|
pub fn client(&self) -> &B {
|
||||||
self.client.as_ref()
|
&self.client
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the Bitcoin network the wallet is using.
|
/// Get the Bitcoin network the wallet is using.
|
||||||
@@ -1399,11 +1426,7 @@ where
|
|||||||
/// Broadcast a transaction to the network
|
/// Broadcast a transaction to the network
|
||||||
#[maybe_async]
|
#[maybe_async]
|
||||||
pub fn broadcast(&self, tx: Transaction) -> Result<Txid, Error> {
|
pub fn broadcast(&self, tx: Transaction) -> Result<Txid, Error> {
|
||||||
maybe_await!(self
|
maybe_await!(self.client.broadcast(&tx))?;
|
||||||
.client
|
|
||||||
.as_ref()
|
|
||||||
.ok_or(Error::OfflineClient)?
|
|
||||||
.broadcast(&tx))?;
|
|
||||||
|
|
||||||
Ok(tx.txid())
|
Ok(tx.txid())
|
||||||
}
|
}
|
||||||
@@ -1424,7 +1447,7 @@ mod test {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_cache_addresses_fixed() {
|
fn test_cache_addresses_fixed() {
|
||||||
let db = MemoryDatabase::new();
|
let db = MemoryDatabase::new();
|
||||||
let wallet: OfflineWallet<_> = Wallet::new_offline(
|
let wallet = Wallet::new_offline(
|
||||||
"wpkh(L5EZftvrYaSudiozVRzTqLcHLNDoVn7H5HSfM9BAN6tMJX8oTWz6)",
|
"wpkh(L5EZftvrYaSudiozVRzTqLcHLNDoVn7H5HSfM9BAN6tMJX8oTWz6)",
|
||||||
None,
|
None,
|
||||||
Network::Testnet,
|
Network::Testnet,
|
||||||
@@ -1458,7 +1481,7 @@ mod test {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_cache_addresses() {
|
fn test_cache_addresses() {
|
||||||
let db = MemoryDatabase::new();
|
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!(
|
assert_eq!(
|
||||||
wallet.get_new_address().unwrap().to_string(),
|
wallet.get_new_address().unwrap().to_string(),
|
||||||
@@ -1486,7 +1509,7 @@ mod test {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_cache_addresses_refill() {
|
fn test_cache_addresses_refill() {
|
||||||
let db = MemoryDatabase::new();
|
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!(
|
assert_eq!(
|
||||||
wallet.get_new_address().unwrap().to_string(),
|
wallet.get_new_address().unwrap().to_string(),
|
||||||
@@ -1533,12 +1556,12 @@ mod test {
|
|||||||
pub(crate) fn get_funded_wallet(
|
pub(crate) fn get_funded_wallet(
|
||||||
descriptor: &str,
|
descriptor: &str,
|
||||||
) -> (
|
) -> (
|
||||||
OfflineWallet<MemoryDatabase>,
|
Wallet<(), MemoryDatabase>,
|
||||||
(String, Option<String>),
|
(String, Option<String>),
|
||||||
bitcoin::Txid,
|
bitcoin::Txid,
|
||||||
) {
|
) {
|
||||||
let descriptors = testutils!(@descriptors (descriptor));
|
let descriptors = testutils!(@descriptors (descriptor));
|
||||||
let wallet: OfflineWallet<_> = Wallet::new_offline(
|
let wallet = Wallet::new_offline(
|
||||||
&descriptors.0,
|
&descriptors.0,
|
||||||
None,
|
None,
|
||||||
Network::Regtest,
|
Network::Regtest,
|
||||||
@@ -1952,7 +1975,7 @@ mod test {
|
|||||||
fn test_create_tx_skip_change_dust() {
|
fn test_create_tx_skip_change_dust() {
|
||||||
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
|
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
|
||||||
let addr = wallet.get_new_address().unwrap();
|
let addr = wallet.get_new_address().unwrap();
|
||||||
let (psbt, _) = wallet
|
let (psbt, details) = wallet
|
||||||
.create_tx(TxBuilder::with_recipients(vec![(
|
.create_tx(TxBuilder::with_recipients(vec![(
|
||||||
addr.script_pubkey(),
|
addr.script_pubkey(),
|
||||||
49_800,
|
49_800,
|
||||||
@@ -1961,6 +1984,7 @@ mod test {
|
|||||||
|
|
||||||
assert_eq!(psbt.global.unsigned_tx.output.len(), 1);
|
assert_eq!(psbt.global.unsigned_tx.output.len(), 1);
|
||||||
assert_eq!(psbt.global.unsigned_tx.output[0].value, 49_800);
|
assert_eq!(psbt.global.unsigned_tx.output[0].value, 49_800);
|
||||||
|
assert_eq!(details.fees, 200);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -79,7 +79,7 @@
|
|||||||
//! let custom_signer = CustomSigner::connect();
|
//! let custom_signer = CustomSigner::connect();
|
||||||
//!
|
//!
|
||||||
//! let descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/*)";
|
//! 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(
|
//! wallet.add_signer(
|
||||||
//! KeychainKind::External,
|
//! KeychainKind::External,
|
||||||
//! Fingerprint::from_str("e30f11b8").unwrap().into(),
|
//! Fingerprint::from_str("e30f11b8").unwrap().into(),
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ use miniscript::descriptor::DescriptorPublicKeyCtx;
|
|||||||
use miniscript::{MiniscriptKey, Satisfier, ToPublicKey};
|
use miniscript::{MiniscriptKey, Satisfier, ToPublicKey};
|
||||||
|
|
||||||
// De-facto standard "dust limit" (even though it should change based on the output type)
|
// 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
|
// 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
|
// 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"
|
documentation = "https://docs.rs/bdk-testutils-macros"
|
||||||
description = "Supporting testing macros for `bdk`"
|
description = "Supporting testing macros for `bdk`"
|
||||||
keywords = ["bdk"]
|
keywords = ["bdk"]
|
||||||
license-file = "../LICENSE"
|
license = "MIT"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
proc-macro = true
|
proc-macro = true
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ repository = "https://github.com/bitcoindevkit/bdk"
|
|||||||
documentation = "https://docs.rs/bdk-testutils"
|
documentation = "https://docs.rs/bdk-testutils"
|
||||||
description = "Supporting testing utilities for `bdk`"
|
description = "Supporting testing utilities for `bdk`"
|
||||||
keywords = ["bdk"]
|
keywords = ["bdk"]
|
||||||
license-file = "../LICENSE"
|
license = "MIT"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
name = "testutils"
|
name = "testutils"
|
||||||
|
|||||||
Reference in New Issue
Block a user