Compare commits
16 Commits
v0.9.0
...
release/0.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e6cf423721 | ||
|
|
c7d0803000 | ||
|
|
1a4b9b440d | ||
|
|
1f914c2b4d | ||
|
|
fadb316451 | ||
|
|
3fefd3c1fb | ||
|
|
2cbb314d0b | ||
|
|
535fc70433 | ||
|
|
10fa276bec | ||
|
|
1cc9afaeb3 | ||
|
|
6f5e621561 | ||
|
|
1dd6f2d9f8 | ||
|
|
f92b45db6a | ||
|
|
c971d54aea | ||
|
|
2abccafb8f | ||
|
|
75d0415bec |
101
.github/ISSUE_TEMPLATE/minor_release.md
vendored
Normal file
101
.github/ISSUE_TEMPLATE/minor_release.md
vendored
Normal file
@@ -0,0 +1,101 @@
|
||||
---
|
||||
name: Minor Release
|
||||
about: Create a new minor release [for release managers only]
|
||||
title: 'Release MAJOR.MINOR+1.0'
|
||||
labels: 'release'
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
## Create a new minor release
|
||||
|
||||
### Summary
|
||||
|
||||
<--release summary to be used in announcements-->
|
||||
|
||||
### Commit
|
||||
|
||||
<--latest commit ID to include in this release-->
|
||||
|
||||
### Changelog
|
||||
|
||||
<--add notices from PRs merged since the prior release, see ["keep a changelog"]-->
|
||||
|
||||
### Checklist
|
||||
|
||||
Release numbering must follow [Semantic Versioning]. These steps assume the current `master`
|
||||
branch **development** version is *MAJOR.MINOR.0*.
|
||||
|
||||
#### On the day of the feature freeze
|
||||
|
||||
Change the `master` branch to the next MINOR+1 version:
|
||||
|
||||
- [ ] Switch to the `master` branch.
|
||||
- [ ] Create a new PR branch called `bump_dev_MAJOR_MINOR+1`, eg. `bump_dev_0_22`.
|
||||
- [ ] Bump the `bump_dev_MAJOR_MINOR+1` branch to the next development MINOR+1 version.
|
||||
- Change the `Cargo.toml` version value to `MAJOR.MINOR+1.0`.
|
||||
- The commit message should be "Bump version to MAJOR.MINOR+1.0".
|
||||
- [ ] Create PR and merge the `bump_dev_MAJOR_MINOR+1` branch to `master`.
|
||||
- Title PR "Bump version to MAJOR.MINOR+1.0".
|
||||
|
||||
Create a new release branch:
|
||||
|
||||
- [ ] Double check that your local `master` is up-to-date with the upstream repo.
|
||||
- [ ] Create a new branch called `release/MAJOR.MINOR+1` from `master`.
|
||||
|
||||
Add a release candidate tag, this is optional and only needed for major `bdk-ffi` changes that
|
||||
require a longer testing cycle:
|
||||
|
||||
- [ ] Bump the `release/MAJOR.MINOR+1` branch to `MAJOR.MINOR+1.0-rc.1` version.
|
||||
- Change the `Cargo.toml` version value to `MAJOR.MINOR+1.0-rc.1`.
|
||||
- The commit message should be "Bump version to MAJOR.MINOR+1.0-rc.1".
|
||||
- [ ] Add a tag to the `HEAD` commit in the `release/MAJOR.MINOR+1` branch.
|
||||
- The tag name should be `vMAJOR.MINOR+1.0-rc.1`
|
||||
- Use message "Release MAJOR.MINOR+1.0 rc.1".
|
||||
- Make sure the tag is signed, for extra safety use the explicit `--sign` flag.
|
||||
- [ ] Push the `release/MAJOR.MINOR` branch and new tag to the `bitcoindevkit/bdk` repo.
|
||||
- Use `git push --tags` option to push the new `vMAJOR.MINOR+1.0-rc.1` tag.
|
||||
|
||||
If any issues need to be fixed before the *MAJOR.MINOR+1.0* version is released:
|
||||
|
||||
- [ ] Merge fix PRs to the `master` branch.
|
||||
- [ ] Git cherry-pick fix commits to the `release/MAJOR.MINOR+1` branch.
|
||||
- [ ] Verify fixes in `release/MAJOR.MINOR+1` branch.
|
||||
- [ ] Bump the `release/MAJOR.MINOR+1` branch to `MAJOR.MINOR+1.0-rc.x+1` version.
|
||||
- Change the `Cargo.toml` version value to `MAJOR.MINOR+1.0-rc.x+1`.
|
||||
- The commit message should be "Bump version to MAJOR.MINOR+1.0-rc.x+1".
|
||||
- [ ] Add a tag to the `HEAD` commit in the `release/MAJOR.MINOR+1` branch.
|
||||
- The tag name should be `vMAJOR.MINOR+1.0-rc.x+1`, where x is the current release candidate number.
|
||||
- Use tag message "Release MAJOR.MINOR+1.0 rc.x+1".
|
||||
- Make sure the tag is signed, for extra safety use the explicit `--sign` flag.
|
||||
- [ ] Push the new tag to the `bitcoindevkit/bdk` repo.
|
||||
- Use `git push --tags` option to push the new `vMAJOR.MINOR+1.0-rc.x+1` tag.
|
||||
|
||||
#### On the day of the release
|
||||
|
||||
Tag and publish new release:
|
||||
|
||||
- [ ] Bump the `release/MAJOR.MINOR+1` branch to `MAJOR.MINOR+1.0` version.
|
||||
- Change the `Cargo.toml` version value to `MAJOR.MINOR+1.0`.
|
||||
- The commit message should be "Bump version to MAJOR.MINOR+1.0".
|
||||
- [ ] Add a tag to the `HEAD` commit in the `release/MAJOR.MINOR+1` branch.
|
||||
- The tag name should be `vMAJOR.MINOR+1.0`
|
||||
- The first line of the tag message should be "Release MAJOR.MINOR+1.0".
|
||||
- In the body of the tag message put a copy of the **Summary** and **Changelog** for the release.
|
||||
- Make sure the tag is signed, for extra safety use the explicit `--sign` flag.
|
||||
- [ ] Wait for the CI to finish one last time.
|
||||
- [ ] Push the new tag to the `bitcoindevkit/bdk` repo.
|
||||
- [ ] Create the release on GitHub.
|
||||
- Go to "tags", click on the dots on the right and select "Create Release".
|
||||
- Set the title to `Release MAJOR.MINOR+1.0`.
|
||||
- In the release notes body put the **Summary** and **Changelog**.
|
||||
- Use the "+ Auto-generate release notes" button to add details from included PRs.
|
||||
- Until we reach a `1.0.0` release check the "Pre-release" box.
|
||||
- [ ] After downstream language repos are also updated announce the release, using the **Summary**,
|
||||
on Discord, Twitter and Mastodon.
|
||||
- [ ] Celebrate 🎉
|
||||
|
||||
[Semantic Versioning]: https://semver.org/
|
||||
[crates.io]: https://crates.io/crates/bdk
|
||||
[docs.rs]: https://docs.rs/bdk/latest/bdk
|
||||
["keep a changelog"]: https://keepachangelog.com/en/1.0.0/
|
||||
69
.github/ISSUE_TEMPLATE/patch_release.md
vendored
Normal file
69
.github/ISSUE_TEMPLATE/patch_release.md
vendored
Normal file
@@ -0,0 +1,69 @@
|
||||
---
|
||||
name: Patch Release
|
||||
about: Create a new patch release [for release managers only]
|
||||
title: 'Release MAJOR.MINOR.PATCH+1'
|
||||
labels: 'release'
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
## Create a new patch release
|
||||
|
||||
### Summary
|
||||
|
||||
<--release summary to be used in announcements-->
|
||||
|
||||
### Commit
|
||||
|
||||
<--latest commit ID to include in this release-->
|
||||
|
||||
### Changelog
|
||||
|
||||
<--add notices from PRs merged since the prior release, see ["keep a changelog"]-->
|
||||
|
||||
### Checklist
|
||||
|
||||
Release numbering must follow [Semantic Versioning]. These steps assume the current `master`
|
||||
branch **development** version is *MAJOR.MINOR.PATCH*.
|
||||
|
||||
### On the day of the patch release
|
||||
|
||||
Change the `master` branch to the new PATCH+1 version:
|
||||
|
||||
- [ ] Switch to the `master` branch.
|
||||
- [ ] Create a new PR branch called `bump_dev_MAJOR_MINOR_PATCH+1`, eg. `bump_dev_0_22_1`.
|
||||
- [ ] Bump the `bump_dev_MAJOR_MINOR` branch to the next development PATCH+1 version.
|
||||
- Change the `Cargo.toml` version value to `MAJOR.MINOR.PATCH+1`.
|
||||
- The commit message should be "Bump version to MAJOR.MINOR.PATCH+1".
|
||||
- [ ] Create PR and merge the `bump_dev_MAJOR_MINOR_PATCH+1` branch to `master`.
|
||||
- Title PR "Bump version to MAJOR.MINOR.PATCH+1".
|
||||
|
||||
Cherry-pick, tag and publish new PATCH+1 release:
|
||||
|
||||
- [ ] Merge fix PRs to the `master` branch.
|
||||
- [ ] Git cherry-pick fix commits to the `release/MAJOR.MINOR` branch to be patched.
|
||||
- [ ] Verify fixes in `release/MAJOR.MINOR` branch.
|
||||
- [ ] Bump the `release/MAJOR.MINOR.PATCH+1` branch to `MAJOR.MINOR.PATCH+1` version.
|
||||
- Change the `Cargo.toml` version value to `MAJOR.MINOR.MINOR.PATCH+1`.
|
||||
- The commit message should be "Bump version to MAJOR.MINOR.PATCH+1".
|
||||
- [ ] Add a tag to the `HEAD` commit in the `release/MAJOR.MINOR` branch.
|
||||
- The tag name should be `vMAJOR.MINOR.PATCH+1`
|
||||
- The first line of the tag message should be "Release MAJOR.MINOR.PATCH+1".
|
||||
- In the body of the tag message put a copy of the **Summary** and **Changelog** for the release.
|
||||
- Make sure the tag is signed, for extra safety use the explicit `--sign` flag.
|
||||
- [ ] Wait for the CI to finish one last time.
|
||||
- [ ] Push the new tag to the `bitcoindevkit/bdk` repo.
|
||||
- [ ] Create the release on GitHub.
|
||||
- Go to "tags", click on the dots on the right and select "Create Release".
|
||||
- Set the title to `Release MAJOR.MINOR.PATCH+1`.
|
||||
- In the release notes body put the **Summary** and **Changelog**.
|
||||
- Use the "+ Auto-generate release notes" button to add details from included PRs.
|
||||
- Until we reach a `1.0.0` release check the "Pre-release" box.
|
||||
- [ ] After downstream language repos are also updated announce the release, using the **Summary**,
|
||||
on Discord, Twitter and Mastodon.
|
||||
- [ ] Celebrate 🎉
|
||||
|
||||
[Semantic Versioning]: https://semver.org/
|
||||
[crates.io]: https://crates.io/crates/bdk
|
||||
[docs.rs]: https://docs.rs/bdk/latest/bdk
|
||||
["keep a changelog"]: https://keepachangelog.com/en/1.0.0/
|
||||
6
.github/pull_request_template.md
vendored
6
.github/pull_request_template.md
vendored
@@ -9,6 +9,11 @@
|
||||
<!-- In this section you can include notes directed to the reviewers, like explaining why some parts
|
||||
of the PR were done in a specific way -->
|
||||
|
||||
### Changelog notice
|
||||
|
||||
<!-- Notice the release manager should include in the release tag message changelog -->
|
||||
<!-- See https://keepachangelog.com/en/1.0.0/ for examples -->
|
||||
|
||||
### Checklists
|
||||
|
||||
#### All Submissions:
|
||||
@@ -21,7 +26,6 @@ of the PR were done in a specific way -->
|
||||
|
||||
* [ ] I've added tests for the new feature
|
||||
* [ ] I've added docs for the new feature
|
||||
* [ ] I've updated `CHANGELOG.md`
|
||||
|
||||
#### Bugfixes:
|
||||
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
# Changelog
|
||||
All notable changes to this project will be documented in this file.
|
||||
All notable changes to this project prior to release **0.9.0** are documented in this file. Future
|
||||
changelog information can be found in each release's git tag and can be viewed with `git tag -ln100 "v*"`.
|
||||
Changelog info is also documented on the [GitHub releases](https://github.com/bitcoindevkit/bdk-ffi/releases)
|
||||
page. See [DEVELOPMENT_CYCLE.md](DEVELOPMENT_CYCLE.md) for more details.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
10
Cargo.toml
10
Cargo.toml
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "bdk-ffi"
|
||||
version = "0.9.0"
|
||||
version = "0.10.0"
|
||||
authors = ["Steve Myers <steve@notmandatory.org>", "Sudarsan Balaji <sudarsan.balaji@artfuldev.com>"]
|
||||
edition = "2018"
|
||||
|
||||
@@ -14,10 +14,10 @@ crate-type = ["staticlib", "cdylib"]
|
||||
name = "bdkffi"
|
||||
|
||||
[dependencies]
|
||||
bdk = { version = "0.22", features = ["all-keys", "use-esplora-ureq", "sqlite-bundled"] }
|
||||
bdk = { version = "0.23", features = ["all-keys", "use-esplora-ureq", "sqlite-bundled"] }
|
||||
|
||||
uniffi_macros = { version = "0.19.3", features = ["builtin-bindgen"] }
|
||||
uniffi = { version = "0.19.3", features = ["builtin-bindgen"] }
|
||||
uniffi_macros = { version = "0.21.0", features = ["builtin-bindgen"] }
|
||||
uniffi = { version = "0.21.0", features = ["builtin-bindgen"] }
|
||||
|
||||
[build-dependencies]
|
||||
uniffi_build = { version = "0.19.3", features = ["builtin-bindgen"] }
|
||||
uniffi_build = { version = "0.21.0", features = ["builtin-bindgen"] }
|
||||
|
||||
35
DEVELOPMENT_CYCLE.md
Normal file
35
DEVELOPMENT_CYCLE.md
Normal file
@@ -0,0 +1,35 @@
|
||||
# Development Cycle
|
||||
|
||||
This project follows a regular releasing schedule similar to the one [used by the Rust language]
|
||||
except releases always follow the latest [`bdk`] release by one to two weeks. In short, this means
|
||||
that a new release is made at a regular cadence, with all the feature/bugfixes that made it to
|
||||
`master` in time. This ensures that we don't keep delaying releases waiting for
|
||||
"just one more little thing".
|
||||
|
||||
After making a new `bdk-ffi` release tag all downstream language bindings should also be updated.
|
||||
|
||||
This project uses [Semantic Versioning], but is currently at MAJOR version zero (0.y.z) meaning it
|
||||
is still in initial development. Anything MAY change at any time. The public API SHOULD NOT be
|
||||
considered stable. Until we reach version `1.0.0` we will do our best to document any breaking API
|
||||
changes in the changelog info attached to each release tag.
|
||||
|
||||
We decided to maintain a faster release cycle while the library is still in "beta", i.e. before
|
||||
release `1.0.0`: since we are constantly adding new features and, even more importantly, fixing
|
||||
issues, we want developers to have access to those updates as fast as possible. For this reason we
|
||||
will make a release **every 4 weeks**.
|
||||
|
||||
Once the project reaches a more mature state (>= `1.0.0`), we will very likely switch to longer
|
||||
release cycles of **6 weeks**.
|
||||
|
||||
The "feature freeze" will happen when [`bdk`] releases a release candidate. This project will then
|
||||
be updated and tested with [`bdk`] release candidates until a final release is published. This
|
||||
means a new branch will be created originating from the `master` tip at that time, and in that
|
||||
branch we will stop adding new features and only focus on ensuring the ones we've added are working
|
||||
properly.
|
||||
|
||||
To create a new release a release manager will create a new issue using a `Release` template and
|
||||
follow the template instructions.
|
||||
|
||||
[used by the Rust language]: https://doc.rust-lang.org/book/appendix-07-nightly-rust.html
|
||||
[Semantic Versioning]: https://semver.org/
|
||||
[`bdk`]: https://github.com/bitcoindevkit/bdk
|
||||
@@ -4,7 +4,7 @@ version = "0.2.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "=1.0.45" # remove after upgrading to next version of uniffi
|
||||
anyhow = "1.0.45" # remove after upgrading to next version of uniffi
|
||||
structopt = "0.3"
|
||||
uniffi_bindgen = "0.19.5"
|
||||
uniffi_bindgen = "0.21.0"
|
||||
camino = "1.0.9"
|
||||
|
||||
33
src/bdk.udl
33
src/bdk.udl
@@ -173,8 +173,8 @@ dictionary LocalUtxo {
|
||||
boolean is_spent;
|
||||
};
|
||||
|
||||
dictionary AddressAmount {
|
||||
string address;
|
||||
dictionary ScriptAmount {
|
||||
Script script;
|
||||
u64 amount;
|
||||
};
|
||||
|
||||
@@ -210,12 +210,22 @@ interface PartiallySignedBitcoinTransaction {
|
||||
string serialize();
|
||||
|
||||
string txid();
|
||||
|
||||
sequence<u8> extract_tx();
|
||||
|
||||
[Throws=BdkError]
|
||||
PartiallySignedBitcoinTransaction combine(PartiallySignedBitcoinTransaction other);
|
||||
};
|
||||
|
||||
dictionary TxBuilderResult {
|
||||
PartiallySignedBitcoinTransaction psbt;
|
||||
TransactionDetails transaction_details;
|
||||
};
|
||||
|
||||
interface TxBuilder {
|
||||
constructor();
|
||||
|
||||
TxBuilder add_recipient(string address, u64 amount);
|
||||
TxBuilder add_recipient(Script script, u64 amount);
|
||||
|
||||
TxBuilder add_unspendable(OutPoint unspendable);
|
||||
|
||||
@@ -245,10 +255,10 @@ interface TxBuilder {
|
||||
|
||||
TxBuilder add_data(sequence<u8> data);
|
||||
|
||||
TxBuilder set_recipients(sequence<AddressAmount> recipients);
|
||||
TxBuilder set_recipients(sequence<ScriptAmount> recipients);
|
||||
|
||||
[Throws=BdkError]
|
||||
PartiallySignedBitcoinTransaction finish([ByRef] Wallet wallet);
|
||||
TxBuilderResult finish([ByRef] Wallet wallet);
|
||||
};
|
||||
|
||||
interface BumpFeeTxBuilder {
|
||||
@@ -280,6 +290,8 @@ interface DescriptorSecretKey {
|
||||
|
||||
DescriptorPublicKey as_public();
|
||||
|
||||
sequence<u8> secret_bytes();
|
||||
|
||||
string as_string();
|
||||
};
|
||||
|
||||
@@ -291,3 +303,14 @@ interface DescriptorPublicKey {
|
||||
|
||||
string as_string();
|
||||
};
|
||||
|
||||
interface Address {
|
||||
[Throws=BdkError]
|
||||
constructor(string address);
|
||||
|
||||
Script script_pubkey();
|
||||
};
|
||||
|
||||
interface Script {
|
||||
constructor(sequence<u8> raw_output_script);
|
||||
};
|
||||
|
||||
187
src/lib.rs
187
src/lib.rs
@@ -1,8 +1,10 @@
|
||||
use bdk::bitcoin::blockdata::script::Script as BdkScript;
|
||||
use bdk::bitcoin::hashes::hex::ToHex;
|
||||
use bdk::bitcoin::secp256k1::Secp256k1;
|
||||
use bdk::bitcoin::util::bip32::DerivationPath as BdkDerivationPath;
|
||||
use bdk::bitcoin::util::psbt::serialize::Serialize;
|
||||
use bdk::bitcoin::util::psbt::PartiallySignedTransaction;
|
||||
use bdk::bitcoin::{Address, Network, OutPoint as BdkOutPoint, Script, Txid};
|
||||
use bdk::bitcoin::{Address as BdkAddress, Network, OutPoint as BdkOutPoint, Txid};
|
||||
use bdk::blockchain::any::{AnyBlockchain, AnyBlockchainConfig};
|
||||
use bdk::blockchain::GetBlockHash;
|
||||
use bdk::blockchain::GetHeight;
|
||||
@@ -23,7 +25,7 @@ use bdk::wallet::tx_builder::ChangeSpendPolicy;
|
||||
use bdk::wallet::AddressIndex as BdkAddressIndex;
|
||||
use bdk::wallet::AddressInfo as BdkAddressInfo;
|
||||
use bdk::{
|
||||
Balance as BdkBalance, BlockTime, Error, FeeRate, KeychainKind, SignOptions,
|
||||
Balance as BdkBalance, BlockTime, Error as BdkError, FeeRate, KeychainKind, SignOptions,
|
||||
SyncOptions as BdkSyncOptions, Wallet as BdkWallet,
|
||||
};
|
||||
use std::collections::HashSet;
|
||||
@@ -35,10 +37,9 @@ use std::sync::{Arc, Mutex, MutexGuard};
|
||||
|
||||
uniffi_macros::include_scaffolding!("bdk");
|
||||
|
||||
type BdkError = Error;
|
||||
|
||||
pub struct AddressAmount {
|
||||
pub address: String,
|
||||
/// A output script and an amount of satoshis.
|
||||
pub struct ScriptAmount {
|
||||
pub script: Arc<Script>,
|
||||
pub amount: u64,
|
||||
}
|
||||
|
||||
@@ -208,16 +209,16 @@ impl Blockchain {
|
||||
self.blockchain_mutex.lock().expect("blockchain")
|
||||
}
|
||||
|
||||
fn broadcast(&self, psbt: &PartiallySignedBitcoinTransaction) -> Result<(), Error> {
|
||||
fn broadcast(&self, psbt: &PartiallySignedBitcoinTransaction) -> Result<(), BdkError> {
|
||||
let tx = psbt.internal.lock().unwrap().clone().extract_tx();
|
||||
self.get_blockchain().broadcast(&tx)
|
||||
}
|
||||
|
||||
fn get_height(&self) -> Result<u32, Error> {
|
||||
fn get_height(&self) -> Result<u32, BdkError> {
|
||||
self.get_blockchain().get_height()
|
||||
}
|
||||
|
||||
fn get_block_hash(&self, height: u32) -> Result<String, Error> {
|
||||
fn get_block_hash(&self, height: u32) -> Result<String, BdkError> {
|
||||
self.get_blockchain()
|
||||
.get_block_hash(u64::from(height))
|
||||
.map(|hash| hash.to_string())
|
||||
@@ -304,12 +305,9 @@ impl NetworkLocalUtxo for LocalUtxo {
|
||||
},
|
||||
txout: TxOut {
|
||||
value: x.txout.value,
|
||||
address: bdk::bitcoin::util::address::Address::from_script(
|
||||
&x.txout.script_pubkey,
|
||||
network,
|
||||
)
|
||||
.unwrap()
|
||||
.to_string(),
|
||||
address: BdkAddress::from_script(&x.txout.script_pubkey, network)
|
||||
.unwrap()
|
||||
.to_string(),
|
||||
},
|
||||
keychain: x.keychain,
|
||||
is_spent: x.is_spent,
|
||||
@@ -329,7 +327,7 @@ struct ProgressHolder {
|
||||
}
|
||||
|
||||
impl BdkProgress for ProgressHolder {
|
||||
fn update(&self, progress: f32, message: Option<String>) -> Result<(), Error> {
|
||||
fn update(&self, progress: f32, message: Option<String>) -> Result<(), BdkError> {
|
||||
self.progress.update(progress, message);
|
||||
Ok(())
|
||||
}
|
||||
@@ -342,12 +340,12 @@ impl fmt::Debug for ProgressHolder {
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct PartiallySignedBitcoinTransaction {
|
||||
pub struct PartiallySignedBitcoinTransaction {
|
||||
internal: Mutex<PartiallySignedTransaction>,
|
||||
}
|
||||
|
||||
impl PartiallySignedBitcoinTransaction {
|
||||
fn new(psbt_base64: String) -> Result<Self, Error> {
|
||||
fn new(psbt_base64: String) -> Result<Self, BdkError> {
|
||||
let psbt: PartiallySignedTransaction = PartiallySignedTransaction::from_str(&psbt_base64)?;
|
||||
Ok(PartiallySignedBitcoinTransaction {
|
||||
internal: Mutex::new(psbt),
|
||||
@@ -364,6 +362,32 @@ impl PartiallySignedBitcoinTransaction {
|
||||
let txid = tx.txid();
|
||||
txid.to_hex()
|
||||
}
|
||||
|
||||
/// Return the transaction as bytes.
|
||||
fn extract_tx(&self) -> Vec<u8> {
|
||||
self.internal
|
||||
.lock()
|
||||
.unwrap()
|
||||
.clone()
|
||||
.extract_tx()
|
||||
.serialize()
|
||||
}
|
||||
|
||||
/// Combines this PartiallySignedTransaction with other PSBT as described by BIP 174.
|
||||
///
|
||||
/// In accordance with BIP 174 this function is commutative i.e., `A.combine(B) == B.combine(A)`
|
||||
fn combine(
|
||||
&self,
|
||||
other: Arc<PartiallySignedBitcoinTransaction>,
|
||||
) -> Result<Arc<PartiallySignedBitcoinTransaction>, BdkError> {
|
||||
let other_psbt = other.internal.lock().unwrap().clone();
|
||||
let mut original_psbt = self.internal.lock().unwrap().clone();
|
||||
|
||||
original_psbt.combine(other_psbt)?;
|
||||
Ok(Arc::new(PartiallySignedBitcoinTransaction {
|
||||
internal: Mutex::new(original_psbt),
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
/// A Bitcoin wallet.
|
||||
@@ -430,18 +454,18 @@ impl Wallet {
|
||||
|
||||
/// Return the balance, meaning the sum of this wallet’s unspent outputs’ values. Note that this method only operates
|
||||
/// on the internal database, which first needs to be Wallet.sync manually.
|
||||
fn get_balance(&self) -> Result<Balance, Error> {
|
||||
fn get_balance(&self) -> Result<Balance, BdkError> {
|
||||
self.get_wallet().get_balance().map(|b| b.into())
|
||||
}
|
||||
|
||||
/// Sign a transaction with all the wallet’s signers.
|
||||
fn sign(&self, psbt: &PartiallySignedBitcoinTransaction) -> Result<bool, Error> {
|
||||
fn sign(&self, psbt: &PartiallySignedBitcoinTransaction) -> Result<bool, BdkError> {
|
||||
let mut psbt = psbt.internal.lock().unwrap();
|
||||
self.get_wallet().sign(&mut psbt, SignOptions::default())
|
||||
}
|
||||
|
||||
/// Return the list of transactions made and received by the wallet. Note that this method only operate on the internal database, which first needs to be [Wallet.sync] manually.
|
||||
fn list_transactions(&self) -> Result<Vec<TransactionDetails>, Error> {
|
||||
fn list_transactions(&self) -> Result<Vec<TransactionDetails>, BdkError> {
|
||||
let transaction_details = self.get_wallet().list_transactions(true)?;
|
||||
Ok(transaction_details
|
||||
.iter()
|
||||
@@ -451,7 +475,7 @@ impl Wallet {
|
||||
|
||||
/// Return the list of unspent outputs of this wallet. Note that this method only operates on the internal database,
|
||||
/// which first needs to be Wallet.sync manually.
|
||||
fn list_unspent(&self) -> Result<Vec<LocalUtxo>, Error> {
|
||||
fn list_unspent(&self) -> Result<Vec<LocalUtxo>, BdkError> {
|
||||
let unspents = self.get_wallet().list_unspent()?;
|
||||
Ok(unspents
|
||||
.iter()
|
||||
@@ -460,24 +484,63 @@ impl Wallet {
|
||||
}
|
||||
}
|
||||
|
||||
fn to_script_pubkey(address: &str) -> Result<Script, BdkError> {
|
||||
Address::from_str(address)
|
||||
fn to_script_pubkey(address: &str) -> Result<BdkScript, BdkError> {
|
||||
BdkAddress::from_str(address)
|
||||
.map(|x| x.script_pubkey())
|
||||
.map_err(|e| BdkError::Generic(e.to_string()))
|
||||
}
|
||||
|
||||
/// A Bitcoin address.
|
||||
struct Address {
|
||||
address: BdkAddress,
|
||||
}
|
||||
|
||||
impl Address {
|
||||
fn new(address: String) -> Result<Self, BdkError> {
|
||||
BdkAddress::from_str(address.as_str())
|
||||
.map(|a| Address { address: a })
|
||||
.map_err(|e| BdkError::Generic(e.to_string()))
|
||||
}
|
||||
|
||||
fn script_pubkey(&self) -> Arc<Script> {
|
||||
Arc::new(Script {
|
||||
script: self.address.script_pubkey(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// A Bitcoin script.
|
||||
#[derive(Clone)]
|
||||
pub struct Script {
|
||||
script: BdkScript,
|
||||
}
|
||||
|
||||
impl Script {
|
||||
fn new(raw_output_script: Vec<u8>) -> Self {
|
||||
let script: BdkScript = BdkScript::from(raw_output_script);
|
||||
Script { script }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
enum RbfValue {
|
||||
Default,
|
||||
Value(u32),
|
||||
}
|
||||
|
||||
/// The result after calling the TxBuilder finish() function. Contains unsigned PSBT and
|
||||
/// transaction details.
|
||||
pub struct TxBuilderResult {
|
||||
pub psbt: Arc<PartiallySignedBitcoinTransaction>,
|
||||
pub transaction_details: TransactionDetails,
|
||||
}
|
||||
|
||||
/// A transaction builder.
|
||||
/// After creating the TxBuilder, you set options on it until finally calling finish to consume the builder and generate the transaction.
|
||||
/// Each method on the TxBuilder returns an instance of a new TxBuilder with the option set/added.
|
||||
#[derive(Clone, Debug)]
|
||||
struct TxBuilder {
|
||||
recipients: Vec<(String, u64)>,
|
||||
recipients: Vec<(BdkScript, u64)>,
|
||||
utxos: Vec<OutPoint>,
|
||||
unspendable: HashSet<OutPoint>,
|
||||
change_policy: ChangeSpendPolicy,
|
||||
@@ -508,19 +571,19 @@ impl TxBuilder {
|
||||
}
|
||||
|
||||
/// Add a recipient to the internal list.
|
||||
fn add_recipient(&self, recipient: String, amount: u64) -> Arc<Self> {
|
||||
let mut recipients = self.recipients.to_vec();
|
||||
recipients.append(&mut vec![(recipient, amount)]);
|
||||
fn add_recipient(&self, script: Arc<Script>, amount: u64) -> Arc<Self> {
|
||||
let mut recipients: Vec<(BdkScript, u64)> = self.recipients.clone();
|
||||
recipients.append(&mut vec![(script.script.clone(), amount)]);
|
||||
Arc::new(TxBuilder {
|
||||
recipients,
|
||||
..self.clone()
|
||||
})
|
||||
}
|
||||
|
||||
fn set_recipients(&self, recipients: Vec<AddressAmount>) -> Arc<Self> {
|
||||
fn set_recipients(&self, recipients: Vec<ScriptAmount>) -> Arc<Self> {
|
||||
let recipients = recipients
|
||||
.iter()
|
||||
.map(|address_amount| (address_amount.address.clone(), address_amount.amount))
|
||||
.map(|script_amount| (script_amount.script.script.clone(), script_amount.amount))
|
||||
.collect();
|
||||
Arc::new(TxBuilder {
|
||||
recipients,
|
||||
@@ -657,11 +720,11 @@ impl TxBuilder {
|
||||
}
|
||||
|
||||
/// Finish building the transaction. Returns the BIP174 PSBT.
|
||||
fn finish(&self, wallet: &Wallet) -> Result<Arc<PartiallySignedBitcoinTransaction>, Error> {
|
||||
fn finish(&self, wallet: &Wallet) -> Result<TxBuilderResult, BdkError> {
|
||||
let wallet = wallet.get_wallet();
|
||||
let mut tx_builder = wallet.build_tx();
|
||||
for (address, amount) in &self.recipients {
|
||||
tx_builder.add_recipient(to_script_pubkey(address)?, *amount);
|
||||
for (script, amount) in &self.recipients {
|
||||
tx_builder.add_recipient(script.clone(), *amount);
|
||||
}
|
||||
tx_builder.change_policy(self.change_policy);
|
||||
if !self.utxos.is_empty() {
|
||||
@@ -705,10 +768,12 @@ impl TxBuilder {
|
||||
|
||||
tx_builder
|
||||
.finish()
|
||||
.map(|(psbt, _)| PartiallySignedBitcoinTransaction {
|
||||
internal: Mutex::new(psbt),
|
||||
.map(|(psbt, tx_details)| TxBuilderResult {
|
||||
psbt: Arc::new(PartiallySignedBitcoinTransaction {
|
||||
internal: Mutex::new(psbt),
|
||||
}),
|
||||
transaction_details: TransactionDetails::from(&tx_details),
|
||||
})
|
||||
.map(Arc::new)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -762,14 +827,14 @@ impl BumpFeeTxBuilder {
|
||||
}
|
||||
|
||||
/// Finish building the transaction. Returns the BIP174 PSBT.
|
||||
fn finish(&self, wallet: &Wallet) -> Result<Arc<PartiallySignedBitcoinTransaction>, Error> {
|
||||
fn finish(&self, wallet: &Wallet) -> Result<Arc<PartiallySignedBitcoinTransaction>, BdkError> {
|
||||
let wallet = wallet.get_wallet();
|
||||
let txid = Txid::from_str(self.txid.as_str())?;
|
||||
let mut tx_builder = wallet.build_fee_bump(txid)?;
|
||||
tx_builder.fee_rate(FeeRate::from_sat_per_vb(self.fee_rate));
|
||||
if let Some(allow_shrinking) = &self.allow_shrinking {
|
||||
let address =
|
||||
Address::from_str(allow_shrinking).map_err(|e| Error::Generic(e.to_string()))?;
|
||||
let address = BdkAddress::from_str(allow_shrinking)
|
||||
.map_err(|e| BdkError::Generic(e.to_string()))?;
|
||||
let script = address.script_pubkey();
|
||||
tx_builder.allow_shrinking(script)?;
|
||||
}
|
||||
@@ -894,6 +959,21 @@ impl DescriptorSecretKey {
|
||||
})
|
||||
}
|
||||
|
||||
/// Get the private key as bytes.
|
||||
fn secret_bytes(&self) -> Vec<u8> {
|
||||
let descriptor_secret_key = self.descriptor_secret_key_mutex.lock().unwrap();
|
||||
let secret_bytes: Vec<u8> = match descriptor_secret_key.deref() {
|
||||
BdkDescriptorSecretKey::XPrv(descriptor_x_key) => {
|
||||
descriptor_x_key.xkey.private_key.secret_bytes().to_vec()
|
||||
}
|
||||
BdkDescriptorSecretKey::SinglePriv(_) => {
|
||||
unreachable!()
|
||||
}
|
||||
};
|
||||
|
||||
secret_bytes
|
||||
}
|
||||
|
||||
fn as_string(&self) -> String {
|
||||
self.descriptor_secret_key_mutex.lock().unwrap().to_string()
|
||||
}
|
||||
@@ -984,12 +1064,12 @@ mod test {
|
||||
let tx_builder = TxBuilder::new()
|
||||
.drain_wallet()
|
||||
.drain_to(drain_to_address.clone());
|
||||
//dbg!(&tx_builder);
|
||||
assert!(tx_builder.drain_wallet);
|
||||
assert_eq!(tx_builder.drain_to, Some(drain_to_address));
|
||||
|
||||
let psbt = tx_builder.finish(&test_wallet).unwrap();
|
||||
let psbt = psbt.internal.lock().unwrap().clone();
|
||||
let tx_builder_result = tx_builder.finish(&test_wallet).unwrap();
|
||||
let psbt = tx_builder_result.psbt.internal.lock().unwrap().clone();
|
||||
let tx_details = tx_builder_result.transaction_details;
|
||||
|
||||
// confirm one input with 50,000 sats
|
||||
assert_eq!(psbt.inputs.len(), 1);
|
||||
@@ -1025,12 +1105,22 @@ mod test {
|
||||
);
|
||||
let output_value = psbt.unsigned_tx.output.get(0).cloned().unwrap().value;
|
||||
assert_eq!(output_value, 49_890_u64); // input - fee
|
||||
|
||||
assert_eq!(
|
||||
tx_details.txid,
|
||||
"312f1733badab22dc26b8dcbc83ba5629fb7b493af802e8abe07d865e49629c5"
|
||||
);
|
||||
assert_eq!(tx_details.received, 0);
|
||||
assert_eq!(tx_details.sent, 50000);
|
||||
assert!(tx_details.fee.is_some());
|
||||
assert_eq!(tx_details.fee.unwrap(), 110);
|
||||
assert!(tx_details.confirmation_time.is_none());
|
||||
}
|
||||
|
||||
fn get_descriptor_secret_key() -> DescriptorSecretKey {
|
||||
let mnemonic =
|
||||
"chaos fabric time speed sponsor all flat solution wisdom trophy crack object robot pave observe combine where aware bench orient secret primary cable detect".to_string();
|
||||
DescriptorSecretKey::new(Network::Testnet, mnemonic, None).unwrap()
|
||||
DescriptorSecretKey::new(Testnet, mnemonic, None).unwrap()
|
||||
}
|
||||
|
||||
fn derive_dsk(
|
||||
@@ -1102,7 +1192,6 @@ mod test {
|
||||
#[test]
|
||||
fn test_derive_and_extend_descriptor_secret_key() {
|
||||
let master_dsk = get_descriptor_secret_key();
|
||||
|
||||
// derive DescriptorSecretKey with path "m/0" from master
|
||||
let derived_dsk: &DescriptorSecretKey = &derive_dsk(&master_dsk, "m/0").unwrap();
|
||||
assert_eq!(derived_dsk.as_string(), "[d1d04177/0]tprv8d7Y4JLmD25jkKbyDZXcdoPHu1YtMHuH21qeN7mFpjfumtSU7eZimFYUCSa3MYzkEYfSNRBV34GEr2QXwZCMYRZ7M1g6PUtiLhbJhBZEGYJ/*");
|
||||
@@ -1118,4 +1207,14 @@ mod test {
|
||||
let derived_dpk = &derive_dpk(&master_dpk, "m/84h/1h/0h");
|
||||
assert!(derived_dpk.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_retrieve_master_secret_key() {
|
||||
let master_dpk = get_descriptor_secret_key();
|
||||
let master_private_key = master_dpk.secret_bytes().to_hex();
|
||||
assert_eq!(
|
||||
master_private_key,
|
||||
"e93315d6ce401eb4db803a56232f0ed3e69b053774e6047df54f1bd00e5ea936"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user