Compare commits

...

16 Commits

Author SHA1 Message Date
Steve Myers
e6cf423721 Update uniffi dependencies to 0.21.0 2022-10-21 13:29:18 -05:00
thunderbiscuit
c7d0803000 Fix use of Error as name for error enum in UDL 2022-10-21 09:30:28 -04:00
Steve Myers
1a4b9b440d Bump version to 0.10.0 2022-10-19 12:47:16 -05:00
Steve Myers
1f914c2b4d Merge bitcoindevkit/bdk-ffi#209: Change TxBuilder.finish() to return new TxBuilderResult
fadb316451 Change TxBuilder.finish() to return new TxBuilderResult (Steve Myers)

Pull request description:

  ### Description

  Change TxBuilder.finish() to return new TxBuilderResult.

  ### Notes to the reviewers

  This fixes #179 in that it return both PartiallySignedBitcoinTransaction and TransactionDetails encapsulated in a new TxBuilderResult structure. It does not calculate the fee rate which requires #208.

  ### Changelog notice

  - Breaking Changes
    - Changed `TxBuilder.finish()` to return new `TxBuilderResult`.
  - APIs Added
    - Added `TxBuilderResult` with PSBT and TransactionDetails.

  ### Checklists

  #### All Submissions:

  * [x] I've signed all my commits
  * [x] I followed the [contribution guidelines](https://github.com/bitcoindevkit/bdk/blob/master/CONTRIBUTING.md)
  * [x] I ran `cargo fmt` and `cargo clippy` before committing

  #### New Features:

  * [x] I've added tests for the new feature
  * [x] I've added docs for the new feature

ACKs for top commit:
  thunderbiscuit:
    ACK [fadb316](fadb316451).

Tree-SHA512: b8aafc53ba86bf7ab12ae3d5bb251d1ec5cb54e4b7d6bfc342155d5cd3726d9a6d114591dc740a7467afb8ef402f3d77ad51e273a27865d6e47192291b570af9
2022-10-19 12:30:06 -05:00
Steve Myers
fadb316451 Change TxBuilder.finish() to return new TxBuilderResult 2022-10-19 12:27:00 -05:00
thunderbiscuit
3fefd3c1fb Allow TxBuilder.add_recipient() to take Script 2022-10-17 17:42:36 -04:00
thunderbiscuit
2cbb314d0b Remove BdkError type alias 2022-10-17 16:43:37 -04:00
thunderbiscuit
535fc70433 Upgrade bdk dependency to v0.23 2022-10-17 16:19:35 -04:00
thunderbiscuit
10fa276bec Update uniffi-rs to 0.20.0 2022-10-17 16:03:27 -04:00
thunderbiscuit
1cc9afaeb3 Rename DescriptorSecretKey.secret_bytes() to mirror upstream API 2022-10-03 14:54:01 -04:00
thunderbiscuit
6f5e621561 Remove changelog entry to follow workflow defined in #202 2022-10-03 14:54:00 -04:00
thunderbiscuit
1dd6f2d9f8 Add test for DescriptorSecretKey.secret_key_bytes() 2022-10-03 14:53:58 -04:00
thunderbiscuit
f92b45db6a Add ability to retrieve private keys as bytes
This feature is needed for compatibility with LDKLite, where the
initial entropy given to LDK is the private key of the root of
the BIP32 derivation tree.

Closes #188
2022-10-03 14:53:25 -04:00
thunderbiscuit
c971d54aea Fix warnings 2022-09-30 19:37:19 -04:00
thunderbiscuit
2abccafb8f Add combine() method on PSBT
Closes #198
2022-09-30 19:37:12 -04:00
Steve Myers
75d0415bec Add release templates and dev cycle docs, update changelog process 2022-09-29 11:18:27 -05:00
9 changed files with 392 additions and 58 deletions

101
.github/ISSUE_TEMPLATE/minor_release.md vendored Normal file
View 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
View 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/

View File

@@ -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:

View File

@@ -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).

View File

@@ -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
View 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

View File

@@ -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"

View File

@@ -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);
};

View File

@@ -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 wallets 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 wallets 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"
)
}
}