Compare commits

..

4 Commits

Author SHA1 Message Date
thunderbiscuit
634d7db36e
Bump bdkpython library version to 0.26.2
This plus the fix to the CI release workflow will allow releasing
libraries for macOS and Windows on PyPI
2023-01-25 09:02:39 -05:00
thunderbiscuit
2233517495
Fix Python CI release workflow 2023-01-25 09:01:04 -05:00
thunderbiscuit
49716471d4
Bump bdkpython library version to 0.26.1
This plus the fix to the CI release workflow will allow releasing
libraries for macOS and Windows on PyPI
2023-01-24 13:27:21 -05:00
thunderbiscuit
ced11cb1ee
Update libraries to official release versions 2023-01-11 15:33:02 -05:00
144 changed files with 5888 additions and 9038 deletions

View File

@ -1,17 +0,0 @@
---
name: Enhancement request
about: Request a new feature or change to an existing feature
title: ''
labels: 'enhancement'
assignees: ''
---
**Describe the enhancement**
<!-- A clear and concise description of what you would like added or changed. -->
**Use case**
<!-- Tell us how you or others will use this new feature or change to an existing feature. -->
**Additional context**
<!-- Add any other context about the enhancement here. -->

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

@ -1,95 +0,0 @@
---
name: Release
about: Create a new release [for release managers only]
title: 'Release MAJOR.MINOR.PATCH'
labels: 'release'
assignees: ''
---
# Part 1: Bump BDK Rust Version
1. - [ ] Open a PR with an update to `Cargo.toml` to the new bdk release candidate and ensure all CI workflows run correctly. Fix errors if necessary.
2. - [ ] Once the new bdk release is out, update the PR to replace the release candidate with the full release and merge.
# Part 2: Prepare Libraries for Release Branch
### _Android_
3. - [ ] Update the API docs to reflect the changes in the API
4. - [ ] Delete the `target` directory in bdk-ffi and all `build` directories (in root, `lib`, and `plugins`) in the bdk-android directory to make sure you're building the library from scratch.
5. - [ ] Build the library and run the offline and live tests, and adjust them if necessary (note that you'll need an Android emulator running).
```shell
# start an emulator prior to running the tests
cd ./bdk-android/
just clean
just build
just test
```
6. - [ ] Update the readme if necessary
### _JVM_
7. - [ ] Update the API docs to reflect the changes in the API
8. - [ ] Delete the `target` directory in bdk-ffi and all `build` directories (in root, `lib`, and `plugins`) in bdk-jvm directory to make sure you're building the library from scratch.
9. - [ ] Build the library and run the tests, and adjust if necessary
```shell
cd ./bdk-jvm/
just clean
just build
just test
```
10. - [ ] Update the readme if necessary
### _Swift_
11. - [ ] Delete the `target` directory in bdk-ffi
12. - [ ] Run the tests and adjust if necessary
```shell
cd ./bdk-swift/
just clean
just build
just test
```
13. - [ ] Update the readme if necessary
### _Python_
14. - [ ] Delete the `dist`, `build`, and `bdkpython.egg-info` and rust `target` directories to make sure you are building the library from scratch without any caches
15. - [ ] Build the library
```shell
cd ./bdk-python/
just clean
pip3 install --requirement requirements.txt
bash ./scripts/generate-macos-arm64.sh # run the script for your particular platform
python3 setup.py --verbose bdist_wheel
```
16. - [ ] Run the tests and adjust if necessary
```shell
pip3 install ./dist/bdkpython-<yourversion>-py3-none-any.whl --force-reinstall
python -m unittest --verbose
```
17. - [ ] Update the readme and `setup.py` if necessary
18. - [ ] Update the Android, JVM, Python, and Swift libraries as per the _Specific Libraries' Workflows_ section above. Open a single PR on master for all of these changes called `Prepare language bindings libraries for 0.X release`. See [example PR here](https://github.com/bitcoindevkit/bdk-ffi/pull/315).
## Part 3: Release Workflow
19. - [ ] Create a new branch off of `master` called `release/<feature version>`, e.g. `release/0.31`
20. - [ ] Update bdk-android version from `SNAPSHOT` version to release version
21. - [ ] Update bdk-jvm version from `SNAPSHOT` version to release version
22. - [ ] Update bdk-python version from `.dev` version to release version
23. - [ ] Open a PR to that release branch that updates the Android, JVM, and Python libraries' versions in the three steps above. See [example PR here](https://github.com/bitcoindevkit/bdk-ffi/pull/316).
24. - [ ] Get a review and ACK and merge the PR updating all the languages to their release versions
25. - [ ] Create the tag for the release and make sure to add the changelog info to the tag (works better if you prepare the tag message on the side in a text editor). Push the tag to GitHub.
```shell
git tag v0.6.0 --sign --edit
git push upstream v0.6.0
```
26. - [ ] Trigger manual releases for all 4 libraries (for Swift, go on the [bdk-swift](https://github.com/bitcoindevkit/bdk-swift) trigger the release on `master` and simply add the version number and tag name in the text fields when running the workflow manually. Note that the version number must not contain the `v`, i.e. `0.26.0`, but the tag will have it, i.e. `v0.26.0`).
27. - [ ] Make sure the released libraries work and contain the artifacts you would expect
28. - [ ] Aggregate all the changelog notices from the PRs and add them to the changelog file
29. - [ ] Bump the versions on master from `0.9.0-SNAPSHOT` to `0.10.0-SNAPSHOT`, `0.6.0.dev0` to `0.7.0.dev0`
30. - [ ] Apply changes to the minor_release and patch_release issue templates if they need any
31. - [ ] Open a PR on master with the changes in steps 29, 30, and 31. See [example PR here](https://github.com/bitcoindevkit/bdk-ffi/pull/317). Get a review and merge the PR.
32. - [ ] Make release on GitHub (set as pre-release and generate auto release notes between the previous tag and the new one)
33. - [ ] Post in the announcement channel
34. - [ ] Tweet about the library

View File

@ -10,16 +10,10 @@ on:
jobs:
security_audit:
name: Security audit
name: Security Audit
runs-on: ubuntu-20.04
defaults:
run:
working-directory: bdk-ffi
steps:
- name: "Check out PR branch"
uses: actions/checkout@v3
- name: "Run audit"
run: |
cargo install cargo-audit
cargo-audit audit
- uses: actions/checkout@v2
- uses: actions-rs/audit-check@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}

View File

@ -0,0 +1,126 @@
name: Build Python wheels
on:
push:
paths:
- "bdk-ffi/**"
- "bdk-python/**"
pull_request:
paths:
- "bdk-ffi/**"
- "bdk-python/**"
# We use manylinux2014 because older CentOS versions used by 2010 and 1 have a very old glibc version, which
# makes it very hard to use GitHub's javascript actions (checkout, upload-artifact, etc).
# They mount their own nodejs interpreter inside your container, but since that's not statically linked it
# tries to load glibc and fails because it requires a more recent version.
jobs:
build-manylinux2014-x86_64-wheel:
name: 'Build Manylinux 2014 x86_64 wheel'
runs-on: ubuntu-latest
defaults:
run:
working-directory: bdk-python
container:
image: quay.io/pypa/manylinux2014_x86_64
env:
PLAT: manylinux2014_x86_64
PYBIN: '/opt/python/${{ matrix.python }}/bin'
strategy:
matrix:
python: # Update this list whenever the docker image is updated (check /opt/python/)
# - cp36-cp36m
# - cp37-cp37m
# - cp38-cp38
# - cp39-cp39
- cp310-cp310
# - pp37-pypy37_pp73
# - pp38-pypy38_pp73
steps:
- name: checkout
uses: actions/checkout@v2
with:
submodules: true
- uses: actions-rs/toolchain@v1
with:
toolchain: stable
- name: install requirements
run: ${PYBIN}/pip install -r requirements.txt
- name: generate bindings
run: bash generate.sh
- name: build wheel
run: ${PYBIN}/pip wheel . --no-deps -w /tmp/wheelhouse
- name: repair wheel
run: auditwheel repair /tmp/wheelhouse/* --plat "$PLAT" -w /tmp/wheelhouse-repaired
- uses: actions/upload-artifact@v2
with:
name: bdkpython-manylinux2014-x86_64-${{ matrix.python }}
path: /tmp/wheelhouse-repaired/*.whl
build-macos-universal-wheel:
name: 'Build macOS universal wheel'
runs-on: macos-latest
defaults:
run:
working-directory: bdk-python
strategy:
matrix:
python:
# - '3.7'
# - '3.8'
# - '3.9'
- '3.10'
steps:
- name: checkout
uses: actions/checkout@v2
with:
submodules: true
- uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python }}
- run: python3 --version
- run: rustup target add aarch64-apple-darwin
- run: pip3 install --user -r requirements.txt
- run: pip3 install --user wheel
- run: bash generate.sh
- name: build wheel
env:
ARCHFLAGS: "-arch x86_64 -arch arm64"
run: python3 setup.py -v bdist_wheel
- uses: actions/upload-artifact@v2
with:
name: bdkpython-macos-${{ matrix.python }}
path: /Users/runner/work/bdk-ffi/bdk-ffi/bdk-python/dist/*.whl
build-windows-wheel:
name: 'Build windows wheel'
runs-on: windows-latest
defaults:
run:
working-directory: bdk-python
strategy:
matrix:
python:
# - '3.7'
# - '3.8'
# - '3.9'
- '3.10'
steps:
- name: checkout
uses: actions/checkout@v2
with:
submodules: true
- uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python }}
- run: python --version
- run: pip install --user -r requirements.txt
- run: bash generate.sh
shell: bash
- run: pip install --user wheel
- name: build wheel
run: python setup.py -v bdist_wheel
- uses: actions/upload-artifact@v2
with:
name: bdkpython-win-${{ matrix.python }}
path: D:\a\bdk-ffi\bdk-ffi\bdk-python\dist\*.whl

View File

@ -1,4 +1,4 @@
name: Rust layer CI
name: bdk-ffi CI
on:
push:
paths:
@ -8,77 +8,59 @@ on:
- "bdk-ffi/**"
jobs:
build-test:
name: "Build and test"
runs-on: ubuntu-20.04
defaults:
run:
working-directory: bdk-ffi
name: Build and test
runs-on: ubuntu-latest
strategy:
matrix:
rust:
- version: 1.77.1
- version: 1.63.0 # STABLE
clippy: true
- version: 1.61.0 # MSRV
steps:
- name: "Checkout"
uses: actions/checkout@v3
- name: "Generate cache key"
- name: checkout
uses: actions/checkout@v2
- name: Generate cache key
run: echo "${{ matrix.rust.version }} ${{ matrix.features }}" | tee .cache_key
- name: "Cache"
uses: actions/cache@v3
- name: cache
uses: actions/cache@v2
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: ${{ runner.os }}-cargo-${{ hashFiles('.cache_key') }}-${{ hashFiles('**/Cargo.toml','**/Cargo.lock') }}
- name: "Set default toolchain"
- name: Set default toolchain
run: rustup default ${{ matrix.rust.version }}
- name: "Set profile"
- name: Set profile
run: rustup set profile minimal
- name: "Add clippy"
- name: Add clippy
if: ${{ matrix.rust.clippy }}
run: rustup component add clippy
- name: "Update toolchain"
- name: Update toolchain
run: rustup update
- name: "Build"
- name: Build
run: cargo build
- name: "Clippy"
- name: Clippy
if: ${{ matrix.rust.clippy }}
run: cargo clippy --all-targets --features "uniffi/bindgen-tests" -- -D warnings
- name: "Test"
run: CLASSPATH=./tests/jna/jna-5.14.0.jar cargo test --features uniffi/bindgen-tests
run: cargo clippy --all-targets -- -D warnings
- name: Test
run: CLASSPATH=./tests/jna/jna-5.8.0.jar cargo test
fmt:
name: "Rust fmt"
runs-on: ubuntu-20.04
defaults:
run:
working-directory: bdk-ffi
name: Rust fmt
runs-on: ubuntu-latest
steps:
- name: "Checkout"
uses: actions/checkout@v3
- name: "Set default toolchain"
- name: Checkout
uses: actions/checkout@v2
- name: Set default toolchain
run: rustup default nightly
- name: "Set profile"
- name: Set profile
run: rustup set profile minimal
- name: "Add rustfmt"
- name: Add rustfmt
run: rustup component add rustfmt
- name: "Update toolchain"
- name: Update toolchain
run: rustup update
- name: "Check fmt"
- name: Check fmt
run: cargo fmt --all -- --config format_code_in_doc_comments=true --check

View File

@ -1,96 +0,0 @@
name: Run All Live Tests
on:
workflow_dispatch:
schedule:
- cron: '0 0 * * 0' # Once per week
jobs:
jvm-tests:
name: "Build and test JVM library on Linux"
runs-on: ubuntu-20.04
steps:
- name: "Checkout publishing branch"
uses: actions/checkout@v3
- name: "Cache"
uses: actions/cache@v3
with:
path: |
~/.cargo/registry
~/.cargo/git
./target
key: ${{ runner.os }}-${{ hashFiles('**/Cargo.toml','**/Cargo.lock') }}
- name: "Set up JDK"
uses: actions/setup-java@v3
with:
distribution: temurin
java-version: 11
- name: "Set default Rust version to 1.77.1"
run: rustup default 1.77.1
- name: "Build bdk-jvm library"
run: |
cd bdk-jvm
./gradlew buildJvmLib
- name: "Run live JVM tests"
run: |
cd bdk-jvm
./gradlew test
swift-tests:
name: "Build and test iOS library on macOS"
runs-on: macos-12
steps:
- name: "Checkout"
uses: actions/checkout@v3
- name: "Build Swift package"
working-directory: bdk-swift
run: bash ./build-xcframework.sh
- name: "Run live Swift tests"
working-directory: bdk-swift
run: swift test
python-tests:
name: "Build and test Python library on Linux"
runs-on: ubuntu-20.04
defaults:
run:
working-directory: bdk-python
container:
image: quay.io/pypa/manylinux2014_x86_64
env:
PLAT: manylinux2014_x86_64
PYBIN: "/opt/python/${{ matrix.python }}/bin"
strategy:
matrix:
python:
- cp310-cp310
steps:
- name: "Checkout"
uses: actions/checkout@v3
with:
submodules: true
- name: "Install Rust 1.77.1"
uses: actions-rs/toolchain@v1
with:
toolchain: 1.77.1
- name: "Generate bdk.py and binaries"
run: bash ./scripts/generate-linux.sh
- name: "Build wheel"
# Specifying the plat-name argument is necessary to build a wheel with the correct name,
# see issue #350 for more information
run: ${PYBIN}/python setup.py bdist_wheel --plat-name manylinux_2_17_x86_64 --verbose
- name: "Install wheel"
run: ${PYBIN}/pip install ./dist/*.whl
- name: "Run live Python tests"
run: ${PYBIN}/python -m unittest --verbose

View File

@ -1,17 +1,27 @@
name: Publish bdk-android to Maven Central
on: [workflow_dispatch]
# The default Android NDK on the ubuntu-22.04 image is 25.2.9519653
env:
ANDROID_NDK_ROOT: /usr/local/lib/android/sdk/ndk/21.4.7075529
# By default, the new ubuntu-20.04 images use the following ANDROID_NDK_ROOT
# ANDROID_NDK_ROOT: /usr/local/lib/android/sdk/ndk/25.0.8775105
jobs:
build:
runs-on: ubuntu-20.04
steps:
- name: "Check out PR branch"
uses: actions/checkout@v3
- name: Install Android NDK 21.4.7075529
run: |
ANDROID_ROOT=/usr/local/lib/android
ANDROID_SDK_ROOT=${ANDROID_ROOT}/sdk
SDKMANAGER=${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin/sdkmanager
echo "y" | $SDKMANAGER "ndk;21.4.7075529"
- name: "Cache"
uses: actions/cache@v3
- name: Check out PR branch
uses: actions/checkout@v2
- name: cache
uses: actions/cache@v2
with:
path: |
~/.cargo/registry
@ -19,24 +29,21 @@ jobs:
./target
key: ${{ runner.os }}-${{ hashFiles('**/Cargo.toml','**/Cargo.lock') }}
- name: "Set up JDK"
uses: actions/setup-java@v3
- name: Set up JDK
uses: actions/setup-java@v2
with:
distribution: temurin
java-version: 17
java-version: 11
- name: "Set default Rust version to 1.77.1"
run: rustup default 1.77.1
- name: "Install Rust Android targets"
- name: Install rust android targets
run: rustup target add x86_64-linux-android aarch64-linux-android armv7-linux-androideabi
- name: "Build bdk-android library"
- name: Build bdk-android library
run: |
cd bdk-android
./gradlew buildAndroidLib
- name: "Publish to Maven Central"
- name: Publish to Maven Local and Maven Central
env:
ORG_GRADLE_PROJECT_signingKeyId: ${{ secrets.PGP_KEY_ID }}
ORG_GRADLE_PROJECT_signingKey: ${{ secrets.PGP_SECRET_KEY }}

View File

@ -2,14 +2,14 @@ name: Publish bdk-jvm to Maven Central
on: [workflow_dispatch]
jobs:
build-macOS-native-libs:
name: "Create M1 and x86_64 native binaries"
build-jvm-macOS-M1-native-lib:
name: Create M1 and x86_64 JVM native binaries
runs-on: macos-12
steps:
- name: "Checkout publishing branch"
uses: actions/checkout@v3
- name: Checkout publishing branch
uses: actions/checkout@v2
- name: "Cache"
- name: Cache
uses: actions/cache@v3
with:
path: |
@ -18,68 +18,42 @@ jobs:
./target
key: ${{ runner.os }}-${{ hashFiles('**/Cargo.toml','**/Cargo.lock') }}
- name: "Set up JDK"
uses: actions/setup-java@v3
- name: Set up JDK
uses: actions/setup-java@v2
with:
distribution: temurin
java-version: 17
java-version: 11
- name: "Set default Rust version to 1.77.1"
run: rustup default 1.77.1
- name: "Install aarch64 Rust target"
- name: Install aarch64 Rust target
run: rustup target add aarch64-apple-darwin
- name: "Build bdk-jvm library"
- name: Build bdk-jvm library
run: |
cd bdk-jvm
./gradlew buildJvmLib
- name: "Upload macOS native libraries for reuse in publishing job"
# build aarch64 + x86_64 native libraries and upload
- name: Upload macOS native libraries for reuse in publishing job
uses: actions/upload-artifact@v3
with:
name: artifact-macos
# name: no name is required because we upload the entire directory
# the default name "artifact" will be used
path: /Users/runner/work/bdk-ffi/bdk-ffi/bdk-jvm/lib/src/main/resources/
build-windows-native-lib:
name: "Create Windows native binaries"
runs-on: windows-2022
steps:
- name: "Checkout publishing branch"
uses: actions/checkout@v3
- name: "Set up JDK"
uses: actions/setup-java@v3
with:
distribution: temurin
java-version: 17
- name: "Set default Rust version to 1.77.1"
run: rustup default 1.77.1
- name: "Install x86_64-pc-windows-msvc Rust target"
run: rustup target add x86_64-pc-windows-msvc
- name: "Build bdk-jvm library"
run: |
cd bdk-jvm
./gradlew buildJvmLib
- name: "Upload Windows native libraries for reuse in publishing job"
uses: actions/upload-artifact@v3
with:
name: artifact-windows
path: D:\a\bdk-ffi\bdk-ffi\bdk-jvm\lib\src\main\resources\
build-full-library:
build-jvm-full-library:
name: Create full bdk-jvm library
needs: [build-macOS-native-libs, build-windows-native-lib]
runs-on: ubuntu-20.04
needs: [build-jvm-macOS-M1-native-lib]
runs-on: ubuntu-22.04
steps:
- name: "Checkout publishing branch"
uses: actions/checkout@v3
- name: Checkout publishing branch
uses: actions/checkout@v2
- name: "Cache"
- name: Update bdk-ffi git submodule
run: |
git submodule set-url bdk-ffi https://github.com/bitcoindevkit/bdk-ffi.git
git submodule update --init bdk-ffi
- name: Cache
uses: actions/cache@v3
with:
path: |
@ -88,39 +62,26 @@ jobs:
./target
key: ${{ runner.os }}-${{ hashFiles('**/Cargo.toml','**/Cargo.lock') }}
- name: "Set up JDK"
uses: actions/setup-java@v3
- name: Set up JDK
uses: actions/setup-java@v2
with:
distribution: temurin
java-version: 17
java-version: 11
- name: "Set default Rust version to 1.77.1"
run: rustup default 1.77.1
- name: "Build bdk-jvm library"
- name: Build bdk-jvm library
run: |
cd bdk-jvm
./gradlew buildJvmLib
- name: "Download macOS native binaries from previous job"
- name: Download macOS native libraries from previous job
uses: actions/download-artifact@v3
id: download
with:
name: artifact-macos
# download the artifact created in the prior job (named "artifact")
name: artifact
path: ./bdk-jvm/lib/src/main/resources/
- name: "Download Windows native libraries from previous job"
uses: actions/download-artifact@v3
with:
name: artifact-windows
path: ./bdk-jvm/lib/src/main/resources/
- name: "Upload library code and binaries"
uses: actions/upload-artifact@v3
with:
name: artifact-full
path: ./bdk-jvm/lib/
- name: "Publish to Maven Central"
- name: Publish to Maven Central
env:
ORG_GRADLE_PROJECT_signingKeyId: ${{ secrets.PGP_KEY_ID }}
ORG_GRADLE_PROJECT_signingKey: ${{ secrets.PGP_SECRET_KEY }}

View File

@ -1,179 +1,143 @@
name: Publish bdkpython to PyPI
name: Build and publish Python wheels
on: [workflow_dispatch]
# We use manylinux2014 because older CentOS versions used by 2010 and 1 have a very old glibc version, which
# makes it very hard to use GitHub's javascript actions (checkout, upload-artifact, etc).
# They mount their own nodejs interpreter inside your container, but since that's not statically linked it
# tries to load glibc and fails because it requires a more recent version.
jobs:
build-manylinux_2_28-x86_64-wheels:
name: "Build Manylinux 2.28 x86_64 wheel"
runs-on: ubuntu-20.04
build-manylinux2014-x86_64-wheel:
name: 'Build Manylinux 2014 x86_64 wheel'
runs-on: ubuntu-latest
defaults:
run:
working-directory: bdk-python
container:
image: quay.io/pypa/manylinux_2_28_x86_64
image: quay.io/pypa/manylinux2014_x86_64
env:
PLAT: manylinux_2_28_x86_64
PYBIN: "/opt/python/${{ matrix.python }}/bin"
PLAT: manylinux2014_x86_64
PYBIN: '/opt/python/${{ matrix.python }}/bin'
strategy:
matrix:
python: # Update this list whenever the docker image is updated (check /opt/python/)
- cp36-cp36m
- cp37-cp37m
- cp38-cp38
- cp39-cp39
- cp310-cp310
- cp311-cp311
- cp312-cp312
- pp37-pypy37_pp73
- pp38-pypy38_pp73
steps:
- name: "Checkout"
uses: actions/checkout@v3
- name: checkout
uses: actions/checkout@v2
with:
submodules: true
- name: "Install Rust 1.77.1"
uses: actions-rs/toolchain@v1
- uses: actions-rs/toolchain@v1
with:
toolchain: 1.77.1
- name: "Generate bdk.py and binaries"
run: bash ./scripts/generate-linux.sh
- name: "Build wheel"
# Specifying the plat-name argument is necessary to build a wheel with the correct name,
# see issue #350 for more information
run: ${PYBIN}/python setup.py bdist_wheel --plat-name manylinux_2_28_x86_64 --verbose
- uses: actions/upload-artifact@v3
toolchain: stable
- name: install requirements
run: ${PYBIN}/pip install -r requirements.txt
- name: generate bindings
run: bash generate.sh
- name: build wheel
run: ${PYBIN}/pip wheel . --no-deps -w /tmp/wheelhouse
- name: repair wheel
run: auditwheel repair /tmp/wheelhouse/* --plat "$PLAT" -w /tmp/wheelhouse-repaired
- uses: actions/upload-artifact@v2
with:
name: bdkpython-manylinux_2_28_x86_64-${{ matrix.python }}
path: /home/runner/work/bdk-ffi/bdk-ffi/bdk-python/dist/*.whl
name: bdkpython-manylinux2014-x86_64-${{ matrix.python }}
path: /tmp/wheelhouse-repaired/*.whl
build-macos-arm64-wheels:
name: "Build macOS arm64 wheel"
runs-on: macos-13
build-macos-universal-wheel:
name: 'Build macOS universal wheel'
runs-on: macos-latest
defaults:
run:
working-directory: bdk-python
strategy:
matrix:
python:
- "3.8"
- "3.9"
- "3.10"
- "3.11"
- "3.12"
- '3.7'
- '3.8'
- '3.9'
- '3.10'
steps:
- name: "Checkout"
uses: actions/checkout@v3
- name: checkout
uses: actions/checkout@v2
with:
submodules: true
- name: "Install Python"
uses: actions/setup-python@v4
- uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python }}
- name: "Generate bdk.py and binaries"
run: bash ./scripts/generate-macos-arm64.sh
- name: "Build wheel"
# Specifying the plat-name argument is necessary to build a wheel with the correct name,
# see issue #350 for more information
run: python3 setup.py bdist_wheel --plat-name macosx_11_0_arm64 --verbose
- name: "Upload artifacts"
uses: actions/upload-artifact@v3
- run: python3 --version
- run: rustup target add aarch64-apple-darwin
- run: pip3 install --user -r requirements.txt
- run: pip3 install --user wheel
- run: bash generate.sh
- name: build wheel
env:
ARCHFLAGS: "-arch x86_64 -arch arm64"
run: python3 setup.py -v bdist_wheel
- uses: actions/upload-artifact@v2
with:
name: bdkpython-macos-arm64-${{ matrix.python }}
name: bdkpython-macos-${{ matrix.python }}
path: /Users/runner/work/bdk-ffi/bdk-ffi/bdk-python/dist/*.whl
build-macos-x86_64-wheels:
name: "Build macOS x86_64 wheel"
runs-on: macos-13
build-windows-wheel:
name: 'Build windows wheel'
runs-on: windows-latest
defaults:
run:
working-directory: bdk-python
strategy:
matrix:
python:
- "3.8"
- "3.9"
- "3.10"
- "3.11"
- "3.12"
- '3.7'
- '3.8'
- '3.9'
- '3.10'
steps:
- name: "Checkout"
uses: actions/checkout@v3
- name: checkout
uses: actions/checkout@v2
with:
submodules: true
- name: "Install Python"
uses: actions/setup-python@v4
- uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python }}
- name: "Generate bdk.py and binaries"
run: bash ./scripts/generate-macos-x86_64.sh
- name: "Build wheel"
# Specifying the plat-name argument is necessary to build a wheel with the correct name,
# see issue #350 for more information
run: python3 setup.py bdist_wheel --plat-name macosx_11_0_x86_64 --verbose
- uses: actions/upload-artifact@v3
with:
name: bdkpython-macos-x86_64-${{ matrix.python }}
path: /Users/runner/work/bdk-ffi/bdk-ffi/bdk-python/dist/*.whl
build-windows-wheels:
name: "Build Windows wheel"
runs-on: windows-2022
defaults:
run:
working-directory: bdk-python
strategy:
matrix:
python:
- "3.8"
- "3.9"
- "3.10"
- "3.11"
- "3.12"
steps:
- name: "Checkout"
uses: actions/checkout@v3
with:
submodules: true
- uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python }}
- name: "Generate bdk.py and binaries"
run: bash ./scripts/generate-windows.sh
- name: "Build wheel"
run: python setup.py bdist_wheel --verbose
- name: "Upload artifacts"
uses: actions/upload-artifact@v3
- run: python --version
- run: pip install --user -r requirements.txt
- run: bash generate.sh
shell: bash
- run: pip install --user wheel
- name: build wheel
run: python setup.py -v bdist_wheel
- uses: actions/upload-artifact@v2
with:
name: bdkpython-win-${{ matrix.python }}
path: D:\a\bdk-ffi\bdk-ffi\bdk-python\dist\*.whl
publish-pypi:
name: "Publish on PyPI"
runs-on: ubuntu-20.04
name: 'Publish on PyPI'
runs-on: ubuntu-latest
defaults:
run:
working-directory: bdk-python
needs: [build-manylinux_2_28-x86_64-wheels, build-macos-arm64-wheels, build-macos-x86_64-wheels, build-windows-wheels]
needs: [build-manylinux2014-x86_64-wheel, build-macos-universal-wheel, build-windows-wheel]
# needs: [build-macos-universal-wheel]
steps:
- name: "Checkout"
uses: actions/checkout@v3
- name: Checkout
uses: actions/checkout@v2
- name: "Download artifacts in dist/ directory"
uses: actions/download-artifact@v3
- name: 'Download artifacts in dist/ directory'
uses: actions/download-artifact@v2
with:
path: dist/
# - name: "Publish on test PyPI"
# - name: Display structure of downloaded files
# run: ls -R
# - name: 'Publish on test PyPI'
# uses: pypa/gh-action-pypi-publish@release/v1
# with:
# user: __token__
@ -181,7 +145,7 @@ jobs:
# repository_url: https://test.pypi.org/legacy/
# packages_dir: dist/*/
- name: "Publish on PyPI"
- name: 'Publish on PyPI'
uses: pypa/gh-action-pypi-publish@release/v1
with:
user: __token__

View File

@ -1,6 +1,5 @@
name: Test Android
on:
workflow_dispatch:
push:
paths:
- "bdk-ffi/**"
@ -10,20 +9,27 @@ on:
- "bdk-ffi/**"
- "bdk-android/**"
# The default Android NDK on the ubuntu-22.04 image is 25.2.9519653
env:
ANDROID_NDK_ROOT: /usr/local/lib/android/sdk/ndk/21.4.7075529
# By default, the new ubuntu-20.04 images use the following ANDROID_NDK_ROOT
# ANDROID_NDK_ROOT: /usr/local/lib/android/sdk/ndk/25.0.8775105
jobs:
build:
runs-on: ubuntu-20.04
steps:
- name: "Show default version of NDK"
run: echo $ANDROID_NDK_ROOT
- name: Install Android NDK 21.4.7075529
run: |
ANDROID_ROOT=/usr/local/lib/android
ANDROID_SDK_ROOT=${ANDROID_ROOT}/sdk
SDKMANAGER=${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin/sdkmanager
echo "y" | $SDKMANAGER "ndk;21.4.7075529"
- name: "Check out PR branch"
uses: actions/checkout@v3
- name: Check out PR branch
uses: actions/checkout@v2
- name: "Cache"
uses: actions/cache@v3
- name: cache
uses: actions/cache@v2
with:
path: |
~/.cargo/registry
@ -31,27 +37,16 @@ jobs:
./target
key: ${{ runner.os }}-${{ hashFiles('**/Cargo.toml','**/Cargo.lock') }}
- name: "Set up JDK"
uses: actions/setup-java@v3
- name: Set up JDK
uses: actions/setup-java@v2
with:
distribution: temurin
java-version: 17
java-version: 11
- name: "Set default Rust version to 1.77.1"
run: rustup default 1.77.1
- name: "Install Rust Android targets"
- name: Install rust android targets
run: rustup target add x86_64-linux-android aarch64-linux-android armv7-linux-androideabi
- name: "Build Android library"
- name: Run Android tests
run: |
cd bdk-android
./gradlew buildAndroidLib --console=plain
# There are currently no unit tests for bdk-android (see the tests in bdk-jvm instead) and the
# integration tests require the macOS image which is not working with the older NDK version we
# are using, so for now we just make sure that the library builds and omit the connectedTest
# - name: "Run Android connected tests"
# run: |
# cd bdk-android
# ./gradlew connectedAndroidTest --console=plain
./gradlew test --console=rich

View File

@ -1,6 +1,5 @@
name: Test Kotlin/JVM
name: Test JVM
on:
workflow_dispatch:
push:
paths:
- "bdk-ffi/**"
@ -14,11 +13,11 @@ jobs:
build:
runs-on: ubuntu-20.04
steps:
- name: "Check out PR branch"
uses: actions/checkout@v3
- name: Check out PR branch
uses: actions/checkout@v2
- name: "Cache"
uses: actions/cache@v3
- name: cache
uses: actions/cache@v2
with:
path: |
~/.cargo/registry
@ -26,16 +25,13 @@ jobs:
./target
key: ${{ runner.os }}-${{ hashFiles('**/Cargo.toml','**/Cargo.lock') }}
- name: "Set up JDK"
uses: actions/setup-java@v3
- name: Set up JDK
uses: actions/setup-java@v2
with:
distribution: temurin
java-version: 17
java-version: 11
- name: "Set default Rust version to 1.77.1"
run: rustup default 1.77.1
- name: "Run JVM tests"
- name: Run JVM tests
run: |
cd bdk-jvm
./gradlew test -P excludeConnectedTests
./gradlew test --console=rich

View File

@ -1,194 +0,0 @@
name: Test Python
on:
workflow_dispatch:
push:
paths:
- "bdk-ffi/**"
- "bdk-python/**"
pull_request:
paths:
- "bdk-ffi/**"
- "bdk-python/**"
jobs:
build-manylinux_2_28-x86_64-wheels:
name: "Build and test Manylinux 2.28 x86_64 wheels"
runs-on: ubuntu-20.04
defaults:
run:
working-directory: bdk-python
container:
image: quay.io/pypa/manylinux_2_28_x86_64
env:
PLAT: manylinux_2_28_x86_64
PYBIN: "/opt/python/${{ matrix.python }}/bin"
strategy:
matrix:
python:
- cp38-cp38
- cp39-cp39
- cp310-cp310
- cp311-cp311
- cp312-cp312
steps:
- name: "Checkout"
uses: actions/checkout@v3
with:
submodules: true
- name: "Install Rust 1.77.1"
uses: actions-rs/toolchain@v1
with:
toolchain: 1.77.1
- name: "Generate bdk.py and binaries"
run: bash ./scripts/generate-linux.sh
- name: "Build wheel"
# Specifying the plat-name argument is necessary to build a wheel with the correct name,
# see issue #350 for more information
run: ${PYBIN}/python setup.py bdist_wheel --plat-name manylinux_2_28_x86_64 --verbose
- name: "Install wheel"
run: ${PYBIN}/pip install ./dist/*.whl
- name: "Run tests"
run: ${PYBIN}/python -m unittest discover --start "./tests/" --pattern "test_offline_*.py" --verbose
- name: "Upload artifact test"
uses: actions/upload-artifact@v3
with:
name: bdkpython-manylinux_2_28_x86_64-${{ matrix.python }}
path: /home/runner/work/bdk-ffi/bdk-ffi/bdk-python/dist/*.whl
build-macos-arm64-wheels:
name: "Build and test macOS arm64 wheels"
runs-on: macos-13
defaults:
run:
working-directory: bdk-python
strategy:
matrix:
python:
- "3.8"
- "3.9"
- "3.10"
- "3.11"
- "3.12"
steps:
- name: "Checkout"
uses: actions/checkout@v3
with:
submodules: true
- name: "Install Python"
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python }}
- name: "Generate bdk.py and binaries"
run: bash ./scripts/generate-macos-arm64.sh
- name: "Build wheel"
# Specifying the plat-name argument is necessary to build a wheel with the correct name,
# see issue #350 for more information
run: python3 setup.py bdist_wheel --plat-name macosx_11_0_arm64 --verbose
# You can't install the arm64 wheel on the CI, so we skip these steps and simply test that the wheel builds
# - name: "Install wheel and run tests"
# run: |
# pip3 install ./dist/*.whl
# python3 -m unittest discover --start "./tests/" --pattern "test_offline_*.py" --verbose
- name: "Upload artifact test"
uses: actions/upload-artifact@v3
with:
name: bdkpython-macos-arm64-${{ matrix.python }}
path: /Users/runner/work/bdk-ffi/bdk-ffi/bdk-python/dist/*.whl
build-macos-x86_64-wheels:
name: "Build and test macOS x86_64 wheels"
runs-on: macos-13
defaults:
run:
working-directory: bdk-python
strategy:
matrix:
python:
- "3.8"
- "3.9"
- "3.10"
- "3.11"
- "3.12"
steps:
- name: "Checkout"
uses: actions/checkout@v3
with:
submodules: true
- uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python }}
- name: "Generate bdk.py and binaries"
run: bash ./scripts/generate-macos-x86_64.sh
- name: "Build wheel"
# Specifying the plat-name argument is necessary to build a wheel with the correct name,
# see issue #350 for more information
run: python3 setup.py bdist_wheel --plat-name macosx_11_0_x86_64 --verbose
- name: "Install wheel"
run: pip3 install ./dist/*.whl
- name: "Run tests"
run: python3 -m unittest discover --start "./tests/" --pattern "test_offline_*.py" --verbose
- name: "Upload artifact test"
uses: actions/upload-artifact@v3
with:
name: bdkpython-macos-x86_64-${{ matrix.python }}
path: /Users/runner/work/bdk-ffi/bdk-ffi/bdk-python/dist/*.whl
build-windows-wheels:
name: "Build and test Windows wheels"
runs-on: windows-2022
defaults:
run:
working-directory: bdk-python
strategy:
matrix:
python:
- "3.8"
- "3.9"
- "3.10"
- "3.11"
- "3.12"
steps:
- name: "Checkout"
uses: actions/checkout@v3
with:
submodules: true
- name: "Install Python"
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python }}
- name: "Generate bdk.py and binaries"
run: bash ./scripts/generate-windows.sh
- name: "Build wheel"
run: python setup.py bdist_wheel --verbose
- name: "Upload artifact test"
uses: actions/upload-artifact@v3
with:
name: bdkpython-windows-${{ matrix.python }}
path: D:\a\bdk-ffi\bdk-ffi\bdk-python\dist\*.whl
- name: "Install dependencies"
run: Get-ChildItem 'D:\a\bdk-ffi\bdk-ffi\bdk-python\dist\*.whl' | ForEach-Object {pip install $_.FullName}
shell: powershell
- name: "Run tests"
run: python -m unittest discover --start "./tests/" --pattern "test_offline_*.py" --verbose

View File

@ -1,6 +1,5 @@
name: Test Swift
on:
workflow_dispatch:
push:
paths:
- "bdk-ffi/**"
@ -12,16 +11,41 @@ on:
jobs:
build:
name: "Build and test"
runs-on: macos-12
steps:
- name: "Checkout"
uses: actions/checkout@v3
- name: Checkout branch
uses: actions/checkout@v2
- name: "Build Swift package"
working-directory: bdk-swift
run: bash ./build-xcframework.sh
- name: Install Rust targets
run: |
rustup install nightly-x86_64-apple-darwin
rustup component add rust-src --toolchain nightly-x86_64-apple-darwin
rustup target add aarch64-apple-darwin x86_64-apple-darwin
- name: "Run Swift tests"
- name: Run bdk-ffi-bindgen
working-directory: bdk-ffi
run: cargo run --package bdk-ffi-bindgen -- --language swift --out-dir ../bdk-swift/Sources/BitcoinDevKit
- name: Build bdk-ffi for x86_64-apple-darwin
run: cargo build --profile release-smaller --target x86_64-apple-darwin
- name: Build bdk-ffi for aarch64-apple-darwin
run: cargo build --profile release-smaller --target aarch64-apple-darwin
- name: Create lipo-ios-sim and lipo-macos
run: |
mkdir -p target/lipo-macos/release-smaller
lipo target/aarch64-apple-darwin/release-smaller/libbdkffi.a target/x86_64-apple-darwin/release-smaller/libbdkffi.a -create -output target/lipo-macos/release-smaller/libbdkffi.a
- name: Create bdkFFI.xcframework
working-directory: bdk-swift
run: swift test --skip LiveElectrumClientTests --skip LiveMemoryWalletTests --skip LiveTransactionTests --skip LiveTxBuilderTests --skip LiveWalletTests
run: |
mv Sources/BitcoinDevKit/bdk.swift Sources/BitcoinDevKit/BitcoinDevKit.swift
cp Sources/BitcoinDevKit/bdkFFI.h bdkFFI.xcframework/macos-arm64_x86_64/bdkFFI.framework/Headers
cp ../target/lipo-macos/release-smaller/libbdkffi.a bdkFFI.xcframework/macos-arm64_x86_64/bdkFFI.framework/bdkFFI
rm Sources/BitcoinDevKit/bdkFFI.h
rm Sources/BitcoinDevkit/bdkFFI.modulemap
- name: Run Swift tests
working-directory: bdk-swift
run: swift test

7
.gitignore vendored
View File

@ -18,7 +18,7 @@ bdk.kt
# Swift related
/.build
.swiftpm
/.swiftpm
/Packages
/*.xcodeproj
xcuserdata/
@ -31,8 +31,3 @@ bdkFFI.h
BitcoinDevKit.swift
bdk.swift
.build
*.xcframework/
Info.plist
# Python related
__pycache__

View File

@ -1,173 +1,13 @@
# Changelog
Changelog information can also be found in each release's git tag (which can be viewed with `git tag -ln100 "v*"`), as well as on the [GitHub releases](https://github.com/bitcoindevkit/bdk-ffi/releases) page. See [DEVELOPMENT_CYCLE.md](DEVELOPMENT_CYCLE.md) for more details.
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).
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).
## [v1.0.0-alpha.11]
This release brings the latest alpha 11 release of the Rust bdk_wallet library, as well as the new Electrum client, the new memory wallet, and a whole lot of new types and APIs across the library. Also of note are the much simpler-to-use full_scan and sync workflows for syncing wallets.
Added:
- `Amount` type [#533]
- `TxIn` type [#536]
- `Transaction.input()` method [#536]
- `Transaction.output()` method [#536]
- `Transaction.lock_time()` method [#536]
- `Electrum` client [#535]
- Memory wallet [#528]
[#528]: https://github.com/bitcoindevkit/bdk-ffi/pull/528
[#533]: https://github.com/bitcoindevkit/bdk-ffi/pull/533
[#535]: https://github.com/bitcoindevkit/bdk-ffi/pull/535
[#536]: https://github.com/bitcoindevkit/bdk-ffi/pull/536
## [v1.0.0-alpha.7]
This release brings back into the 1.0 API a number of APIs from the 0.31 release, and adds the new flat file persistence feature, as well as more fine-grain errors.
## [v1.0.0-alpha.2a]
This release is the first alpha release of the 1.0 API for the bindings libraries. Here is what is now available:
- Create and recover wallets using descriptors, including the four descriptor templates
- Sync a wallet using a blocking Esplora client
- Query the wallet for balance and addresses
- Create and sign transactions using the transaction builder
- Broadcast transactions
## [v0.31.0]
This release updates the bindings libraries to bdk version 0.29.0, updating rust-bitcoin to version 0.30.2.
- APIs Changed:
- `BumpFeeTxBuilder.allow_shrinking()` now takes a `Script` as its argument [#443]
- The `Address` constructor now takes a `Network` argument [#443]
- The `Payload::PubkeyHash` and `Payload::ScriptHash` now have string arguments instead of byte arrays [#443]
- APIs Added:
- The `Address` type now has the `is_valid_for_network()` method [#443]
[#443]: https://github.com/bitcoindevkit/bdk-ffi/pull/443
## [v0.30.0]
This release has a new API and a few internal optimizations and refactorings.
- APIs Added
- Add BIP-86 descriptor templates [#388]
[#388]: https://github.com/bitcoindevkit/bdk-ffi/pull/388
## [v0.29.0]
This release has a number of new APIs, and adds support for Windows in bdk-jvm.
Changelog
- Add support for Windows in bdk-jvm [#336]
- Add support for older version of Linux distros in bdk-jvm [#345]
- APIs added
- Expose `is_mine()` method on the `Wallet` type [#355]
- Expose `to_bytes()` method on the `Script` type [#369]
[#336]: https://github.com/bitcoindevkit/bdk-ffi/pull/336
[#345]: https://github.com/bitcoindevkit/bdk-ffi/pull/345
[#355]: https://github.com/bitcoindevkit/bdk-ffi/pull/355
[#369]: https://github.com/bitcoindevkit/bdk-ffi/pull/369
## [v0.28.0]
- Update BDK to version 0.28.0 [#341]
- Drop support of pypy releases of Python libraries [#351]
- Drop support for Python 3.6 and 3.7 [#351]
- Drop support for very old Linux versions that do not support the manylinux_2_17_x86_64 platform tag [#351]
- APIs changed:
- Expose Address payload and network properties. [#325]
- Add SignOptions to Wallet.sign() params. [#326]
- address field on `AddressInfo` type is now of type `Address` [#333]
- new PartiallySignedTransaction.json_serialize() function to get JSON serialized value of all PSBT fields. [#334]
- Add from_script constructor to `Address` type [#337]
[#325]: https://github.com/bitcoindevkit/bdk-ffi/pull/325
[#326]: https://github.com/bitcoindevkit/bdk-ffi/pull/326
[#333]: https://github.com/bitcoindevkit/bdk-ffi/pull/333
[#334]: https://github.com/bitcoindevkit/bdk-ffi/pull/334
[#337]: https://github.com/bitcoindevkit/bdk-ffi/pull/337
[#341]: https://github.com/bitcoindevkit/bdk-ffi/pull/341
[#351]: https://github.com/bitcoindevkit/bdk-ffi/pull/351
## [v0.27.1]
- Update BDK to version 0.27.1 [#312]
- APIs changed
- `PartiallySignedTransaction.extract_tx()` returns a `Transaction` instead of the transaction bytes. [#296]
- `Blockchain.broadcast()` takes a `Transaction` instead of a `PartiallySignedTransaction`. [#296]
- APIs added
- New `Transaction` structure that can be created from or serialized to consensus encoded bytes. [#296]
- Add Wallet.get_internal_address() API [#304]
- Add `AddressIndex::Peek(index)` and `AddressIndex::Reset(index)` APIs [#305]
[#296]: https://github.com/bitcoindevkit/bdk-ffi/pull/296
[#304]: https://github.com/bitcoindevkit/bdk-ffi/pull/304
[#305]: https://github.com/bitcoindevkit/bdk-ffi/pull/305
[#312]: https://github.com/bitcoindevkit/bdk-ffi/pull/312
## [v0.26.0]
- Update BDK to version 0.26.0 [#288]
- APIs changed
- The descriptor and change_descriptor arguments on the wallet constructor now take a `Descriptor` instead of a `String`. [#260]
- TxBuilder.drain_to() argument is now `Script` instead of address `String`. [#279]
- APIs added
- Added RpcConfig, BlockchainConfig::Rpc, and Auth [#125]
- Added Descriptor type in [#260] with the following methods:
- Default constructor requires a descriptor in String format and a Network
- new_bip44 constructor returns a Descriptor with structure pkh(key/44'/{0,1}'/0'/{0,1}/*)
- new_bip44_public constructor returns a Descriptor with structure pkh(key/{0,1}/*)
- new_bip49 constructor returns a Descriptor with structure sh(wpkh(key/49'/{0,1}'/0'/{0,1}/*))
- new_bip49_public constructor returns a Descriptor with structure sh(wpkh(key/{0,1}/*))
- new_bip84 constructor returns a Descriptor with structure wpkh(key/84'/{0,1}'/0'/{0,1}/*)
- new_bip84_public constructor returns a Descriptor with structure wpkh(key/{0,1}/*)
- as_string returns the public version of the output descriptor
- as_string_private returns the private version of the output descriptor if available, otherwise return the public version
[#125]: https://github.com/bitcoindevkit/bdk-ffi/pull/125
[#260]: https://github.com/bitcoindevkit/bdk-ffi/pull/260
[#279]: https://github.com/bitcoindevkit/bdk-ffi/pull/279
[#288]: https://github.com/bitcoindevkit/bdk-ffi/pull/288
## [v0.25.0]
- Update BDK to version 0.25.0 [#272]
- APIs Added:
- from_string() constructors now available on DescriptorSecretKey and DescriptorPublicKey [#247]
[#247]: https://github.com/bitcoindevkit/bdk-ffi/pull/247
[#272]: https://github.com/bitcoindevkit/bdk-ffi/pull/272
## [v0.11.0]
- Update BDK to version 0.24.0 [#221]
- APIs changed
- The constructor on the DescriptorSecretKey type now takes a Mnemonic instead of a String.
- APIs added
- Added Mnemonic struct [#219] with following methods:
- new(word_count: WordCount) generates and returns Mnemonic with random entropy
- from_string(mnemonic: String) converts string Mnemonic to Mnemonic type with error
- from_entropy(entropy: Vec<u8>) generates and returns Mnemonic with given entropy
- as_string() view Mnemonic as string
- APIs removed
- generate_mnemonic(word_count: WordCount)
[#219]: https://github.com/bitcoindevkit/bdk-ffi/pull/219
[#221]: https://github.com/bitcoindevkit/bdk-ffi/pull/221
## [v0.10.0]
- Update BDK to version 0.23.0 [#204]
- Update uniffi-rs to latest version 0.21.0 [#216]
- Breaking Changes
- Changed `TxBuilder.finish()` to return new `TxBuilderResult` [#209]
- `TxBuilder.add_recipient()` now takes a `Script` instead of an `Address` [#192]
- `AddressAmount` is now `ScriptAmount` [#192]
- APIs Added
- Added `TxBuilderResult` with PSBT and TransactionDetails [#209]
- `Address` and `Script` structs have been added [#192]
- Add `PartiallySignedBitcoinTransaction.extract_tx()` function [#192]
- Add `secret_bytes()` method on the `DescriptorSecretKey` [#199]
- Add `PartiallySignedBitcoinTransaction.combine()` method [#200]
[#192]: https://github.com/bitcoindevkit/bdk-ffi/pull/192
[#199]: https://github.com/bitcoindevkit/bdk-ffi/pull/199
[#200]: https://github.com/bitcoindevkit/bdk-ffi/pull/200
[#204]: https://github.com/bitcoindevkit/bdk-ffi/pull/204
[#209]: https://github.com/bitcoindevkit/bdk-ffi/pull/209
[#216]: https://github.com/bitcoindevkit/bdk-ffi/pull/216
## [Unreleased]
## [v0.9.0]
- Breaking Changes
@ -182,7 +22,7 @@ Changelog
- APIs Added [#154]
- `generate_mnemonic()`, returns string mnemonic
- `interface DescriptorSecretKey`
- `new(Network, string_mnenoinc, password)`, constructs DescriptorSecretKey
- `new(Network, string_mnenoinc, password)`, contructs DescriptorSecretKey
- `derive(DerivationPath)`, derives and returns child DescriptorSecretKey
- `extend(DerivationPath)`, extends and returns DescriptorSecretKey
- `as_public()`, returns DescriptorSecretKey as DescriptorPublicKey
@ -262,18 +102,9 @@ Changelog
[BIP 0174]:https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki#encoding
[v1.0.0-alpha.11]: https://github.com/bitcoindevkit/bdk-ffi/compare/v1.0.0-alpha.7...v1.0.0-alpha.11
[v1.0.0-alpha.7]: https://github.com/bitcoindevkit/bdk-ffi/compare/v1.0.0-alpha.2a...v1.0.0-alpha.7
[v1.0.0-alpha.2a]: https://github.com/bitcoindevkit/bdk-ffi/compare/v0.31.0...v1.0.0-alpha.2a
[v0.31.0]: https://github.com/bitcoindevkit/bdk-ffi/compare/v0.30.0...v0.31.0
[v0.30.0]: https://github.com/bitcoindevkit/bdk-ffi/compare/v0.29.0...v0.30.0
[v0.29.0]: https://github.com/bitcoindevkit/bdk-ffi/compare/v0.28.0...v0.29.0
[v0.28.0]: https://github.com/bitcoindevkit/bdk-ffi/compare/v0.27.1...v0.28.0
[v0.27.1]: https://github.com/bitcoindevkit/bdk-ffi/compare/v0.26.0...v0.27.1
[v0.26.0]: https://github.com/bitcoindevkit/bdk-ffi/compare/v0.25.0...v0.26.0
[v0.25.0]: https://github.com/bitcoindevkit/bdk-ffi/compare/v0.11.0...v0.25.0
[v0.11.0]: https://github.com/bitcoindevkit/bdk-ffi/compare/v0.10.0...v0.11.0
[v0.10.0]: https://github.com/bitcoindevkit/bdk-ffi/compare/v0.9.0...v0.10.0
## [v0.2.0]
[unreleased]: https://github.com/bitcoindevkit/bdk-ffi/compare/v0.9.0...HEAD
[v0.9.0]: https://github.com/bitcoindevkit/bdk-ffi/compare/v0.8.0...v0.9.0
[v0.8.0]: https://github.com/bitcoindevkit/bdk-ffi/compare/v0.7.0...v0.8.0
[v0.7.0]: https://github.com/bitcoindevkit/bdk-ffi/compare/v0.6.0...v0.7.0

File diff suppressed because it is too large Load Diff

12
Cargo.toml Normal file
View File

@ -0,0 +1,12 @@
[workspace]
members = ["bdk-ffi", "bdk-ffi-bindgen"]
default-members = ["bdk-ffi", "bdk-ffi-bindgen"]
exclude = ["api-docs", "bdk-android", "bdk-jvm", "bdk-python", "bdk-swift"]
[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*

View File

@ -7,12 +7,9 @@
<a href="https://discord.gg/d7NkDKm"><img alt="Chat on Discord" src="https://img.shields.io/discord/753336465005608961?logo=discord"></a>
</p>
## 🚨 Warning 🚨
The `master` branch of this repository is being migrated to the [bdk 1.0 API](https://github.com/bitcoindevkit/bdk) and is incomplete. For production-ready libraries, use the [`0.31.X`](https://github.com/bitcoindevkit/bdk-ffi/tree/release/0.30) releases.
## Readme
The workspace in this repository creates the `libbdkffi` multi-language library for the Rust-based
[bdk] library from the [Bitcoin Dev Kit] project.
[bdk] library from the [Bitcoin Dev Kit] project. The `bdk-ffi-bindgen` package builds a tool for
generating the actual language binding code used to access the `libbdkffi` library.
Each supported language and the platform(s) it's packaged for has its own directory. The Rust code in this project is in the bdk-ffi directory and is a wrapper around the [bdk] library to expose its APIs in a uniform way using the [mozilla/uniffi-rs] bindings generator for each supported target language.
@ -26,21 +23,25 @@ The below directories (a separate repository in the case of bdk-swift) include i
| Swift | iOS, macOS | [bdk-swift (GitHub)] | [Readme bdk-swift] | |
| Python | linux, macOS, Windows | [bdk-python (PyPI)] | [Readme bdk-python] | |
## Building and Testing the Libraries
If you are familiar with the build tools for the specific languages you wish to build the libraries for, you can use their normal build/test workflows. We also include some [just](https://just.systems/) files to simplify the work across different languages. If you have the just tool installed on your system, you can simply call the commands defined in the `justfile`s, for example:
```sh
cd bdk-android
just build
just offlinetests
just publishlocal
## Language bindings generator tool
Use the `bdk-ffi-bindgen` tool to generate language binding code for the above supported languages.
To run `bdk-ffi-bindgen` and see the available options use the command:
```shell
cargo run -p bdk-ffi-bindgen -- --help
```
## Minimum Supported Rust Version (MSRV)
This library should compile with any combination of features with Rust 1.77.1.
## Contributing
To add new structs and functions, see the [UniFFI User Guide](https://mozilla.github.io/uniffi-rs/) and the [uniffi-examples](https://thunderbiscuit.github.io/uniffi-examples/) repository.
### Adding new structs and functions
See the [UniFFI User Guide](https://mozilla.github.io/uniffi-rs/)
#### For pass by value objects
1. Create new rust struct with only fields that are supported UniFFI types
2. Update mapping `bdk.udl` file with new `dictionary`
#### For pass by reference values
1. Create wrapper rust struct/impl with only fields that are `Sync + Send`
2. Update mapping `bdk.udl` file with new `interface`
## Goals
1. Language bindings should feel idiomatic in target languages/platforms

6
api-docs/README.md Normal file
View File

@ -0,0 +1,6 @@
# API documentation
The Bitcoin Dev Kit language bindings make use of the [uniffi-rs](https://github.com/mozilla/uniffi-rs) library to produce their bindings. While efforts are ongoing to allow inline documentation on the Rust side to be ported to the bindings code, this is not currently possible.
This directory contains our temporary solution to this problem. A set of files mimicking the bindings libraries in their function signatures, but without any implementation. This allows for documentation build tools to produce API docs similarly to how we would like them to be if they could be inlined.
You can find the resulting API documentation websites in the ["API Reference" section of the sidebar](https://bitcoindevkit.org/getting-started/) under the "Docs" tab on the bitcoindevkit official website.

View File

@ -0,0 +1,4 @@
# Module bdk-android
The [bitcoindevkit](https://bitcoindevkit.org/) language bindings library for Android.
# Package org.bitcoindevkit

View File

@ -0,0 +1,4 @@
# Module bdk-jvm
The [bitcoindevkit](https://bitcoindevkit.org/) language bindings library for Kotlin and Java on the JVM.
# Package org.bitcoindevkit

View File

@ -0,0 +1,46 @@
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
kotlin("jvm") version "1.7.10"
// API docs
id("org.jetbrains.dokka") version "1.7.10"
}
repositories {
mavenCentral()
}
dependencies {
testImplementation(kotlin("test"))
}
tasks.test {
useJUnitPlatform()
}
tasks.withType<KotlinCompile> {
kotlinOptions.jvmTarget = "1.8"
}
// tasks.withType<org.jetbrains.dokka.gradle.DokkaTask>().configureEach {
// dokkaSourceSets {
// named("main") {
// moduleName.set("bdk-android")
// moduleVersion.set("0.11.0")
// includes.from("Module1.md")
// samples.from("src/test/kotlin/org/bitcoindevkit/Samples.kt")
// }
// }
// }
tasks.withType<org.jetbrains.dokka.gradle.DokkaTask>().configureEach {
dokkaSourceSets {
named("main") {
moduleName.set("bdk-jvm")
moduleVersion.set("0.11.0")
includes.from("Module2.md")
samples.from("src/test/kotlin/org/bitcoindevkit/Samples.kt")
}
}
}

View File

@ -0,0 +1,8 @@
./gradlew dokkaHtml
cd build/dokka/html
git init .
git add .
git switch --create gh-pages
git commit -m "Deploy"
git remote add origin git@github.com:bitcoindevkit/bdk-kotlin.git
git push --set-upstream origin gh-pages --force

View File

@ -0,0 +1 @@
kotlin.code.style=official

Binary file not shown.

View File

@ -0,0 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

234
api-docs/kotlin/gradlew vendored Executable file
View File

@ -0,0 +1,234 @@
#!/bin/sh
#
# Copyright © 2015-2021 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
APP_NAME="Gradle"
APP_BASE_NAME=${0##*/}
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
warn () {
echo "$*"
} >&2
die () {
echo
echo "$*"
echo
exit 1
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD=java
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in
# double quotes to make sure that they get re-expanded; and
# * put everything else in single quotes, so that it's not re-expanded.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"

89
api-docs/kotlin/gradlew.bat vendored Normal file
View File

@ -0,0 +1,89 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

View File

@ -0,0 +1 @@
rootProject.name = "BDK Android and BDK JVM API Docs"

View File

@ -0,0 +1,706 @@
package org.bitcoindevkit
/**
* The cryptocurrency to act on.
*
* @sample org.bitcoindevkit.networkSample
*/
enum class Network {
/** Bitcoin's mainnet. */
BITCOIN,
/** Bitcoins testnet. */
TESTNET,
/** Bitcoins signet. */
SIGNET,
/** Bitcoins regtest. */
REGTEST,
}
/**
* A derived address and the index it was found at.
*
* @property index Child index of this address.
* @property address Address.
*
* @sample org.bitcoindevkit.addressInfoSample
*/
data class AddressInfo (
var index: UInt,
var address: String
)
/**
* The address index selection strategy to use to derive an address from the wallets external descriptor.
*
* If youre unsure which one to use, use `AddressIndex.NEW`.
*
* @sample org.bitcoindevkit.addressIndexSample
*/
enum class AddressIndex {
/** Return a new address after incrementing the current descriptor index. */
NEW,
/** Return the address for the current descriptor index if it has not been used in a received transaction.
* Otherwise return a new address as with `AddressIndex.NEW`. Use with caution, if the wallet
* has not yet detected an address has been used it could return an already used address.
* This function is primarily meant for situations where the caller is untrusted;
* for example when deriving donation addresses on-demand for a public web page.
*/
LAST_UNUSED,
}
/**
* Balance differentiated in various categories.
*
* @property immature All coinbase outputs not yet matured.
* @property trustedPending Unconfirmed UTXOs generated by a wallet tx.
* @property untrustedPending Unconfirmed UTXOs received from an external wallet.
* @property confirmed Confirmed and immediately spendable balance.
* @property spendable The sum of trustedPending and confirmed coins.
* @property total The whole balance visible to the wallet.
*
* @sample org.bitcoindevkit.balanceSample
*/
data class Balance (
var immature: ULong,
var trustedPending: ULong,
var untrustedPending: ULong,
var confirmed: ULong,
var spendable: ULong,
var total: ULong
)
/**
* Type that can contain any of the database configurations defined by the library.
*
* @sample org.bitcoindevkit.memoryDatabaseConfigSample
* @sample org.bitcoindevkit.sqliteDatabaseConfigSample
*/
sealed class DatabaseConfig {
/** Configuration for an in-memory database. */
object Memory : DatabaseConfig()
/** Configuration for a Sled database. */
data class Sled(val config: SledDbConfiguration) : DatabaseConfig()
/** Configuration for a SQLite database. */
data class Sqlite(val config: SqliteDbConfiguration) : DatabaseConfig()
}
/**
* Configuration type for a SQLite database.
*
* @property path Main directory of the DB.
*
* @sample org.bitcoindevkit.sqliteDatabaseConfigSample
*/
data class SqliteDbConfiguration(
var path: String,
)
/**
* Configuration type for a SledDB database.
*
* @property path Main directory of the DB.
* @property treeName Name of the database tree, a separated namespace for the data.
*/
data class SledDbConfiguration(
var path: String,
var treeName: String,
)
/**
* Configuration for an Electrum blockchain.
*
* @property url URL of the Electrum server (such as ElectrumX, Esplora, BWT) may start with `ssl://` or `tcp://` and include a port, e.g. `ssl://electrum.blockstream.info:60002`.
* @property socks5 URL of the socks5 proxy server or a Tor service.
* @property retry Request retry count.
* @property timeout Request timeout (seconds).
* @property stopGap Stop searching addresses for transactions after finding an unused gap of this length.
* @property validateDomain Validate the domain when using SSL.
*
* @sample org.bitcoindevkit.electrumBlockchainConfigSample
*/
data class ElectrumConfig(
var url: String,
var socks5: String?,
var retry: UByte,
var timeout: UByte?,
var stopGap: ULong,
var validateDomain: Boolean
)
/**
* Configuration for an Esplora blockchain.
*
* @property baseUrl Base URL of the esplora service, e.g. `https://blockstream.info/api/`.
* @property proxy Optional URL of the proxy to use to make requests to the Esplora server.
* @property concurrency Number of parallel requests sent to the esplora service (default: 4).
* @property stopGap Stop searching addresses for transactions after finding an unused gap of this length.
* @property timeout Socket timeout.
*
* @sample org.bitcoindevkit.esploraBlockchainConfigSample
*/
data class EsploraConfig(
var baseUrl: String,
var proxy: String?,
var concurrency: UByte?,
var stopGap: ULong,
var timeout: ULong?
)
/**
* Authentication mechanism for RPC connection to full node.
*/
sealed class Auth {
/** No authentication */
object None: Auth()
/** Authentication with username and password, usually [Auth.Cookie] should be preferred */
data class UserPass(val username: String, val password: String): Auth()
/** Authentication with a cookie file */
data class Cookie(val file: String): Auth()
}
/**
* Sync parameters for Bitcoin Core RPC.
*
* In general, BDK tries to sync `scriptPubKey`s cached in `Database` with
* `scriptPubKey`s imported in the Bitcoin Core Wallet. These parameters are used for determining
* how the `importdescriptors` RPC calls are to be made.
*
* @property startScriptCount The minimum number of scripts to scan for on initial sync.
* @property startTime Time in unix seconds in which initial sync will start scanning from (0 to start from genesis).
* @property forceStartTime Forces every sync to use `start_time` as import timestamp.
* @property pollRateSec RPC poll rate (in seconds) to get state updates.
*/
data class RcpSyncParams(
val startScriptCount: ULong,
val startTime: Ulong,
val forceStartTime: Boolean,
val pollRateSec: ULong,
)
/**
* RpcBlockchain configuration options
*
* @property url The bitcoin node url.
* @property auth The bicoin node authentication mechanism.
* @property network The network we are using (it will be checked the bitcoin node network matches this).
* @property walletName The wallet name in the bitcoin node.
* @property syncParams Sync parameters.
*/
data class RpcConfig(
val url: String,
val auth: Auth,
val network: Network,
val walletName: String,
val syncParams: RcpSyncParams?,
)
/**
* Type that can contain any of the blockchain configurations defined by the library.
*
* @sample org.bitcoindevkit.electrumBlockchainConfigSample
*/
sealed class BlockchainConfig {
/** Electrum client. */
data class Electrum(val config: ElectrumConfig) : BlockchainConfig()
/** Esplora client. */
data class Esplora(val config: EsploraConfig) : BlockchainConfig()
/** Bitcoin Core RPC client. */
data class Rpc(val config: RpcConfig) : BlockchainConfig()
}
/**
* A wallet transaction.
*
* @property fee Fee value (sats) if available. The availability of the fee depends on the backend. Its never None with an Electrum server backend, but it could be None with a Bitcoin RPC node without txindex that receive funds while offline.
* @property received Received value (sats) Sum of owned outputs of this transaction.
* @property sent Sent value (sats) Sum of owned inputs of this transaction.
* @property txid Transaction id.
* @property confirmationTime If the transaction is confirmed, [BlockTime] contains height and timestamp of the block containing the transaction. This property is null for unconfirmed transactions.
*/
data class TransactionDetails (
var fee: ULong?,
var received: ULong,
var sent: ULong,
var txid: String,
var confirmationTime: BlockTime?
)
/**
* A blockchain backend.
*
* @constructor Create the new blockchain client.
*
* @param config The blockchain configuration required.
*
* @sample org.bitcoindevkit.blockchainSample
*/
class Blockchain(
config: BlockchainConfig
) {
/** Broadcast a transaction. */
fun broadcast(psbt: PartiallySignedBitcoinTransaction) {}
/** Get the current height of the blockchain. */
fun getHeight(): UInt {}
/** Get the block hash of a given block. */
fun getBlockHash(height: UInt): String {}
}
/**
* A partially signed bitcoin transaction.
*
* @constructor Build a new Partially Signed Bitcoin Transaction.
*
* @param psbtBase64 The PSBT in base64 format.
*/
class PartiallySignedBitcoinTransaction(psbtBase64: String) {
/** Return the PSBT in string format, using a base64 encoding. */
fun serialize(): String {}
/** Get the txid of the PSBT. */
fun txid(): String {}
/** Return the transaction as bytes. */
fun extractTx(): List<UByte>
/**
* Combines this PartiallySignedTransaction with another PSBT as described by BIP 174.
* In accordance with BIP 174 this function is commutative i.e., `A.combine(B) == B.combine(A)`
*/
fun combine(other: PartiallySignedBitcoinTransaction): PartiallySignedBitcoinTransaction
}
/**
* A reference to a transaction output.
*
* @property txid The referenced transactions txid.
* @property vout The index of the referenced output in its transactions vout.
*/
data class OutPoint (
var txid: String,
var vout: UInt
)
/**
* A transaction output, which defines new coins to be created from old ones.
*
* @property value The value of the output, in satoshis.
* @property address The address of the output.
*/
data class TxOut (
var value: ULong,
var address: String
)
/**
* An unspent output owned by a [Wallet].
*
* @property outpoint Reference to a transaction output.
* @property txout Transaction output.
* @property keychain Type of keychain.
* @property isSpent Whether this UTXO is spent or not.
*/
data class LocalUtxo (
var outpoint: OutPoint,
var txout: TxOut,
var keychain: KeychainKind,
var isSpent: Boolean
)
/**
* Types of keychains.
*/
enum class KeychainKind {
/** External. */
EXTERNAL,
/** Internal, usually used for change outputs. */
INTERNAL,
}
/**
* Block height and timestamp of a block.
*
* @property height Confirmation block height.
* @property timestamp Confirmation block timestamp.
*/
data class BlockTime (
var height: UInt,
var timestamp: ULong,
)
/**
* A Bitcoin wallet.
* The Wallet acts as a way of coherently interfacing with output descriptors and related transactions. Its main components are:
* 1. Output descriptors from which it can derive addresses.
* 2. A Database where it tracks transactions and utxos related to the descriptors.
* 3. Signers that can contribute signatures to addresses instantiated from the descriptors.
*
* @constructor Create a BDK wallet.
*
* @param descriptor The main (or "external") descriptor.
* @param changeDescriptor? The change (or "internal") descriptor.
* @param network The network to act on.
* @param databaseConfig The database configuration.
*
* @sample org.bitcoindevkit.walletSample
*/
class Wallet(
descriptor: Descriptor,
changeDescriptor: Descriptor?,
network: Network,
databaseConfig: DatabaseConfig,
) {
/**
* Return a derived address using the external descriptor, see [AddressIndex] for available address index
* selection strategies. If none of the keys in the descriptor are derivable (i.e. the descriptor does not end
* with a * character) then the same address will always be returned for any [AddressIndex].
*/
fun getAddress(addressIndex: AddressIndex): AddressInfo {}
/** Return the wallet's balance, across different categories. See [Balance] for the categories. Note that this method only operates on the internal database, which first needs to be [Wallet.sync] manually. */
fun getBalance(): Balance {}
/** Sign a transaction with all the wallets signers. */
fun sign(psbt: PartiallySignedBitcoinTransaction): Boolean {}
/** 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. */
fun listTransactions(): List<TransactionDetails> {}
/** Get the Bitcoin network the wallet is using. */
fun network(): Network {}
/** Sync the internal database with the blockchain. */
fun sync(blockchain: Blockchain, progress: Progress?) {}
/** 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. */
fun listUnspent(): List<LocalUtxo> {}
}
/**
* Class that logs at level INFO every update received (if any).
*/
class Progress {
/** Send a new progress update. The progress value should be in the range 0.0 - 100.0, and the message value is an optional text message that can be displayed to the user. */
fun update(progress: Float, message: String?) {}
}
/**
* 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.
*
* @sample org.bitcoindevkit.txBuilderResultSample1
* @sample org.bitcoindevkit.txBuilderResultSample2
*/
class TxBuilder() {
/** Add data as an output using OP_RETURN. */
fun addData(data: List<UByte>): TxBuilder {}
/** Add a recipient to the internal list. */
fun addRecipient(script: Script, amount: ULong): TxBuilder {}
/** Set the list of recipients by providing a list of [ScriptAmount]. */
fun setRecipients(recipients: List<ScriptAmount>): TxBuilder {}
/** Add a utxo to the internal list of unspendable utxos. Its important to note that the "must-be-spent" utxos added with [TxBuilder.addUtxo] have priority over this. See the Rust docs of the two linked methods for more details. */
fun addUnspendable(unspendable: OutPoint): TxBuilder {}
/** Add an outpoint to the internal list of UTXOs that must be spent. These have priority over the "unspendable" utxos, meaning that if a utxo is present both in the "utxos" and the "unspendable" list, it will be spent. */
fun addUtxo(outpoint: OutPoint): TxBuilder {}
/**
* Add the list of outpoints to the internal list of UTXOs that must be spent. If an error
* occurs while adding any of the UTXOs then none of them are added and the error is returned.
* These have priority over the "unspendable" utxos, meaning that if a utxo is present both
* in the "utxos" and the "unspendable" list, it will be spent.
*/
fun addUtxos(outpoints: List<OutPoint>): TxBuilder {}
/** Do not spend change outputs. This effectively adds all the change outputs to the "unspendable" list. See [TxBuilder.unspendable]. */
fun doNotSpendChange(): TxBuilder {}
/** Only spend utxos added by [add_utxo]. The wallet will not add additional utxos to the transaction even if they are needed to make the transaction valid. */
fun manuallySelectedOnly(): TxBuilder {}
/** Only spend change outputs. This effectively adds all the non-change outputs to the "unspendable" list. See [TxBuilder.unspendable]. */
fun onlySpendChange(): TxBuilder {}
/**
* Replace the internal list of unspendable utxos with a new list. Its important to note that the "must-be-spent" utxos
* added with [TxBuilder.addUtxo] have priority over these. See the Rust docs of the two linked methods for more details.
*/
fun unspendable(unspendable: List<OutPoint>): TxBuilder {}
/** Set a custom fee rate. */
fun feeRate(satPerVbyte: Float): TxBuilder {}
/** Set an absolute fee. */
fun feeAbsolute(feeAmount: ULong): TxBuilder {}
/** Spend all the available inputs. This respects filters like [TxBuilder.unspendable] and the change policy. */
fun drainWallet(): TxBuilder {}
/**
* Sets the address to drain excess coins to. Usually, when there are excess coins they are
* sent to a change address generated by the wallet. This option replaces the usual change address
* with an arbitrary ScriptPubKey of your choosing. Just as with a change output, if the
* drain output is not needed (the excess coins are too small) it will not be included in the resulting
* transaction. The only difference is that it is valid to use [drainTo] without setting any ordinary recipients
* with [addRecipient] (but it is perfectly fine to add recipients as well). If you choose not to set any
* recipients, you should either provide the utxos that the transaction should spend via [addUtxos], or set
* [drainWallet] to spend all of them. When bumping the fees of a transaction made with this option,
* you probably want to use [BumpFeeTxBuilder.allowShrinking] to allow this output to be reduced to pay for the extra fees.
*/
fun drainTo(script: Script): TxBuilder {}
/** Enable signaling RBF. This will use the default `nsequence` value of `0xFFFFFFFD`. */
fun enableRbf(): TxBuilder {}
/**
* Enable signaling RBF with a specific nSequence value. This can cause conflicts if the wallet's descriptors
* contain an "older" (OP_CSV) operator and the given `nsequence` is lower than the CSV value. If the `nsequence`
* is higher than `0xFFFFFFFD` an error will be thrown, since it would not be a valid nSequence to signal RBF.
*/
fun enableRbfWithSequence(nsequence: UInt): TxBuilder {}
/** Finish building the transaction. Returns a [TxBuilderResult]. */
fun finish(wallet: Wallet): TxBuilderResult {}
}
/**
* A object holding a ScriptPubKey and an amount.
*
* @property script The ScriptPubKey.
* @property amount The amount.
*/
data class ScriptAmount (
var script: Script,
var amount: ULong
)
/**
* The BumpFeeTxBuilder is used to bump the fee on a transaction that has been broadcast and has its RBF flag set to true.
*/
class BumpFeeTxBuilder() {
/**
* Explicitly tells the wallet that it is allowed to reduce the amount of the output matching this scriptPubKey
* in order to bump the transaction fee. Without specifying this the wallet will attempt to find a change output
* to shrink instead. Note that the output may shrink to below the dust limit and therefore be removed. If it is
* preserved then it is currently not guaranteed to be in the same position as it was originally. Returns an error
* if scriptPubkey cant be found among the recipients of the transaction we are bumping.
*/
fun allowShrinking(address: String): BumpFeeTxBuilder {}
/** Enable signaling RBF. This will use the default `nsequence` value of `0xFFFFFFFD`. */
fun enableRbf(): BumpFeeTxBuilder {}
/**
* Enable signaling RBF with a specific nSequence value. This can cause conflicts if the wallet's descriptors
* contain an "older" (OP_CSV) operator and the given `nsequence` is lower than the CSV value. If the `nsequence`
* is higher than `0xFFFFFFFD` an error will be thrown, since it would not be a valid nSequence to signal RBF.
*/
fun enableRbfWithSequence(nsequence: UInt): BumpFeeTxBuilder {}
/** Finish building the transaction. Returns a [TxBuilderResult]. */
fun finish(wallet: Wallet): TxBuilderResult {}
}
/**
* A BIP-32 derivation path.
*
* @param path The derivation path. Must start with `m`. Use this type to derive or extend a [DescriptorSecretKey]
* or [DescriptorPublicKey].
*/
class DerivationPath(path: String) {}
/**
* An extended secret key.
*
* @param network The network this DescriptorSecretKey is to be used on.
* @param mnemonic The mnemonic.
* @param password The optional passphrase that can be provided as per BIP-39.
*
* @sample org.bitcoindevkit.descriptorSecretKeyDeriveSample
* @sample org.bitcoindevkit.descriptorSecretKeyExtendSample
*/
class DescriptorSecretKey(network: Network, mnemonic: Mnemonic, password: String?) {
/** Build a DescriptorSecretKey from a String */
fun fromString(secretKey: String): DescriptorSecretKey {}
/** Derive a private descriptor at a given path. */
fun derive(path: DerivationPath): DescriptorSecretKey {}
/** Extend the private descriptor with a custom path. */
fun extend(path: DerivationPath): DescriptorSecretKey {}
/** Return the public version of the descriptor. */
fun asPublic(): DescriptorPublicKey {}
/** Return the raw private key as bytes. */
fun secretBytes(): List<UByte>
/** Return the private descriptor as a string. */
fun asString(): String {}
}
/**
* An extended public key.
*
* @param network The network this DescriptorPublicKey is to be used on.
* @param mnemonic The mnemonic.
* @param password The optional passphrase that can be provided as per BIP-39.
*/
class DescriptorPublicKey(network: Network, mnemonic: String, password: String?) {
/** Build a DescriptorPublicKey from a String */
fun fromString(publicKey: String): DescriptorPublicKey {}
/** Derive a public descriptor at a given path. */
fun derive(path: DerivationPath): DescriptorPublicKey
/** Extend the public descriptor with a custom path. */
fun extend(path: DerivationPath): DescriptorPublicKey
/** Return the public descriptor as a string. */
fun asString(): String
}
/**
* A output descriptor.
*
* @param descriptor The descriptor in string format.
* @param network The network this descriptor is to be used on.
*
* @sample org.bitcoindevkit.descriptorTemplates1
* @sample org.bitcoindevkit.descriptorTemplates2
*/
class Descriptor(descriptor: String, network: Network) {
/**
* BIP44 template. Expands to pkh(key/44'/{0,1}'/0'/{0,1}/\*)
* Since there are hardened derivation steps, this template requires a private derivable key (generally a xprv/tprv).
*/
fun newBip44(secretKey: DescriptorSecretKey, keychain: KeychainKind, network: Network) {}
/**
* BIP44 public template. Expands to pkh(key/{0,1}/\*)
* This assumes that the key used has already been derived with m/44'/0'/0' for Mainnet or m/44'/1'/0' for Testnet.
* This template requires the parent fingerprint to populate correctly the metadata of PSBTs.
*/
fun newBip44Public(publicKey: DescriptorPublicKey, fingerprint: String, keychain: KeychainKind, network: Network) {}
/**
* BIP49 template. Expands to sh(wpkh(key/49'/{0,1}'/0'/{0,1}/\*))
* Since there are hardened derivation steps, this template requires a private derivable key (generally a xprv/tprv).
*/
fun newBip49(secretKey: DescriptorSecretKey, keychain: KeychainKind, network: Network) {}
/**
* BIP49 public template. Expands to sh(wpkh(key/{0,1}/\*))
* This assumes that the key used has already been derived with m/49'/0'/0' for Mainnet or m/49'/1'/0' for Testnet.
* This template requires the parent fingerprint to populate correctly the metadata of PSBTs.
*/
fun newBip49Public(publicKey: DescriptorPublicKey, fingerprint: String, keychain: KeychainKind, network: Network) {}
/**
* BIP84 template. Expands to wpkh(key/84'/{0,1}'/0'/{0,1}/\*)
* Since there are hardened derivation steps, this template requires a private derivable key (generally a xprv/tprv).
*/
fun newBip84(secretKey: DescriptorSecretKey, keychain: KeychainKind, network: Network) {}
/**
* BIP84 public template. Expands to wpkh(key/{0,1}/\*)
* This assumes that the key used has already been derived with m/84'/0'/0' for Mainnet or m/84'/1'/0' for Testnet.
* This template requires the parent fingerprint to populate correctly the metadata of PSBTs.
*/
fun newBip84Public(publicKey: DescriptorPublicKey, fingerprint: String, keychain: KeychainKind, network: Network) {}
/** Return the public version of the output descriptor. */
fun asString(): String {}
/** Return the private version of the output descriptor if available, otherwise return the public version. */
fun asStringPrivate(): String {}
}
/**
* An enum describing entropy length (aka word count) in the mnemonic.
*/
enum class WordCount {
/** 12 words mnemonic (128 bits entropy). */
WORDS12,
/** 15 words mnemonic (160 bits entropy). */
WORDS15,
/** 18 words mnemonic (192 bits entropy). */
WORDS18,
/** 21 words mnemonic (224 bits entropy). */
WORDS21,
/** 24 words mnemonic (256 bits entropy). */
WORDS24,
}
/**
* The value returned from calling the `.finish()` method on the [TxBuilder] or [BumpFeeTxBuilder].
*
* @property psbt The PSBT
* @property transactionDetails The transaction details.
*
* @sample org.bitcoindevkit.txBuilderResultSample1
* @sample org.bitcoindevkit.txBuilderResultSample2
*/
data class TxBuilderResult (
var psbt: PartiallySignedBitcoinTransaction,
var transactionDetails: TransactionDetails
)
/**
* A bitcoin script.
*/
class Script(rawOutputScript: List<UByte>)
/**
* A bitcoin address.
*
* @param address The address in string format.
*/
class Address(address: String) {
/** Return the ScriptPubKey. */
fun scriptPubkey(): Script
}
/**
* Mnemonic phrases are a human-readable version of the private keys. Supported number of words are 12, 15, 18, 21 and 24.
*
* @constructor Generates Mnemonic with a random entropy.
* @param mnemonic The mnemonic as a string of space-separated words.
*
* @sample org.bitcoindevkit.mnemonicSample
*/
class Mnemonic(mnemonic: String) {
/* Returns Mnemonic as string */
fun asString(): String
/* Parse a Mnemonic from a given string. */
fun fromString(): Mnemonic
/*
* 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.
*/
fun fromEntropy(): Mnemonic
}

View File

@ -0,0 +1,276 @@
package org.bitcoindevkit
fun networkSample() {
val wallet = Wallet(
descriptor = descriptor,
changeDescriptor = changeDescriptor,
network = Network.TESTNET,
databaseConfig = DatabaseConfig.Memory
)
}
fun balanceSample() {
object LogProgress : Progress {
override fun update(progress: Float, message: String?) {}
}
val memoryDatabaseConfig = DatabaseConfig.Memory
private val blockchainConfig = BlockchainConfig.Electrum(
ElectrumConfig(
"ssl://electrum.blockstream.info:60002",
null,
5u,
null,
200u
)
)
val wallet = Wallet(descriptor, null, Network.TESTNET, memoryDatabaseConfig)
val blockchain = Blockchain(blockchainConfig)
wallet.sync(blockchain, LogProgress)
val balance: Balance = wallet.getBalance()
println("Total wallet balance is ${balance.total}")
}
fun electrumBlockchainConfigSample() {
val blockchainConfig = BlockchainConfig.Electrum(
ElectrumConfig(
url = "ssl://electrum.blockstream.info:60002",
socks5 = null,
retry = 5u,
timeout = null,
stopGap = 200u
)
)
}
fun esploraBlockchainConfigSample() {
val esploraURL: String = "http://10.0.2.2:3002"
val esploraConfig: EsploraConfig = EsploraConfig(
baseUrl = esploraURL,
proxy = null,
concurrency = 4u,
stopGap = 20UL,
timeout = null
)
val blockchainConfig = BlockchainConfig.Esplora(config = esploraConfig)
}
fun memoryDatabaseConfigSample() {
val memoryDatabaseConfig = DatabaseConfig.Memory
}
fun sqliteDatabaseConfigSample() {
val databaseConfig = DatabaseConfig.Sqlite(SqliteDbConfiguration("bdk-sqlite"))
}
fun addressIndexSample() {
val wallet: Wallet = Wallet(
descriptor = descriptor,
changeDescriptor = changeDescriptor,
network = Network.TESTNET,
databaseConfig = DatabaseConfig.Memory
)
fun getLastUnusedAddress(): AddressInfo {
return wallet.getAddress(AddressIndex.LAST_UNUSED)
}
}
fun addressInfoSample() {
val wallet: Wallet = Wallet(
descriptor = descriptor,
changeDescriptor = changeDescriptor,
network = Network.TESTNET,
databaseConfig = DatabaseConfig.Memory
)
fun getLastUnusedAddress(): AddressInfo {
return wallet.getAddress(AddressIndex.NEW)
}
val newAddress: AddressInfo = getLastUnusedAddress()
println("New address at index ${newAddress.index} is ${newAddress.address}")
}
fun blockchainSample() {
val blockchainConfig: BlockchainConfig = BlockchainConfig.Electrum(
ElectrumConfig(
electrumURL,
null,
5u,
null,
10u
)
)
val blockchain: Blockchain = Blockchain(blockchainConfig)
blockchain.broadcast(signedPsbt)
}
fun txBuilderResultSample1() {
val faucetAddress = Address("tb1ql7w62elx9ucw4pj5lgw4l028hmuw80sndtntxt")
// TxBuilderResult is a data class, which means you can use destructuring declarations on it to
// open it up into its component parts
val (psbt, txDetails) = TxBuilder()
.addRecipient(faucetAddress.scriptPubkey(), 1000uL)
.feeRate(1.2f)
.finish(wallet)
println("Txid is ${txDetails.txid}")
wallet.sign(psbt)
}
fun txBuilderResultSample2() {
val faucetAddress = Address("tb1ql7w62elx9ucw4pj5lgw4l028hmuw80sndtntxt")
val txBuilderResult: TxBuilderResult = TxBuilder()
.addRecipient(faucetAddress.scriptPubkey(), 1000uL)
.feeRate(1.2f)
.finish(wallet)
val psbt = txBuilderResult.psbt
val txDetails = txBuilderResult.transactionDetails
println("Txid is ${txDetails.txid}")
wallet.sign(psbt)
}
fun descriptorSecretKeyExtendSample() {
// The `DescriptorSecretKey.extend()` method allows you to extend a key to any given path.
// val mnemonic: String = generateMnemonic(WordCount.WORDS12)
val mnemonic: Mnemonic = Mnemonic("scene change clap smart together mind wheel knee clip normal trial unusual")
// the initial DescriptorSecretKey will always be at the "master" node,
// i.e. the derivation path is empty
val bip32RootKey: DescriptorSecretKey = DescriptorSecretKey(
network = Network.TESTNET,
mnemonic = mnemonic,
password = ""
)
println(bip32RootKey.asString())
// tprv8ZgxMBicQKsPfM8Trx2apvdEkmxbJkYY3ZsmcgKb2bfnLNcBhtCstqQTeFesMRLEJXpjGDinAUJUHprXMwph8dQBdS1HAoxEis8Knimxovf/*
// the derive method will also automatically apply the wildcard (*) to your path,
// i.e the following will generate the typical testnet BIP84 external wallet path
// m/84h/1h/0h/0/*
val bip84ExternalPath: DerivationPath = DerivationPath("m/84h/1h/0h/0")
val externalExtendedKey: DescriptorSecretKey = bip32RootKey.extend(bip84ExternalPath).asString()
println(externalExtendedKey)
// tprv8ZgxMBicQKsPfM8Trx2apvdEkmxbJkYY3ZsmcgKb2bfnLNcBhtCstqQTeFesMRLEJXpjGDinAUJUHprXMwph8dQBdS1HAoxEis8Knimxovf/84'/1'/0'/0/*
// to create the descriptor you'll need to use this extended key in a descriptor function,
// i.e. wpkh(), tr(), etc.
val externalDescriptor = "wpkh($externalExtendedKey)"
}
fun descriptorSecretKeyDeriveSample() {
// The DescriptorSecretKey.derive() method allows you to derive an extended key for a given
// node in the derivation tree (for example to create an xpub for a particular account)
val mnemonic: Mnemonic = Mnemonic("scene change clap smart together mind wheel knee clip normal trial unusual")
val bip32RootKey: DescriptorSecretKey = DescriptorSecretKey(
network = Network.TESTNET,
mnemonic = mnemonic,
password = ""
)
val bip84Account0: DerivationPath = DerivationPath("m/84h/1h/0h")
val xpubAccount0: DescriptorSecretKey = bip32RootKey.derive(bip84Account0)
println(xpubAccount0.asString())
// [5512949b/84'/1'/0']tprv8ghw3FWfWTeLCEXcr8f8Q8Lz4QPCELYv3jhBXjAm7XagA6R5hreeWLTJeLBfMj7Ni6Q3PdV1o8NbvNBHE59W97EkRJSU4JkvTQjaNUmQubE/*
val internalPath: DerivationPath = DerivationPath("m/0")
val externalExtendedKey = xpubAccount0.extend(internalPath).asString()
println(externalExtendedKey)
// [5512949b/84'/1'/0']tprv8ghw3FWfWTeLCEXcr8f8Q8Lz4QPCELYv3jhBXjAm7XagA6R5hreeWLTJeLBfMj7Ni6Q3PdV1o8NbvNBHE59W97EkRJSU4JkvTQjaNUmQubE/0/*
// to create the descriptor you'll need to use this extended key in a descriptor function,
// i.e. wpkh(), tr(), etc.
val externalDescriptor = "wpkh($externalExtendedKey)"
}
fun createTransaction() {
val wallet = BdkWallet(
descriptor = externalDescriptor,
changeDescriptor = internalDescriptor,
network = Network.TESTNET,
databaseConfig = memoryDatabaseConfig,
)
val blockchainConfig = BlockchainConfig.Electrum(
ElectrumConfig(
"ssl://electrum.blockstream.info:60002",
null,
5u,
null,
200u
)
)
val paymentAddress: Address = Address("tb1ql7w62elx9ucw4pj5lgw4l028hmuw80sndtntxt")
val (psbt, txDetails) = TxBuilder()
.addRecipient(faucetAddress.scriptPubkey(), 1000uL)
.feeRate(1.2f)
.finish(wallet)
wallet.sign(psbt)
blockchain.broadcast(psbt)
}
fun walletSample() {
val externalDescriptor = "wpkh(tprv8hwWMmPE4BVNxGdVt3HhEERZhondQvodUY7Ajyseyhudr4WabJqWKWLr4Wi2r26CDaNCQhhxEfVULesmhEfZYyBXdE/84h/1h/0h/0/*)"
val internalDescriptor = "wpkh(tprv8hwWMmPE4BVNxGdVt3HhEERZhondQvodUY7Ajyseyhudr4WabJqWKWLr4Wi2r26CDaNCQhhxEfVULesmhEfZYyBXdE/84h/1h/0h/1/*)"
val sqliteDatabaseConfig = DatabaseConfig.Sqlite(SqliteDbConfiguration("bdk-sqlite"))
val wallet = Wallet(
descriptor = externalDescriptor,
changeDescriptor = internalDescriptor,
network = Network.TESTNET,
databaseConfig = sqliteDatabaseConfig,
)
}
fun mnemonicSample() {
val mnemonic0: Mnemonic = Mnemonic(WordCount.WORDS12)
val mnemonic1: Mnemonic = Mnemonic.fromString("scene change clap smart together mind wheel knee clip normal trial unusual")
val entropy: List<UByte> = listOf<UByte>(0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u)
val mnemonic2: Mnemonic = Mnemonic.fromEntropy(entropy)
println(mnemonic0.asString(), mnemonic1.asString(), mnemonic2.asString())
}
fun descriptorTemplates1() {
// Bip84 private descriptor
val recoveryPhrase: String = "scene change clap smart together mind wheel knee clip normal trial unusual"
val mnemonic = Mnemonic.fromString(recoveryPhrase)
val bip32ExtendedRootKey: DescriptorSecretKey = DescriptorSecretKey(Network.TESTNET, mnemonic, null)
val bip84ExternalDescriptor: Descriptor = Descriptor.newBip84(bip32ExtendedRootKey, KeychainKind.EXTERNAL, Network.TESTNET)
}
fun descriptorTemplates2() {
// Bip49 public descriptor
// assume we already have the xpub for m/49'/0'/1' created on an external device that only shared the xpub with the wallet
// using the template requires the parent fingerprint to populate correctly the metadata of PSBTs, which the external device would provide
// the xpub (tpub for testnet): tpubDC65ZRvk1NDddHrVAUAZrUPJ772QXzooNYmPywYF9tMyNLYKf5wpKE7ZJvK9kvfG3FV7rCsHBNXy1LVKW95jrmC7c7z4hq7a27aD2sRrAhR
// the fingerprint: d1d04177
val descriptorPublicKey: DescriptorPublicKey = DescriptorPublicKey.fromString("tpubDC65ZRvk1NDddHrVAUAZrUPJ772QXzooNYmPywYF9tMyNLYKf5wpKE7ZJvK9kvfG3FV7rCsHBNXy1LVKW95jrmC7c7z4hq7a27aD2sRrAhR")
val bip49PublicDescriptor: Descriptor = Descriptor.newBip49Public(
publicKey = descriptorPublicKey,
fingerprint = "d1d04177",
keychain = KeychainKind.EXTERNAL,
network = Network.TESTNET,
)
println(bip49PublicDescriptor.asString()) // sh(wpkh([d1d04177/49'/1'/0']tpubDC65ZRvk1NDddHrVAUAZrUPJ772QXzooNYmPywYF9tMyNLYKf5wpKE7ZJvK9kvfG3FV7rCsHBNXy1LVKW95jrmC7c7z4hq7a27aD2sRrAhR/0/*))#a7lxzefl
println(bip49PublicDescriptor.asStringPrivate()) // sh(wpkh([d1d04177/49'/1'/0']tpubDC65ZRvk1NDddHrVAUAZrUPJ772QXzooNYmPywYF9tMyNLYKf5wpKE7ZJvK9kvfG3FV7rCsHBNXy1LVKW95jrmC7c7z4hq7a27aD2sRrAhR/0/*))#a7lxzefl
// Creating it starting from the full xprv derived from a mnemonic will give you the same public descriptor
val mnemonic = Mnemonic.fromString("chaos fabric time speed sponsor all flat solution wisdom trophy crack object robot pave observe combine where aware bench orient secret primary cable detect")
val bip32ExtendedRootKey: DescriptorSecretKey = DescriptorSecretKey(Network.TESTNET, mnemonic, null)
val bip49PrivateDescriptor: Descriptor = Descriptor.newBip49(bip32ExtendedRootKey, KeychainKind.EXTERNAL, Network.TESTNET)
println(bip49PrivateDescriptor.asString()) // sh(wpkh([d1d04177/49'/1'/0']tpubDC65ZRvk1NDddHrVAUAZrUPJ772QXzooNYmPywYF9tMyNLYKf5wpKE7ZJvK9kvfG3FV7rCsHBNXy1LVKW95jrmC7c7z4hq7a27aD2sRrAhR/0/*))#a7lxzefl
}

View File

@ -13,6 +13,24 @@ dependencies {
}
```
You may then import and use the `org.bitcoindevkit` library in your Kotlin code. For example:
```kotlin
import org.bitcoindevkit.*
// ...
val externalDescriptor = "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)"
val internalDescriptor = "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)"
val databaseConfig = DatabaseConfig.Memory
val blockchainConfig = BlockchainConfig.Electrum(
ElectrumConfig("ssl://electrum.blockstream.info:60002", null, 5u, null, 10u, true)
)
val wallet = Wallet(externalDescriptor, internalDescriptor, Network.TESTNET, databaseConfig, blockchainConfig)
val newAddress = wallet.getAddress(AddressIndex.LAST_UNUSED)
```
### Snapshot releases
To use a snapshot release, specify the snapshot repository url in the `repositories` block and use the snapshot version in the `dependencies` block:
```kotlin
@ -26,46 +44,36 @@ dependencies {
```
### Example Projects
* [bdk-kotlin-example-wallet](https://github.com/bitcoindevkit/bdk-kotlin-example-wallet)
* [Devkit Wallet](https://github.com/thunderbiscuit/devkit-wallet)
* [Padawan Wallet](https://github.com/thunderbiscuit/padawan-wallet)
### How to build
_Note that Kotlin version `1.9.23` or later is required to build the library._
_Note that Kotlin version `1.6.10` or later is required to build the library._
1. Clone this repository.
```shell
git clone https://github.com/bitcoindevkit/bdk-ffi
```
2. Follow the "General" bdk-ffi ["Getting Started (Developer)"] instructions.
3. Install Rust (note that we are currently building using Rust 1.77.1):
```shell
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
rustup default 1.77.1
```
3. If building on macOS install required intel and m1 jvm targets
4. Install required targets
```sh
rustup target add x86_64-linux-android aarch64-linux-android armv7-linux-androideabi
```
```sh
rustup target add x86_64-linux-android aarch64-linux-android armv7-linux-androideabi
```
5. Install Android SDK and Build-Tools for API level 30+
6. Setup `ANDROID_SDK_ROOT` and `ANDROID_NDK_ROOT` path variables which are required by the build tool. Note that currently, NDK version 25.2.9519653 or above is required. For example:
```shell
# macOS
export ANDROID_SDK_ROOT=~/Library/Android/sdk
export ANDROID_NDK_ROOT=$ANDROID_SDK_ROOT/ndk/25.2.9519653
# linux
export ANDROID_SDK_ROOT=/usr/local/lib/android/sdk
export ANDROID_NDK_ROOT=$ANDROID_SDK_ROOT/ndk/25.2.9519653
```
6. Setup `$ANDROID_SDK_ROOT` and `$ANDROID_NDK_ROOT` path variables (which are required by the
build tool), for example (NDK major version 21 is required):
```shell
export ANDROID_SDK_ROOT=~/Android/Sdk
export ANDROID_NDK_ROOT=$ANDROID_SDK_ROOT/ndk/21.<NDK_VERSION>
```
7. Build kotlin bindings
```sh
# build Android library
cd bdk-android
./gradlew buildAndroidLib
```
1. Start android emulator and run tests
8. Start android emulator (must be x86_64) and run tests
```sh
./gradlew connectedAndroidTest
```
@ -73,7 +81,7 @@ export ANDROID_NDK_ROOT=$ANDROID_SDK_ROOT/ndk/25.2.9519653
## How to publish to your local Maven repo
```shell
cd bdk-android
./gradlew publishToMavenLocal -P localBuild
./gradlew publishToMavenLocal --exclude-task signMavenPublication
```
Note that the commands assume you don't need the local libraries to be signed. If you do wish to sign them, simply set your `~/.gradle/gradle.properties` signing key values like so:
@ -82,13 +90,12 @@ signing.gnupg.keyName=<YOUR_GNUPG_ID>
signing.gnupg.passphrase=<YOUR_GNUPG_PASSPHRASE>
```
and use the `publishToMavenLocal` task without the `localBuild` flag:
and use the `publishToMavenLocal` task without excluding the signing task:
```shell
./gradlew publishToMavenLocal
```
## Known issues
### JNA dependency
Depending on the JVM version you use, you might not have the JNA dependency on your classpath. The exception thrown will be
```shell
class file for com.sun.jna.Pointer not found
@ -101,8 +108,5 @@ dependencies {
}
```
### x86 emulators
For some older versions of macOS, Android Studio will recommend users install the x86 version of the emulator by default. This will not work with the bdk-android library, as we do not support 32-bit architectures. Make sure you install an x86_64 emulator to work with bdk-android.
[`bdk`]: https://github.com/bitcoindevkit/bdk
[`bdk-ffi`]: https://github.com/bitcoindevkit/bdk-ffi

View File

@ -1,10 +1,14 @@
buildscript {
repositories {
google()
}
dependencies {
classpath("com.android.tools.build:gradle:7.1.2")
}
}
plugins {
id("com.android.library").version("8.3.1").apply(false)
id("org.jetbrains.kotlin.android").version("1.9.23").apply(false)
id("org.gradle.maven-publish")
id("org.gradle.signing")
id("org.bitcoindevkit.plugins.generate-android-bindings").apply(false)
id("io.github.gradle-nexus.publish-plugin").version("1.1.0").apply(true)
id("io.github.gradle-nexus.publish-plugin") version "1.1.0"
}
// library version is defined in gradle.properties

View File

@ -2,4 +2,4 @@ org.gradle.jvmargs=-Xmx1536m
android.useAndroidX=true
android.enableJetifier=true
kotlin.code.style=official
libraryVersion=1.0.0-alpha.12-SNAPSHOT
libraryVersion=0.26.0

Binary file not shown.

View File

@ -1,7 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-all.zip
networkTimeout=10000
validateDistributionUrl=true
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.2-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

41
bdk-android/gradlew vendored
View File

@ -55,7 +55,7 @@
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
@ -80,11 +80,13 @@ do
esac
done
# This is normally unused
# shellcheck disable=SC2034
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
APP_NAME="Gradle"
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
@ -131,29 +133,22 @@ location of your Java installation."
fi
else
JAVACMD=java
if ! command -v java >/dev/null 2>&1
then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
@ -198,15 +193,11 @@ if "$cygwin" || "$msys" ; then
done
fi
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in
# double quotes to make sure that they get re-expanded; and
# * put everything else in single quotes, so that it's not re-expanded.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
@ -214,12 +205,6 @@ set -- \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.

View File

@ -14,7 +14,7 @@
@rem limitations under the License.
@rem
@if "%DEBUG%"=="" @echo off
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@ -25,8 +25,7 @@
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@ -41,7 +40,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
if "%ERRORLEVEL%" == "0" goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
@ -76,15 +75,13 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
:end
@rem End local scope for the variables with windows NT shell
if %ERRORLEVEL% equ 0 goto mainEnd
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal

View File

@ -1,20 +0,0 @@
default:
just --list
build:
./gradlew buildAndroidLib
clean:
rm -rf ../bdk-ffi/target/
rm -rf ./build/
rm -rf ./lib/build/
rm -rf ./plugins/build/
publish-local:
./gradlew publishToMavenLocal -P localBuild
test:
./gradlew connectedAndroidTest
test-specific TEST:
./gradlew test --tests {{TEST}}

View File

@ -5,21 +5,25 @@ val libraryVersion: String by project
plugins {
id("com.android.library")
id("org.jetbrains.kotlin.android")
id("org.gradle.maven-publish")
id("org.gradle.signing")
id("org.jetbrains.kotlin.android") version "1.6.10"
id("maven-publish")
id("signing")
// Custom plugin to generate the native libs and bindings file
id("org.bitcoindevkit.plugins.generate-android-bindings")
}
repositories {
mavenCentral()
google()
}
android {
namespace = "org.bitcoindevkit"
compileSdk = 34
compileSdk = 31
defaultConfig {
minSdk = 24
targetSdk = 34
minSdk = 21
targetSdk = 31
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles("consumer-rules.pro")
}
@ -39,22 +43,8 @@ android {
}
}
kotlin {
tasks.withType<KotlinCompile>().configureEach {
kotlinOptions {
jvmTarget = "17"
}
}
}
java {
toolchain {
languageVersion.set(JavaLanguageVersion.of(17))
}
}
dependencies {
implementation("net.java.dev.jna:jna:5.14.0@aar")
implementation("net.java.dev.jna:jna:5.8.0@aar")
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk7")
implementation("androidx.appcompat:appcompat:1.4.0")
implementation("androidx.core:core-ktx:1.7.0")
@ -63,8 +53,7 @@ dependencies {
androidTestImplementation("com.github.tony19:logback-android:2.0.0")
androidTestImplementation("androidx.test.ext:junit:1.1.3")
androidTestImplementation("androidx.test.espresso:espresso-core:3.4.0")
androidTestImplementation("org.jetbrains.kotlin:kotlin-test:1.6.10")
androidTestImplementation("org.jetbrains.kotlin:kotlin-test-junit:1.6.10")
androidTestImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.1")
}
afterEvaluate {
@ -92,9 +81,14 @@ afterEvaluate {
}
developers {
developer {
id.set("bdkdevelopers")
name.set("Bitcoin Dev Kit Developers")
email.set("dev@bitcoindevkit.org")
id.set("notmandatory")
name.set("Steve Myers")
email.set("notmandatory@noreply.github.org")
}
developer {
id.set("artfuldev")
name.set("Sudarsan Balaji")
email.set("sudarsan.balaji@artfuldev.com")
}
}
scm {
@ -106,22 +100,9 @@ afterEvaluate {
}
}
}
// This is required because we must ensure the moveNativeAndroidLibs task is executed after
// the mergeReleaseJniLibFolders (hard requirement introduced by our upgrade to Gradle 8.7)
tasks.named("mergeReleaseJniLibFolders") {
dependsOn(":lib:moveNativeAndroidLibs")
}
tasks.named("mergeDebugJniLibFolders") {
dependsOn(":lib:moveNativeAndroidLibs")
}
}
signing {
if (project.hasProperty("localBuild")) {
isRequired = false
}
val signingKeyId: String? by project
val signingKey: String? by project
val signingPassword: String? by project
@ -129,7 +110,8 @@ signing {
sign(publishing.publications)
}
// This task dependency ensures that we build the bindings binaries before running the tests
// This task dependency ensures that we build the bindings
// binaries before running the tests
tasks.withType<KotlinCompile> {
dependsOn("buildAndroidLib")
}

View File

@ -0,0 +1,81 @@
package org.bitcoindevkit
import org.junit.Assert.*
import org.junit.Test
import android.app.Application
import android.content.Context.MODE_PRIVATE
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.runner.RunWith
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import java.io.File
/**
* Instrumented test, which will execute on an Android device.
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
@RunWith(AndroidJUnit4::class)
class AndroidLibTest {
private fun getTestDataDir(): String {
val context = ApplicationProvider.getApplicationContext<Application>()
return context.getDir("bdk-test", MODE_PRIVATE).toString()
}
private fun cleanupTestDataDir(testDataDir: String) {
File(testDataDir).deleteRecursively()
}
class LogProgress : Progress {
private val log: Logger = LoggerFactory.getLogger(AndroidLibTest::class.java)
override fun update(progress: Float, message: String?) {
log.debug("Syncing...")
}
}
private val descriptor = Descriptor("wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)", Network.TESTNET)
private val databaseConfig = DatabaseConfig.Memory
private val blockchainConfig = BlockchainConfig.Electrum(
ElectrumConfig(
"ssl://electrum.blockstream.info:60002",
null,
5u,
null,
100u,
true,
)
)
@Test
fun memoryWalletNewAddress() {
val wallet = Wallet(descriptor, null, Network.TESTNET, databaseConfig)
val address = wallet.getAddress(AddressIndex.NEW).address
assertEquals("tb1qzg4mckdh50nwdm9hkzq06528rsu73hjxxzem3e", address)
}
@Test
fun memoryWalletSyncGetBalance() {
val wallet = Wallet(descriptor, null, Network.TESTNET, databaseConfig)
val blockchain = Blockchain(blockchainConfig)
wallet.sync(blockchain, LogProgress())
val balance: Balance = wallet.getBalance()
assertTrue(balance.total > 0u)
}
@Test
fun sqliteWalletSyncGetBalance() {
val testDataDir = getTestDataDir() + "/bdk-wallet.sqlite"
val databaseConfig = DatabaseConfig.Sqlite(SqliteDbConfiguration(testDataDir))
val wallet = Wallet(descriptor, null, Network.TESTNET, databaseConfig)
val blockchain = Blockchain(blockchainConfig)
wallet.sync(blockchain, LogProgress())
val balance: Balance = wallet.getBalance()
assertTrue(balance.total > 0u)
cleanupTestDataDir(testDataDir)
}
}

View File

@ -1,82 +0,0 @@
package org.bitcoindevkit
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import org.junit.Test
import org.junit.runner.RunWith
import java.io.File
import kotlin.test.AfterTest
import kotlin.test.assertTrue
private const val SIGNET_ESPLORA_URL = "http://signet.bitcoindevkit.net"
private const val TESTNET_ESPLORA_URL = "https://esplora.testnet.kuutamo.cloud"
@RunWith(AndroidJUnit4::class)
class LiveTxBuilderTest {
private val persistenceFilePath = InstrumentationRegistry.getInstrumentation().targetContext.filesDir.path + "/bdk_persistence3.sqlite"
private val descriptor = Descriptor("wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)", Network.SIGNET)
private val changeDescriptor = Descriptor("wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/1/*)", Network.SIGNET)
@AfterTest
fun cleanup() {
val file = File(persistenceFilePath)
if (file.exists()) {
file.delete()
}
}
@Test
fun testTxBuilder() {
val wallet = Wallet(descriptor, changeDescriptor, Network.SIGNET)
val esploraClient: EsploraClient = EsploraClient(SIGNET_ESPLORA_URL)
val fullScanRequest: FullScanRequest = wallet.startFullScan()
val update = esploraClient.fullScan(fullScanRequest, 10uL, 1uL)
wallet.applyUpdate(update)
println("Balance: ${wallet.balance().total.toSat()}")
assert(wallet.balance().total.toSat() > 0uL) {
"Wallet balance must be greater than 0! Please send funds to ${wallet.revealNextAddress(KeychainKind.EXTERNAL).address} and try again."
}
val recipient: Address = Address("tb1qrnfslnrve9uncz9pzpvf83k3ukz22ljgees989", Network.SIGNET)
val psbt: Psbt = TxBuilder()
.addRecipient(recipient.scriptPubkey(), Amount.fromSat(4200uL))
.feeRate(FeeRate.fromSatPerVb(2uL))
.finish(wallet)
println(psbt.serialize())
assertTrue(psbt.serialize().startsWith("cHNi"), "PSBT should start with 'cHNi'")
}
@Test
fun complexTxBuilder() {
val wallet = Wallet(descriptor, changeDescriptor, Network.SIGNET)
val esploraClient: EsploraClient = EsploraClient(SIGNET_ESPLORA_URL)
val fullScanRequest: FullScanRequest = wallet.startFullScan()
val update = esploraClient.fullScan(fullScanRequest, 10uL, 1uL)
wallet.applyUpdate(update)
println("Balance: ${wallet.balance().total.toSat()}")
assert(wallet.balance().total.toSat() > 0uL) {
"Wallet balance must be greater than 0! Please send funds to ${wallet.revealNextAddress(KeychainKind.EXTERNAL).address} and try again."
}
val recipient1: Address = Address("tb1qrnfslnrve9uncz9pzpvf83k3ukz22ljgees989", Network.SIGNET)
val recipient2: Address = Address("tb1qw2c3lxufxqe2x9s4rdzh65tpf4d7fssjgh8nv6", Network.SIGNET)
val allRecipients: List<ScriptAmount> = listOf(
ScriptAmount(recipient1.scriptPubkey(), Amount.fromSat(4200uL)),
ScriptAmount(recipient2.scriptPubkey(), Amount.fromSat(4200uL)),
)
val psbt: Psbt = TxBuilder()
.setRecipients(allRecipients)
.feeRate(FeeRate.fromSatPerVb(4uL))
.changePolicy(ChangeSpendPolicy.CHANGE_FORBIDDEN)
.enableRbf()
.finish(wallet)
wallet.sign(psbt)
assertTrue(psbt.serialize().startsWith("cHNi"), "PSBT should start with 'cHNi'")
}
}

View File

@ -1,91 +0,0 @@
package org.bitcoindevkit
import org.junit.Test
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import org.junit.runner.RunWith
import java.io.File
import kotlin.test.AfterTest
import kotlin.test.assertTrue
private const val SIGNET_ESPLORA_URL = "http://signet.bitcoindevkit.net"
private const val TESTNET_ESPLORA_URL = "https://esplora.testnet.kuutamo.cloud"
@RunWith(AndroidJUnit4::class)
class LiveWalletTest {
private val persistenceFilePath = InstrumentationRegistry
.getInstrumentation().targetContext.filesDir.path + "/bdk_persistence2.sqlite"
private val descriptor: Descriptor = Descriptor("wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)", Network.SIGNET)
private val changeDescriptor: Descriptor = Descriptor("wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/1/*)", Network.SIGNET)
@AfterTest
fun cleanup() {
val file = File(persistenceFilePath)
if (file.exists()) {
file.delete()
}
}
@Test
fun testSyncedBalance() {
val wallet: Wallet = Wallet(descriptor, changeDescriptor, Network.SIGNET)
val esploraClient: EsploraClient = EsploraClient(SIGNET_ESPLORA_URL)
val fullScanRequest: FullScanRequest = wallet.startFullScan()
val update = esploraClient.fullScan(fullScanRequest, 10uL, 1uL)
wallet.applyUpdate(update)
println("Balance: ${wallet.balance().total.toSat()}")
val balance: Balance = wallet.balance()
println("Balance: $balance")
assert(wallet.balance().total.toSat() > 0uL) {
"Wallet balance must be greater than 0! Please send funds to ${wallet.revealNextAddress(KeychainKind.EXTERNAL).address} and try again."
}
println("Transactions count: ${wallet.transactions().count()}")
val transactions = wallet.transactions().take(3)
for (tx in transactions) {
val sentAndReceived = wallet.sentAndReceived(tx.transaction)
println("Transaction: ${tx.transaction.computeTxid()}")
println("Sent ${sentAndReceived.sent}")
println("Received ${sentAndReceived.received}")
}
}
@Test
fun testBroadcastTransaction() {
val wallet = Wallet(descriptor, changeDescriptor, Network.SIGNET)
val esploraClient = EsploraClient(SIGNET_ESPLORA_URL)
val fullScanRequest: FullScanRequest = wallet.startFullScan()
val update = esploraClient.fullScan(fullScanRequest, 10uL, 1uL)
wallet.applyUpdate(update)
println("Balance: ${wallet.balance().total.toSat()}")
assert(wallet.balance().total.toSat() > 0uL) {
"Wallet balance must be greater than 0! Please send funds to ${wallet.revealNextAddress(KeychainKind.EXTERNAL).address} and try again."
}
val recipient: Address = Address("tb1qrnfslnrve9uncz9pzpvf83k3ukz22ljgees989", Network.SIGNET)
val psbt: Psbt = TxBuilder()
.addRecipient(recipient.scriptPubkey(), Amount.fromSat(4200uL))
.feeRate(FeeRate.fromSatPerVb(4uL))
.finish(wallet)
println(psbt.serialize())
assertTrue(psbt.serialize().startsWith("cHNi"), "PSBT should start with 'cHNi'")
val walletDidSign = wallet.sign(psbt)
assertTrue(walletDidSign)
val tx: Transaction = psbt.extractTx()
println("Txid is: ${tx.computeTxid()}")
val txFee: Amount = wallet.calculateFee(tx)
println("Tx fee is: ${txFee.toSat()}")
val feeRate: FeeRate = wallet.calculateFeeRate(tx)
println("Tx fee rate is: ${feeRate.toSatPerVbCeil()} sat/vB")
esploraClient.broadcast(tx)
}
}

View File

@ -1,21 +0,0 @@
package org.bitcoindevkit
import kotlin.test.Test
import kotlin.test.assertEquals
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class OfflineDescriptorTest {
@Test
fun testDescriptorBip86() {
val mnemonic: Mnemonic = Mnemonic.fromString("space echo position wrist orient erupt relief museum myself grain wisdom tumble")
val descriptorSecretKey: DescriptorSecretKey = DescriptorSecretKey(Network.TESTNET, mnemonic, null)
val descriptor: Descriptor = Descriptor.newBip86(descriptorSecretKey, KeychainKind.EXTERNAL, Network.TESTNET)
assertEquals(
expected = "tr([be1eec8f/86'/1'/0']tpubDCTtszwSxPx3tATqDrsSyqScPNnUChwQAVAkanuDUCJQESGBbkt68nXXKRDifYSDbeMa2Xg2euKbXaU3YphvGWftDE7ozRKPriT6vAo3xsc/0/*)#m7puekcx",
actual = descriptor.toString()
)
}
}

View File

@ -1,70 +0,0 @@
package org.bitcoindevkit
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertTrue
import kotlin.test.assertFalse
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import org.junit.runner.RunWith
import java.io.File
import kotlin.test.AfterTest
@RunWith(AndroidJUnit4::class)
class OfflineWalletTest {
private val persistenceFilePath = InstrumentationRegistry
.getInstrumentation().targetContext.filesDir.path + "/bdk_persistence1.sqlite"
private val descriptor = Descriptor("wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)", Network.TESTNET)
private val changeDescriptor = Descriptor("wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/1/*)", Network.TESTNET)
@AfterTest
fun cleanup() {
val file = File(persistenceFilePath)
if (file.exists()) {
file.delete()
}
}
@Test
fun testDescriptorBip86() {
val mnemonic: Mnemonic = Mnemonic(WordCount.WORDS12)
val descriptorSecretKey: DescriptorSecretKey = DescriptorSecretKey(Network.TESTNET, mnemonic, null)
val descriptor: Descriptor = Descriptor.newBip86(descriptorSecretKey, KeychainKind.EXTERNAL, Network.TESTNET)
assertTrue(descriptor.toString().startsWith("tr"), "Bip86 Descriptor does not start with 'tr'")
}
@Test
fun testNewAddress() {
val wallet: Wallet = Wallet(
descriptor,
changeDescriptor,
Network.TESTNET
)
val addressInfo: AddressInfo = wallet.revealNextAddress(KeychainKind.EXTERNAL)
assertTrue(addressInfo.address.isValidForNetwork(Network.TESTNET), "Address is not valid for testnet network")
assertTrue(addressInfo.address.isValidForNetwork(Network.SIGNET), "Address is not valid for signet network")
assertFalse(addressInfo.address.isValidForNetwork(Network.REGTEST), "Address is valid for regtest network, but it shouldn't be")
assertFalse(addressInfo.address.isValidForNetwork(Network.BITCOIN), "Address is valid for bitcoin network, but it shouldn't be")
assertEquals(
expected = "tb1qrnfslnrve9uncz9pzpvf83k3ukz22ljgees989",
actual = addressInfo.address.toString()
)
}
@Test
fun testBalance() {
val wallet: Wallet = Wallet(
descriptor,
changeDescriptor,
Network.TESTNET
)
assertEquals(
expected = 0uL,
actual = wallet.balance().total.toSat()
)
}
}

View File

@ -1,3 +1,6 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.bitcoindevkit">
<uses-permission android:name="android.permission.INTERNET" />
</manifest>

View File

@ -1,5 +1,6 @@
package org.bitcoindevkit.plugins
val operatingSystem: OS = when {
System.getProperty("os.name").contains("mac", ignoreCase = true) -> OS.MAC
System.getProperty("os.name").contains("linux", ignoreCase = true) -> OS.LINUX

View File

@ -17,27 +17,31 @@ internal class UniFfiAndroidPlugin : Plugin<Project> {
OS.OTHER -> throw Error("Cannot build Android library from current architecture")
}
// if ANDROID_NDK_ROOT is not set, stop build
if (System.getenv("ANDROID_NDK_ROOT") == null) {
throw IllegalStateException("ANDROID_NDK_ROOT environment variable is not set; cannot build library")
}
// arm64-v8a is the most popular hardware architecture for Android
val buildAndroidAarch64Binary by tasks.register<Exec>("buildAndroidAarch64Binary") {
workingDir("${projectDir}/../../bdk-ffi")
val cargoArgs: List<String> = listOf("build", "--profile", "release-smaller", "--target", "aarch64-linux-android")
val cargoArgs: MutableList<String> =
mutableListOf("build", "--profile", "release-smaller", "--target", "aarch64-linux-android")
executable("cargo")
args(cargoArgs)
// if ANDROID_NDK_ROOT is not set then set it to github actions default
if (System.getenv("ANDROID_NDK_ROOT") == null) {
environment(
Pair("ANDROID_NDK_ROOT", "${System.getenv("ANDROID_SDK_ROOT")}/ndk-bundle")
)
}
environment(
// add build toolchain to PATH
Pair("PATH", "${System.getenv("PATH")}:${System.getenv("ANDROID_NDK_ROOT")}/toolchains/llvm/prebuilt/$llvmArchPath/bin"),
Pair("CFLAGS", "-D__ANDROID_MIN_SDK_VERSION__=24"),
Pair("AR", "llvm-ar"),
Pair("CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER", "aarch64-linux-android24-clang"),
Pair("CC", "aarch64-linux-android24-clang")
Pair("PATH",
"${System.getenv("PATH")}:${System.getenv("ANDROID_NDK_ROOT")}/toolchains/llvm/prebuilt/$llvmArchPath/bin"),
Pair("CFLAGS", "-D__ANDROID_API__=21"),
Pair("CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER", "aarch64-linux-android21-clang"),
Pair("CC", "aarch64-linux-android21-clang")
)
doLast {
@ -49,18 +53,27 @@ internal class UniFfiAndroidPlugin : Plugin<Project> {
val buildAndroidX86_64Binary by tasks.register<Exec>("buildAndroidX86_64Binary") {
workingDir("${project.projectDir}/../../bdk-ffi")
val cargoArgs: List<String> = listOf("build", "--profile", "release-smaller", "--target", "x86_64-linux-android")
val cargoArgs: MutableList<String> =
mutableListOf("build", "--profile", "release-smaller", "--target", "x86_64-linux-android")
executable("cargo")
args(cargoArgs)
// if ANDROID_NDK_ROOT is not set then set it to github actions default
if (System.getenv("ANDROID_NDK_ROOT") == null) {
environment(
Pair("ANDROID_NDK_ROOT", "${System.getenv("ANDROID_SDK_ROOT")}/ndk-bundle")
)
}
environment(
// add build toolchain to PATH
Pair("PATH", "${System.getenv("PATH")}:${System.getenv("ANDROID_NDK_ROOT")}/toolchains/llvm/prebuilt/$llvmArchPath/bin"),
Pair("CFLAGS", "-D__ANDROID_MIN_SDK_VERSION__=24"),
Pair("AR", "llvm-ar"),
Pair("CARGO_TARGET_X86_64_LINUX_ANDROID_LINKER", "x86_64-linux-android24-clang"),
Pair("CC", "x86_64-linux-android24-clang")
Pair("PATH",
"${System.getenv("PATH")}:${System.getenv("ANDROID_NDK_ROOT")}/toolchains/llvm/prebuilt/$llvmArchPath/bin"),
Pair("CFLAGS", "-D__ANDROID_API__=21"),
Pair("CARGO_TARGET_X86_64_LINUX_ANDROID_LINKER", "x86_64-linux-android21-clang"),
Pair("CC", "x86_64-linux-android21-clang")
)
doLast {
@ -72,18 +85,28 @@ internal class UniFfiAndroidPlugin : Plugin<Project> {
val buildAndroidArmv7Binary by tasks.register<Exec>("buildAndroidArmv7Binary") {
workingDir("${project.projectDir}/../../bdk-ffi")
val cargoArgs: List<String> = listOf("build", "--profile", "release-smaller", "--target", "armv7-linux-androideabi")
val cargoArgs: MutableList<String> =
mutableListOf("build", "--profile", "release-smaller", "--target", "armv7-linux-androideabi")
executable("cargo")
args(cargoArgs)
// if ANDROID_NDK_ROOT is not set then set it to github actions default
if (System.getenv("ANDROID_NDK_ROOT") == null) {
environment(
Pair("ANDROID_NDK_ROOT", "${System.getenv("ANDROID_SDK_ROOT")}/ndk-bundle")
)
}
environment(
// add build toolchain to PATH
Pair("PATH", "${System.getenv("PATH")}:${System.getenv("ANDROID_NDK_ROOT")}/toolchains/llvm/prebuilt/$llvmArchPath/bin"),
Pair("CFLAGS", "-D__ANDROID_MIN_SDK_VERSION__=24"),
Pair("AR", "llvm-ar"),
Pair("CARGO_TARGET_ARMV7_LINUX_ANDROIDEABI_LINKER", "armv7a-linux-androideabi24-clang"),
Pair("CC", "armv7a-linux-androideabi24-clang")
Pair("PATH",
"${System.getenv("PATH")}:${System.getenv("ANDROID_NDK_ROOT")}/toolchains/llvm/prebuilt/$llvmArchPath/bin"),
Pair("CFLAGS", "-D__ANDROID_API__=21"),
Pair("CARGO_TARGET_ARMV7_LINUX_ANDROIDEABI_LINKER",
"armv7a-linux-androideabi21-clang"),
Pair("CC", "armv7a-linux-androideabi21-clang")
)
doLast {
@ -97,21 +120,19 @@ internal class UniFfiAndroidPlugin : Plugin<Project> {
val moveNativeAndroidLibs by tasks.register<Copy>("moveNativeAndroidLibs") {
dependsOn(buildAndroidAarch64Binary)
dependsOn(buildAndroidArmv7Binary)
dependsOn(buildAndroidX86_64Binary)
into("${project.projectDir}/../lib/src/main/jniLibs/")
into("arm64-v8a") {
from("${project.projectDir}/../../bdk-ffi/target/aarch64-linux-android/release-smaller/libbdkffi.so")
from("${project.projectDir}/../../target/aarch64-linux-android/release-smaller/libbdkffi.so")
}
into("x86_64") {
from("${project.projectDir}/../../bdk-ffi/target/x86_64-linux-android/release-smaller/libbdkffi.so")
from("${project.projectDir}/../../target/x86_64-linux-android/release-smaller/libbdkffi.so")
}
into("armeabi-v7a") {
from("${project.projectDir}/../../bdk-ffi/target/armv7-linux-androideabi/release-smaller/libbdkffi.so")
from("${project.projectDir}/../../target/armv7-linux-androideabi/release-smaller/libbdkffi.so")
}
doLast {
@ -123,17 +144,18 @@ internal class UniFfiAndroidPlugin : Plugin<Project> {
val generateAndroidBindings by tasks.register<Exec>("generateAndroidBindings") {
dependsOn(moveNativeAndroidLibs)
val libraryPath = "${project.projectDir}/../../bdk-ffi/target/aarch64-linux-android/release-smaller/libbdkffi.so"
workingDir("${project.projectDir}/../../bdk-ffi")
val cargoArgs: List<String> = listOf("run", "--bin", "uniffi-bindgen", "generate", "--library", libraryPath, "--language", "kotlin", "--out-dir", "../bdk-android/lib/src/main/kotlin", "--no-format")
// The code above worked for uniffi 0.24.3 using the --library flag
// The code below works for uniffi 0.23.0
// workingDir("${project.projectDir}/../../bdk-ffi")
// val cargoArgs: List<String> = listOf("run", "--bin", "uniffi-bindgen", "generate", "src/bdk.udl", "--language", "kotlin", "--config", "uniffi-android.toml", "--out-dir", "../bdk-android/lib/src/main/kotlin", "--no-format")
executable("cargo")
args(cargoArgs)
args(
"run",
"--package",
"bdk-ffi-bindgen",
"--",
"--language",
"kotlin",
"--out-dir",
"../bdk-android/lib/src/main/kotlin"
)
doLast {
println("Android bindings file successfully created")

View File

@ -2,17 +2,3 @@ rootProject.name = "bdk-android"
include(":lib")
includeBuild("plugins")
pluginManagement {
repositories {
gradlePluginPortal()
google()
}
}
dependencyResolutionManagement {
repositories {
mavenCentral()
google()
}
}

View File

@ -0,0 +1,10 @@
[package]
name = "bdk-ffi-bindgen"
version = "0.2.0"
edition = "2021"
[dependencies]
anyhow = "1.0.45" # remove after upgrading to next version of uniffi
structopt = "0.3"
uniffi_bindgen = "0.21.0"
camino = "1.0.9"

138
bdk-ffi-bindgen/src/main.rs Normal file
View File

@ -0,0 +1,138 @@
use camino::Utf8Path;
use std::fmt;
use std::path::{Path, PathBuf};
use std::str::FromStr;
use structopt::StructOpt;
#[derive(Debug, Eq, PartialEq)]
pub enum Language {
Kotlin,
Python,
Swift,
}
impl fmt::Display for Language {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Language::Kotlin => write!(f, "kotlin"),
Language::Swift => write!(f, "swift"),
Language::Python => write!(f, "python"),
}
}
}
#[derive(Debug)]
pub enum Error {
UnsupportedLanguage,
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:?}", self)
}
}
impl FromStr for Language {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"kotlin" => Ok(Language::Kotlin),
"python" => Ok(Language::Python),
"swift" => Ok(Language::Swift),
_ => Err(Error::UnsupportedLanguage),
}
}
}
fn generate_bindings(opt: &Opt) -> anyhow::Result<(), anyhow::Error> {
let path: &Utf8Path = Utf8Path::from_path(&opt.udl_file).unwrap();
let out_dir: &Utf8Path = Utf8Path::from_path(&opt.out_dir).unwrap();
uniffi_bindgen::generate_bindings(
path,
None,
vec![opt.language.to_string().as_str()],
Some(out_dir),
None,
false,
)?;
Ok(())
}
fn fixup_python_lib_path(
out_dir: &Path,
lib_name: &Path,
) -> Result<(), Box<dyn std::error::Error>> {
use std::fs;
use std::io::Write;
const LOAD_INDIRECT_DEF: &str = "def loadIndirect():";
let bindings_file = out_dir.join("bdk.py");
let mut data = fs::read_to_string(&bindings_file)?;
let pos = data
.find(LOAD_INDIRECT_DEF)
.unwrap_or_else(|| panic!("loadIndirect not found in `{}`", bindings_file.display()));
let range = pos..pos + LOAD_INDIRECT_DEF.len();
let replacement = format!(
r#"
def loadIndirect():
import glob
return getattr(ctypes.cdll, glob.glob(os.path.join(os.path.dirname(os.path.abspath(__file__)), '{}.*'))[0])
def _loadIndirectOld():"#,
&lib_name.to_str().expect("lib name")
);
data.replace_range(range, &replacement);
let mut file = fs::OpenOptions::new()
.write(true)
.truncate(true)
.open(&bindings_file)?;
file.write_all(data.as_bytes())?;
Ok(())
}
#[derive(Debug, StructOpt)]
#[structopt(
name = "bdk-ffi-bindgen",
about = "A tool to generate bdk-ffi language bindings"
)]
struct Opt {
/// UDL file
#[structopt(env = "BDKFFI_BINDGEN_UDL", short, long, default_value("src/bdk.udl"), parse(try_from_str = PathBuf::from_str))]
udl_file: PathBuf,
/// Language to generate bindings for
#[structopt(env = "BDKFFI_BINDGEN_LANGUAGE", short, long, possible_values(&["kotlin","swift","python"]), parse(try_from_str = Language::from_str))]
language: Language,
/// Output directory to put generated language bindings
#[structopt(env = "BDKFFI_BINDGEN_OUTPUT_DIR", short, long, parse(try_from_str = PathBuf::from_str))]
out_dir: PathBuf,
/// Python fix up lib path
#[structopt(env = "BDKFFI_BINDGEN_PYTHON_FIXUP_PATH", short, long, parse(try_from_str = PathBuf::from_str))]
python_fixup_path: Option<PathBuf>,
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let opt = Opt::from_args();
println!("Input UDL file is {:?}", opt.udl_file);
println!("Chosen language is {}", opt.language);
println!("Output directory is {:?}", opt.out_dir);
generate_bindings(&opt)?;
if opt.language == Language::Python {
if let Some(path) = opt.python_fixup_path {
println!("Fixing up python lib path, {:?}", &path);
fixup_python_lib_path(&opt.out_dir, &path)?;
}
}
Ok(())
}

View File

@ -1,47 +1,21 @@
[package]
name = "bdk-ffi"
version = "1.0.0-alpha.11"
homepage = "https://bitcoindevkit.org"
repository = "https://github.com/bitcoindevkit/bdk"
version = "0.26.0"
authors = ["Steve Myers <steve@notmandatory.org>", "Sudarsan Balaji <sudarsan.balaji@artfuldev.com>"]
edition = "2018"
license = "MIT OR Apache-2.0"
[lib]
crate-type = ["lib", "staticlib", "cdylib"]
crate-type = ["staticlib", "cdylib"]
name = "bdkffi"
[[bin]]
name = "uniffi-bindgen"
path = "uniffi-bindgen.rs"
[features]
default = ["uniffi/cli"]
[dependencies]
bdk_wallet = { version = "1.0.0-alpha.13", features = ["all-keys", "keys-bip39"] }
bdk_esplora = { version = "0.15.0", default-features = false, features = ["std", "blocking", "blocking-https-rustls"] }
# NOTE: This is a temporary workaround to use the electrum-client with the use-rustls-ring feature. It points to a fork
# of bdk in which the bdk_electrum library uses the electrum-client with the use-rustls-ring feature.
bdk_electrum = { git = "https://github.com/thunderbiscuit/bdk/", package = "bdk_electrum", branch = "feature/electrum-client-ring-ffi-alpha13", default-features = false, features = ["use-rustls-ring"] }
# bdk_electrum = { version = "0.15.0" }
bdk_sqlite = { version = "0.2.0" }
bdk_bitcoind_rpc = { version = "0.12.0" }
bitcoin-internals = { version = "0.2.0", features = ["alloc"] }
uniffi = { version = "=0.28.0" }
thiserror = "1.0.58"
bdk = { version = "0.26", features = ["all-keys", "use-esplora-ureq", "sqlite-bundled", "rpc"] }
uniffi_macros = { version = "0.21.0", features = ["builtin-bindgen"] }
uniffi = { version = "0.21.0", features = ["builtin-bindgen"] }
[build-dependencies]
uniffi = { version = "=0.28.0", features = ["build"] }
uniffi_build = { version = "0.21.0", features = ["builtin-bindgen"] }
[dev-dependencies]
uniffi = { version = "=0.28.0", features = ["bindgen-tests"] }
assert_matches = "1.5.0"
[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 = "debuginfo" # Partially strip symbols from binary

View File

@ -1,3 +1,3 @@
fn main() {
uniffi::generate_scaffolding("./src/bdk.udl").unwrap();
uniffi_build::generate_scaffolding("src/bdk.udl").unwrap();
}

View File

@ -1,12 +0,0 @@
default:
just --list
build:
cargo build
check:
cargo fmt
cargo clippy
test:
cargo test --lib

File diff suppressed because it is too large Load Diff

View File

@ -1,679 +0,0 @@
use crate::error::{
AddressParseError, FeeRateError, FromScriptError, PsbtError, PsbtParseError, TransactionError,
};
use bdk_bitcoind_rpc::bitcoincore_rpc::jsonrpc::serde_json;
use bdk_wallet::bitcoin::address::{NetworkChecked, NetworkUnchecked};
use bdk_wallet::bitcoin::amount::ParseAmountError;
use bdk_wallet::bitcoin::consensus::encode::serialize;
use bdk_wallet::bitcoin::consensus::Decodable;
use bdk_wallet::bitcoin::io::Cursor;
use bdk_wallet::bitcoin::psbt::ExtractTxError;
use bdk_wallet::bitcoin::Address as BdkAddress;
use bdk_wallet::bitcoin::Amount as BdkAmount;
use bdk_wallet::bitcoin::FeeRate as BdkFeeRate;
use bdk_wallet::bitcoin::Network;
use bdk_wallet::bitcoin::OutPoint as BdkOutPoint;
use bdk_wallet::bitcoin::Psbt as BdkPsbt;
use bdk_wallet::bitcoin::ScriptBuf as BdkScriptBuf;
use bdk_wallet::bitcoin::Transaction as BdkTransaction;
use bdk_wallet::bitcoin::TxIn as BdkTxIn;
use bdk_wallet::bitcoin::TxOut as BdkTxOut;
use bdk_wallet::bitcoin::Txid;
use std::fmt::Display;
use std::ops::Deref;
use std::str::FromStr;
use std::sync::{Arc, Mutex};
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Amount(pub(crate) BdkAmount);
impl Amount {
pub fn from_sat(sat: u64) -> Self {
Amount(BdkAmount::from_sat(sat))
}
pub fn from_btc(btc: f64) -> Result<Self, ParseAmountError> {
let bdk_amount = BdkAmount::from_btc(btc).map_err(ParseAmountError::from)?;
Ok(Amount(bdk_amount))
}
pub fn to_sat(&self) -> u64 {
self.0.to_sat()
}
pub fn to_btc(&self) -> f64 {
self.0.to_btc()
}
}
impl From<Amount> for BdkAmount {
fn from(amount: Amount) -> Self {
amount.0
}
}
impl From<BdkAmount> for Amount {
fn from(amount: BdkAmount) -> Self {
Amount(amount)
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Script(pub(crate) BdkScriptBuf);
impl Script {
pub fn new(raw_output_script: Vec<u8>) -> Self {
let script: BdkScriptBuf = raw_output_script.into();
Script(script)
}
pub fn to_bytes(&self) -> Vec<u8> {
self.0.to_bytes()
}
}
impl From<BdkScriptBuf> for Script {
fn from(script: BdkScriptBuf) -> Self {
Script(script)
}
}
#[derive(Debug, PartialEq, Eq)]
pub struct Address(BdkAddress<NetworkChecked>);
impl Address {
pub fn new(address: String, network: Network) -> Result<Self, AddressParseError> {
let parsed_address = address.parse::<bdk_wallet::bitcoin::Address<NetworkUnchecked>>()?;
let network_checked_address = parsed_address.require_network(network)?;
Ok(Address(network_checked_address))
}
pub fn from_script(script: Arc<Script>, network: Network) -> Result<Self, FromScriptError> {
let address = BdkAddress::from_script(&script.0.clone(), network)?;
Ok(Address(address))
}
pub fn script_pubkey(&self) -> Arc<Script> {
Arc::new(Script(self.0.script_pubkey()))
}
pub fn to_qr_uri(&self) -> String {
self.0.to_qr_uri()
}
pub fn is_valid_for_network(&self, network: Network) -> bool {
let address_str = self.0.to_string();
if let Ok(unchecked_address) = address_str.parse::<BdkAddress<NetworkUnchecked>>() {
unchecked_address.is_valid_for_network(network)
} else {
false
}
}
}
impl Display for Address {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
impl From<Address> for BdkAddress {
fn from(address: Address) -> Self {
address.0
}
}
impl From<BdkAddress> for Address {
fn from(address: BdkAddress) -> Self {
Address(address)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Transaction(BdkTransaction);
impl Transaction {
pub fn new(transaction_bytes: Vec<u8>) -> Result<Self, TransactionError> {
let mut decoder = Cursor::new(transaction_bytes);
let tx: BdkTransaction = BdkTransaction::consensus_decode(&mut decoder)?;
Ok(Transaction(tx))
}
pub fn compute_txid(&self) -> String {
self.0.compute_txid().to_string()
}
pub fn weight(&self) -> u64 {
self.0.weight().to_wu()
}
pub fn total_size(&self) -> u64 {
self.0.total_size() as u64
}
pub fn vsize(&self) -> u64 {
self.0.vsize() as u64
}
pub fn is_coinbase(&self) -> bool {
self.0.is_coinbase()
}
pub fn is_explicitly_rbf(&self) -> bool {
self.0.is_explicitly_rbf()
}
pub fn is_lock_time_enabled(&self) -> bool {
self.0.is_lock_time_enabled()
}
pub fn version(&self) -> i32 {
self.0.version.0
}
pub fn serialize(&self) -> Vec<u8> {
serialize(&self.0)
}
pub fn input(&self) -> Vec<TxIn> {
self.0.input.iter().map(|tx_in| tx_in.into()).collect()
}
pub fn output(&self) -> Vec<TxOut> {
self.0.output.iter().map(|tx_out| tx_out.into()).collect()
}
pub fn lock_time(&self) -> u32 {
self.0.lock_time.to_consensus_u32()
}
}
impl From<BdkTransaction> for Transaction {
fn from(tx: BdkTransaction) -> Self {
Transaction(tx)
}
}
impl From<&BdkTransaction> for Transaction {
fn from(tx: &BdkTransaction) -> Self {
Transaction(tx.clone())
}
}
impl From<&Transaction> for BdkTransaction {
fn from(tx: &Transaction) -> Self {
tx.0.clone()
}
}
pub struct Psbt(pub(crate) Mutex<BdkPsbt>);
impl Psbt {
pub(crate) fn new(psbt_base64: String) -> Result<Self, PsbtParseError> {
let psbt: BdkPsbt = BdkPsbt::from_str(&psbt_base64)?;
Ok(Psbt(Mutex::new(psbt)))
}
pub(crate) fn serialize(&self) -> String {
let psbt = self.0.lock().unwrap().clone();
psbt.to_string()
}
pub(crate) fn extract_tx(&self) -> Result<Arc<Transaction>, ExtractTxError> {
let tx: BdkTransaction = self.0.lock().unwrap().clone().extract_tx()?;
let transaction: Transaction = tx.into();
Ok(Arc::new(transaction))
}
pub(crate) fn fee(&self) -> Result<u64, PsbtError> {
self.0
.lock()
.unwrap()
.fee()
.map(|fee| fee.to_sat())
.map_err(PsbtError::from)
}
pub(crate) fn combine(&self, other: Arc<Psbt>) -> Result<Arc<Psbt>, PsbtError> {
let mut original_psbt = self.0.lock().unwrap().clone();
let other_psbt = other.0.lock().unwrap().clone();
original_psbt.combine(other_psbt)?;
Ok(Arc::new(Psbt(Mutex::new(original_psbt))))
}
pub(crate) fn json_serialize(&self) -> String {
let psbt = self.0.lock().unwrap();
serde_json::to_string(psbt.deref()).unwrap()
}
}
impl From<BdkPsbt> for Psbt {
fn from(psbt: BdkPsbt) -> Self {
Psbt(Mutex::new(psbt))
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct OutPoint {
pub txid: String,
pub vout: u32,
}
impl From<&OutPoint> for BdkOutPoint {
fn from(outpoint: &OutPoint) -> Self {
BdkOutPoint {
txid: Txid::from_str(&outpoint.txid).unwrap(),
vout: outpoint.vout,
}
}
}
impl From<&BdkOutPoint> for OutPoint {
fn from(outpoint: &BdkOutPoint) -> Self {
OutPoint {
txid: outpoint.txid.to_string(),
vout: outpoint.vout,
}
}
}
#[derive(Debug, Clone)]
pub struct TxIn {
pub previous_output: OutPoint,
pub script_sig: Arc<Script>,
pub sequence: u32,
pub witness: Vec<Vec<u8>>,
}
impl From<&BdkTxIn> for TxIn {
fn from(tx_in: &BdkTxIn) -> Self {
TxIn {
previous_output: OutPoint {
txid: tx_in.previous_output.txid.to_string(),
vout: tx_in.previous_output.vout,
},
script_sig: Arc::new(Script(tx_in.script_sig.clone())),
sequence: tx_in.sequence.0,
witness: tx_in.witness.to_vec(),
}
}
}
#[derive(Debug, Clone)]
pub struct TxOut {
pub value: u64,
pub script_pubkey: Arc<Script>,
}
impl From<&BdkTxOut> for TxOut {
fn from(tx_out: &BdkTxOut) -> Self {
TxOut {
value: tx_out.value.to_sat(),
script_pubkey: Arc::new(Script(tx_out.script_pubkey.clone())),
}
}
}
#[derive(Clone, Debug)]
pub struct FeeRate(pub(crate) BdkFeeRate);
impl FeeRate {
pub fn from_sat_per_vb(sat_per_vb: u64) -> Result<Self, FeeRateError> {
let fee_rate: Option<BdkFeeRate> = BdkFeeRate::from_sat_per_vb(sat_per_vb);
match fee_rate {
Some(fee_rate) => Ok(FeeRate(fee_rate)),
None => Err(FeeRateError::ArithmeticOverflow),
}
}
pub fn from_sat_per_kwu(sat_per_kwu: u64) -> Self {
FeeRate(BdkFeeRate::from_sat_per_kwu(sat_per_kwu))
}
pub fn to_sat_per_vb_ceil(&self) -> u64 {
self.0.to_sat_per_vb_ceil()
}
pub fn to_sat_per_vb_floor(&self) -> u64 {
self.0.to_sat_per_vb_floor()
}
pub fn to_sat_per_kwu(&self) -> u64 {
self.0.to_sat_per_kwu()
}
}
#[cfg(test)]
mod tests {
use crate::bitcoin::Address;
use crate::bitcoin::Network;
#[test]
fn test_is_valid_for_network() {
// ====Docs tests====
// https://docs.rs/bitcoin/0.29.2/src/bitcoin/util/address.rs.html#798-802
let docs_address_testnet_str = "2N83imGV3gPwBzKJQvWJ7cRUY2SpUyU6A5e";
let docs_address_testnet =
Address::new(docs_address_testnet_str.to_string(), Network::Testnet).unwrap();
assert!(
docs_address_testnet.is_valid_for_network(Network::Testnet),
"Address should be valid for Testnet"
);
assert!(
docs_address_testnet.is_valid_for_network(Network::Signet),
"Address should be valid for Signet"
);
assert!(
docs_address_testnet.is_valid_for_network(Network::Regtest),
"Address should be valid for Regtest"
);
let docs_address_mainnet_str = "32iVBEu4dxkUQk9dJbZUiBiQdmypcEyJRf";
let docs_address_mainnet =
Address::new(docs_address_mainnet_str.to_string(), Network::Bitcoin).unwrap();
assert!(
docs_address_mainnet.is_valid_for_network(Network::Bitcoin),
"Address should be valid for Bitcoin"
);
// ====Bech32====
// | Network | Prefix | Address Type |
// |-----------------|---------|--------------|
// | Bitcoin Mainnet | `bc1` | Bech32 |
// | Bitcoin Testnet | `tb1` | Bech32 |
// | Bitcoin Signet | `tb1` | Bech32 |
// | Bitcoin Regtest | `bcrt1` | Bech32 |
// Bech32 - Bitcoin
// Valid for:
// - Bitcoin
// Not valid for:
// - Testnet
// - Signet
// - Regtest
let bitcoin_mainnet_bech32_address_str = "bc1qxhmdufsvnuaaaer4ynz88fspdsxq2h9e9cetdj";
let bitcoin_mainnet_bech32_address = Address::new(
bitcoin_mainnet_bech32_address_str.to_string(),
Network::Bitcoin,
)
.unwrap();
assert!(
bitcoin_mainnet_bech32_address.is_valid_for_network(Network::Bitcoin),
"Address should be valid for Bitcoin"
);
assert!(
!bitcoin_mainnet_bech32_address.is_valid_for_network(Network::Testnet),
"Address should not be valid for Testnet"
);
assert!(
!bitcoin_mainnet_bech32_address.is_valid_for_network(Network::Signet),
"Address should not be valid for Signet"
);
assert!(
!bitcoin_mainnet_bech32_address.is_valid_for_network(Network::Regtest),
"Address should not be valid for Regtest"
);
// Bech32 - Testnet
// Valid for:
// - Testnet
// - Regtest
// Not valid for:
// - Bitcoin
// - Regtest
let bitcoin_testnet_bech32_address_str =
"tb1p4nel7wkc34raczk8c4jwk5cf9d47u2284rxn98rsjrs4w3p2sheqvjmfdh";
let bitcoin_testnet_bech32_address = Address::new(
bitcoin_testnet_bech32_address_str.to_string(),
Network::Testnet,
)
.unwrap();
assert!(
!bitcoin_testnet_bech32_address.is_valid_for_network(Network::Bitcoin),
"Address should not be valid for Bitcoin"
);
assert!(
bitcoin_testnet_bech32_address.is_valid_for_network(Network::Testnet),
"Address should be valid for Testnet"
);
assert!(
bitcoin_testnet_bech32_address.is_valid_for_network(Network::Signet),
"Address should be valid for Signet"
);
assert!(
!bitcoin_testnet_bech32_address.is_valid_for_network(Network::Regtest),
"Address should not not be valid for Regtest"
);
// Bech32 - Signet
// Valid for:
// - Signet
// - Testnet
// Not valid for:
// - Bitcoin
// - Regtest
let bitcoin_signet_bech32_address_str =
"tb1pwzv7fv35yl7ypwj8w7al2t8apd6yf4568cs772qjwper74xqc99sk8x7tk";
let bitcoin_signet_bech32_address = Address::new(
bitcoin_signet_bech32_address_str.to_string(),
Network::Signet,
)
.unwrap();
assert!(
!bitcoin_signet_bech32_address.is_valid_for_network(Network::Bitcoin),
"Address should not be valid for Bitcoin"
);
assert!(
bitcoin_signet_bech32_address.is_valid_for_network(Network::Testnet),
"Address should be valid for Testnet"
);
assert!(
bitcoin_signet_bech32_address.is_valid_for_network(Network::Signet),
"Address should be valid for Signet"
);
assert!(
!bitcoin_signet_bech32_address.is_valid_for_network(Network::Regtest),
"Address should not not be valid for Regtest"
);
// Bech32 - Regtest
// Valid for:
// - Regtest
// Not valid for:
// - Bitcoin
// - Testnet
// - Signet
let bitcoin_regtest_bech32_address_str = "bcrt1q39c0vrwpgfjkhasu5mfke9wnym45nydfwaeems";
let bitcoin_regtest_bech32_address = Address::new(
bitcoin_regtest_bech32_address_str.to_string(),
Network::Regtest,
)
.unwrap();
assert!(
!bitcoin_regtest_bech32_address.is_valid_for_network(Network::Bitcoin),
"Address should not be valid for Bitcoin"
);
assert!(
!bitcoin_regtest_bech32_address.is_valid_for_network(Network::Testnet),
"Address should not be valid for Testnet"
);
assert!(
!bitcoin_regtest_bech32_address.is_valid_for_network(Network::Signet),
"Address should not be valid for Signet"
);
assert!(
bitcoin_regtest_bech32_address.is_valid_for_network(Network::Regtest),
"Address should be valid for Regtest"
);
// ====P2PKH====
// | Network | Prefix for P2PKH | Prefix for P2SH |
// |------------------------------------|------------------|-----------------|
// | Bitcoin Mainnet | `1` | `3` |
// | Bitcoin Testnet, Regtest, Signet | `m` or `n` | `2` |
// P2PKH - Bitcoin
// Valid for:
// - Bitcoin
// Not valid for:
// - Testnet
// - Regtest
let bitcoin_mainnet_p2pkh_address_str = "1FfmbHfnpaZjKFvyi1okTjJJusN455paPH";
let bitcoin_mainnet_p2pkh_address = Address::new(
bitcoin_mainnet_p2pkh_address_str.to_string(),
Network::Bitcoin,
)
.unwrap();
assert!(
bitcoin_mainnet_p2pkh_address.is_valid_for_network(Network::Bitcoin),
"Address should be valid for Bitcoin"
);
assert!(
!bitcoin_mainnet_p2pkh_address.is_valid_for_network(Network::Testnet),
"Address should not be valid for Testnet"
);
assert!(
!bitcoin_mainnet_p2pkh_address.is_valid_for_network(Network::Regtest),
"Address should not be valid for Regtest"
);
// P2PKH - Testnet
// Valid for:
// - Testnet
// - Regtest
// Not valid for:
// - Bitcoin
let bitcoin_testnet_p2pkh_address_str = "mucFNhKMYoBQYUAEsrFVscQ1YaFQPekBpg";
let bitcoin_testnet_p2pkh_address = Address::new(
bitcoin_testnet_p2pkh_address_str.to_string(),
Network::Testnet,
)
.unwrap();
assert!(
!bitcoin_testnet_p2pkh_address.is_valid_for_network(Network::Bitcoin),
"Address should not be valid for Bitcoin"
);
assert!(
bitcoin_testnet_p2pkh_address.is_valid_for_network(Network::Testnet),
"Address should be valid for Testnet"
);
assert!(
bitcoin_testnet_p2pkh_address.is_valid_for_network(Network::Regtest),
"Address should be valid for Regtest"
);
// P2PKH - Regtest
// Valid for:
// - Testnet
// - Regtest
// Not valid for:
// - Bitcoin
let bitcoin_regtest_p2pkh_address_str = "msiGFK1PjCk8E6FXeoGkQPTscmcpyBdkgS";
let bitcoin_regtest_p2pkh_address = Address::new(
bitcoin_regtest_p2pkh_address_str.to_string(),
Network::Regtest,
)
.unwrap();
assert!(
!bitcoin_regtest_p2pkh_address.is_valid_for_network(Network::Bitcoin),
"Address should not be valid for Bitcoin"
);
assert!(
bitcoin_regtest_p2pkh_address.is_valid_for_network(Network::Testnet),
"Address should be valid for Testnet"
);
assert!(
bitcoin_regtest_p2pkh_address.is_valid_for_network(Network::Regtest),
"Address should be valid for Regtest"
);
// ====P2SH====
// | Network | Prefix for P2PKH | Prefix for P2SH |
// |------------------------------------|------------------|-----------------|
// | Bitcoin Mainnet | `1` | `3` |
// | Bitcoin Testnet, Regtest, Signet | `m` or `n` | `2` |
// P2SH - Bitcoin
// Valid for:
// - Bitcoin
// Not valid for:
// - Testnet
// - Regtest
let bitcoin_mainnet_p2sh_address_str = "3J98t1WpEZ73CNmQviecrnyiWrnqRhWNLy";
let bitcoin_mainnet_p2sh_address = Address::new(
bitcoin_mainnet_p2sh_address_str.to_string(),
Network::Bitcoin,
)
.unwrap();
assert!(
bitcoin_mainnet_p2sh_address.is_valid_for_network(Network::Bitcoin),
"Address should be valid for Bitcoin"
);
assert!(
!bitcoin_mainnet_p2sh_address.is_valid_for_network(Network::Testnet),
"Address should not be valid for Testnet"
);
assert!(
!bitcoin_mainnet_p2sh_address.is_valid_for_network(Network::Regtest),
"Address should not be valid for Regtest"
);
// P2SH - Testnet
// Valid for:
// - Testnet
// - Regtest
// Not valid for:
// - Bitcoin
let bitcoin_testnet_p2sh_address_str = "2NFUBBRcTJbYc1D4HSCbJhKZp6YCV4PQFpQ";
let bitcoin_testnet_p2sh_address = Address::new(
bitcoin_testnet_p2sh_address_str.to_string(),
Network::Testnet,
)
.unwrap();
assert!(
!bitcoin_testnet_p2sh_address.is_valid_for_network(Network::Bitcoin),
"Address should not be valid for Bitcoin"
);
assert!(
bitcoin_testnet_p2sh_address.is_valid_for_network(Network::Testnet),
"Address should be valid for Testnet"
);
assert!(
bitcoin_testnet_p2sh_address.is_valid_for_network(Network::Regtest),
"Address should be valid for Regtest"
);
// P2SH - Regtest
// Valid for:
// - Testnet
// - Regtest
// Not valid for:
// - Bitcoin
let bitcoin_regtest_p2sh_address_str = "2NEb8N5B9jhPUCBchz16BB7bkJk8VCZQjf3";
let bitcoin_regtest_p2sh_address = Address::new(
bitcoin_regtest_p2sh_address_str.to_string(),
Network::Regtest,
)
.unwrap();
assert!(
!bitcoin_regtest_p2sh_address.is_valid_for_network(Network::Bitcoin),
"Address should not be valid for Bitcoin"
);
assert!(
bitcoin_regtest_p2sh_address.is_valid_for_network(Network::Testnet),
"Address should be valid for Testnet"
);
assert!(
bitcoin_regtest_p2sh_address.is_valid_for_network(Network::Regtest),
"Address should be valid for Regtest"
);
}
}

View File

@ -1,402 +0,0 @@
use crate::error::DescriptorError;
use crate::keys::DescriptorPublicKey;
use crate::keys::DescriptorSecretKey;
use std::fmt::Display;
use bdk_wallet::bitcoin::bip32::Fingerprint;
use bdk_wallet::bitcoin::key::Secp256k1;
use bdk_wallet::bitcoin::Network;
use bdk_wallet::descriptor::{ExtendedDescriptor, IntoWalletDescriptor};
use bdk_wallet::keys::DescriptorPublicKey as BdkDescriptorPublicKey;
use bdk_wallet::keys::{DescriptorSecretKey as BdkDescriptorSecretKey, KeyMap};
use bdk_wallet::template::{
Bip44, Bip44Public, Bip49, Bip49Public, Bip84, Bip84Public, Bip86, Bip86Public,
DescriptorTemplate,
};
use bdk_wallet::KeychainKind;
use std::str::FromStr;
#[derive(Debug)]
pub struct Descriptor {
pub extended_descriptor: ExtendedDescriptor,
pub key_map: KeyMap,
}
impl Descriptor {
pub(crate) fn new(descriptor: String, network: Network) -> Result<Self, DescriptorError> {
let secp = Secp256k1::new();
let (extended_descriptor, key_map) = descriptor.into_wallet_descriptor(&secp, network)?;
Ok(Self {
extended_descriptor,
key_map,
})
}
pub(crate) fn new_bip44(
secret_key: &DescriptorSecretKey,
keychain_kind: KeychainKind,
network: Network,
) -> Self {
let derivable_key = &secret_key.0;
match derivable_key {
BdkDescriptorSecretKey::Single(_) => {
unreachable!()
}
BdkDescriptorSecretKey::XPrv(descriptor_x_key) => {
let derivable_key = descriptor_x_key.xkey;
let (extended_descriptor, key_map, _) =
Bip44(derivable_key, keychain_kind).build(network).unwrap();
Self {
extended_descriptor,
key_map,
}
}
BdkDescriptorSecretKey::MultiXPrv(_) => {
unreachable!()
}
}
}
pub(crate) fn new_bip44_public(
public_key: &DescriptorPublicKey,
fingerprint: String,
keychain_kind: KeychainKind,
network: Network,
) -> Self {
let fingerprint = Fingerprint::from_str(fingerprint.as_str()).unwrap();
let derivable_key = &public_key.0;
match derivable_key {
BdkDescriptorPublicKey::Single(_) => {
unreachable!()
}
BdkDescriptorPublicKey::XPub(descriptor_x_key) => {
let derivable_key = descriptor_x_key.xkey;
let (extended_descriptor, key_map, _) =
Bip44Public(derivable_key, fingerprint, keychain_kind)
.build(network)
.unwrap();
Self {
extended_descriptor,
key_map,
}
}
BdkDescriptorPublicKey::MultiXPub(_) => {
unreachable!()
}
}
}
pub(crate) fn new_bip49(
secret_key: &DescriptorSecretKey,
keychain_kind: KeychainKind,
network: Network,
) -> Self {
let derivable_key = &secret_key.0;
match derivable_key {
BdkDescriptorSecretKey::Single(_) => {
unreachable!()
}
BdkDescriptorSecretKey::XPrv(descriptor_x_key) => {
let derivable_key = descriptor_x_key.xkey;
let (extended_descriptor, key_map, _) =
Bip49(derivable_key, keychain_kind).build(network).unwrap();
Self {
extended_descriptor,
key_map,
}
}
BdkDescriptorSecretKey::MultiXPrv(_) => {
unreachable!()
}
}
}
pub(crate) fn new_bip49_public(
public_key: &DescriptorPublicKey,
fingerprint: String,
keychain_kind: KeychainKind,
network: Network,
) -> Self {
let fingerprint = Fingerprint::from_str(fingerprint.as_str()).unwrap();
let derivable_key = &public_key.0;
match derivable_key {
BdkDescriptorPublicKey::Single(_) => {
unreachable!()
}
BdkDescriptorPublicKey::XPub(descriptor_x_key) => {
let derivable_key = descriptor_x_key.xkey;
let (extended_descriptor, key_map, _) =
Bip49Public(derivable_key, fingerprint, keychain_kind)
.build(network)
.unwrap();
Self {
extended_descriptor,
key_map,
}
}
BdkDescriptorPublicKey::MultiXPub(_) => {
unreachable!()
}
}
}
pub(crate) fn new_bip84(
secret_key: &DescriptorSecretKey,
keychain_kind: KeychainKind,
network: Network,
) -> Self {
let derivable_key = &secret_key.0;
match derivable_key {
BdkDescriptorSecretKey::Single(_) => {
unreachable!()
}
BdkDescriptorSecretKey::XPrv(descriptor_x_key) => {
let derivable_key = descriptor_x_key.xkey;
let (extended_descriptor, key_map, _) =
Bip84(derivable_key, keychain_kind).build(network).unwrap();
Self {
extended_descriptor,
key_map,
}
}
BdkDescriptorSecretKey::MultiXPrv(_) => {
unreachable!()
}
}
}
pub(crate) fn new_bip84_public(
public_key: &DescriptorPublicKey,
fingerprint: String,
keychain_kind: KeychainKind,
network: Network,
) -> Self {
let fingerprint = Fingerprint::from_str(fingerprint.as_str()).unwrap();
let derivable_key = &public_key.0;
match derivable_key {
BdkDescriptorPublicKey::Single(_) => {
unreachable!()
}
BdkDescriptorPublicKey::XPub(descriptor_x_key) => {
let derivable_key = descriptor_x_key.xkey;
let (extended_descriptor, key_map, _) =
Bip84Public(derivable_key, fingerprint, keychain_kind)
.build(network)
.unwrap();
Self {
extended_descriptor,
key_map,
}
}
BdkDescriptorPublicKey::MultiXPub(_) => {
unreachable!()
}
}
}
pub(crate) fn new_bip86(
secret_key: &DescriptorSecretKey,
keychain_kind: KeychainKind,
network: Network,
) -> Self {
let derivable_key = &secret_key.0;
match derivable_key {
BdkDescriptorSecretKey::Single(_) => {
unreachable!()
}
BdkDescriptorSecretKey::XPrv(descriptor_x_key) => {
let derivable_key = descriptor_x_key.xkey;
let (extended_descriptor, key_map, _) =
Bip86(derivable_key, keychain_kind).build(network).unwrap();
Self {
extended_descriptor,
key_map,
}
}
BdkDescriptorSecretKey::MultiXPrv(_) => {
unreachable!()
}
}
}
pub(crate) fn new_bip86_public(
public_key: &DescriptorPublicKey,
fingerprint: String,
keychain_kind: KeychainKind,
network: Network,
) -> Self {
let fingerprint = Fingerprint::from_str(fingerprint.as_str()).unwrap();
let derivable_key = &public_key.0;
match derivable_key {
BdkDescriptorPublicKey::Single(_) => {
unreachable!()
}
BdkDescriptorPublicKey::XPub(descriptor_x_key) => {
let derivable_key = descriptor_x_key.xkey;
let (extended_descriptor, key_map, _) =
Bip86Public(derivable_key, fingerprint, keychain_kind)
.build(network)
.unwrap();
Self {
extended_descriptor,
key_map,
}
}
BdkDescriptorPublicKey::MultiXPub(_) => {
unreachable!()
}
}
}
pub(crate) fn to_string_with_secret(&self) -> String {
let descriptor = &self.extended_descriptor;
let key_map = &self.key_map;
descriptor.to_string_with_secret(key_map)
}
}
impl Display for Descriptor {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.extended_descriptor)
}
}
#[cfg(test)]
mod test {
use crate::*;
use assert_matches::assert_matches;
use bdk_wallet::bitcoin::Network;
use bdk_wallet::KeychainKind;
fn get_descriptor_secret_key() -> DescriptorSecretKey {
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();
DescriptorSecretKey::new(Network::Testnet, &mnemonic, None)
}
#[test]
fn test_descriptor_templates() {
let master: DescriptorSecretKey = get_descriptor_secret_key();
println!("Master: {:?}", master.as_string());
// tprv8ZgxMBicQKsPdWuqM1t1CDRvQtQuBPyfL6GbhQwtxDKgUAVPbxmj71pRA8raTqLrec5LyTs5TqCxdABcZr77bt2KyWA5bizJHnC4g4ysm4h
let handmade_public_44 = master
.derive(&DerivationPath::new("m/44h/1h/0h".to_string()).unwrap())
.unwrap()
.as_public();
println!("Public 44: {}", handmade_public_44.as_string());
// Public 44: [d1d04177/44'/1'/0']tpubDCoPjomfTqh1e7o1WgGpQtARWtkueXQAepTeNpWiitS3Sdv8RKJ1yvTrGHcwjDXp2SKyMrTEca4LoN7gEUiGCWboyWe2rz99Kf4jK4m2Zmx/*
let handmade_public_49 = master
.derive(&DerivationPath::new("m/49h/1h/0h".to_string()).unwrap())
.unwrap()
.as_public();
println!("Public 49: {}", handmade_public_49.as_string());
// Public 49: [d1d04177/49'/1'/0']tpubDC65ZRvk1NDddHrVAUAZrUPJ772QXzooNYmPywYF9tMyNLYKf5wpKE7ZJvK9kvfG3FV7rCsHBNXy1LVKW95jrmC7c7z4hq7a27aD2sRrAhR/*
let handmade_public_84 = master
.derive(&DerivationPath::new("m/84h/1h/0h".to_string()).unwrap())
.unwrap()
.as_public();
println!("Public 84: {}", handmade_public_84.as_string());
// Public 84: [d1d04177/84'/1'/0']tpubDDNxbq17egjFk2edjv8oLnzxk52zny9aAYNv9CMqTzA4mQDiQq818sEkNe9Gzmd4QU8558zftqbfoVBDQorG3E4Wq26tB2JeE4KUoahLkx6/*
let handmade_public_86 = master
.derive(&DerivationPath::new("m/86h/1h/0h".to_string()).unwrap())
.unwrap()
.as_public();
println!("Public 86: {}", handmade_public_86.as_string());
// Public 86: [d1d04177/86'/1'/0']tpubDCJzjbcGbdEfXMWaL6QmgVmuSfXkrue7m2YNoacWwyc7a2XjXaKojRqNEbo41CFL3PyYmKdhwg2fkGpLX4SQCbQjCGxAkWHJTw9WEeenrJb/*
let template_private_44 =
Descriptor::new_bip44(&master, KeychainKind::External, Network::Testnet);
let template_private_49 =
Descriptor::new_bip49(&master, KeychainKind::External, Network::Testnet);
let template_private_84 =
Descriptor::new_bip84(&master, KeychainKind::External, Network::Testnet);
let template_private_86 =
Descriptor::new_bip86(&master, KeychainKind::External, Network::Testnet);
// the extended public keys are the same when creating them manually as they are with the templates
println!("Template 49: {}", template_private_49);
println!("Template 44: {}", template_private_44);
println!("Template 84: {}", template_private_84);
println!("Template 86: {}", template_private_86);
let template_public_44 = Descriptor::new_bip44_public(
&handmade_public_44,
"d1d04177".to_string(),
KeychainKind::External,
Network::Testnet,
);
let template_public_49 = Descriptor::new_bip49_public(
&handmade_public_49,
"d1d04177".to_string(),
KeychainKind::External,
Network::Testnet,
);
let template_public_84 = Descriptor::new_bip84_public(
&handmade_public_84,
"d1d04177".to_string(),
KeychainKind::External,
Network::Testnet,
);
let template_public_86 = Descriptor::new_bip86_public(
&handmade_public_86,
"d1d04177".to_string(),
KeychainKind::External,
Network::Testnet,
);
println!("Template public 49: {}", template_public_49);
println!("Template public 44: {}", template_public_44);
println!("Template public 84: {}", template_public_84);
println!("Template public 86: {}", template_public_86);
// when using a public key, both to_string and as_string_private return the same string
assert_eq!(
template_public_44.to_string_with_secret(),
template_public_44.to_string()
);
assert_eq!(
template_public_49.to_string_with_secret(),
template_public_49.to_string()
);
assert_eq!(
template_public_84.to_string_with_secret(),
template_public_84.to_string()
);
assert_eq!(
template_public_86.to_string_with_secret(),
template_public_86.to_string()
);
// when using to_string on a private key, we get the same result as when using it on a public key
assert_eq!(
template_private_44.to_string(),
template_public_44.to_string()
);
assert_eq!(
template_private_49.to_string(),
template_public_49.to_string()
);
assert_eq!(
template_private_84.to_string(),
template_public_84.to_string()
);
assert_eq!(
template_private_86.to_string(),
template_public_86.to_string()
);
}
#[test]
fn test_descriptor_from_string() {
let descriptor1 = Descriptor::new("wpkh(tprv8hwWMmPE4BVNxGdVt3HhEERZhondQvodUY7Ajyseyhudr4WabJqWKWLr4Wi2r26CDaNCQhhxEftEaNzz7dPGhWuKFU4VULesmhEfZYyBXdE/0/*)".to_string(), Network::Testnet);
let descriptor2 = Descriptor::new("wpkh(tprv8hwWMmPE4BVNxGdVt3HhEERZhondQvodUY7Ajyseyhudr4WabJqWKWLr4Wi2r26CDaNCQhhxEftEaNzz7dPGhWuKFU4VULesmhEfZYyBXdE/0/*)".to_string(), Network::Bitcoin);
// Creating a Descriptor using an extended key that doesn't match the network provided will throw a DescriptorError::Key with inner InvalidNetwork error
assert!(descriptor1.is_ok());
assert_matches!(descriptor2.unwrap_err(), DescriptorError::Key { .. });
}
}

View File

@ -1,101 +0,0 @@
use crate::bitcoin::Transaction;
use crate::error::ElectrumError;
use crate::types::{FullScanRequest, SyncRequest};
use crate::wallet::Update;
use bdk_electrum::BdkElectrumClient as BdkBdkElectrumClient;
use bdk_electrum::{ElectrumFullScanResult, ElectrumSyncResult};
use bdk_wallet::bitcoin::Transaction as BdkTransaction;
use bdk_wallet::chain::spk_client::FullScanRequest as BdkFullScanRequest;
use bdk_wallet::chain::spk_client::FullScanResult as BdkFullScanResult;
use bdk_wallet::chain::spk_client::SyncRequest as BdkSyncRequest;
use bdk_wallet::chain::spk_client::SyncResult as BdkSyncResult;
use bdk_wallet::wallet::Update as BdkUpdate;
use bdk_wallet::KeychainKind;
use std::collections::BTreeMap;
use std::sync::Arc;
// NOTE: We are keeping our naming convention where the alias of the inner type is the Rust type
// prefixed with `Bdk`. In this case the inner type is `BdkElectrumClient`, so the alias is
// funnily enough named `BdkBdkElectrumClient`.
pub struct ElectrumClient(BdkBdkElectrumClient<bdk_electrum::electrum_client::Client>);
impl ElectrumClient {
pub fn new(url: String) -> Result<Self, ElectrumError> {
let inner_client: bdk_electrum::electrum_client::Client =
bdk_electrum::electrum_client::Client::new(url.as_str())?;
let client = BdkBdkElectrumClient::new(inner_client);
Ok(Self(client))
}
pub fn full_scan(
&self,
request: Arc<FullScanRequest>,
stop_gap: u64,
batch_size: u64,
fetch_prev_txouts: bool,
) -> Result<Arc<Update>, ElectrumError> {
// using option and take is not ideal but the only way to take full ownership of the request
let request: BdkFullScanRequest<KeychainKind> = request
.0
.lock()
.unwrap()
.take()
.ok_or(ElectrumError::RequestAlreadyConsumed)?;
let electrum_result: ElectrumFullScanResult<KeychainKind> = self.0.full_scan(
request,
stop_gap as usize,
batch_size as usize,
fetch_prev_txouts,
)?;
let full_scan_result: BdkFullScanResult<KeychainKind> =
electrum_result.with_confirmation_time_height_anchor(&self.0)?;
let update = BdkUpdate {
last_active_indices: full_scan_result.last_active_indices,
graph: full_scan_result.graph_update,
chain: Some(full_scan_result.chain_update),
};
Ok(Arc::new(Update(update)))
}
pub fn sync(
&self,
request: Arc<SyncRequest>,
batch_size: u64,
fetch_prev_txouts: bool,
) -> Result<Arc<Update>, ElectrumError> {
// using option and take is not ideal but the only way to take full ownership of the request
let request: BdkSyncRequest = request
.0
.lock()
.unwrap()
.take()
.ok_or(ElectrumError::RequestAlreadyConsumed)?;
let electrum_result: ElectrumSyncResult =
self.0
.sync(request, batch_size as usize, fetch_prev_txouts)?;
let sync_result: BdkSyncResult =
electrum_result.with_confirmation_time_height_anchor(&self.0)?;
let update = BdkUpdate {
last_active_indices: BTreeMap::default(),
graph: sync_result.graph_update,
chain: Some(sync_result.chain_update),
};
Ok(Arc::new(Update(update)))
}
pub fn broadcast(&self, transaction: &Transaction) -> Result<String, ElectrumError> {
let bdk_transaction: BdkTransaction = transaction.into();
self.0
.transaction_broadcast(&bdk_transaction)
.map_err(ElectrumError::from)
.map(|txid| txid.to_string())
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,84 +0,0 @@
use crate::bitcoin::Transaction;
use crate::error::EsploraError;
use crate::types::{FullScanRequest, SyncRequest};
use crate::wallet::Update;
use bdk_esplora::esplora_client::{BlockingClient, Builder};
use bdk_esplora::EsploraExt;
use bdk_wallet::bitcoin::Transaction as BdkTransaction;
use bdk_wallet::chain::spk_client::FullScanRequest as BdkFullScanRequest;
use bdk_wallet::chain::spk_client::FullScanResult as BdkFullScanResult;
use bdk_wallet::chain::spk_client::SyncRequest as BdkSyncRequest;
use bdk_wallet::chain::spk_client::SyncResult as BdkSyncResult;
use bdk_wallet::wallet::Update as BdkUpdate;
use bdk_wallet::KeychainKind;
use std::collections::BTreeMap;
use std::sync::Arc;
pub struct EsploraClient(BlockingClient);
impl EsploraClient {
pub fn new(url: String) -> Self {
let client = Builder::new(url.as_str()).build_blocking();
Self(client)
}
pub fn full_scan(
&self,
request: Arc<FullScanRequest>,
stop_gap: u64,
parallel_requests: u64,
) -> Result<Arc<Update>, EsploraError> {
// using option and take is not ideal but the only way to take full ownership of the request
let request: BdkFullScanRequest<KeychainKind> = request
.0
.lock()
.unwrap()
.take()
.ok_or(EsploraError::RequestAlreadyConsumed)?;
let result: BdkFullScanResult<KeychainKind> =
self.0
.full_scan(request, stop_gap as usize, parallel_requests as usize)?;
let update = BdkUpdate {
last_active_indices: result.last_active_indices,
graph: result.graph_update,
chain: Some(result.chain_update),
};
Ok(Arc::new(Update(update)))
}
pub fn sync(
&self,
request: Arc<SyncRequest>,
parallel_requests: u64,
) -> Result<Arc<Update>, EsploraError> {
// using option and take is not ideal but the only way to take full ownership of the request
let request: BdkSyncRequest = request
.0
.lock()
.unwrap()
.take()
.ok_or(EsploraError::RequestAlreadyConsumed)?;
let result: BdkSyncResult = self.0.sync(request, parallel_requests as usize)?;
let update = BdkUpdate {
last_active_indices: BTreeMap::default(),
graph: result.graph_update,
chain: Some(result.chain_update),
};
Ok(Arc::new(Update(update)))
}
pub fn broadcast(&self, transaction: &Transaction) -> Result<(), EsploraError> {
let bdk_transaction: BdkTransaction = transaction.into();
self.0
.broadcast(&bdk_transaction)
.map_err(EsploraError::from)
}
}

View File

@ -1,357 +0,0 @@
use crate::error::{Bip32Error, Bip39Error, DescriptorKeyError};
use std::fmt::Display;
use bdk_wallet::bitcoin::bip32::DerivationPath as BdkDerivationPath;
use bdk_wallet::bitcoin::key::Secp256k1;
use bdk_wallet::bitcoin::secp256k1::rand;
use bdk_wallet::bitcoin::secp256k1::rand::Rng;
use bdk_wallet::bitcoin::Network;
use bdk_wallet::keys::bip39::WordCount;
use bdk_wallet::keys::bip39::{Language, Mnemonic as BdkMnemonic};
use bdk_wallet::keys::{
DerivableKey, DescriptorPublicKey as BdkDescriptorPublicKey,
DescriptorSecretKey as BdkDescriptorSecretKey, ExtendedKey, GeneratableKey, GeneratedKey,
};
use bdk_wallet::miniscript::descriptor::{DescriptorXKey, Wildcard};
use bdk_wallet::miniscript::BareCtx;
use std::ops::Deref;
use std::str::FromStr;
use std::sync::{Arc, Mutex};
pub(crate) struct Mnemonic(BdkMnemonic);
impl Mnemonic {
pub(crate) fn new(word_count: WordCount) -> Self {
// TODO 4: I DON'T KNOW IF THIS IS A DECENT WAY TO GENERATE ENTROPY PLEASE CONFIRM
let mut rng = rand::thread_rng();
let mut entropy = [0u8; 32];
rng.fill(&mut entropy);
let generated_key: GeneratedKey<_, BareCtx> =
BdkMnemonic::generate_with_entropy((word_count, Language::English), entropy).unwrap();
let mnemonic = BdkMnemonic::parse_in(Language::English, generated_key.to_string()).unwrap();
Mnemonic(mnemonic)
}
pub(crate) fn from_string(mnemonic: String) -> Result<Self, Bip39Error> {
BdkMnemonic::from_str(&mnemonic)
.map(Mnemonic)
.map_err(Bip39Error::from)
}
pub(crate) fn from_entropy(entropy: Vec<u8>) -> Result<Self, Bip39Error> {
BdkMnemonic::from_entropy(entropy.as_slice())
.map(Mnemonic)
.map_err(Bip39Error::from)
}
}
impl Display for Mnemonic {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
pub(crate) struct DerivationPath {
inner_mutex: Mutex<BdkDerivationPath>,
}
impl DerivationPath {
pub(crate) fn new(path: String) -> Result<Self, Bip32Error> {
BdkDerivationPath::from_str(&path)
.map(|x| DerivationPath {
inner_mutex: Mutex::new(x),
})
.map_err(Bip32Error::from)
}
}
#[derive(Debug)]
pub struct DescriptorSecretKey(pub(crate) BdkDescriptorSecretKey);
impl DescriptorSecretKey {
pub(crate) fn new(network: Network, mnemonic: &Mnemonic, password: Option<String>) -> Self {
let mnemonic = mnemonic.0.clone();
let xkey: ExtendedKey = (mnemonic, password).into_extended_key().unwrap();
let descriptor_secret_key = BdkDescriptorSecretKey::XPrv(DescriptorXKey {
origin: None,
xkey: xkey.into_xprv(network).unwrap(),
derivation_path: BdkDerivationPath::master(),
wildcard: Wildcard::Unhardened,
});
Self(descriptor_secret_key)
}
pub(crate) fn from_string(private_key: String) -> Result<Self, DescriptorKeyError> {
let descriptor_secret_key = BdkDescriptorSecretKey::from_str(private_key.as_str())
.map_err(DescriptorKeyError::from)?;
Ok(Self(descriptor_secret_key))
}
pub(crate) fn derive(&self, path: &DerivationPath) -> Result<Arc<Self>, DescriptorKeyError> {
let secp = Secp256k1::new();
let descriptor_secret_key = &self.0;
let path = path.inner_mutex.lock().unwrap().deref().clone();
match descriptor_secret_key {
BdkDescriptorSecretKey::Single(_) => Err(DescriptorKeyError::InvalidKeyType),
BdkDescriptorSecretKey::XPrv(descriptor_x_key) => {
let derived_xprv = descriptor_x_key
.xkey
.derive_priv(&secp, &path)
.map_err(DescriptorKeyError::from)?;
let key_source = match descriptor_x_key.origin.clone() {
Some((fingerprint, origin_path)) => (fingerprint, origin_path.extend(path)),
None => (descriptor_x_key.xkey.fingerprint(&secp), path),
};
let derived_descriptor_secret_key = BdkDescriptorSecretKey::XPrv(DescriptorXKey {
origin: Some(key_source),
xkey: derived_xprv,
derivation_path: BdkDerivationPath::default(),
wildcard: descriptor_x_key.wildcard,
});
Ok(Arc::new(Self(derived_descriptor_secret_key)))
}
BdkDescriptorSecretKey::MultiXPrv(_) => Err(DescriptorKeyError::InvalidKeyType),
}
}
pub(crate) fn extend(&self, path: &DerivationPath) -> Result<Arc<Self>, DescriptorKeyError> {
let descriptor_secret_key = &self.0;
let path = path.inner_mutex.lock().unwrap().deref().clone();
match descriptor_secret_key {
BdkDescriptorSecretKey::Single(_) => Err(DescriptorKeyError::InvalidKeyType),
BdkDescriptorSecretKey::XPrv(descriptor_x_key) => {
let extended_path = descriptor_x_key.derivation_path.extend(path);
let extended_descriptor_secret_key = BdkDescriptorSecretKey::XPrv(DescriptorXKey {
origin: descriptor_x_key.origin.clone(),
xkey: descriptor_x_key.xkey,
derivation_path: extended_path,
wildcard: descriptor_x_key.wildcard,
});
Ok(Arc::new(Self(extended_descriptor_secret_key)))
}
BdkDescriptorSecretKey::MultiXPrv(_) => Err(DescriptorKeyError::InvalidKeyType),
}
}
pub(crate) fn as_public(&self) -> Arc<DescriptorPublicKey> {
let secp = Secp256k1::new();
let descriptor_public_key = self.0.to_public(&secp).unwrap();
Arc::new(DescriptorPublicKey(descriptor_public_key))
}
pub(crate) fn secret_bytes(&self) -> Vec<u8> {
let inner = &self.0;
let secret_bytes: Vec<u8> = match inner {
BdkDescriptorSecretKey::Single(_) => {
unreachable!()
}
BdkDescriptorSecretKey::XPrv(descriptor_x_key) => {
descriptor_x_key.xkey.private_key.secret_bytes().to_vec()
}
BdkDescriptorSecretKey::MultiXPrv(_) => {
unreachable!()
}
};
secret_bytes
}
pub(crate) fn as_string(&self) -> String {
self.0.to_string()
}
}
#[derive(Debug)]
pub struct DescriptorPublicKey(pub(crate) BdkDescriptorPublicKey);
impl DescriptorPublicKey {
pub(crate) fn from_string(public_key: String) -> Result<Self, DescriptorKeyError> {
let descriptor_public_key = BdkDescriptorPublicKey::from_str(public_key.as_str())
.map_err(DescriptorKeyError::from)?;
Ok(Self(descriptor_public_key))
}
pub(crate) fn derive(&self, path: &DerivationPath) -> Result<Arc<Self>, DescriptorKeyError> {
let secp = Secp256k1::new();
let descriptor_public_key = &self.0;
let path = path.inner_mutex.lock().unwrap().deref().clone();
match descriptor_public_key {
BdkDescriptorPublicKey::Single(_) => Err(DescriptorKeyError::InvalidKeyType),
BdkDescriptorPublicKey::XPub(descriptor_x_key) => {
let derived_xpub = descriptor_x_key
.xkey
.derive_pub(&secp, &path)
.map_err(DescriptorKeyError::from)?;
let key_source = match descriptor_x_key.origin.clone() {
Some((fingerprint, origin_path)) => (fingerprint, origin_path.extend(path)),
None => (descriptor_x_key.xkey.fingerprint(), path),
};
let derived_descriptor_public_key = BdkDescriptorPublicKey::XPub(DescriptorXKey {
origin: Some(key_source),
xkey: derived_xpub,
derivation_path: BdkDerivationPath::default(),
wildcard: descriptor_x_key.wildcard,
});
Ok(Arc::new(Self(derived_descriptor_public_key)))
}
BdkDescriptorPublicKey::MultiXPub(_) => Err(DescriptorKeyError::InvalidKeyType),
}
}
pub(crate) fn extend(&self, path: &DerivationPath) -> Result<Arc<Self>, DescriptorKeyError> {
let descriptor_public_key = &self.0;
let path = path.inner_mutex.lock().unwrap().deref().clone();
match descriptor_public_key {
BdkDescriptorPublicKey::Single(_) => Err(DescriptorKeyError::InvalidKeyType),
BdkDescriptorPublicKey::XPub(descriptor_x_key) => {
let extended_path = descriptor_x_key.derivation_path.extend(path);
let extended_descriptor_public_key = BdkDescriptorPublicKey::XPub(DescriptorXKey {
origin: descriptor_x_key.origin.clone(),
xkey: descriptor_x_key.xkey,
derivation_path: extended_path,
wildcard: descriptor_x_key.wildcard,
});
Ok(Arc::new(Self(extended_descriptor_public_key)))
}
BdkDescriptorPublicKey::MultiXPub(_) => Err(DescriptorKeyError::InvalidKeyType),
}
}
pub(crate) fn as_string(&self) -> String {
self.0.to_string()
}
}
#[cfg(test)]
mod test {
use crate::error::DescriptorKeyError;
use crate::keys::{DerivationPath, DescriptorPublicKey, DescriptorSecretKey, Mnemonic};
use bdk_wallet::bitcoin::Network;
use std::sync::Arc;
fn get_inner() -> DescriptorSecretKey {
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();
DescriptorSecretKey::new(Network::Testnet, &mnemonic, None)
}
fn derive_dsk(
key: &DescriptorSecretKey,
path: &str,
) -> Result<Arc<DescriptorSecretKey>, DescriptorKeyError> {
let path = DerivationPath::new(path.to_string()).unwrap();
key.derive(&path)
}
fn extend_dsk(
key: &DescriptorSecretKey,
path: &str,
) -> Result<Arc<DescriptorSecretKey>, DescriptorKeyError> {
let path = DerivationPath::new(path.to_string()).unwrap();
key.extend(&path)
}
fn derive_dpk(
key: &DescriptorPublicKey,
path: &str,
) -> Result<Arc<DescriptorPublicKey>, DescriptorKeyError> {
let path = DerivationPath::new(path.to_string()).unwrap();
key.derive(&path)
}
fn extend_dpk(
key: &DescriptorPublicKey,
path: &str,
) -> Result<Arc<DescriptorPublicKey>, DescriptorKeyError> {
let path = DerivationPath::new(path.to_string()).unwrap();
key.extend(&path)
}
#[test]
fn test_generate_descriptor_secret_key() {
let master_dsk = get_inner();
assert_eq!(master_dsk.as_string(), "tprv8ZgxMBicQKsPdWuqM1t1CDRvQtQuBPyfL6GbhQwtxDKgUAVPbxmj71pRA8raTqLrec5LyTs5TqCxdABcZr77bt2KyWA5bizJHnC4g4ysm4h/*");
assert_eq!(master_dsk.as_public().as_string(), "tpubD6NzVbkrYhZ4WywdEfYbbd62yuvqLjAZuPsNyvzCNV85JekAEMbKHWSHLF9h3j45SxewXDcLv328B1SEZrxg4iwGfmdt1pDFjZiTkGiFqGa/*");
}
#[test]
fn test_derive_self() {
let master_dsk = get_inner();
let derived_dsk: &DescriptorSecretKey = &derive_dsk(&master_dsk, "m").unwrap();
assert_eq!(derived_dsk.as_string(), "[d1d04177]tprv8ZgxMBicQKsPdWuqM1t1CDRvQtQuBPyfL6GbhQwtxDKgUAVPbxmj71pRA8raTqLrec5LyTs5TqCxdABcZr77bt2KyWA5bizJHnC4g4ysm4h/*");
let master_dpk: &DescriptorPublicKey = &master_dsk.as_public();
let derived_dpk: &DescriptorPublicKey = &derive_dpk(master_dpk, "m").unwrap();
assert_eq!(derived_dpk.as_string(), "[d1d04177]tpubD6NzVbkrYhZ4WywdEfYbbd62yuvqLjAZuPsNyvzCNV85JekAEMbKHWSHLF9h3j45SxewXDcLv328B1SEZrxg4iwGfmdt1pDFjZiTkGiFqGa/*");
}
#[test]
fn test_derive_descriptors_keys() {
let master_dsk = get_inner();
let derived_dsk: &DescriptorSecretKey = &derive_dsk(&master_dsk, "m/0").unwrap();
assert_eq!(derived_dsk.as_string(), "[d1d04177/0]tprv8d7Y4JLmD25jkKbyDZXcdoPHu1YtMHuH21qeN7mFpjfumtSU7eZimFYUCSa3MYzkEYfSNRBV34GEr2QXwZCMYRZ7M1g6PUtiLhbJhBZEGYJ/*");
let master_dpk: &DescriptorPublicKey = &master_dsk.as_public();
let derived_dpk: &DescriptorPublicKey = &derive_dpk(master_dpk, "m/0").unwrap();
assert_eq!(derived_dpk.as_string(), "[d1d04177/0]tpubD9oaCiP1MPmQdndm7DCD3D3QU34pWd6BbKSRedoZF1UJcNhEk3PJwkALNYkhxeTKL29oGNR7psqvT1KZydCGqUDEKXN6dVQJY2R8ooLPy8m/*");
}
#[test]
fn test_extend_descriptor_keys() {
let master_dsk = get_inner();
let extended_dsk: &DescriptorSecretKey = &extend_dsk(&master_dsk, "m/0").unwrap();
assert_eq!(extended_dsk.as_string(), "tprv8ZgxMBicQKsPdWuqM1t1CDRvQtQuBPyfL6GbhQwtxDKgUAVPbxmj71pRA8raTqLrec5LyTs5TqCxdABcZr77bt2KyWA5bizJHnC4g4ysm4h/0/*");
let master_dpk: &DescriptorPublicKey = &master_dsk.as_public();
let extended_dpk: &DescriptorPublicKey = &extend_dpk(master_dpk, "m/0").unwrap();
assert_eq!(extended_dpk.as_string(), "tpubD6NzVbkrYhZ4WywdEfYbbd62yuvqLjAZuPsNyvzCNV85JekAEMbKHWSHLF9h3j45SxewXDcLv328B1SEZrxg4iwGfmdt1pDFjZiTkGiFqGa/0/*");
let wif = "L2wTu6hQrnDMiFNWA5na6jB12ErGQqtXwqpSL7aWquJaZG8Ai3ch";
let extended_key = DescriptorSecretKey::from_string(wif.to_string()).unwrap();
let result = extended_key.derive(&DerivationPath::new("m/0".to_string()).unwrap());
dbg!(&result);
assert!(result.is_err());
}
#[test]
fn test_from_str_inner() {
let key1 = "L2wTu6hQrnDMiFNWA5na6jB12ErGQqtXwqpSL7aWquJaZG8Ai3ch";
let key2 = "tprv8ZgxMBicQKsPcwcD4gSnMti126ZiETsuX7qwrtMypr6FBwAP65puFn4v6c3jrN9VwtMRMph6nyT63NrfUL4C3nBzPcduzVSuHD7zbX2JKVc/1/1/1/*";
let private_descriptor_key1 = DescriptorSecretKey::from_string(key1.to_string()).unwrap();
let private_descriptor_key2 = DescriptorSecretKey::from_string(key2.to_string()).unwrap();
dbg!(private_descriptor_key1);
dbg!(private_descriptor_key2);
// Should error out because you can't produce a DescriptorSecretKey from an xpub
let key0 = "tpubDBrgjcxBxnXyL575sHdkpKohWu5qHKoQ7TJXKNrYznh5fVEGBv89hA8ENW7A8MFVpFUSvgLqc4Nj1WZcpePX6rrxviVtPowvMuGF5rdT2Vi";
assert!(DescriptorSecretKey::from_string(key0.to_string()).is_err());
}
#[test]
fn test_derive_and_extend_inner() {
let master_dsk = get_inner();
// 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/*");
// extend derived_dsk with path "m/0"
let extended_dsk: &DescriptorSecretKey = &extend_dsk(derived_dsk, "m/0").unwrap();
assert_eq!(extended_dsk.as_string(), "[d1d04177/0]tprv8d7Y4JLmD25jkKbyDZXcdoPHu1YtMHuH21qeN7mFpjfumtSU7eZimFYUCSa3MYzkEYfSNRBV34GEr2QXwZCMYRZ7M1g6PUtiLhbJhBZEGYJ/0/*");
}
#[test]
fn test_derive_hardened_path_using_public() {
let master_dpk = get_inner().as_public();
let derived_dpk = &derive_dpk(&master_dpk, "m/84h/1h/0h");
assert!(derived_dpk.is_err());
}
// TODO 7: It appears that the to_hex() method is not available anymore.
// Look into the correct way to pull the hex out of the DescriptorSecretKey.
// Note: ToHex was removed in bitcoin_hashes 0.12.0
// #[test]
// fn test_retrieve_master_secret_key() {
// let master_dpk = get_inner();
// let master_private_key = master_dpk.secret_bytes().to_hex();
// assert_eq!(
// master_private_key,
// "e93315d6ce401eb4db803a56232f0ed3e69b053774e6047df54f1bd00e5ea936"
// )
// }
}

File diff suppressed because it is too large Load Diff

View File

@ -1,39 +0,0 @@
use crate::error::SqliteError;
use crate::types::ChangeSet;
use bdk_sqlite::rusqlite::Connection;
use bdk_sqlite::{Store as BdkSqliteStore, Store};
use bdk_wallet::chain::ConfirmationTimeHeightAnchor;
use bdk_wallet::KeychainKind;
use std::sync::{Arc, Mutex, MutexGuard};
pub struct SqliteStore(Mutex<BdkSqliteStore<KeychainKind, ConfirmationTimeHeightAnchor>>);
impl SqliteStore {
pub fn new(path: String) -> Result<Self, SqliteError> {
let connection = Connection::open(path)?;
let db = Store::new(connection)?;
Ok(Self(Mutex::new(db)))
}
pub(crate) fn get_store(
&self,
) -> MutexGuard<BdkSqliteStore<KeychainKind, ConfirmationTimeHeightAnchor>> {
self.0.lock().expect("sqlite store")
}
pub fn write(&self, changeset: &ChangeSet) -> Result<(), SqliteError> {
self.get_store()
.write(&changeset.0)
.map_err(SqliteError::from)
}
pub fn read(&self) -> Result<Option<Arc<ChangeSet>>, SqliteError> {
self.get_store()
.read()
.map_err(SqliteError::from)
.map(|optional_bdk_change_set| optional_bdk_change_set.map(ChangeSet::from))
.map(|optional_change_set| optional_change_set.map(Arc::new))
}
}

View File

@ -1,173 +0,0 @@
use crate::bitcoin::Amount;
use crate::bitcoin::{Address, OutPoint, Script, Transaction, TxOut};
use crate::InspectError;
use bdk_wallet::bitcoin::ScriptBuf as BdkScriptBuf;
use bdk_wallet::bitcoin::Transaction as BdkTransaction;
use bdk_wallet::chain::spk_client::FullScanRequest as BdkFullScanRequest;
use bdk_wallet::chain::spk_client::SyncRequest as BdkSyncRequest;
use bdk_wallet::chain::tx_graph::CanonicalTx as BdkCanonicalTx;
use bdk_wallet::chain::{ChainPosition as BdkChainPosition, ConfirmationTimeHeightAnchor};
use bdk_wallet::wallet::AddressInfo as BdkAddressInfo;
use bdk_wallet::wallet::Balance as BdkBalance;
use bdk_wallet::KeychainKind;
use bdk_wallet::LocalOutput as BdkLocalOutput;
use bdk_electrum::bdk_chain::CombinedChangeSet;
use std::sync::{Arc, Mutex};
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ChainPosition {
Confirmed { height: u32, timestamp: u64 },
Unconfirmed { timestamp: u64 },
}
pub struct CanonicalTx {
pub transaction: Arc<Transaction>,
pub chain_position: ChainPosition,
}
impl From<BdkCanonicalTx<'_, Arc<BdkTransaction>, ConfirmationTimeHeightAnchor>> for CanonicalTx {
fn from(tx: BdkCanonicalTx<'_, Arc<BdkTransaction>, ConfirmationTimeHeightAnchor>) -> Self {
let chain_position = match tx.chain_position {
BdkChainPosition::Confirmed(anchor) => ChainPosition::Confirmed {
height: anchor.confirmation_height,
timestamp: anchor.confirmation_time,
},
BdkChainPosition::Unconfirmed(timestamp) => ChainPosition::Unconfirmed { timestamp },
};
CanonicalTx {
transaction: Arc::new(Transaction::from(tx.tx_node.tx.as_ref().clone())),
chain_position,
}
}
}
pub struct ScriptAmount {
pub script: Arc<Script>,
pub amount: Arc<Amount>,
}
pub struct AddressInfo {
pub index: u32,
pub address: Arc<Address>,
pub keychain: KeychainKind,
}
impl From<BdkAddressInfo> for AddressInfo {
fn from(address_info: BdkAddressInfo) -> Self {
AddressInfo {
index: address_info.index,
address: Arc::new(address_info.address.into()),
keychain: address_info.keychain,
}
}
}
pub struct Balance {
pub immature: Arc<Amount>,
pub trusted_pending: Arc<Amount>,
pub untrusted_pending: Arc<Amount>,
pub confirmed: Arc<Amount>,
pub trusted_spendable: Arc<Amount>,
pub total: Arc<Amount>,
}
impl From<BdkBalance> for Balance {
fn from(bdk_balance: BdkBalance) -> Self {
Balance {
immature: Arc::new(bdk_balance.immature.into()),
trusted_pending: Arc::new(bdk_balance.trusted_pending.into()),
untrusted_pending: Arc::new(bdk_balance.untrusted_pending.into()),
confirmed: Arc::new(bdk_balance.confirmed.into()),
trusted_spendable: Arc::new(bdk_balance.trusted_spendable().into()),
total: Arc::new(bdk_balance.total().into()),
}
}
}
pub struct ChangeSet(pub(crate) CombinedChangeSet<KeychainKind, ConfirmationTimeHeightAnchor>);
impl From<CombinedChangeSet<KeychainKind, ConfirmationTimeHeightAnchor>> for ChangeSet {
fn from(change_set: CombinedChangeSet<KeychainKind, ConfirmationTimeHeightAnchor>) -> Self {
ChangeSet(change_set)
}
}
pub struct LocalOutput {
pub outpoint: OutPoint,
pub txout: TxOut,
pub keychain: KeychainKind,
pub is_spent: bool,
}
impl From<BdkLocalOutput> for LocalOutput {
fn from(local_utxo: BdkLocalOutput) -> Self {
LocalOutput {
outpoint: OutPoint {
txid: local_utxo.outpoint.txid.to_string(),
vout: local_utxo.outpoint.vout,
},
txout: TxOut {
value: local_utxo.txout.value.to_sat(),
script_pubkey: Arc::new(Script(local_utxo.txout.script_pubkey)),
},
keychain: local_utxo.keychain,
is_spent: local_utxo.is_spent,
}
}
}
// Callback for the FullScanRequest
pub trait FullScanScriptInspector: Sync + Send {
fn inspect(&self, keychain: KeychainKind, index: u32, script: Arc<Script>);
}
// Callback for the SyncRequest
pub trait SyncScriptInspector: Sync + Send {
fn inspect(&self, script: Arc<Script>, total: u64);
}
pub struct FullScanRequest(pub(crate) Mutex<Option<BdkFullScanRequest<KeychainKind>>>);
pub struct SyncRequest(pub(crate) Mutex<Option<BdkSyncRequest>>);
impl SyncRequest {
pub fn inspect_spks(
&self,
inspector: Arc<dyn SyncScriptInspector>,
) -> Result<Arc<Self>, InspectError> {
let mut guard = self.0.lock().unwrap();
if let Some(sync_request) = guard.take() {
let total = sync_request.spks.len() as u64;
let sync_request = sync_request.inspect_spks(move |spk| {
inspector.inspect(Arc::new(BdkScriptBuf::from(spk).into()), total)
});
Ok(Arc::new(SyncRequest(Mutex::new(Some(sync_request)))))
} else {
Err(InspectError::RequestAlreadyConsumed)
}
}
}
impl FullScanRequest {
pub fn inspect_spks_for_all_keychains(
&self,
inspector: Arc<dyn FullScanScriptInspector>,
) -> Result<Arc<Self>, InspectError> {
let mut guard = self.0.lock().unwrap();
if let Some(full_scan_request) = guard.take() {
let inspector = Arc::new(inspector);
let full_scan_request =
full_scan_request.inspect_spks_for_all_keychains(move |k, spk_i, script| {
inspector.inspect(k, spk_i, Arc::new(BdkScriptBuf::from(script).into()))
});
Ok(Arc::new(FullScanRequest(Mutex::new(Some(
full_scan_request,
)))))
} else {
Err(InspectError::RequestAlreadyConsumed)
}
}
}

View File

@ -1,726 +0,0 @@
use crate::bitcoin::Amount;
use crate::bitcoin::{FeeRate, OutPoint, Psbt, Script, Transaction};
use crate::descriptor::Descriptor;
use crate::error::{
CalculateFeeError, CannotConnectError, CreateTxError, SignerError, TxidParseError,
WalletCreationError,
};
use crate::types::{
AddressInfo, Balance, CanonicalTx, ChangeSet, FullScanRequest, LocalOutput, ScriptAmount,
SyncRequest,
};
use bdk_wallet::bitcoin::amount::Amount as BdkAmount;
use bdk_wallet::bitcoin::Network;
use bdk_wallet::bitcoin::Psbt as BdkPsbt;
use bdk_wallet::bitcoin::ScriptBuf as BdkScriptBuf;
use bdk_wallet::bitcoin::{OutPoint as BdkOutPoint, Sequence, Txid};
use bdk_wallet::chain::{CombinedChangeSet, ConfirmationTimeHeightAnchor};
use bdk_wallet::wallet::tx_builder::ChangeSpendPolicy;
use bdk_wallet::wallet::Update as BdkUpdate;
use bdk_wallet::Wallet as BdkWallet;
use bdk_wallet::{KeychainKind, SignOptions};
use std::collections::HashSet;
use std::str::FromStr;
use std::sync::{Arc, Mutex, MutexGuard};
pub struct Wallet {
inner_mutex: Mutex<BdkWallet>,
}
impl Wallet {
pub fn new(
descriptor: Arc<Descriptor>,
change_descriptor: Arc<Descriptor>,
network: Network,
) -> Result<Self, WalletCreationError> {
let descriptor = descriptor.to_string_with_secret();
let change_descriptor = change_descriptor.to_string_with_secret();
let wallet: BdkWallet = BdkWallet::new(&descriptor, &change_descriptor, network)?;
Ok(Wallet {
inner_mutex: Mutex::new(wallet),
})
}
pub fn new_or_load(
descriptor: Arc<Descriptor>,
change_descriptor: Arc<Descriptor>,
change_set: Option<Arc<ChangeSet>>,
network: Network,
) -> Result<Self, WalletCreationError> {
let descriptor = descriptor.to_string_with_secret();
let change_descriptor = change_descriptor.to_string_with_secret();
let change_set: Option<CombinedChangeSet<KeychainKind, ConfirmationTimeHeightAnchor>> =
change_set.map(|cs| cs.0.clone());
let wallet: BdkWallet =
BdkWallet::new_or_load(&descriptor, &change_descriptor, change_set, network)?;
Ok(Wallet {
inner_mutex: Mutex::new(wallet),
})
}
pub(crate) fn get_wallet(&self) -> MutexGuard<BdkWallet> {
self.inner_mutex.lock().expect("wallet")
}
pub fn reveal_next_address(&self, keychain_kind: KeychainKind) -> AddressInfo {
self.get_wallet().reveal_next_address(keychain_kind).into()
}
pub fn apply_update(&self, update: Arc<Update>) -> Result<(), CannotConnectError> {
self.get_wallet()
.apply_update(update.0.clone())
.map_err(CannotConnectError::from)
}
pub fn network(&self) -> Network {
self.get_wallet().network()
}
pub fn balance(&self) -> Balance {
let bdk_balance = self.get_wallet().balance();
Balance::from(bdk_balance)
}
pub fn is_mine(&self, script: &Script) -> bool {
self.get_wallet().is_mine(&script.0)
}
pub(crate) fn sign(
&self,
psbt: Arc<Psbt>,
// sign_options: Option<SignOptions>,
) -> Result<bool, SignerError> {
let mut psbt = psbt.0.lock().unwrap();
self.get_wallet()
.sign(&mut psbt, SignOptions::default())
.map_err(SignerError::from)
}
pub fn sent_and_received(&self, tx: &Transaction) -> SentAndReceivedValues {
let (sent, received) = self.get_wallet().sent_and_received(&tx.into());
SentAndReceivedValues {
sent: Arc::new(sent.into()),
received: Arc::new(received.into()),
}
}
pub fn transactions(&self) -> Vec<CanonicalTx> {
self.get_wallet()
.transactions()
.map(|tx| tx.into())
.collect()
}
pub fn get_tx(&self, txid: String) -> Result<Option<CanonicalTx>, TxidParseError> {
let txid =
Txid::from_str(txid.as_str()).map_err(|_| TxidParseError::InvalidTxid { txid })?;
Ok(self.get_wallet().get_tx(txid).map(|tx| tx.into()))
}
pub fn calculate_fee(&self, tx: &Transaction) -> Result<Arc<Amount>, CalculateFeeError> {
self.get_wallet()
.calculate_fee(&tx.into())
.map(Amount::from)
.map(Arc::new)
.map_err(|e| e.into())
}
pub fn calculate_fee_rate(&self, tx: &Transaction) -> Result<Arc<FeeRate>, CalculateFeeError> {
self.get_wallet()
.calculate_fee_rate(&tx.into())
.map(|bdk_fee_rate| Arc::new(FeeRate(bdk_fee_rate)))
.map_err(|e| e.into())
}
pub fn list_unspent(&self) -> Vec<LocalOutput> {
self.get_wallet().list_unspent().map(|o| o.into()).collect()
}
pub fn list_output(&self) -> Vec<LocalOutput> {
self.get_wallet().list_output().map(|o| o.into()).collect()
}
pub fn start_full_scan(&self) -> Arc<FullScanRequest> {
let request = self.get_wallet().start_full_scan();
Arc::new(FullScanRequest(Mutex::new(Some(request))))
}
pub fn start_sync_with_revealed_spks(&self) -> Arc<SyncRequest> {
let request = self.get_wallet().start_sync_with_revealed_spks();
Arc::new(SyncRequest(Mutex::new(Some(request))))
}
pub fn take_staged(&self) -> Option<Arc<ChangeSet>> {
self.get_wallet()
.take_staged()
.map(|change_set| Arc::new(change_set.into()))
}
}
pub struct SentAndReceivedValues {
pub sent: Arc<Amount>,
pub received: Arc<Amount>,
}
pub struct Update(pub(crate) BdkUpdate);
#[derive(Clone, Debug)]
pub struct TxBuilder {
pub(crate) recipients: Vec<(BdkScriptBuf, BdkAmount)>,
pub(crate) utxos: Vec<OutPoint>,
pub(crate) unspendable: HashSet<OutPoint>,
pub(crate) change_policy: ChangeSpendPolicy,
pub(crate) manually_selected_only: bool,
pub(crate) fee_rate: Option<FeeRate>,
pub(crate) fee_absolute: Option<Arc<Amount>>,
pub(crate) drain_wallet: bool,
pub(crate) drain_to: Option<BdkScriptBuf>,
pub(crate) rbf: Option<RbfValue>,
// pub(crate) data: Vec<u8>,
}
impl TxBuilder {
pub(crate) fn new() -> Self {
TxBuilder {
recipients: Vec::new(),
utxos: Vec::new(),
unspendable: HashSet::new(),
change_policy: ChangeSpendPolicy::ChangeAllowed,
manually_selected_only: false,
fee_rate: None,
fee_absolute: None,
drain_wallet: false,
drain_to: None,
rbf: None,
// data: Vec::new(),
}
}
pub(crate) fn add_recipient(&self, script: &Script, amount: Arc<Amount>) -> Arc<Self> {
let mut recipients: Vec<(BdkScriptBuf, BdkAmount)> = self.recipients.clone();
recipients.append(&mut vec![(script.0.clone(), amount.0)]);
Arc::new(TxBuilder {
recipients,
..self.clone()
})
}
pub(crate) fn set_recipients(&self, recipients: Vec<ScriptAmount>) -> Arc<Self> {
let recipients = recipients
.iter()
.map(|script_amount| (script_amount.script.0.clone(), script_amount.amount.0)) //;
.collect();
Arc::new(TxBuilder {
recipients,
..self.clone()
})
}
pub(crate) fn add_unspendable(&self, unspendable: OutPoint) -> Arc<Self> {
let mut unspendable_hash_set = self.unspendable.clone();
unspendable_hash_set.insert(unspendable);
Arc::new(TxBuilder {
unspendable: unspendable_hash_set,
..self.clone()
})
}
pub(crate) fn unspendable(&self, unspendable: Vec<OutPoint>) -> Arc<Self> {
Arc::new(TxBuilder {
unspendable: unspendable.into_iter().collect(),
..self.clone()
})
}
pub(crate) fn add_utxo(&self, outpoint: OutPoint) -> Arc<Self> {
self.add_utxos(vec![outpoint])
}
pub(crate) fn add_utxos(&self, mut outpoints: Vec<OutPoint>) -> Arc<Self> {
let mut utxos = self.utxos.to_vec();
utxos.append(&mut outpoints);
Arc::new(TxBuilder {
utxos,
..self.clone()
})
}
pub(crate) fn change_policy(&self, change_policy: ChangeSpendPolicy) -> Arc<Self> {
Arc::new(TxBuilder {
change_policy,
..self.clone()
})
}
pub(crate) fn do_not_spend_change(&self) -> Arc<Self> {
Arc::new(TxBuilder {
change_policy: ChangeSpendPolicy::ChangeForbidden,
..self.clone()
})
}
pub(crate) fn only_spend_change(&self) -> Arc<Self> {
Arc::new(TxBuilder {
change_policy: ChangeSpendPolicy::OnlyChange,
..self.clone()
})
}
pub(crate) fn manually_selected_only(&self) -> Arc<Self> {
Arc::new(TxBuilder {
manually_selected_only: true,
..self.clone()
})
}
pub(crate) fn fee_rate(&self, fee_rate: &FeeRate) -> Arc<Self> {
Arc::new(TxBuilder {
fee_rate: Some(fee_rate.clone()),
..self.clone()
})
}
pub(crate) fn fee_absolute(&self, fee_amount: Arc<Amount>) -> Arc<Self> {
Arc::new(TxBuilder {
fee_absolute: Some(fee_amount),
..self.clone()
})
}
pub(crate) fn drain_wallet(&self) -> Arc<Self> {
Arc::new(TxBuilder {
drain_wallet: true,
..self.clone()
})
}
pub(crate) fn drain_to(&self, script: &Script) -> Arc<Self> {
Arc::new(TxBuilder {
drain_to: Some(script.0.clone()),
..self.clone()
})
}
pub(crate) fn enable_rbf(&self) -> Arc<Self> {
Arc::new(TxBuilder {
rbf: Some(RbfValue::Default),
..self.clone()
})
}
pub(crate) fn enable_rbf_with_sequence(&self, nsequence: u32) -> Arc<Self> {
Arc::new(TxBuilder {
rbf: Some(RbfValue::Value(nsequence)),
..self.clone()
})
}
pub(crate) fn finish(&self, wallet: &Arc<Wallet>) -> Result<Arc<Psbt>, CreateTxError> {
// TODO: I had to change the wallet here to be mutable. Why is that now required with the 1.0 API?
let mut wallet = wallet.get_wallet();
let mut tx_builder = wallet.build_tx();
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() {
let bdk_utxos: Vec<BdkOutPoint> = self.utxos.iter().map(BdkOutPoint::from).collect();
tx_builder
.add_utxos(&bdk_utxos)
.map_err(CreateTxError::from)?;
}
if !self.unspendable.is_empty() {
let bdk_unspendable: Vec<BdkOutPoint> =
self.unspendable.iter().map(BdkOutPoint::from).collect();
tx_builder.unspendable(bdk_unspendable);
}
if self.manually_selected_only {
tx_builder.manually_selected_only();
}
if let Some(fee_rate) = &self.fee_rate {
tx_builder.fee_rate(fee_rate.0);
}
if let Some(fee_amount) = &self.fee_absolute {
tx_builder.fee_absolute(fee_amount.0);
}
if self.drain_wallet {
tx_builder.drain_wallet();
}
if let Some(script) = &self.drain_to {
tx_builder.drain_to(script.clone());
}
if let Some(rbf) = &self.rbf {
match *rbf {
RbfValue::Default => {
tx_builder.enable_rbf();
}
RbfValue::Value(nsequence) => {
tx_builder.enable_rbf_with_sequence(Sequence(nsequence));
}
}
}
let psbt = tx_builder.finish().map_err(CreateTxError::from)?;
Ok(Arc::new(psbt.into()))
}
}
#[derive(Clone)]
pub(crate) struct BumpFeeTxBuilder {
pub(crate) txid: String,
pub(crate) fee_rate: Arc<FeeRate>,
pub(crate) rbf: Option<RbfValue>,
}
impl BumpFeeTxBuilder {
pub(crate) fn new(txid: String, fee_rate: Arc<FeeRate>) -> Self {
Self {
txid,
fee_rate,
rbf: None,
}
}
pub(crate) fn enable_rbf(&self) -> Arc<Self> {
Arc::new(Self {
rbf: Some(RbfValue::Default),
..self.clone()
})
}
pub(crate) fn enable_rbf_with_sequence(&self, nsequence: u32) -> Arc<Self> {
Arc::new(Self {
rbf: Some(RbfValue::Value(nsequence)),
..self.clone()
})
}
pub(crate) fn finish(&self, wallet: &Wallet) -> Result<Arc<Psbt>, CreateTxError> {
let txid = Txid::from_str(self.txid.as_str()).map_err(|_| CreateTxError::UnknownUtxo {
outpoint: self.txid.clone(),
})?;
let mut wallet = wallet.get_wallet();
let mut tx_builder = wallet.build_fee_bump(txid).map_err(CreateTxError::from)?;
tx_builder.fee_rate(self.fee_rate.0);
if let Some(rbf) = &self.rbf {
match *rbf {
RbfValue::Default => {
tx_builder.enable_rbf();
}
RbfValue::Value(nsequence) => {
tx_builder.enable_rbf_with_sequence(Sequence(nsequence));
}
}
}
let psbt: BdkPsbt = tx_builder.finish()?;
Ok(Arc::new(psbt.into()))
}
}
#[derive(Clone, Debug)]
pub enum RbfValue {
Default,
Value(u32),
}
// #[cfg(test)]
// mod test {
// use crate::database::DatabaseConfig;
// use crate::descriptor::Descriptor;
// use crate::keys::{DescriptorSecretKey, Mnemonic};
// use crate::wallet::{AddressIndex, TxBuilder, Wallet};
// use crate::Script;
// use assert_matches::assert_matches;
// use bdk::bitcoin::{Address, Network};
// // use bdk::wallet::get_funded_wallet;
// use bdk::KeychainKind;
// use std::str::FromStr;
// use std::sync::{Arc, Mutex};
//
// // #[test]
// // fn test_drain_wallet() {
// // let test_wpkh = "wpkh(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW)";
// // let (funded_wallet, _, _) = get_funded_wallet(test_wpkh);
// // let test_wallet = Wallet {
// // inner_mutex: Mutex::new(funded_wallet),
// // };
// // let drain_to_address = "tb1ql7w62elx9ucw4pj5lgw4l028hmuw80sndtntxt".to_string();
// // let drain_to_script = crate::Address::new(drain_to_address)
// // .unwrap()
// // .script_pubkey();
// // let tx_builder = TxBuilder::new()
// // .drain_wallet()
// // .drain_to(drain_to_script.clone());
// // assert!(tx_builder.drain_wallet);
// // assert_eq!(tx_builder.drain_to, Some(drain_to_script.inner.clone()));
// //
// // let tx_builder_result = tx_builder.finish(&test_wallet).unwrap();
// // let psbt = tx_builder_result.psbt.inner.lock().unwrap().clone();
// // let tx_details = tx_builder_result.transaction_details;
// //
// // // confirm one input with 50,000 sats
// // assert_eq!(psbt.inputs.len(), 1);
// // let input_value = psbt
// // .inputs
// // .get(0)
// // .cloned()
// // .unwrap()
// // .non_witness_utxo
// // .unwrap()
// // .output
// // .get(0)
// // .unwrap()
// // .value;
// // assert_eq!(input_value, 50_000_u64);
// //
// // // confirm one output to correct address with all sats - fee
// // assert_eq!(psbt.outputs.len(), 1);
// // let output_address = Address::from_script(
// // &psbt
// // .unsigned_tx
// // .output
// // .get(0)
// // .cloned()
// // .unwrap()
// // .script_pubkey,
// // Network::Testnet,
// // )
// // .unwrap();
// // assert_eq!(
// // output_address,
// // Address::from_str("tb1ql7w62elx9ucw4pj5lgw4l028hmuw80sndtntxt").unwrap()
// // );
// // 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());
// // }
//
// #[test]
// fn test_peek_reset_address() {
// let test_wpkh = "wpkh(tprv8hwWMmPE4BVNxGdVt3HhEERZhondQvodUY7Ajyseyhudr4WabJqWKWLr4Wi2r26CDaNCQhhxEftEaNzz7dPGhWuKFU4VULesmhEfZYyBXdE/0/*)";
// let descriptor = Descriptor::new(test_wpkh.to_string(), Network::Regtest).unwrap();
// let change_descriptor = Descriptor::new(
// test_wpkh.to_string().replace("/0/*", "/1/*"),
// Network::Regtest,
// )
// .unwrap();
//
// let wallet = Wallet::new(
// Arc::new(descriptor),
// Some(Arc::new(change_descriptor)),
// Network::Regtest,
// DatabaseConfig::Memory,
// )
// .unwrap();
//
// assert_eq!(
// wallet
// .get_address(AddressIndex::Peek { index: 2 })
// .unwrap()
// .address
// .as_string(),
// "bcrt1q5g0mq6dkmwzvxscqwgc932jhgcxuqqkjv09tkj"
// );
//
// assert_eq!(
// wallet
// .get_address(AddressIndex::Peek { index: 1 })
// .unwrap()
// .address
// .as_string(),
// "bcrt1q0xs7dau8af22rspp4klya4f7lhggcnqfun2y3a"
// );
//
// // new index still 0
// assert_eq!(
// wallet
// .get_address(AddressIndex::New)
// .unwrap()
// .address
// .as_string(),
// "bcrt1qqjn9gky9mkrm3c28e5e87t5akd3twg6xezp0tv"
// );
//
// // new index now 1
// assert_eq!(
// wallet
// .get_address(AddressIndex::New)
// .unwrap()
// .address
// .as_string(),
// "bcrt1q0xs7dau8af22rspp4klya4f7lhggcnqfun2y3a"
// );
//
// // new index now 2
// assert_eq!(
// wallet
// .get_address(AddressIndex::New)
// .unwrap()
// .address
// .as_string(),
// "bcrt1q5g0mq6dkmwzvxscqwgc932jhgcxuqqkjv09tkj"
// );
//
// // peek index 1
// assert_eq!(
// wallet
// .get_address(AddressIndex::Peek { index: 1 })
// .unwrap()
// .address
// .as_string(),
// "bcrt1q0xs7dau8af22rspp4klya4f7lhggcnqfun2y3a"
// );
//
// // reset to index 0
// // assert_eq!(
// // wallet
// // .get_address(AddressIndex::Reset { index: 0 })
// // .unwrap()
// // .address
// // .as_string(),
// // "bcrt1qqjn9gky9mkrm3c28e5e87t5akd3twg6xezp0tv"
// // );
//
// // new index 1 again
// assert_eq!(
// wallet
// .get_address(AddressIndex::New)
// .unwrap()
// .address
// .as_string(),
// "bcrt1q0xs7dau8af22rspp4klya4f7lhggcnqfun2y3a"
// );
// }
//
// #[test]
// fn test_get_address() {
// let test_wpkh = "wpkh(tprv8hwWMmPE4BVNxGdVt3HhEERZhondQvodUY7Ajyseyhudr4WabJqWKWLr4Wi2r26CDaNCQhhxEftEaNzz7dPGhWuKFU4VULesmhEfZYyBXdE/0/*)";
// let descriptor = Descriptor::new(test_wpkh.to_string(), Network::Regtest).unwrap();
// let change_descriptor = Descriptor::new(
// test_wpkh.to_string().replace("/0/*", "/1/*"),
// Network::Regtest,
// )
// .unwrap();
//
// let wallet = Wallet::new(
// Arc::new(descriptor),
// Some(Arc::new(change_descriptor)),
// Network::Regtest,
// DatabaseConfig::Memory,
// )
// .unwrap();
//
// assert_eq!(
// wallet
// .get_address(AddressIndex::New)
// .unwrap()
// .address
// .as_string(),
// "bcrt1qqjn9gky9mkrm3c28e5e87t5akd3twg6xezp0tv"
// );
//
// assert_eq!(
// wallet
// .get_address(AddressIndex::New)
// .unwrap()
// .address
// .as_string(),
// "bcrt1q0xs7dau8af22rspp4klya4f7lhggcnqfun2y3a"
// );
//
// assert_eq!(
// wallet
// .get_address(AddressIndex::LastUnused)
// .unwrap()
// .address
// .as_string(),
// "bcrt1q0xs7dau8af22rspp4klya4f7lhggcnqfun2y3a"
// );
//
// assert_eq!(
// wallet
// .get_internal_address(AddressIndex::New)
// .unwrap()
// .address
// .as_string(),
// "bcrt1qpmz73cyx00r4a5dea469j40ax6d6kqyd67nnpj"
// );
//
// assert_eq!(
// wallet
// .get_internal_address(AddressIndex::New)
// .unwrap()
// .address
// .as_string(),
// "bcrt1qaux734vuhykww9632v8cmdnk7z2mw5lsf74v6k"
// );
//
// assert_eq!(
// wallet
// .get_internal_address(AddressIndex::LastUnused)
// .unwrap()
// .address
// .as_string(),
// "bcrt1qaux734vuhykww9632v8cmdnk7z2mw5lsf74v6k"
// );
// }
//
// #[test]
// fn test_is_mine() {
// // is_mine should return true for addresses generated by the wallet
// let mnemonic: 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();
// let secret_key: DescriptorSecretKey =
// DescriptorSecretKey::new(Network::Testnet, Arc::new(mnemonic), None);
// let descriptor: Descriptor = Descriptor::new_bip84(
// Arc::new(secret_key),
// KeychainKind::External,
// Network::Testnet,
// );
// let wallet: Wallet = Wallet::new(
// Arc::new(descriptor),
// None,
// Network::Testnet,
// DatabaseConfig::Memory,
// )
// .unwrap();
//
// // let address = wallet.get_address(AddressIndex::New).unwrap();
// // let script: Arc<Script> = address.address.script_pubkey();
//
// // let is_mine_1: bool = wallet.is_mine(script).unwrap();
// // assert!(is_mine_1);
//
// // is_mine returns false when provided a script that is not in the wallet
// let other_wpkh = "wpkh(tprv8hwWMmPE4BVNxGdVt3HhEERZhondQvodUY7Ajyseyhudr4WabJqWKWLr4Wi2r26CDaNCQhhxEftEaNzz7dPGhWuKFU4VULesmhEfZYyBXdE/0/*)";
// let other_descriptor = Descriptor::new(other_wpkh.to_string(), Network::Testnet).unwrap();
//
// let other_wallet = Wallet::new(
// Arc::new(other_descriptor),
// None,
// Network::Testnet,
// DatabaseConfig::Memory,
// )
// .unwrap();
//
// let other_address = other_wallet.get_address(AddressIndex::New).unwrap();
// let other_script: Arc<Script> = other_address.address.script_pubkey();
// let is_mine_2: bool = wallet.is_mine(other_script).unwrap();
// assert_matches!(is_mine_2, false);
// }
// }

View File

@ -5,7 +5,7 @@ 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.14.0.jar cargo test`.
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:

View File

@ -6,5 +6,10 @@ 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()

View File

@ -4,6 +4,6 @@
*/
import Foundation
import BitcoinDevKit
import bdk
let network = Network.testnet

Binary file not shown.

Binary file not shown.

View File

@ -1,5 +1,8 @@
uniffi::build_foreign_language_testcases!(
uniffi_macros::build_foreign_language_testcases!(
["src/bdk.udl",],
[
"tests/bindings/test.kts",
"tests/bindings/test.swift",
"tests/bindings/test.py",
"tests/bindings/test.py"
]
);

View File

@ -1,3 +0,0 @@
[bindings.kotlin]
android = true
android_cleaner = true

View File

@ -1,3 +0,0 @@
fn main() {
uniffi::uniffi_bindgen_main()
}

View File

@ -5,6 +5,8 @@ cdylib_name = "bdkffi"
[bindings.python]
cdylib_name = "bdkffi"
[bindings.swift]
module_name = "BitcoinDevKit"
[bindings.ruby]
cdylib_name = "bdkffi"
[bindings.swift]
cdylib_name = "bdkffi"

View File

@ -1,5 +1,5 @@
# bdk-jvm
This project builds a .jar package for the JVM platform that provides Kotlin language bindings for the [`bdk`] library. The Kotlin language bindings are created by the `bdk-ffi` project which is included in the root of this repository.
# bdk-android
This project builds a .jar package for the JVM platform that provide Kotlin language bindings for the [`bdk`] library. The Kotlin language bindings are created by the `bdk-ffi` project which is included in the root of this repository.
## How to Use
To use the Kotlin language bindings for [`bdk`] in your JVM project add the following to your gradle dependencies:
@ -13,6 +13,24 @@ dependencies {
}
```
You may then import and use the `org.bitcoindevkit` library in your Kotlin code. For example:
```kotlin
import org.bitcoindevkit.*
// ...
val externalDescriptor = "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)"
val internalDescriptor = "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)"
val databaseConfig = DatabaseConfig.Memory
val blockchainConfig = BlockchainConfig.Electrum(
ElectrumConfig("ssl://electrum.blockstream.info:60002", null, 5u, null, 10u, true)
)
val wallet = Wallet(externalDescriptor, internalDescriptor, Network.TESTNET, databaseConfig, blockchainConfig)
val newAddress = wallet.getAddress(AddressIndex.LAST_UNUSED)
```
### Snapshot releases
To use a snapshot release, specify the snapshot repository url in the `repositories` block and use the snapshot version in the `dependencies` block:
```kotlin
@ -25,39 +43,32 @@ dependencies {
}
```
## Example Projects
* [Tatooine Faucet](https://github.com/thunderbiscuit/tatooine)
## How to build
_Note that Kotlin version `1.9.23` or later is required to build the library._
1. Install JDK 17. For example, with SDKMAN!:
```shell
curl -s "https://get.sdkman.io" | bash
source "$HOME/.sdkman/bin/sdkman-init.sh"
sdk install java 17.0.2-tem
```
2. Install Rust (note that we are currently building using Rust 1.77.1):
```shell
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
rustup default 1.77.1
```
3. Clone this repository.
_Note that Kotlin version `1.6.10` or later is required to build the library._
1. Clone this repository.
```shell
git clone https://github.com/bitcoindevkit/bdk-ffi
```
4. If building on macOS install required intel and m1 jvm targets
2. Follow the "General" bdk-ffi ["Getting Started (Developer)"] instructions.
3. If building on macOS install required intel and m1 jvm targets
```sh
rustup target add x86_64-apple-darwin aarch64-apple-darwin
```
5. Build kotlin bindings
```sh
./gradlew buildJvmLib
```
4. Build kotlin bindings
```sh
# build JVM library
./gradlew buildJvmLib
```
## How to publish to your local Maven repo
```shell
cd bdk-jvm
./gradlew publishToMavenLocal -P localBuild
./gradlew publishToMavenLocal --exclude-task signMavenPublication
```
Note that the commands assume you don't need the local libraries to be signed. If you do wish to sign them, simply set your `~/.gradle/gradle.properties` signing key values like so:
@ -66,18 +77,16 @@ signing.gnupg.keyName=<YOUR_GNUPG_ID>
signing.gnupg.passphrase=<YOUR_GNUPG_PASSPHRASE>
```
and use the `publishToMavenLocal` task without the `localBuild` flag:
and use the `publishToMavenLocal` task without excluding the signing task:
```shell
./gradlew publishToMavenLocal
```
## Known issues
## JNA dependency
Depending on the JVM version you use, you might not have the JNA dependency on your classpath. The exception thrown will be
```shell
class file for com.sun.jna.Pointer not found
```
The solution is to add JNA as a dependency like so:
```kotlin
dependencies {

View File

@ -1,9 +1,4 @@
plugins {
id("org.jetbrains.kotlin.jvm").version("1.9.23").apply(false)
id("org.gradle.java-library")
id("org.gradle.maven-publish")
id("org.gradle.signing")
id("org.bitcoindevkit.plugins.generate-jvm-bindings").apply(false)
id("io.github.gradle-nexus.publish-plugin") version "1.1.0"
}

View File

@ -1,4 +1,4 @@
org.gradle.jvmargs=-Xmx1536m
android.enableJetifier=true
kotlin.code.style=official
libraryVersion=1.0.0-alpha.12-SNAPSHOT
libraryVersion=0.26.0

Binary file not shown.

View File

@ -1,7 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-all.zip
networkTimeout=10000
validateDistributionUrl=true
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.2-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

31
bdk-jvm/gradlew vendored
View File

@ -55,7 +55,7 @@
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
@ -80,11 +80,13 @@ do
esac
done
# This is normally unused
# shellcheck disable=SC2034
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
APP_NAME="Gradle"
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
@ -131,29 +133,22 @@ location of your Java installation."
fi
else
JAVACMD=java
if ! command -v java >/dev/null 2>&1
then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
@ -198,10 +193,6 @@ if "$cygwin" || "$msys" ; then
done
fi
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in
@ -214,12 +205,6 @@ set -- \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.

15
bdk-jvm/gradlew.bat vendored
View File

@ -14,7 +14,7 @@
@rem limitations under the License.
@rem
@if "%DEBUG%"=="" @echo off
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@ -25,8 +25,7 @@
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@ -41,7 +40,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
if "%ERRORLEVEL%" == "0" goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
@ -76,15 +75,13 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
:end
@rem End local scope for the variables with windows NT shell
if %ERRORLEVEL% equ 0 goto mainEnd
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal

View File

@ -1,23 +0,0 @@
default:
just --list
build:
./gradlew buildJvmLib
clean:
rm -rf ../bdk-ffi/target/
rm -rf ./build/
rm -rf ./lib/build/
rm -rf ./plugins/build/
publish-local:
./gradlew publishToMavenLocal -P localBuild
test:
./gradlew test
test-offline:
./gradlew test -P excludeConnectedTests
test-specific TEST:
./gradlew test --tests {{TEST}}

View File

@ -6,64 +6,45 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
val libraryVersion: String by project
plugins {
id("org.jetbrains.kotlin.jvm")
id("org.gradle.java-library")
id("org.gradle.maven-publish")
id("org.gradle.signing")
id("org.jetbrains.kotlin.jvm") version "1.6.10"
id("java-library")
id("maven-publish")
id("signing")
// Custom plugin to generate the native libs and bindings file
id("org.bitcoindevkit.plugins.generate-jvm-bindings")
}
repositories {
mavenCentral()
}
java {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
withSourcesJar()
withJavadocJar()
}
// This block ensures that the tests that require access to a blockchain are not
// run if the -P excludeConnectedTests flag is passed to gradle.
// This ensures our CI runs are not fickle by not requiring access to testnet or signet.
// This is a workaround until we have a proper regtest setup for the CI.
// Note that the command in the CI is ./gradlew test -P excludeConnectedTests
tasks.test {
if (project.hasProperty("excludeConnectedTests")) {
exclude("**/LiveElectrumClientTest.class")
exclude("**/LiveMemoryWalletTest.class")
exclude("**/LiveTransactionTest.class")
exclude("**/LiveTxBuilderTest.class")
exclude("**/LiveWalletTest.class")
}
}
testing {
suites {
val test by getting(JvmTestSuite::class) {
useKotlinTest("1.9.23")
}
}
}
tasks.withType<Test> {
useJUnitPlatform()
testLogging {
events(PASSED, SKIPPED, FAILED, STANDARD_OUT, STANDARD_ERROR)
exceptionFormat = FULL
showExceptions = true
showStackTraces = true
showCauses = true
showStackTraces = true
}
}
dependencies {
implementation(platform("org.jetbrains.kotlin:kotlin-bom"))
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk7")
implementation("net.java.dev.jna:jna:5.14.0")
implementation("net.java.dev.jna:jna:5.8.0")
api("org.slf4j:slf4j-api:1.7.30")
// testImplementation("org.junit.jupiter:junit-jupiter-api:5.10.1")
// testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.10.1")
// testRuntimeOnly("org.junit.vintage:junit-vintage-engine:5.8.2")
testImplementation("junit:junit:4.13.2")
testRuntimeOnly("org.junit.vintage:junit-vintage-engine:5.8.2")
testImplementation("ch.qos.logback:logback-classic:1.2.3")
testImplementation("ch.qos.logback:logback-core:1.2.3")
}
@ -93,9 +74,14 @@ afterEvaluate {
}
developers {
developer {
id.set("bdkdevelopers")
name.set("Bitcoin Dev Kit Developers")
email.set("dev@bitcoindevkit.org")
id.set("notmandatory")
name.set("Steve Myers")
email.set("notmandatory@noreply.github.org")
}
developer {
id.set("artfuldev")
name.set("Sudarsan Balaji")
email.set("sudarsan.balaji@artfuldev.com")
}
}
scm {
@ -110,10 +96,6 @@ afterEvaluate {
}
signing {
if (project.hasProperty("localBuild")) {
isRequired = false
}
val signingKeyId: String? by project
val signingKey: String? by project
val signingPassword: String? by project
@ -125,8 +107,4 @@ signing {
// binaries before running the tests
tasks.withType<KotlinCompile> {
dependsOn("buildJvmLib")
kotlinOptions {
jvmTarget = "11"
}
}

View File

@ -0,0 +1,73 @@
package org.bitcoindevkit
import org.junit.Assert.*
import org.junit.Test
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import java.io.File
import java.nio.file.Files
/**
* Library test, which will execute on linux host.
*/
class JvmLibTest {
private fun getTestDataDir(): String {
return Files.createTempDirectory("bdk-test").toString()
}
private fun cleanupTestDataDir(testDataDir: String) {
File(testDataDir).deleteRecursively()
}
class LogProgress : Progress {
private val log: Logger = LoggerFactory.getLogger(JvmLibTest::class.java)
override fun update(progress: Float, message: String?) {
log.debug("Syncing...")
}
}
private val descriptor = Descriptor("wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)", Network.TESTNET)
private val databaseConfig = DatabaseConfig.Memory
private val blockchainConfig = BlockchainConfig.Electrum(
ElectrumConfig(
"ssl://electrum.blockstream.info:60002",
null,
5u,
null,
100u,
true,
)
)
@Test
fun memoryWalletNewAddress() {
val wallet = Wallet(descriptor, null, Network.TESTNET, databaseConfig)
val address = wallet.getAddress(AddressIndex.NEW).address
assertEquals("tb1qzg4mckdh50nwdm9hkzq06528rsu73hjxxzem3e", address)
}
@Test
fun memoryWalletSyncGetBalance() {
val wallet = Wallet(descriptor, null, Network.TESTNET, databaseConfig)
val blockchain = Blockchain(blockchainConfig)
wallet.sync(blockchain, LogProgress())
val balance: Balance = wallet.getBalance()
assertTrue(balance.total > 0u)
}
@Test
fun sqliteWalletSyncGetBalance() {
val testDataDir = getTestDataDir() + "/bdk-wallet.sqlite"
val databaseConfig = DatabaseConfig.Sqlite(SqliteDbConfiguration(testDataDir))
val wallet = Wallet(descriptor, null, Network.TESTNET, databaseConfig)
val blockchain = Blockchain(blockchainConfig)
wallet.sync(blockchain, LogProgress())
val balance: Balance = wallet.getBalance()
assertTrue(balance.total > 0u)
cleanupTestDataDir(testDataDir)
}
}

View File

@ -1,39 +0,0 @@
package org.bitcoindevkit
import kotlin.test.Test
private const val SIGNET_ELECTRUM_URL = "ssl://mempool.space:60602"
class LiveElectrumClientTest {
private val descriptor: Descriptor = Descriptor(
"wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)",
Network.SIGNET
)
private val changeDescriptor: Descriptor = Descriptor(
"wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/1/*)",
Network.SIGNET
)
@Test
fun testSyncedBalance() {
val wallet: Wallet = Wallet(descriptor, changeDescriptor, Network.SIGNET)
val electrumClient: ElectrumClient = ElectrumClient(SIGNET_ELECTRUM_URL)
val fullScanRequest: FullScanRequest = wallet.startFullScan()
val update = electrumClient.fullScan(fullScanRequest, 10uL, 10uL, false)
wallet.applyUpdate(update)
println("Balance: ${wallet.balance().total.toSat()}")
assert(wallet.balance().total.toSat() > 0uL) {
"Wallet balance must be greater than 0! Please send funds to ${wallet.revealNextAddress(KeychainKind.EXTERNAL).address} and try again."
}
println("Transactions count: ${wallet.transactions().count()}")
val transactions = wallet.transactions().take(3)
for (tx in transactions) {
val sentAndReceived = wallet.sentAndReceived(tx.transaction)
println("Transaction: ${tx.transaction.computeTxid()}")
println("Sent ${sentAndReceived.sent.toSat()}")
println("Received ${sentAndReceived.received.toSat()}")
}
}
}

View File

@ -1,67 +0,0 @@
package org.bitcoindevkit
import kotlin.test.Test
private const val SIGNET_ESPLORA_URL = "http://signet.bitcoindevkit.net"
private const val TESTNET_ESPLORA_URL = "https://esplora.testnet.kuutamo.cloud"
class LiveMemoryWalletTest {
private val descriptor: Descriptor = Descriptor(
"wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)",
Network.SIGNET
)
private val changeDescriptor: Descriptor = Descriptor(
"wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/1/*)",
Network.SIGNET
)
@Test
fun testSyncedBalance() {
val descriptor: Descriptor = Descriptor(
"wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)",
Network.SIGNET
)
val wallet: Wallet = Wallet(descriptor, changeDescriptor, Network.SIGNET)
val esploraClient: EsploraClient = EsploraClient(SIGNET_ESPLORA_URL)
val fullScanRequest: FullScanRequest = wallet.startFullScan()
val update = esploraClient.fullScan(fullScanRequest, 10uL, 1uL)
wallet.applyUpdate(update)
println("Balance: ${wallet.balance().total.toSat()}")
assert(wallet.balance().total.toSat() > 0uL) {
"Wallet balance must be greater than 0! Please send funds to ${wallet.revealNextAddress(KeychainKind.EXTERNAL).address} and try again."
}
println("Transactions count: ${wallet.transactions().count()}")
val transactions = wallet.transactions().take(3)
for (tx in transactions) {
val sentAndReceived = wallet.sentAndReceived(tx.transaction)
println("Transaction: ${tx.transaction.computeTxid()}")
println("Sent ${sentAndReceived.sent.toSat()}")
println("Received ${sentAndReceived.received.toSat()}")
}
}
@Test
fun testScriptInspector() {
val wallet: Wallet = Wallet(descriptor, changeDescriptor, Network.SIGNET)
val esploraClient: EsploraClient = EsploraClient(SIGNET_ESPLORA_URL)
val scriptInspector: FullScriptInspector = FullScriptInspector()
val fullScanRequest: FullScanRequest = wallet.startFullScan().inspectSpksForAllKeychains(scriptInspector)
val update = esploraClient.fullScan(fullScanRequest, 21uL, 1uL)
wallet.applyUpdate(update)
println("Balance: ${wallet.balance().total.toSat()}")
assert(wallet.balance().total.toSat() > 0uL) {
"Wallet balance must be greater than 0! Please send funds to ${wallet.revealNextAddress(KeychainKind.EXTERNAL).address} and try again."
}
}
}
class FullScriptInspector: FullScanScriptInspector {
override fun inspect(keychain: KeychainKind, index: UInt, script: Script){
println("Inspecting index $index for keychain $keychain")
}
}

View File

@ -1,47 +0,0 @@
package org.bitcoindevkit
import kotlin.test.Test
private const val SIGNET_ESPLORA_URL = "http://signet.bitcoindevkit.net"
private const val TESTNET_ESPLORA_URL = "https://esplora.testnet.kuutamo.cloud"
class LiveTransactionTests {
private val descriptor: Descriptor = Descriptor(
"wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)",
Network.SIGNET
)
private val changeDescriptor: Descriptor = Descriptor(
"wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/1/*)",
Network.SIGNET
)
@Test
fun testSyncedBalance() {
val descriptor: Descriptor = Descriptor(
"wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)",
Network.SIGNET
)
val wallet: Wallet = Wallet(descriptor, changeDescriptor, Network.SIGNET)
val esploraClient: EsploraClient = EsploraClient(SIGNET_ESPLORA_URL)
val fullScanRequest: FullScanRequest = wallet.startFullScan()
val update = esploraClient.fullScan(fullScanRequest, 10uL, 1uL)
wallet.applyUpdate(update)
println("Wallet balance: ${wallet.balance().total.toSat()}")
assert(wallet.balance().total.toSat() > 0uL) {
"Wallet balance must be greater than 0! Please send funds to ${wallet.revealNextAddress(KeychainKind.EXTERNAL).address} and try again."
}
val transaction: Transaction = wallet.transactions().first().transaction
println("First transaction:")
println("Txid: ${transaction.computeTxid()}")
println("Version: ${transaction.version()}")
println("Total size: ${transaction.totalSize()}")
println("Vsize: ${transaction.vsize()}")
println("Weight: ${transaction.weight()}")
println("Coinbase transaction: ${transaction.isCoinbase()}")
println("Is explicitly RBF: ${transaction.isExplicitlyRbf()}")
println("Inputs: ${transaction.input()}")
println("Outputs: ${transaction.output()}")
}
}

View File

@ -1,88 +0,0 @@
package org.bitcoindevkit
import java.io.File
import kotlin.test.AfterTest
import kotlin.test.Test
import kotlin.test.assertTrue
private const val SIGNET_ESPLORA_URL = "http://signet.bitcoindevkit.net"
private const val TESTNET_ESPLORA_URL = "https://esplora.testnet.kuutamo.cloud"
class LiveTxBuilderTest {
private val persistenceFilePath = run {
val currentDirectory = System.getProperty("user.dir")
"$currentDirectory/bdk_persistence.sqlite"
}
private val descriptor: Descriptor = Descriptor(
"wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)",
Network.SIGNET
)
private val changeDescriptor: Descriptor = Descriptor(
"wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/1/*)",
Network.SIGNET
)
@AfterTest
fun cleanup() {
val file = File(persistenceFilePath)
if (file.exists()) {
file.delete()
}
}
@Test
fun testTxBuilder() {
val descriptor = Descriptor("wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)", Network.TESTNET)
val wallet = Wallet(descriptor, changeDescriptor, Network.SIGNET)
val esploraClient = EsploraClient(SIGNET_ESPLORA_URL)
val fullScanRequest: FullScanRequest = wallet.startFullScan()
val update = esploraClient.fullScan(fullScanRequest, 10uL, 1uL)
wallet.applyUpdate(update)
println("Balance: ${wallet.balance().total.toSat()}")
assert(wallet.balance().total.toSat() > 0uL) {
"Wallet balance must be greater than 0! Please send funds to ${wallet.revealNextAddress(KeychainKind.EXTERNAL).address} and try again."
}
val recipient: Address = Address("tb1qrnfslnrve9uncz9pzpvf83k3ukz22ljgees989", Network.SIGNET)
val psbt: Psbt = TxBuilder()
.addRecipient(recipient.scriptPubkey(), Amount.fromSat(4200uL))
.feeRate(FeeRate.fromSatPerVb(2uL))
.finish(wallet)
println(psbt.serialize())
assertTrue(psbt.serialize().startsWith("cHNi"), "PSBT should start with 'cHNi'")
}
@Test
fun complexTxBuilder() {
val wallet = Wallet(descriptor, changeDescriptor, Network.SIGNET)
val esploraClient = EsploraClient(SIGNET_ESPLORA_URL)
val fullScanRequest: FullScanRequest = wallet.startFullScan()
val update = esploraClient.fullScan(fullScanRequest, 10uL, 1uL)
wallet.applyUpdate(update)
println("Balance: ${wallet.balance().total.toSat()}")
assert(wallet.balance().total.toSat() > 0uL) {
"Wallet balance must be greater than 0! Please send funds to ${wallet.revealNextAddress(KeychainKind.EXTERNAL).address} and try again."
}
val recipient1: Address = Address("tb1qrnfslnrve9uncz9pzpvf83k3ukz22ljgees989", Network.SIGNET)
val recipient2: Address = Address("tb1qw2c3lxufxqe2x9s4rdzh65tpf4d7fssjgh8nv6", Network.SIGNET)
val allRecipients: List<ScriptAmount> = listOf(
ScriptAmount(recipient1.scriptPubkey(), Amount.fromSat(4200uL)),
ScriptAmount(recipient2.scriptPubkey(), Amount.fromSat(4200uL)),
)
val psbt: Psbt = TxBuilder()
.setRecipients(allRecipients)
.feeRate(FeeRate.fromSatPerVb(4uL))
.changePolicy(ChangeSpendPolicy.CHANGE_FORBIDDEN)
.enableRbf()
.finish(wallet)
wallet.sign(psbt)
assertTrue(psbt.serialize().startsWith("cHNi"), "PSBT should start with 'cHNi'")
}
}

View File

@ -1,94 +0,0 @@
package org.bitcoindevkit
import java.io.File
import kotlin.test.AfterTest
import kotlin.test.Test
import kotlin.test.assertTrue
private const val SIGNET_ESPLORA_URL = "http://signet.bitcoindevkit.net"
private const val TESTNET_ESPLORA_URL = "https://esplora.testnet.kuutamo.cloud"
class LiveWalletTest {
private val persistenceFilePath = run {
val currentDirectory = System.getProperty("user.dir")
"$currentDirectory/bdk_persistence.sqlite"
}
private val descriptor: Descriptor = Descriptor(
"wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)",
Network.SIGNET
)
private val changeDescriptor: Descriptor = Descriptor(
"wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/1/*)",
Network.SIGNET
)
@AfterTest
fun cleanup() {
val file = File(persistenceFilePath)
if (file.exists()) {
file.delete()
}
}
@Test
fun testSyncedBalance() {
val wallet: Wallet = Wallet(descriptor, changeDescriptor, Network.SIGNET)
val esploraClient: EsploraClient = EsploraClient(SIGNET_ESPLORA_URL)
val fullScanRequest: FullScanRequest = wallet.startFullScan()
val update = esploraClient.fullScan(fullScanRequest, 10uL, 1uL)
wallet.applyUpdate(update)
println("Balance: ${wallet.balance().total.toSat()}")
assert(wallet.balance().total.toSat() > 0uL) {
"Wallet balance must be greater than 0! Please send funds to ${wallet.revealNextAddress(KeychainKind.EXTERNAL).address} and try again."
}
println("Transactions count: ${wallet.transactions().count()}")
val transactions = wallet.transactions().take(3)
for (tx in transactions) {
val sentAndReceived = wallet.sentAndReceived(tx.transaction)
println("Transaction: ${tx.transaction.computeTxid()}")
println("Sent ${sentAndReceived.sent}")
println("Received ${sentAndReceived.received}")
}
}
@Test
fun testBroadcastTransaction() {
val descriptor = Descriptor("wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)", Network.SIGNET)
val wallet: Wallet = Wallet(descriptor, changeDescriptor, Network.SIGNET)
val esploraClient = EsploraClient(SIGNET_ESPLORA_URL)
val fullScanRequest: FullScanRequest = wallet.startFullScan()
val update = esploraClient.fullScan(fullScanRequest, 10uL, 1uL)
wallet.applyUpdate(update)
println("Balance: ${wallet.balance().total.toSat()}")
assert(wallet.balance().total.toSat() > 0uL) {
"Wallet balance must be greater than 0! Please send funds to ${wallet.revealNextAddress(KeychainKind.EXTERNAL).address} and try again."
}
val recipient: Address = Address("tb1qrnfslnrve9uncz9pzpvf83k3ukz22ljgees989", Network.SIGNET)
val psbt: Psbt = TxBuilder()
.addRecipient(recipient.scriptPubkey(), Amount.fromSat(4200uL))
.feeRate(FeeRate.fromSatPerVb(2uL))
.finish(wallet)
println(psbt.serialize())
assertTrue(psbt.serialize().startsWith("cHNi"), "PSBT should start with 'cHNi'")
val walletDidSign = wallet.sign(psbt)
assertTrue(walletDidSign)
val tx: Transaction = psbt.extractTx()
println("Txid is: ${tx.computeTxid()}")
val txFee: Amount = wallet.calculateFee(tx)
println("Tx fee is: ${txFee}")
val feeRate: FeeRate = wallet.calculateFeeRate(tx)
println("Tx fee rate is: ${feeRate.toSatPerVbCeil()} sat/vB")
esploraClient.broadcast(tx)
}
}

View File

@ -1,18 +0,0 @@
package org.bitcoindevkit
import kotlin.test.Test
import kotlin.test.assertEquals
class OfflineDescriptorTest {
@Test
fun testDescriptorBip86() {
val mnemonic: Mnemonic = Mnemonic.fromString("space echo position wrist orient erupt relief museum myself grain wisdom tumble")
val descriptorSecretKey: DescriptorSecretKey = DescriptorSecretKey(Network.TESTNET, mnemonic, null)
val descriptor: Descriptor = Descriptor.newBip86(descriptorSecretKey, KeychainKind.EXTERNAL, Network.TESTNET)
assertEquals(
expected = "tr([be1eec8f/86'/1'/0']tpubDCTtszwSxPx3tATqDrsSyqScPNnUChwQAVAkanuDUCJQESGBbkt68nXXKRDifYSDbeMa2Xg2euKbXaU3YphvGWftDE7ozRKPriT6vAo3xsc/0/*)#m7puekcx",
actual = descriptor.toString()
)
}
}

View File

@ -1,45 +0,0 @@
package org.bitcoindevkit
import kotlin.test.Test
import kotlin.test.assertEquals
class OfflinePersistenceTest {
private val persistenceFilePath = run {
val currentDirectory = System.getProperty("user.dir")
val dbFileName = "pre_existing_wallet_persistence_test.sqlite"
"$currentDirectory/src/test/resources/$dbFileName"
}
private val descriptor: Descriptor = Descriptor(
"wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)",
Network.SIGNET
)
private val changeDescriptor: Descriptor = Descriptor(
"wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/1/*)",
Network.SIGNET
)
@Test
fun testPersistence() {
val sqliteStore: SqliteStore = SqliteStore(persistenceFilePath)
val initialChangeSet: ChangeSet? = sqliteStore.read()
requireNotNull(initialChangeSet) { "ChangeSet should not be null after loading a valid database" }
val wallet: Wallet = Wallet.newOrLoad(
descriptor,
changeDescriptor,
initialChangeSet,
Network.SIGNET,
)
val addressInfo: AddressInfo = wallet.revealNextAddress(KeychainKind.EXTERNAL)
println("Address: $addressInfo")
assertEquals(
expected = 7u,
actual = addressInfo.index,
)
assertEquals(
expected = "tb1qan3lldunh37ma6c0afeywgjyjgnyc8uz975zl2",
actual = addressInfo.address.toString(),
)
}
}

View File

@ -1,74 +0,0 @@
package org.bitcoindevkit
import java.io.File
import kotlin.test.AfterTest
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertTrue
import kotlin.test.assertFalse
class OfflineWalletTest {
private val persistenceFilePath = run {
val currentDirectory = System.getProperty("user.dir")
"$currentDirectory/bdk_persistence.sqlite"
}
private val descriptor: Descriptor = Descriptor(
"wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)",
Network.TESTNET
)
private val changeDescriptor: Descriptor = Descriptor(
"wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/1/*)",
Network.TESTNET
)
@AfterTest
fun cleanup() {
val file = File(persistenceFilePath)
if (file.exists()) {
file.delete()
}
}
@Test
fun testDescriptorBip86() {
val mnemonic: Mnemonic = Mnemonic(WordCount.WORDS12)
val descriptorSecretKey: DescriptorSecretKey = DescriptorSecretKey(Network.TESTNET, mnemonic, null)
val descriptor: Descriptor = Descriptor.newBip86(descriptorSecretKey, KeychainKind.EXTERNAL, Network.TESTNET)
assertTrue(descriptor.toString().startsWith("tr"), "Bip86 Descriptor does not start with 'tr'")
}
@Test
fun testNewAddress() {
val wallet: Wallet = Wallet(
descriptor,
changeDescriptor,
Network.TESTNET
)
val addressInfo: AddressInfo = wallet.revealNextAddress(KeychainKind.EXTERNAL)
assertTrue(addressInfo.address.isValidForNetwork(Network.TESTNET), "Address is not valid for testnet network")
assertTrue(addressInfo.address.isValidForNetwork(Network.SIGNET), "Address is not valid for signet network")
assertFalse(addressInfo.address.isValidForNetwork(Network.REGTEST), "Address is valid for regtest network, but it shouldn't be")
assertFalse(addressInfo.address.isValidForNetwork(Network.BITCOIN), "Address is valid for bitcoin network, but it shouldn't be")
assertEquals(
expected = "tb1qrnfslnrve9uncz9pzpvf83k3ukz22ljgees989",
actual = addressInfo.address.toString()
)
}
@Test
fun testBalance() {
val wallet: Wallet = Wallet(
descriptor,
changeDescriptor,
Network.TESTNET
)
assertEquals(
expected = 0uL,
actual = wallet.balance().total.toSat()
)
}
}

View File

@ -4,13 +4,11 @@ package org.bitcoindevkit.plugins
val operatingSystem: OS = when {
System.getProperty("os.name").contains("mac", ignoreCase = true) -> OS.MAC
System.getProperty("os.name").contains("linux", ignoreCase = true) -> OS.LINUX
System.getProperty("os.name").contains("windows", ignoreCase = true) -> OS.WINDOWS
else -> OS.OTHER
}
enum class OS {
MAC,
LINUX,
WINDOWS,
OTHER,
}

View File

@ -8,8 +8,6 @@ import org.gradle.kotlin.dsl.getValue
import org.gradle.kotlin.dsl.provideDelegate
import org.gradle.kotlin.dsl.register
// TODO 18: Migrate hard coded strings to constants all in the same location so they're at least easy
// to find and reason about.
internal class UniFfiJvmPlugin : Plugin<Project> {
override fun apply(target: Project): Unit = target.run {
@ -29,20 +27,13 @@ internal class UniFfiJvmPlugin : Plugin<Project> {
val cargoArgs: List<String> = listOf("build", "--profile", "release-smaller", "--target", "aarch64-apple-darwin")
args(cargoArgs)
}
} else if (operatingSystem == OS.LINUX) {
} else if(operatingSystem == OS.LINUX) {
exec {
workingDir("${project.projectDir}/../../bdk-ffi")
executable("cargo")
val cargoArgs: List<String> = listOf("build", "--profile", "release-smaller", "--target", "x86_64-unknown-linux-gnu")
args(cargoArgs)
}
} else if (operatingSystem == OS.WINDOWS) {
exec {
workingDir("${project.projectDir}/../../bdk-ffi")
executable("cargo")
val cargoArgs: List<String> = listOf("build", "--profile", "release-smaller", "--target", "x86_64-pc-windows-msvc")
args(cargoArgs)
}
}
}
@ -79,25 +70,13 @@ internal class UniFfiJvmPlugin : Plugin<Project> {
ext = "so"
)
)
} else if (operatingSystem == OS.WINDOWS) {
libsToCopy.add(
CopyMetadata(
targetDir = "x86_64-pc-windows-msvc",
resDir = "win32-x86-64",
ext = "dll"
)
)
}
val libName = when (operatingSystem) {
OS.WINDOWS -> "bdkffi"
else -> "libbdkffi"
}
libsToCopy.forEach {
doFirst {
copy {
with(it) {
from("${project.projectDir}/../../bdk-ffi/target/${this.targetDir}/release-smaller/${libName}.${this.ext}")
from("${project.projectDir}/../../target/${this.targetDir}/release-smaller/libbdkffi.${this.ext}")
into("${project.projectDir}/../../bdk-jvm/lib/src/main/resources/${this.resDir}/")
}
}
@ -110,24 +89,18 @@ internal class UniFfiJvmPlugin : Plugin<Project> {
dependsOn(moveNativeJvmLibs)
// TODO 2: Is the Windows name the correct one?
// TODO 3: This will not work on mac Intel (x86_64 architecture)
val libraryPath = when (operatingSystem) {
OS.LINUX -> "./target/x86_64-unknown-linux-gnu/release-smaller/libbdkffi.so"
OS.MAC -> "./target/aarch64-apple-darwin/release-smaller/libbdkffi.dylib"
OS.WINDOWS -> "./target/x86_64-pc-windows-msvc/release-smaller/bdkffi.dll"
else -> throw Exception("Unsupported OS")
}
workingDir("${project.projectDir}/../../bdk-ffi/")
val cargoArgs: List<String> = listOf("run", "--bin", "uniffi-bindgen", "generate", "--library", libraryPath, "--language", "kotlin", "--out-dir", "../bdk-jvm/lib/src/main/kotlin/", "--no-format")
// The code above was for the migration to uniffi 0.24.3 using the --library flag
// The code below works with uniffi 0.23.0
// workingDir("${project.projectDir}/../../bdk-ffi/")
// val cargoArgs: List<String> = listOf("run", "--bin", "uniffi-bindgen", "generate", "src/bdk.udl", "--language", "kotlin", "--out-dir", "../bdk-jvm/lib/src/main/kotlin", "--no-format")
workingDir("${project.projectDir}/../../bdk-ffi")
executable("cargo")
args(cargoArgs)
args(
"run",
"--package",
"bdk-ffi-bindgen",
"--",
"--language",
"kotlin",
"--out-dir",
"../bdk-jvm/lib/src/main/kotlin"
)
doLast {
println("JVM bindings file successfully created")

View File

@ -2,15 +2,3 @@ rootProject.name = "bdk-jvm"
include(":lib")
includeBuild("plugins")
pluginManagement {
repositories {
gradlePluginPortal()
}
}
dependencyResolutionManagement {
repositories {
mavenCentral()
}
}

14
bdk-python/LICENSE Normal file
View File

@ -0,0 +1,14 @@
This software is licensed under [Apache 2.0](LICENSE-APACHE) or
[MIT](LICENSE-MIT), at your option.
Some files retain their own copyright notice, however, for full authorship
information, see version control history.
Except as otherwise noted in individual files, all files in this repository are
licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
http://www.apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
http://opensource.org/licenses/MIT>, at your option.
You may not use, copy, modify, merge, publish, distribute, sublicense, and/or
sell copies of this software or any files in this repository except in
accordance with one or both of these licenses.

201
bdk-python/LICENSE-APACHE Normal file
View File

@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

Some files were not shown because too many files have changed in this diff Show More