Compare commits
30 Commits
release/0.
...
v0.11.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0648075555 | ||
|
|
3f81346e6b | ||
|
|
a25fb1348d | ||
|
|
ae1ea99ed3 | ||
|
|
9a381f6d32 | ||
|
|
3a07b4838a | ||
|
|
29de6f2d06 | ||
|
|
73ba73fd03 | ||
|
|
9866649fdc | ||
|
|
c2aecb0597 | ||
|
|
ba71a7a27c | ||
|
|
036e790a75 | ||
|
|
ee6ee8139a | ||
|
|
3bec5d2cab | ||
|
|
b9aa0a2cf1 | ||
|
|
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
|
<!-- 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 -->
|
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
|
### Checklists
|
||||||
|
|
||||||
#### All Submissions:
|
#### 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 tests for the new feature
|
||||||
* [ ] I've added docs for the new feature
|
* [ ] I've added docs for the new feature
|
||||||
* [ ] I've updated `CHANGELOG.md`
|
|
||||||
|
|
||||||
#### Bugfixes:
|
#### Bugfixes:
|
||||||
|
|
||||||
|
|||||||
2
.github/workflows/cont_integration.yml
vendored
2
.github/workflows/cont_integration.yml
vendored
@@ -41,7 +41,7 @@ jobs:
|
|||||||
if: ${{ matrix.rust.clippy }}
|
if: ${{ matrix.rust.clippy }}
|
||||||
run: cargo clippy --all-targets -- -D warnings
|
run: cargo clippy --all-targets -- -D warnings
|
||||||
- name: Test
|
- name: Test
|
||||||
run: cargo test
|
run: CLASSPATH=./tests/jna/jna-5.8.0.jar cargo test
|
||||||
|
|
||||||
fmt:
|
fmt:
|
||||||
name: Rust fmt
|
name: Rust fmt
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
# Changelog
|
# 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/),
|
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).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|||||||
20
Cargo.toml
20
Cargo.toml
@@ -1,23 +1,31 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "bdk-ffi"
|
name = "bdk-ffi"
|
||||||
version = "0.9.0"
|
version = "0.11.0"
|
||||||
authors = ["Steve Myers <steve@notmandatory.org>", "Sudarsan Balaji <sudarsan.balaji@artfuldev.com>"]
|
authors = ["Steve Myers <steve@notmandatory.org>", "Sudarsan Balaji <sudarsan.balaji@artfuldev.com>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
license = "MIT OR Apache-2.0"
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
members = [".","bdk-ffi-bindgen"]
|
members = [".","bdk-ffi-bindgen"]
|
||||||
default-members = [".", "bdk-ffi-bindgen"]
|
default-members = [".", "bdk-ffi-bindgen"]
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
|
||||||
[lib]
|
[lib]
|
||||||
crate-type = ["staticlib", "cdylib"]
|
crate-type = ["staticlib", "cdylib"]
|
||||||
name = "bdkffi"
|
name = "bdkffi"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
bdk = { version = "0.22", features = ["all-keys", "use-esplora-ureq", "sqlite-bundled"] }
|
bdk = { version = "0.24", features = ["all-keys", "use-esplora-ureq", "sqlite-bundled"] }
|
||||||
|
|
||||||
uniffi_macros = { version = "0.19.3", features = ["builtin-bindgen"] }
|
uniffi_macros = { version = "0.21.0", features = ["builtin-bindgen"] }
|
||||||
uniffi = { version = "0.19.3", features = ["builtin-bindgen"] }
|
uniffi = { version = "0.21.0", features = ["builtin-bindgen"] }
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
uniffi_build = { version = "0.19.3", features = ["builtin-bindgen"] }
|
uniffi_build = { version = "0.21.0", features = ["builtin-bindgen"] }
|
||||||
|
|
||||||
|
[profile.release-smaller]
|
||||||
|
inherits = "release"
|
||||||
|
opt-level = 'z' # Optimize for size.
|
||||||
|
lto = true # Enable Link Time Optimization
|
||||||
|
codegen-units = 1 # Reduce number of codegen units to increase optimizations.
|
||||||
|
panic = 'abort' # Abort on panic
|
||||||
|
strip = true # Strip symbols from binary*
|
||||||
|
|||||||
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"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[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"
|
structopt = "0.3"
|
||||||
uniffi_bindgen = "0.19.5"
|
uniffi_bindgen = "0.21.0"
|
||||||
camino = "1.0.9"
|
camino = "1.0.9"
|
||||||
|
|||||||
72
src/bdk.udl
72
src/bdk.udl
@@ -1,6 +1,5 @@
|
|||||||
namespace bdk {
|
namespace bdk {
|
||||||
[Throws=BdkError]
|
|
||||||
string generate_mnemonic(WordCount word_count);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
[Error]
|
[Error]
|
||||||
@@ -33,9 +32,9 @@ enum BdkError {
|
|||||||
"ProgressUpdateError",
|
"ProgressUpdateError",
|
||||||
"InvalidOutpoint",
|
"InvalidOutpoint",
|
||||||
"Descriptor",
|
"Descriptor",
|
||||||
"AddressValidator",
|
|
||||||
"Encode",
|
"Encode",
|
||||||
"Miniscript",
|
"Miniscript",
|
||||||
|
"MiniscriptPsbt",
|
||||||
"Bip32",
|
"Bip32",
|
||||||
"Secp256k1",
|
"Secp256k1",
|
||||||
"Json",
|
"Json",
|
||||||
@@ -138,7 +137,7 @@ interface Blockchain {
|
|||||||
constructor(BlockchainConfig config);
|
constructor(BlockchainConfig config);
|
||||||
|
|
||||||
[Throws=BdkError]
|
[Throws=BdkError]
|
||||||
void broadcast([ByRef] PartiallySignedBitcoinTransaction psbt);
|
void broadcast([ByRef] PartiallySignedTransaction psbt);
|
||||||
|
|
||||||
[Throws=BdkError]
|
[Throws=BdkError]
|
||||||
u32 get_height();
|
u32 get_height();
|
||||||
@@ -173,8 +172,8 @@ dictionary LocalUtxo {
|
|||||||
boolean is_spent;
|
boolean is_spent;
|
||||||
};
|
};
|
||||||
|
|
||||||
dictionary AddressAmount {
|
dictionary ScriptAmount {
|
||||||
string address;
|
Script script;
|
||||||
u64 amount;
|
u64 amount;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -189,7 +188,7 @@ interface Wallet {
|
|||||||
Balance get_balance();
|
Balance get_balance();
|
||||||
|
|
||||||
[Throws=BdkError]
|
[Throws=BdkError]
|
||||||
boolean sign([ByRef] PartiallySignedBitcoinTransaction psbt);
|
boolean sign([ByRef] PartiallySignedTransaction psbt);
|
||||||
|
|
||||||
[Throws=BdkError]
|
[Throws=BdkError]
|
||||||
sequence<TransactionDetails> list_transactions();
|
sequence<TransactionDetails> list_transactions();
|
||||||
@@ -203,19 +202,40 @@ interface Wallet {
|
|||||||
sequence<LocalUtxo> list_unspent();
|
sequence<LocalUtxo> list_unspent();
|
||||||
};
|
};
|
||||||
|
|
||||||
interface PartiallySignedBitcoinTransaction {
|
interface FeeRate {
|
||||||
|
[Name=from_sat_per_vb]
|
||||||
|
constructor(float sat_per_vb);
|
||||||
|
|
||||||
|
float as_sat_per_vb();
|
||||||
|
};
|
||||||
|
|
||||||
|
interface PartiallySignedTransaction {
|
||||||
[Throws=BdkError]
|
[Throws=BdkError]
|
||||||
constructor(string psbt_base64);
|
constructor(string psbt_base64);
|
||||||
|
|
||||||
string serialize();
|
string serialize();
|
||||||
|
|
||||||
string txid();
|
string txid();
|
||||||
|
|
||||||
|
sequence<u8> extract_tx();
|
||||||
|
|
||||||
|
[Throws=BdkError]
|
||||||
|
PartiallySignedTransaction combine(PartiallySignedTransaction other);
|
||||||
|
|
||||||
|
u64? fee_amount();
|
||||||
|
|
||||||
|
FeeRate? fee_rate();
|
||||||
|
};
|
||||||
|
|
||||||
|
dictionary TxBuilderResult {
|
||||||
|
PartiallySignedTransaction psbt;
|
||||||
|
TransactionDetails transaction_details;
|
||||||
};
|
};
|
||||||
|
|
||||||
interface TxBuilder {
|
interface TxBuilder {
|
||||||
constructor();
|
constructor();
|
||||||
|
|
||||||
TxBuilder add_recipient(string address, u64 amount);
|
TxBuilder add_recipient(Script script, u64 amount);
|
||||||
|
|
||||||
TxBuilder add_unspendable(OutPoint unspendable);
|
TxBuilder add_unspendable(OutPoint unspendable);
|
||||||
|
|
||||||
@@ -245,10 +265,10 @@ interface TxBuilder {
|
|||||||
|
|
||||||
TxBuilder add_data(sequence<u8> data);
|
TxBuilder add_data(sequence<u8> data);
|
||||||
|
|
||||||
TxBuilder set_recipients(sequence<AddressAmount> recipients);
|
TxBuilder set_recipients(sequence<ScriptAmount> recipients);
|
||||||
|
|
||||||
[Throws=BdkError]
|
[Throws=BdkError]
|
||||||
PartiallySignedBitcoinTransaction finish([ByRef] Wallet wallet);
|
TxBuilderResult finish([ByRef] Wallet wallet);
|
||||||
};
|
};
|
||||||
|
|
||||||
interface BumpFeeTxBuilder {
|
interface BumpFeeTxBuilder {
|
||||||
@@ -261,7 +281,19 @@ interface BumpFeeTxBuilder {
|
|||||||
BumpFeeTxBuilder enable_rbf_with_sequence(u32 nsequence);
|
BumpFeeTxBuilder enable_rbf_with_sequence(u32 nsequence);
|
||||||
|
|
||||||
[Throws=BdkError]
|
[Throws=BdkError]
|
||||||
PartiallySignedBitcoinTransaction finish([ByRef] Wallet wallet);
|
PartiallySignedTransaction finish([ByRef] Wallet wallet);
|
||||||
|
};
|
||||||
|
|
||||||
|
interface Mnemonic {
|
||||||
|
constructor(WordCount word_count);
|
||||||
|
|
||||||
|
[Name=from_string, Throws=BdkError]
|
||||||
|
constructor(string mnemonic);
|
||||||
|
|
||||||
|
[Name=from_entropy, Throws=BdkError]
|
||||||
|
constructor(sequence<u8> entropy);
|
||||||
|
|
||||||
|
string as_string();
|
||||||
};
|
};
|
||||||
|
|
||||||
interface DerivationPath {
|
interface DerivationPath {
|
||||||
@@ -270,8 +302,7 @@ interface DerivationPath {
|
|||||||
};
|
};
|
||||||
|
|
||||||
interface DescriptorSecretKey {
|
interface DescriptorSecretKey {
|
||||||
[Throws=BdkError]
|
constructor(Network network, Mnemonic mnemonic, string? password);
|
||||||
constructor(Network network, string mnemonic, string? password);
|
|
||||||
|
|
||||||
[Throws=BdkError]
|
[Throws=BdkError]
|
||||||
DescriptorSecretKey derive(DerivationPath path);
|
DescriptorSecretKey derive(DerivationPath path);
|
||||||
@@ -280,6 +311,8 @@ interface DescriptorSecretKey {
|
|||||||
|
|
||||||
DescriptorPublicKey as_public();
|
DescriptorPublicKey as_public();
|
||||||
|
|
||||||
|
sequence<u8> secret_bytes();
|
||||||
|
|
||||||
string as_string();
|
string as_string();
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -291,3 +324,14 @@ interface DescriptorPublicKey {
|
|||||||
|
|
||||||
string as_string();
|
string as_string();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
interface Address {
|
||||||
|
[Throws=BdkError]
|
||||||
|
constructor(string address);
|
||||||
|
|
||||||
|
Script script_pubkey();
|
||||||
|
};
|
||||||
|
|
||||||
|
interface Script {
|
||||||
|
constructor(sequence<u8> raw_output_script);
|
||||||
|
};
|
||||||
|
|||||||
312
src/lib.rs
312
src/lib.rs
@@ -1,8 +1,11 @@
|
|||||||
|
use bdk::bitcoin::blockdata::script::Script as BdkScript;
|
||||||
use bdk::bitcoin::hashes::hex::ToHex;
|
use bdk::bitcoin::hashes::hex::ToHex;
|
||||||
use bdk::bitcoin::secp256k1::Secp256k1;
|
use bdk::bitcoin::secp256k1::Secp256k1;
|
||||||
use bdk::bitcoin::util::bip32::DerivationPath as BdkDerivationPath;
|
use bdk::bitcoin::util::bip32::DerivationPath as BdkDerivationPath;
|
||||||
use bdk::bitcoin::util::psbt::PartiallySignedTransaction;
|
use bdk::bitcoin::util::psbt::serialize::Serialize;
|
||||||
use bdk::bitcoin::{Address, Network, OutPoint as BdkOutPoint, Script, Txid};
|
use bdk::bitcoin::util::psbt::PartiallySignedTransaction as BdkPartiallySignedTransaction;
|
||||||
|
use bdk::bitcoin::Sequence;
|
||||||
|
use bdk::bitcoin::{Address as BdkAddress, Network, OutPoint as BdkOutPoint, Txid};
|
||||||
use bdk::blockchain::any::{AnyBlockchain, AnyBlockchainConfig};
|
use bdk::blockchain::any::{AnyBlockchain, AnyBlockchainConfig};
|
||||||
use bdk::blockchain::GetBlockHash;
|
use bdk::blockchain::GetBlockHash;
|
||||||
use bdk::blockchain::GetHeight;
|
use bdk::blockchain::GetHeight;
|
||||||
@@ -13,17 +16,18 @@ use bdk::blockchain::{Blockchain as BdkBlockchain, Progress as BdkProgress};
|
|||||||
use bdk::database::any::{AnyDatabase, SledDbConfiguration, SqliteDbConfiguration};
|
use bdk::database::any::{AnyDatabase, SledDbConfiguration, SqliteDbConfiguration};
|
||||||
use bdk::database::{AnyDatabaseConfig, ConfigurableDatabase};
|
use bdk::database::{AnyDatabaseConfig, ConfigurableDatabase};
|
||||||
use bdk::descriptor::DescriptorXKey;
|
use bdk::descriptor::DescriptorXKey;
|
||||||
use bdk::keys::bip39::{Language, Mnemonic, WordCount};
|
use bdk::keys::bip39::{Language, Mnemonic as BdkMnemonic, WordCount};
|
||||||
use bdk::keys::{
|
use bdk::keys::{
|
||||||
DerivableKey, DescriptorPublicKey as BdkDescriptorPublicKey,
|
DerivableKey, DescriptorPublicKey as BdkDescriptorPublicKey,
|
||||||
DescriptorSecretKey as BdkDescriptorSecretKey, ExtendedKey, GeneratableKey, GeneratedKey,
|
DescriptorSecretKey as BdkDescriptorSecretKey, ExtendedKey, GeneratableKey, GeneratedKey,
|
||||||
};
|
};
|
||||||
use bdk::miniscript::BareCtx;
|
use bdk::miniscript::BareCtx;
|
||||||
|
use bdk::psbt::PsbtUtils;
|
||||||
use bdk::wallet::tx_builder::ChangeSpendPolicy;
|
use bdk::wallet::tx_builder::ChangeSpendPolicy;
|
||||||
use bdk::wallet::AddressIndex as BdkAddressIndex;
|
use bdk::wallet::AddressIndex as BdkAddressIndex;
|
||||||
use bdk::wallet::AddressInfo as BdkAddressInfo;
|
use bdk::wallet::AddressInfo as BdkAddressInfo;
|
||||||
use bdk::{
|
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,
|
SyncOptions as BdkSyncOptions, Wallet as BdkWallet,
|
||||||
};
|
};
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
@@ -35,10 +39,9 @@ use std::sync::{Arc, Mutex, MutexGuard};
|
|||||||
|
|
||||||
uniffi_macros::include_scaffolding!("bdk");
|
uniffi_macros::include_scaffolding!("bdk");
|
||||||
|
|
||||||
type BdkError = Error;
|
/// A output script and an amount of satoshis.
|
||||||
|
pub struct ScriptAmount {
|
||||||
pub struct AddressAmount {
|
pub script: Arc<Script>,
|
||||||
pub address: String,
|
|
||||||
pub amount: u64,
|
pub amount: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -208,16 +211,16 @@ impl Blockchain {
|
|||||||
self.blockchain_mutex.lock().expect("blockchain")
|
self.blockchain_mutex.lock().expect("blockchain")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn broadcast(&self, psbt: &PartiallySignedBitcoinTransaction) -> Result<(), Error> {
|
fn broadcast(&self, psbt: &PartiallySignedTransaction) -> Result<(), BdkError> {
|
||||||
let tx = psbt.internal.lock().unwrap().clone().extract_tx();
|
let tx = psbt.internal.lock().unwrap().clone().extract_tx();
|
||||||
self.get_blockchain().broadcast(&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()
|
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()
|
self.get_blockchain()
|
||||||
.get_block_hash(u64::from(height))
|
.get_block_hash(u64::from(height))
|
||||||
.map(|hash| hash.to_string())
|
.map(|hash| hash.to_string())
|
||||||
@@ -304,12 +307,9 @@ impl NetworkLocalUtxo for LocalUtxo {
|
|||||||
},
|
},
|
||||||
txout: TxOut {
|
txout: TxOut {
|
||||||
value: x.txout.value,
|
value: x.txout.value,
|
||||||
address: bdk::bitcoin::util::address::Address::from_script(
|
address: BdkAddress::from_script(&x.txout.script_pubkey, network)
|
||||||
&x.txout.script_pubkey,
|
.unwrap()
|
||||||
network,
|
.to_string(),
|
||||||
)
|
|
||||||
.unwrap()
|
|
||||||
.to_string(),
|
|
||||||
},
|
},
|
||||||
keychain: x.keychain,
|
keychain: x.keychain,
|
||||||
is_spent: x.is_spent,
|
is_spent: x.is_spent,
|
||||||
@@ -329,7 +329,7 @@ struct ProgressHolder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl BdkProgress for 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);
|
self.progress.update(progress, message);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -342,14 +342,15 @@ impl fmt::Debug for ProgressHolder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct PartiallySignedBitcoinTransaction {
|
pub struct PartiallySignedTransaction {
|
||||||
internal: Mutex<PartiallySignedTransaction>,
|
internal: Mutex<BdkPartiallySignedTransaction>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PartiallySignedBitcoinTransaction {
|
impl PartiallySignedTransaction {
|
||||||
fn new(psbt_base64: String) -> Result<Self, Error> {
|
fn new(psbt_base64: String) -> Result<Self, BdkError> {
|
||||||
let psbt: PartiallySignedTransaction = PartiallySignedTransaction::from_str(&psbt_base64)?;
|
let psbt: BdkPartiallySignedTransaction =
|
||||||
Ok(PartiallySignedBitcoinTransaction {
|
BdkPartiallySignedTransaction::from_str(&psbt_base64)?;
|
||||||
|
Ok(PartiallySignedTransaction {
|
||||||
internal: Mutex::new(psbt),
|
internal: Mutex::new(psbt),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -364,6 +365,46 @@ impl PartiallySignedBitcoinTransaction {
|
|||||||
let txid = tx.txid();
|
let txid = tx.txid();
|
||||||
txid.to_hex()
|
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<PartiallySignedTransaction>,
|
||||||
|
) -> Result<Arc<PartiallySignedTransaction>, 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(PartiallySignedTransaction {
|
||||||
|
internal: Mutex::new(original_psbt),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The total transaction fee amount, sum of input amounts minus sum of output amounts, in Sats.
|
||||||
|
/// If the PSBT is missing a TxOut for an input returns None.
|
||||||
|
fn fee_amount(&self) -> Option<u64> {
|
||||||
|
self.internal.lock().unwrap().fee_amount()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The transaction's fee rate. This value will only be accurate if calculated AFTER the
|
||||||
|
/// `PartiallySignedTransaction` is finalized and all witness/signature data is added to the
|
||||||
|
/// transaction.
|
||||||
|
/// If the PSBT is missing a TxOut for an input returns None.
|
||||||
|
fn fee_rate(&self) -> Option<Arc<FeeRate>> {
|
||||||
|
self.internal.lock().unwrap().fee_rate().map(Arc::new)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A Bitcoin wallet.
|
/// A Bitcoin wallet.
|
||||||
@@ -430,18 +471,18 @@ impl Wallet {
|
|||||||
|
|
||||||
/// Return the balance, meaning the sum of this wallet’s unspent outputs’ values. Note that this method only operates
|
/// 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.
|
/// 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())
|
self.get_wallet().get_balance().map(|b| b.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sign a transaction with all the wallet’s signers.
|
/// Sign a transaction with all the wallet’s signers.
|
||||||
fn sign(&self, psbt: &PartiallySignedBitcoinTransaction) -> Result<bool, Error> {
|
fn sign(&self, psbt: &PartiallySignedTransaction) -> Result<bool, BdkError> {
|
||||||
let mut psbt = psbt.internal.lock().unwrap();
|
let mut psbt = psbt.internal.lock().unwrap();
|
||||||
self.get_wallet().sign(&mut psbt, SignOptions::default())
|
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.
|
/// 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)?;
|
let transaction_details = self.get_wallet().list_transactions(true)?;
|
||||||
Ok(transaction_details
|
Ok(transaction_details
|
||||||
.iter()
|
.iter()
|
||||||
@@ -451,7 +492,7 @@ impl Wallet {
|
|||||||
|
|
||||||
/// Return the list of unspent outputs of this wallet. Note that this method only operates on the internal database,
|
/// 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.
|
/// 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()?;
|
let unspents = self.get_wallet().list_unspent()?;
|
||||||
Ok(unspents
|
Ok(unspents
|
||||||
.iter()
|
.iter()
|
||||||
@@ -460,24 +501,63 @@ impl Wallet {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn to_script_pubkey(address: &str) -> Result<Script, BdkError> {
|
fn to_script_pubkey(address: &str) -> Result<BdkScript, BdkError> {
|
||||||
Address::from_str(address)
|
BdkAddress::from_str(address)
|
||||||
.map(|x| x.script_pubkey())
|
.map(|x| x.script_pubkey())
|
||||||
.map_err(|e| BdkError::Generic(e.to_string()))
|
.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)]
|
#[derive(Clone, Debug)]
|
||||||
enum RbfValue {
|
enum RbfValue {
|
||||||
Default,
|
Default,
|
||||||
Value(u32),
|
Value(u32),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The result after calling the TxBuilder finish() function. Contains unsigned PSBT and
|
||||||
|
/// transaction details.
|
||||||
|
pub struct TxBuilderResult {
|
||||||
|
pub psbt: Arc<PartiallySignedTransaction>,
|
||||||
|
pub transaction_details: TransactionDetails,
|
||||||
|
}
|
||||||
|
|
||||||
/// A transaction builder.
|
/// A transaction builder.
|
||||||
/// After creating the TxBuilder, you set options on it until finally calling finish to consume the builder and generate the transaction.
|
/// 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.
|
/// Each method on the TxBuilder returns an instance of a new TxBuilder with the option set/added.
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
struct TxBuilder {
|
struct TxBuilder {
|
||||||
recipients: Vec<(String, u64)>,
|
recipients: Vec<(BdkScript, u64)>,
|
||||||
utxos: Vec<OutPoint>,
|
utxos: Vec<OutPoint>,
|
||||||
unspendable: HashSet<OutPoint>,
|
unspendable: HashSet<OutPoint>,
|
||||||
change_policy: ChangeSpendPolicy,
|
change_policy: ChangeSpendPolicy,
|
||||||
@@ -508,19 +588,19 @@ impl TxBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Add a recipient to the internal list.
|
/// Add a recipient to the internal list.
|
||||||
fn add_recipient(&self, recipient: String, amount: u64) -> Arc<Self> {
|
fn add_recipient(&self, script: Arc<Script>, amount: u64) -> Arc<Self> {
|
||||||
let mut recipients = self.recipients.to_vec();
|
let mut recipients: Vec<(BdkScript, u64)> = self.recipients.clone();
|
||||||
recipients.append(&mut vec![(recipient, amount)]);
|
recipients.append(&mut vec![(script.script.clone(), amount)]);
|
||||||
Arc::new(TxBuilder {
|
Arc::new(TxBuilder {
|
||||||
recipients,
|
recipients,
|
||||||
..self.clone()
|
..self.clone()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_recipients(&self, recipients: Vec<AddressAmount>) -> Arc<Self> {
|
fn set_recipients(&self, recipients: Vec<ScriptAmount>) -> Arc<Self> {
|
||||||
let recipients = recipients
|
let recipients = recipients
|
||||||
.iter()
|
.iter()
|
||||||
.map(|address_amount| (address_amount.address.clone(), address_amount.amount))
|
.map(|script_amount| (script_amount.script.script.clone(), script_amount.amount))
|
||||||
.collect();
|
.collect();
|
||||||
Arc::new(TxBuilder {
|
Arc::new(TxBuilder {
|
||||||
recipients,
|
recipients,
|
||||||
@@ -657,11 +737,11 @@ impl TxBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Finish building the transaction. Returns the BIP174 PSBT.
|
/// 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 wallet = wallet.get_wallet();
|
||||||
let mut tx_builder = wallet.build_tx();
|
let mut tx_builder = wallet.build_tx();
|
||||||
for (address, amount) in &self.recipients {
|
for (script, amount) in &self.recipients {
|
||||||
tx_builder.add_recipient(to_script_pubkey(address)?, *amount);
|
tx_builder.add_recipient(script.clone(), *amount);
|
||||||
}
|
}
|
||||||
tx_builder.change_policy(self.change_policy);
|
tx_builder.change_policy(self.change_policy);
|
||||||
if !self.utxos.is_empty() {
|
if !self.utxos.is_empty() {
|
||||||
@@ -695,7 +775,7 @@ impl TxBuilder {
|
|||||||
tx_builder.enable_rbf();
|
tx_builder.enable_rbf();
|
||||||
}
|
}
|
||||||
RbfValue::Value(nsequence) => {
|
RbfValue::Value(nsequence) => {
|
||||||
tx_builder.enable_rbf_with_sequence(nsequence);
|
tx_builder.enable_rbf_with_sequence(Sequence(nsequence));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -705,10 +785,12 @@ impl TxBuilder {
|
|||||||
|
|
||||||
tx_builder
|
tx_builder
|
||||||
.finish()
|
.finish()
|
||||||
.map(|(psbt, _)| PartiallySignedBitcoinTransaction {
|
.map(|(psbt, tx_details)| TxBuilderResult {
|
||||||
internal: Mutex::new(psbt),
|
psbt: Arc::new(PartiallySignedTransaction {
|
||||||
|
internal: Mutex::new(psbt),
|
||||||
|
}),
|
||||||
|
transaction_details: TransactionDetails::from(&tx_details),
|
||||||
})
|
})
|
||||||
.map(Arc::new)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -762,14 +844,14 @@ impl BumpFeeTxBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Finish building the transaction. Returns the BIP174 PSBT.
|
/// Finish building the transaction. Returns the BIP174 PSBT.
|
||||||
fn finish(&self, wallet: &Wallet) -> Result<Arc<PartiallySignedBitcoinTransaction>, Error> {
|
fn finish(&self, wallet: &Wallet) -> Result<Arc<PartiallySignedTransaction>, BdkError> {
|
||||||
let wallet = wallet.get_wallet();
|
let wallet = wallet.get_wallet();
|
||||||
let txid = Txid::from_str(self.txid.as_str())?;
|
let txid = Txid::from_str(self.txid.as_str())?;
|
||||||
let mut tx_builder = wallet.build_fee_bump(txid)?;
|
let mut tx_builder = wallet.build_fee_bump(txid)?;
|
||||||
tx_builder.fee_rate(FeeRate::from_sat_per_vb(self.fee_rate));
|
tx_builder.fee_rate(FeeRate::from_sat_per_vb(self.fee_rate));
|
||||||
if let Some(allow_shrinking) = &self.allow_shrinking {
|
if let Some(allow_shrinking) = &self.allow_shrinking {
|
||||||
let address =
|
let address = BdkAddress::from_str(allow_shrinking)
|
||||||
Address::from_str(allow_shrinking).map_err(|e| Error::Generic(e.to_string()))?;
|
.map_err(|e| BdkError::Generic(e.to_string()))?;
|
||||||
let script = address.script_pubkey();
|
let script = address.script_pubkey();
|
||||||
tx_builder.allow_shrinking(script)?;
|
tx_builder.allow_shrinking(script)?;
|
||||||
}
|
}
|
||||||
@@ -779,23 +861,53 @@ impl BumpFeeTxBuilder {
|
|||||||
tx_builder.enable_rbf();
|
tx_builder.enable_rbf();
|
||||||
}
|
}
|
||||||
RbfValue::Value(nsequence) => {
|
RbfValue::Value(nsequence) => {
|
||||||
tx_builder.enable_rbf_with_sequence(nsequence);
|
tx_builder.enable_rbf_with_sequence(Sequence(nsequence));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
tx_builder
|
tx_builder
|
||||||
.finish()
|
.finish()
|
||||||
.map(|(psbt, _)| PartiallySignedBitcoinTransaction {
|
.map(|(psbt, _)| PartiallySignedTransaction {
|
||||||
internal: Mutex::new(psbt),
|
internal: Mutex::new(psbt),
|
||||||
})
|
})
|
||||||
.map(Arc::new)
|
.map(Arc::new)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn generate_mnemonic(word_count: WordCount) -> Result<String, BdkError> {
|
/// Mnemonic phrases are a human-readable version of the private keys.
|
||||||
let mnemonic: GeneratedKey<_, BareCtx> =
|
/// Supported number of words are 12, 15, 18, 21 and 24.
|
||||||
Mnemonic::generate((word_count, Language::English)).unwrap();
|
struct Mnemonic {
|
||||||
Ok(mnemonic.to_string())
|
internal: BdkMnemonic,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Mnemonic {
|
||||||
|
/// Generates Mnemonic with a random entropy
|
||||||
|
fn new(word_count: WordCount) -> Self {
|
||||||
|
let generated_key: GeneratedKey<_, BareCtx> =
|
||||||
|
BdkMnemonic::generate((word_count, Language::English)).unwrap();
|
||||||
|
let mnemonic = BdkMnemonic::parse_in(Language::English, generated_key.to_string()).unwrap();
|
||||||
|
Mnemonic { internal: mnemonic }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parse a Mnemonic with given string
|
||||||
|
fn from_string(mnemonic: String) -> Result<Self, BdkError> {
|
||||||
|
BdkMnemonic::from_str(&mnemonic)
|
||||||
|
.map(|m| Mnemonic { internal: m })
|
||||||
|
.map_err(|e| BdkError::Generic(e.to_string()))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new Mnemonic in the specified language from the given entropy.
|
||||||
|
/// Entropy must be a multiple of 32 bits (4 bytes) and 128-256 bits in length.
|
||||||
|
fn from_entropy(entropy: Vec<u8>) -> Result<Self, BdkError> {
|
||||||
|
BdkMnemonic::from_entropy(entropy.as_slice())
|
||||||
|
.map(|m| Mnemonic { internal: m })
|
||||||
|
.map_err(|e| BdkError::Generic(e.to_string()))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns Mnemonic as string
|
||||||
|
fn as_string(&self) -> String {
|
||||||
|
self.internal.to_string()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct DerivationPath {
|
struct DerivationPath {
|
||||||
@@ -817,19 +929,18 @@ struct DescriptorSecretKey {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl DescriptorSecretKey {
|
impl DescriptorSecretKey {
|
||||||
fn new(network: Network, mnemonic: String, password: Option<String>) -> Result<Self, BdkError> {
|
fn new(network: Network, mnemonic: Arc<Mnemonic>, password: Option<String>) -> Self {
|
||||||
let mnemonic = Mnemonic::parse_in(Language::English, mnemonic)
|
let mnemonic = mnemonic.internal.clone();
|
||||||
.map_err(|e| BdkError::Generic(e.to_string()))?;
|
let xkey: ExtendedKey = (mnemonic, password).into_extended_key().unwrap();
|
||||||
let xkey: ExtendedKey = (mnemonic, password).into_extended_key()?;
|
|
||||||
let descriptor_secret_key = BdkDescriptorSecretKey::XPrv(DescriptorXKey {
|
let descriptor_secret_key = BdkDescriptorSecretKey::XPrv(DescriptorXKey {
|
||||||
origin: None,
|
origin: None,
|
||||||
xkey: xkey.into_xprv(network).unwrap(),
|
xkey: xkey.into_xprv(network).unwrap(),
|
||||||
derivation_path: BdkDerivationPath::master(),
|
derivation_path: BdkDerivationPath::master(),
|
||||||
wildcard: bdk::descriptor::Wildcard::Unhardened,
|
wildcard: bdk::descriptor::Wildcard::Unhardened,
|
||||||
});
|
});
|
||||||
Ok(Self {
|
Self {
|
||||||
descriptor_secret_key_mutex: Mutex::new(descriptor_secret_key),
|
descriptor_secret_key_mutex: Mutex::new(descriptor_secret_key),
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn derive(&self, path: Arc<DerivationPath>) -> Result<Arc<Self>, BdkError> {
|
fn derive(&self, path: Arc<DerivationPath>) -> Result<Arc<Self>, BdkError> {
|
||||||
@@ -853,7 +964,7 @@ impl DescriptorSecretKey {
|
|||||||
descriptor_secret_key_mutex: Mutex::new(derived_descriptor_secret_key),
|
descriptor_secret_key_mutex: Mutex::new(derived_descriptor_secret_key),
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
BdkDescriptorSecretKey::SinglePriv(_) => {
|
BdkDescriptorSecretKey::Single(_) => {
|
||||||
unreachable!()
|
unreachable!()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -875,7 +986,7 @@ impl DescriptorSecretKey {
|
|||||||
descriptor_secret_key_mutex: Mutex::new(extended_descriptor_secret_key),
|
descriptor_secret_key_mutex: Mutex::new(extended_descriptor_secret_key),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
BdkDescriptorSecretKey::SinglePriv(_) => {
|
BdkDescriptorSecretKey::Single(_) => {
|
||||||
unreachable!()
|
unreachable!()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -887,13 +998,28 @@ impl DescriptorSecretKey {
|
|||||||
.descriptor_secret_key_mutex
|
.descriptor_secret_key_mutex
|
||||||
.lock()
|
.lock()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.as_public(&secp)
|
.to_public(&secp)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
Arc::new(DescriptorPublicKey {
|
Arc::new(DescriptorPublicKey {
|
||||||
descriptor_public_key_mutex: Mutex::new(descriptor_public_key),
|
descriptor_public_key_mutex: Mutex::new(descriptor_public_key),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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::Single(_) => {
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
secret_bytes
|
||||||
|
}
|
||||||
|
|
||||||
fn as_string(&self) -> String {
|
fn as_string(&self) -> String {
|
||||||
self.descriptor_secret_key_mutex.lock().unwrap().to_string()
|
self.descriptor_secret_key_mutex.lock().unwrap().to_string()
|
||||||
}
|
}
|
||||||
@@ -926,7 +1052,7 @@ impl DescriptorPublicKey {
|
|||||||
descriptor_public_key_mutex: Mutex::new(derived_descriptor_public_key),
|
descriptor_public_key_mutex: Mutex::new(derived_descriptor_public_key),
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
BdkDescriptorPublicKey::SinglePub(_) => {
|
BdkDescriptorPublicKey::Single(_) => {
|
||||||
unreachable!()
|
unreachable!()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -948,7 +1074,7 @@ impl DescriptorPublicKey {
|
|||||||
descriptor_public_key_mutex: Mutex::new(extended_descriptor_public_key),
|
descriptor_public_key_mutex: Mutex::new(extended_descriptor_public_key),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
BdkDescriptorPublicKey::SinglePub(_) => {
|
BdkDescriptorPublicKey::Single(_) => {
|
||||||
unreachable!()
|
unreachable!()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -984,12 +1110,12 @@ mod test {
|
|||||||
let tx_builder = TxBuilder::new()
|
let tx_builder = TxBuilder::new()
|
||||||
.drain_wallet()
|
.drain_wallet()
|
||||||
.drain_to(drain_to_address.clone());
|
.drain_to(drain_to_address.clone());
|
||||||
//dbg!(&tx_builder);
|
|
||||||
assert!(tx_builder.drain_wallet);
|
assert!(tx_builder.drain_wallet);
|
||||||
assert_eq!(tx_builder.drain_to, Some(drain_to_address));
|
assert_eq!(tx_builder.drain_to, Some(drain_to_address));
|
||||||
|
|
||||||
let psbt = tx_builder.finish(&test_wallet).unwrap();
|
let tx_builder_result = tx_builder.finish(&test_wallet).unwrap();
|
||||||
let psbt = psbt.internal.lock().unwrap().clone();
|
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
|
// confirm one input with 50,000 sats
|
||||||
assert_eq!(psbt.inputs.len(), 1);
|
assert_eq!(psbt.inputs.len(), 1);
|
||||||
@@ -1025,12 +1151,21 @@ mod test {
|
|||||||
);
|
);
|
||||||
let output_value = psbt.unsigned_tx.output.get(0).cloned().unwrap().value;
|
let output_value = psbt.unsigned_tx.output.get(0).cloned().unwrap().value;
|
||||||
assert_eq!(output_value, 49_890_u64); // input - fee
|
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 {
|
fn get_descriptor_secret_key() -> DescriptorSecretKey {
|
||||||
let mnemonic =
|
let mnemonic = Mnemonic::from_string("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()).unwrap();
|
||||||
"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(Testnet, Arc::new(mnemonic), None)
|
||||||
DescriptorSecretKey::new(Network::Testnet, mnemonic, None).unwrap()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn derive_dsk(
|
fn derive_dsk(
|
||||||
@@ -1102,7 +1237,6 @@ mod test {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_derive_and_extend_descriptor_secret_key() {
|
fn test_derive_and_extend_descriptor_secret_key() {
|
||||||
let master_dsk = get_descriptor_secret_key();
|
let master_dsk = get_descriptor_secret_key();
|
||||||
|
|
||||||
// derive DescriptorSecretKey with path "m/0" from master
|
// derive DescriptorSecretKey with path "m/0" from master
|
||||||
let derived_dsk: &DescriptorSecretKey = &derive_dsk(&master_dsk, "m/0").unwrap();
|
let derived_dsk: &DescriptorSecretKey = &derive_dsk(&master_dsk, "m/0").unwrap();
|
||||||
assert_eq!(derived_dsk.as_string(), "[d1d04177/0]tprv8d7Y4JLmD25jkKbyDZXcdoPHu1YtMHuH21qeN7mFpjfumtSU7eZimFYUCSa3MYzkEYfSNRBV34GEr2QXwZCMYRZ7M1g6PUtiLhbJhBZEGYJ/*");
|
assert_eq!(derived_dsk.as_string(), "[d1d04177/0]tprv8d7Y4JLmD25jkKbyDZXcdoPHu1YtMHuH21qeN7mFpjfumtSU7eZimFYUCSa3MYzkEYfSNRBV34GEr2QXwZCMYRZ7M1g6PUtiLhbJhBZEGYJ/*");
|
||||||
@@ -1118,4 +1252,42 @@ mod test {
|
|||||||
let derived_dpk = &derive_dpk(&master_dpk, "m/84h/1h/0h");
|
let derived_dpk = &derive_dpk(&master_dpk, "m/84h/1h/0h");
|
||||||
assert!(derived_dpk.is_err());
|
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"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_psbt_fee() {
|
||||||
|
let test_wpkh = "wpkh(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW)";
|
||||||
|
let (funded_wallet, _, _) = get_funded_wallet(test_wpkh);
|
||||||
|
let test_wallet = Wallet {
|
||||||
|
wallet_mutex: Mutex::new(funded_wallet),
|
||||||
|
};
|
||||||
|
let drain_to_address = "tb1ql7w62elx9ucw4pj5lgw4l028hmuw80sndtntxt".to_string();
|
||||||
|
let tx_builder = TxBuilder::new()
|
||||||
|
.fee_rate(2.0)
|
||||||
|
.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 tx_builder_result = tx_builder.finish(&test_wallet).unwrap();
|
||||||
|
|
||||||
|
assert!(tx_builder_result.psbt.fee_rate().is_some());
|
||||||
|
assert_eq!(
|
||||||
|
tx_builder_result.psbt.fee_rate().unwrap().as_sat_per_vb(),
|
||||||
|
2.682927
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(tx_builder_result.psbt.fee_amount().is_some());
|
||||||
|
assert_eq!(tx_builder_result.psbt.fee_amount().unwrap(), 220);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
21
tests/README.md
Normal file
21
tests/README.md
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
# Integration tests for bdk-ffi
|
||||||
|
|
||||||
|
This contains simple tests to make sure bdk-ffi can be used as a dependency for each of the
|
||||||
|
supported bindings languages.
|
||||||
|
|
||||||
|
To skip integration tests and only run unit tests use `cargo test --lib`.
|
||||||
|
|
||||||
|
To run all tests including integration tests use `CLASSPATH=./tests/jna/jna-5.8.0.jar cargo test`.
|
||||||
|
|
||||||
|
Before running integration tests you must install the following development tools:
|
||||||
|
|
||||||
|
1. [Java](https://openjdk.org/) and [Kotlin](https://kotlinlang.org/),
|
||||||
|
[sdkman](https://sdkman.io/) can help:
|
||||||
|
```shell
|
||||||
|
sdk install java 11.0.16.1-zulu
|
||||||
|
sdk install kotlin 1.7.20`
|
||||||
|
```
|
||||||
|
|
||||||
|
2. [Swift](https://www.swift.org/)
|
||||||
|
|
||||||
|
3. [Python](https://www.python.org/)
|
||||||
8
tests/bindings/test.kts
Normal file
8
tests/bindings/test.kts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
/*
|
||||||
|
* This is a basic test kotlin program that does nothing but confirm that the kotlin bindings compile
|
||||||
|
* and that a program that depends on them will run.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import org.bitcoindevkit.*
|
||||||
|
|
||||||
|
val network = Network.TESTNET
|
||||||
15
tests/bindings/test.py
Normal file
15
tests/bindings/test.py
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import unittest
|
||||||
|
from bdk import *
|
||||||
|
|
||||||
|
class TestBdk(unittest.TestCase):
|
||||||
|
|
||||||
|
def test_some_enum(self):
|
||||||
|
network = Network.TESTNET
|
||||||
|
|
||||||
|
def test_some_dict(self):
|
||||||
|
a = AddressInfo(index=42, address="testaddress")
|
||||||
|
self.assertEqual(42, a.index)
|
||||||
|
self.assertEqual("testaddress", a.address)
|
||||||
|
|
||||||
|
if __name__=='__main__':
|
||||||
|
unittest.main()
|
||||||
9
tests/bindings/test.swift
Normal file
9
tests/bindings/test.swift
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
/*
|
||||||
|
* This is a basic test swift program that does nothing but confirm that the swift bindings compile
|
||||||
|
* and that a program that depends on them will run.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import bdk
|
||||||
|
|
||||||
|
let network = Network.testnet
|
||||||
BIN
tests/jna/jna-5.8.0.jar
Normal file
BIN
tests/jna/jna-5.8.0.jar
Normal file
Binary file not shown.
8
tests/test_generated_bindings.rs
Normal file
8
tests/test_generated_bindings.rs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
uniffi_macros::build_foreign_language_testcases!(
|
||||||
|
["src/bdk.udl",],
|
||||||
|
[
|
||||||
|
"tests/bindings/test.kts",
|
||||||
|
"tests/bindings/test.swift",
|
||||||
|
"tests/bindings/test.py"
|
||||||
|
]
|
||||||
|
);
|
||||||
Reference in New Issue
Block a user