Compare commits
1 Commits
frost
...
release/1.
Author | SHA1 | Date | |
---|---|---|---|
|
dc58ddc262 |
87
.github/ISSUE_TEMPLATE/minor_release.md
vendored
Normal file
87
.github/ISSUE_TEMPLATE/minor_release.md
vendored
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
---
|
||||||
|
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
|
||||||
|
## Bumping 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.
|
||||||
|
|
||||||
|
### Specific Libraries' Workflows
|
||||||
|
#### _Android_
|
||||||
|
3. - [ ] Update the API docs to reflect the changes in the API
|
||||||
|
4. - [ ] Delete the `target` directory in bdk-ffi and all previous artifacts to make sure you're building the library from scratch.
|
||||||
|
5. - [ ] Build the library and run the tests, and adjust if necessary.
|
||||||
|
```shell
|
||||||
|
# start an emulator prior to running the tests
|
||||||
|
cd ./bdk-android/
|
||||||
|
./gradlew buildAndroidLib
|
||||||
|
./gradlew connectedAndroidTest
|
||||||
|
```
|
||||||
|
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 previous artifacts 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/
|
||||||
|
./gradlew buildJvmLib
|
||||||
|
./gradlew test
|
||||||
|
```
|
||||||
|
10. - [ ] Update the readme if necessary
|
||||||
|
#### _Swift_
|
||||||
|
11. - [ ] Run the tests and adjust if necessary
|
||||||
|
```shell
|
||||||
|
./bdk-swift/build-local-swift.sh
|
||||||
|
cd ./bdk-swift/
|
||||||
|
swift test
|
||||||
|
```
|
||||||
|
12. - [ ] Update the readme if necessary
|
||||||
|
#### _Python_
|
||||||
|
13. - [ ] Delete the `.tox`, `dist`, `build`, and `bdkpython.egg-info` and rust `target` directories to make sure you are building the library from scratch without any caches
|
||||||
|
14. - [ ] Build the library
|
||||||
|
```shell
|
||||||
|
cd ./bdk-python/
|
||||||
|
pip3 install --requirement requirements.txt
|
||||||
|
bash ./scripts/generate-macos-arm64.sh # run the script for your particular platform
|
||||||
|
python3 setup.py --verbose bdist_wheel
|
||||||
|
```
|
||||||
|
15. - [ ] Run the tests and adjust if necessary
|
||||||
|
```shell
|
||||||
|
pip3 install ./dist/bdkpython-<yourversion>-py3-none-any.whl --force-reinstall
|
||||||
|
python -m unittest --verbose tests/test_bdk.py
|
||||||
|
```
|
||||||
|
16. - [ ] Update the readme and `setup.py` if necessary
|
||||||
|
|
||||||
|
### Release Workflow
|
||||||
|
17. - [ ] 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).
|
||||||
|
18. - [ ] Create a new branch off of `master` called `release/version`
|
||||||
|
19. - [ ] Update bdk-android version from `SNAPSHOT` version to release version
|
||||||
|
20. - [ ] Update bdk-jvm version from `SNAPSHOT` version to release version
|
||||||
|
21. - [ ] Update bdk-python version from `.dev` version to release version
|
||||||
|
22. - [ ] Open a PR to that release branch that updates the Android, JVM, and Python libraries' versions in step 19, 20, and 21. See [example PR here](https://github.com/bitcoindevkit/bdk-ffi/pull/316).
|
||||||
|
23. - [ ] Get a review and ACK and merge the PR updating all the languages to their release versions
|
||||||
|
24. - [ ] 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
|
||||||
|
```
|
||||||
|
25. - [ ] Trigger manual releases for all 4 libraries (for Swift, trigger the release on `master` and simply add the version number in the text field when running the workflow manually. Note that the version number must not contain the `v`, i.e. `0.26.0`)
|
||||||
|
26. - [ ] Make sure the released libraries work and contain the artifacts you would expect
|
||||||
|
27. - [ ] Aggregate all the changelog notices from the PRs and add them to the changelog file
|
||||||
|
28. - [ ] Bump the versions on master from `0.9.0-SNAPSHOT` to `0.10.0-SNAPSHOT`, `0.6.0.dev0` to `0.7.0.dev0`
|
||||||
|
29. - [ ] Apply changes to the minor_release and patch_release issue templates if they need any
|
||||||
|
30. - [ ] 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.
|
||||||
|
31. - [ ] Make release on GitHub (set as pre-release and generate auto release notes between the previous tag and the new one)
|
||||||
|
32. - [ ] Build and publish API docs for JVM, Android, and Java on the website
|
||||||
|
```shell
|
||||||
|
./gradlew dokkaHtml # bdk-jvm (Dokka)
|
||||||
|
./gradlew dokkaJavadoc # bdk-jvm (java-style documentation)
|
||||||
|
./gradlew dokkaHtml # bdk-android (Dokka)
|
||||||
|
```
|
||||||
|
33. - [ ] Post in the announcement channel
|
||||||
|
34. - [ ] Tweet about the library
|
95
.github/ISSUE_TEMPLATE/release.md
vendored
95
.github/ISSUE_TEMPLATE/release.md
vendored
@ -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
|
|
4
.github/workflows/cont_integration.yml
vendored
4
.github/workflows/cont_integration.yml
vendored
@ -17,7 +17,7 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
rust:
|
rust:
|
||||||
- version: 1.77.1
|
- version: 1.73.0
|
||||||
clippy: true
|
clippy: true
|
||||||
steps:
|
steps:
|
||||||
- name: "Checkout"
|
- name: "Checkout"
|
||||||
@ -56,7 +56,7 @@ jobs:
|
|||||||
run: cargo clippy --all-targets --features "uniffi/bindgen-tests" -- -D warnings
|
run: cargo clippy --all-targets --features "uniffi/bindgen-tests" -- -D warnings
|
||||||
|
|
||||||
- name: "Test"
|
- name: "Test"
|
||||||
run: CLASSPATH=./tests/jna/jna-5.14.0.jar cargo test --features uniffi/bindgen-tests
|
run: CLASSPATH=./tests/jna/jna-5.8.0.jar cargo test --features uniffi/bindgen-tests
|
||||||
|
|
||||||
fmt:
|
fmt:
|
||||||
name: "Rust fmt"
|
name: "Rust fmt"
|
||||||
|
13
.github/workflows/live-tests.yaml
vendored
13
.github/workflows/live-tests.yaml
vendored
@ -27,8 +27,8 @@ jobs:
|
|||||||
distribution: temurin
|
distribution: temurin
|
||||||
java-version: 11
|
java-version: 11
|
||||||
|
|
||||||
- name: "Set default Rust version to 1.77.1"
|
- name: "Set default Rust version to 1.73.0"
|
||||||
run: rustup default 1.77.1
|
run: rustup default 1.73.0
|
||||||
|
|
||||||
- name: "Build bdk-jvm library"
|
- name: "Build bdk-jvm library"
|
||||||
run: |
|
run: |
|
||||||
@ -48,8 +48,7 @@ jobs:
|
|||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: "Build Swift package"
|
- name: "Build Swift package"
|
||||||
working-directory: bdk-swift
|
run: bash ./bdk-swift/build-local-swift.sh
|
||||||
run: bash ./build-xcframework.sh
|
|
||||||
|
|
||||||
- name: "Run live Swift tests"
|
- name: "Run live Swift tests"
|
||||||
working-directory: bdk-swift
|
working-directory: bdk-swift
|
||||||
@ -75,11 +74,9 @@ jobs:
|
|||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
submodules: true
|
submodules: true
|
||||||
|
- uses: actions-rs/toolchain@v1
|
||||||
- name: "Install Rust 1.77.1"
|
|
||||||
uses: actions-rs/toolchain@v1
|
|
||||||
with:
|
with:
|
||||||
toolchain: 1.77.1
|
toolchain: stable
|
||||||
|
|
||||||
- name: "Generate bdk.py and binaries"
|
- name: "Generate bdk.py and binaries"
|
||||||
run: bash ./scripts/generate-linux.sh
|
run: bash ./scripts/generate-linux.sh
|
||||||
|
8
.github/workflows/publish-android.yaml
vendored
8
.github/workflows/publish-android.yaml
vendored
@ -23,10 +23,10 @@ jobs:
|
|||||||
uses: actions/setup-java@v3
|
uses: actions/setup-java@v3
|
||||||
with:
|
with:
|
||||||
distribution: temurin
|
distribution: temurin
|
||||||
java-version: 17
|
java-version: 11
|
||||||
|
|
||||||
- name: "Set default Rust version to 1.77.1"
|
- name: "Set default Rust version to 1.73.0"
|
||||||
run: rustup default 1.77.1
|
run: rustup default 1.73.0
|
||||||
|
|
||||||
- name: "Install Rust Android targets"
|
- name: "Install Rust Android targets"
|
||||||
run: rustup target add x86_64-linux-android aarch64-linux-android armv7-linux-androideabi
|
run: rustup target add x86_64-linux-android aarch64-linux-android armv7-linux-androideabi
|
||||||
@ -36,7 +36,7 @@ jobs:
|
|||||||
cd bdk-android
|
cd bdk-android
|
||||||
./gradlew buildAndroidLib
|
./gradlew buildAndroidLib
|
||||||
|
|
||||||
- name: "Publish to Maven Central"
|
- name: "Publish to Maven Local and Maven Central"
|
||||||
env:
|
env:
|
||||||
ORG_GRADLE_PROJECT_signingKeyId: ${{ secrets.PGP_KEY_ID }}
|
ORG_GRADLE_PROJECT_signingKeyId: ${{ secrets.PGP_KEY_ID }}
|
||||||
ORG_GRADLE_PROJECT_signingKey: ${{ secrets.PGP_SECRET_KEY }}
|
ORG_GRADLE_PROJECT_signingKey: ${{ secrets.PGP_SECRET_KEY }}
|
||||||
|
18
.github/workflows/publish-jvm.yaml
vendored
18
.github/workflows/publish-jvm.yaml
vendored
@ -22,10 +22,10 @@ jobs:
|
|||||||
uses: actions/setup-java@v3
|
uses: actions/setup-java@v3
|
||||||
with:
|
with:
|
||||||
distribution: temurin
|
distribution: temurin
|
||||||
java-version: 17
|
java-version: 11
|
||||||
|
|
||||||
- name: "Set default Rust version to 1.77.1"
|
- name: "Set default Rust version to 1.73.0"
|
||||||
run: rustup default 1.77.1
|
run: rustup default 1.73.0
|
||||||
|
|
||||||
- name: "Install aarch64 Rust target"
|
- name: "Install aarch64 Rust target"
|
||||||
run: rustup target add aarch64-apple-darwin
|
run: rustup target add aarch64-apple-darwin
|
||||||
@ -52,10 +52,10 @@ jobs:
|
|||||||
uses: actions/setup-java@v3
|
uses: actions/setup-java@v3
|
||||||
with:
|
with:
|
||||||
distribution: temurin
|
distribution: temurin
|
||||||
java-version: 17
|
java-version: 11
|
||||||
|
|
||||||
- name: "Set default Rust version to 1.77.1"
|
- name: "Set default Rust version to 1.73.0"
|
||||||
run: rustup default 1.77.1
|
run: rustup default 1.73.0
|
||||||
|
|
||||||
- name: "Install x86_64-pc-windows-msvc Rust target"
|
- name: "Install x86_64-pc-windows-msvc Rust target"
|
||||||
run: rustup target add x86_64-pc-windows-msvc
|
run: rustup target add x86_64-pc-windows-msvc
|
||||||
@ -92,10 +92,10 @@ jobs:
|
|||||||
uses: actions/setup-java@v3
|
uses: actions/setup-java@v3
|
||||||
with:
|
with:
|
||||||
distribution: temurin
|
distribution: temurin
|
||||||
java-version: 17
|
java-version: 11
|
||||||
|
|
||||||
- name: "Set default Rust version to 1.77.1"
|
- name: "Set default Rust version to 1.73.0"
|
||||||
run: rustup default 1.77.1
|
run: rustup default 1.73.0
|
||||||
|
|
||||||
- name: "Build bdk-jvm library"
|
- name: "Build bdk-jvm library"
|
||||||
run: |
|
run: |
|
||||||
|
36
.github/workflows/publish-python.yaml
vendored
36
.github/workflows/publish-python.yaml
vendored
@ -1,17 +1,22 @@
|
|||||||
name: Publish bdkpython to PyPI
|
name: Publish bdkpython to PyPI
|
||||||
on: [workflow_dispatch]
|
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:
|
jobs:
|
||||||
build-manylinux_2_28-x86_64-wheels:
|
build-manylinux2014-x86_64-wheels:
|
||||||
name: "Build Manylinux 2.28 x86_64 wheel"
|
name: "Build Manylinux 2014 x86_64 wheel"
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-20.04
|
||||||
defaults:
|
defaults:
|
||||||
run:
|
run:
|
||||||
working-directory: bdk-python
|
working-directory: bdk-python
|
||||||
container:
|
container:
|
||||||
image: quay.io/pypa/manylinux_2_28_x86_64
|
image: quay.io/pypa/manylinux2014_x86_64
|
||||||
env:
|
env:
|
||||||
PLAT: manylinux_2_28_x86_64
|
PLAT: manylinux2014_x86_64
|
||||||
PYBIN: "/opt/python/${{ matrix.python }}/bin"
|
PYBIN: "/opt/python/${{ matrix.python }}/bin"
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
@ -19,30 +24,27 @@ jobs:
|
|||||||
- cp38-cp38
|
- cp38-cp38
|
||||||
- cp39-cp39
|
- cp39-cp39
|
||||||
- cp310-cp310
|
- cp310-cp310
|
||||||
- cp311-cp311
|
|
||||||
- cp312-cp312
|
|
||||||
steps:
|
steps:
|
||||||
- name: "Checkout"
|
- name: "Checkout"
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
submodules: true
|
submodules: true
|
||||||
|
# TODO 2: Other CI workflows use explicit Rust compiler versions, I think we should do the same here
|
||||||
- name: "Install Rust 1.77.1"
|
- uses: actions-rs/toolchain@v1
|
||||||
uses: actions-rs/toolchain@v1
|
|
||||||
with:
|
with:
|
||||||
toolchain: 1.77.1
|
toolchain: stable
|
||||||
|
|
||||||
- name: "Generate bdk.py and binaries"
|
- name: "Generate bdk.py and binaries"
|
||||||
run: bash ./scripts/generate-linux.sh
|
run: bash ./scripts/generate-linux.sh
|
||||||
|
|
||||||
- name: "Build wheel"
|
- name: "Build wheel"
|
||||||
# Specifying the plat-name argument is necessary to build a wheel with the correct name,
|
# Specifying the plat-name argument is necessary to build a wheel with the correct name,
|
||||||
# see issue #350 for more information
|
# see issue #350 for more information
|
||||||
run: ${PYBIN}/python setup.py bdist_wheel --plat-name manylinux_2_28_x86_64 --verbose
|
run: ${PYBIN}/python setup.py bdist_wheel --plat-name manylinux_2_17_x86_64 --verbose
|
||||||
|
|
||||||
- uses: actions/upload-artifact@v3
|
- uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: bdkpython-manylinux_2_28_x86_64-${{ matrix.python }}
|
name: bdkpython-manylinux2014-x86_64-${{ matrix.python }}
|
||||||
path: /home/runner/work/bdk-ffi/bdk-ffi/bdk-python/dist/*.whl
|
path: /home/runner/work/bdk-ffi/bdk-ffi/bdk-python/dist/*.whl
|
||||||
|
|
||||||
build-macos-arm64-wheels:
|
build-macos-arm64-wheels:
|
||||||
@ -57,8 +59,6 @@ jobs:
|
|||||||
- "3.8"
|
- "3.8"
|
||||||
- "3.9"
|
- "3.9"
|
||||||
- "3.10"
|
- "3.10"
|
||||||
- "3.11"
|
|
||||||
- "3.12"
|
|
||||||
steps:
|
steps:
|
||||||
- name: "Checkout"
|
- name: "Checkout"
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
@ -96,8 +96,6 @@ jobs:
|
|||||||
- "3.8"
|
- "3.8"
|
||||||
- "3.9"
|
- "3.9"
|
||||||
- "3.10"
|
- "3.10"
|
||||||
- "3.11"
|
|
||||||
- "3.12"
|
|
||||||
steps:
|
steps:
|
||||||
- name: "Checkout"
|
- name: "Checkout"
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
@ -134,8 +132,6 @@ jobs:
|
|||||||
- "3.8"
|
- "3.8"
|
||||||
- "3.9"
|
- "3.9"
|
||||||
- "3.10"
|
- "3.10"
|
||||||
- "3.11"
|
|
||||||
- "3.12"
|
|
||||||
steps:
|
steps:
|
||||||
- name: "Checkout"
|
- name: "Checkout"
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
@ -163,7 +159,7 @@ jobs:
|
|||||||
defaults:
|
defaults:
|
||||||
run:
|
run:
|
||||||
working-directory: bdk-python
|
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-wheels, build-macos-arm64-wheels, build-macos-x86_64-wheels, build-windows-wheels]
|
||||||
steps:
|
steps:
|
||||||
- name: "Checkout"
|
- name: "Checkout"
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
|
6
.github/workflows/test-android.yaml
vendored
6
.github/workflows/test-android.yaml
vendored
@ -35,10 +35,10 @@ jobs:
|
|||||||
uses: actions/setup-java@v3
|
uses: actions/setup-java@v3
|
||||||
with:
|
with:
|
||||||
distribution: temurin
|
distribution: temurin
|
||||||
java-version: 17
|
java-version: 11
|
||||||
|
|
||||||
- name: "Set default Rust version to 1.77.1"
|
- name: "Set default Rust version to 1.73.0"
|
||||||
run: rustup default 1.77.1
|
run: rustup default 1.73.0
|
||||||
|
|
||||||
- name: "Install Rust Android targets"
|
- name: "Install Rust Android targets"
|
||||||
run: rustup target add x86_64-linux-android aarch64-linux-android armv7-linux-androideabi
|
run: rustup target add x86_64-linux-android aarch64-linux-android armv7-linux-androideabi
|
||||||
|
6
.github/workflows/test-jvm.yaml
vendored
6
.github/workflows/test-jvm.yaml
vendored
@ -30,10 +30,10 @@ jobs:
|
|||||||
uses: actions/setup-java@v3
|
uses: actions/setup-java@v3
|
||||||
with:
|
with:
|
||||||
distribution: temurin
|
distribution: temurin
|
||||||
java-version: 17
|
java-version: 11
|
||||||
|
|
||||||
- name: "Set default Rust version to 1.77.1"
|
- name: "Set default Rust version to 1.73.0"
|
||||||
run: rustup default 1.77.1
|
run: rustup default 1.73.0
|
||||||
|
|
||||||
- name: "Run JVM tests"
|
- name: "Run JVM tests"
|
||||||
run: |
|
run: |
|
||||||
|
33
.github/workflows/test-python.yaml
vendored
33
.github/workflows/test-python.yaml
vendored
@ -10,17 +10,22 @@ on:
|
|||||||
- "bdk-ffi/**"
|
- "bdk-ffi/**"
|
||||||
- "bdk-python/**"
|
- "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:
|
jobs:
|
||||||
build-manylinux_2_28-x86_64-wheels:
|
build-manylinux2014-x86_64-wheels:
|
||||||
name: "Build and test Manylinux 2.28 x86_64 wheels"
|
name: "Build and test Manylinux 2014 x86_64 wheels"
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-20.04
|
||||||
defaults:
|
defaults:
|
||||||
run:
|
run:
|
||||||
working-directory: bdk-python
|
working-directory: bdk-python
|
||||||
container:
|
container:
|
||||||
image: quay.io/pypa/manylinux_2_28_x86_64
|
image: quay.io/pypa/manylinux2014_x86_64
|
||||||
env:
|
env:
|
||||||
PLAT: manylinux_2_28_x86_64
|
PLAT: manylinux2014_x86_64
|
||||||
PYBIN: "/opt/python/${{ matrix.python }}/bin"
|
PYBIN: "/opt/python/${{ matrix.python }}/bin"
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
@ -28,26 +33,22 @@ jobs:
|
|||||||
- cp38-cp38
|
- cp38-cp38
|
||||||
- cp39-cp39
|
- cp39-cp39
|
||||||
- cp310-cp310
|
- cp310-cp310
|
||||||
- cp311-cp311
|
|
||||||
- cp312-cp312
|
|
||||||
steps:
|
steps:
|
||||||
- name: "Checkout"
|
- name: "Checkout"
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
submodules: true
|
submodules: true
|
||||||
|
- uses: actions-rs/toolchain@v1
|
||||||
- name: "Install Rust 1.77.1"
|
|
||||||
uses: actions-rs/toolchain@v1
|
|
||||||
with:
|
with:
|
||||||
toolchain: 1.77.1
|
toolchain: stable
|
||||||
|
|
||||||
- name: "Generate bdk.py and binaries"
|
- name: "Generate bdk.py and binaries"
|
||||||
run: bash ./scripts/generate-linux.sh
|
run: bash ./scripts/generate-linux.sh
|
||||||
|
|
||||||
- name: "Build wheel"
|
- name: "Build wheel"
|
||||||
# Specifying the plat-name argument is necessary to build a wheel with the correct name,
|
# Specifying the plat-name argument is necessary to build a wheel with the correct name,
|
||||||
# see issue #350 for more information
|
# see issue #350 for more information
|
||||||
run: ${PYBIN}/python setup.py bdist_wheel --plat-name manylinux_2_28_x86_64 --verbose
|
run: ${PYBIN}/python setup.py bdist_wheel --plat-name manylinux_2_17_x86_64 --verbose
|
||||||
|
|
||||||
- name: "Install wheel"
|
- name: "Install wheel"
|
||||||
run: ${PYBIN}/pip install ./dist/*.whl
|
run: ${PYBIN}/pip install ./dist/*.whl
|
||||||
@ -58,7 +59,7 @@ jobs:
|
|||||||
- name: "Upload artifact test"
|
- name: "Upload artifact test"
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: bdkpython-manylinux_2_28_x86_64-${{ matrix.python }}
|
name: bdkpython-manylinux2014-x86_64-${{ matrix.python }}
|
||||||
path: /home/runner/work/bdk-ffi/bdk-ffi/bdk-python/dist/*.whl
|
path: /home/runner/work/bdk-ffi/bdk-ffi/bdk-python/dist/*.whl
|
||||||
|
|
||||||
build-macos-arm64-wheels:
|
build-macos-arm64-wheels:
|
||||||
@ -73,8 +74,6 @@ jobs:
|
|||||||
- "3.8"
|
- "3.8"
|
||||||
- "3.9"
|
- "3.9"
|
||||||
- "3.10"
|
- "3.10"
|
||||||
- "3.11"
|
|
||||||
- "3.12"
|
|
||||||
steps:
|
steps:
|
||||||
- name: "Checkout"
|
- name: "Checkout"
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
@ -118,8 +117,6 @@ jobs:
|
|||||||
- "3.8"
|
- "3.8"
|
||||||
- "3.9"
|
- "3.9"
|
||||||
- "3.10"
|
- "3.10"
|
||||||
- "3.11"
|
|
||||||
- "3.12"
|
|
||||||
steps:
|
steps:
|
||||||
- name: "Checkout"
|
- name: "Checkout"
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
@ -161,8 +158,6 @@ jobs:
|
|||||||
- "3.8"
|
- "3.8"
|
||||||
- "3.9"
|
- "3.9"
|
||||||
- "3.10"
|
- "3.10"
|
||||||
- "3.11"
|
|
||||||
- "3.12"
|
|
||||||
steps:
|
steps:
|
||||||
- name: "Checkout"
|
- name: "Checkout"
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
|
5
.github/workflows/test-swift.yaml
vendored
5
.github/workflows/test-swift.yaml
vendored
@ -19,9 +19,8 @@ jobs:
|
|||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: "Build Swift package"
|
- name: "Build Swift package"
|
||||||
working-directory: bdk-swift
|
run: bash ./bdk-swift/build-local-swift.sh
|
||||||
run: bash ./build-xcframework.sh
|
|
||||||
|
|
||||||
- name: "Run Swift tests"
|
- name: "Run Swift tests"
|
||||||
working-directory: bdk-swift
|
working-directory: bdk-swift
|
||||||
run: swift test --skip LiveElectrumClientTests --skip LiveMemoryWalletTests --skip LiveTransactionTests --skip LiveTxBuilderTests --skip LiveWalletTests
|
run: swift test --skip LiveWalletTests --skip LiveTxBuilderTests
|
||||||
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -31,8 +31,6 @@ bdkFFI.h
|
|||||||
BitcoinDevKit.swift
|
BitcoinDevKit.swift
|
||||||
bdk.swift
|
bdk.swift
|
||||||
.build
|
.build
|
||||||
*.xcframework/
|
|
||||||
Info.plist
|
|
||||||
|
|
||||||
# Python related
|
# Python related
|
||||||
__pycache__
|
__pycache__
|
51
CHANGELOG.md
51
CHANGELOG.md
@ -1,49 +1,10 @@
|
|||||||
# Changelog
|
# 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.
|
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.
|
||||||
|
|
||||||
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]
|
## [0.30.0]
|
||||||
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.
|
This release has a new API and a few internal optimizations and refactorings.
|
||||||
|
|
||||||
- APIs Added
|
- APIs Added
|
||||||
@ -51,7 +12,7 @@ This release has a new API and a few internal optimizations and refactorings.
|
|||||||
|
|
||||||
[#388]: https://github.com/bitcoindevkit/bdk-ffi/pull/388
|
[#388]: https://github.com/bitcoindevkit/bdk-ffi/pull/388
|
||||||
|
|
||||||
## [v0.29.0]
|
## [0.29.0]
|
||||||
This release has a number of new APIs, and adds support for Windows in bdk-jvm.
|
This release has a number of new APIs, and adds support for Windows in bdk-jvm.
|
||||||
|
|
||||||
Changelog
|
Changelog
|
||||||
@ -262,10 +223,6 @@ Changelog
|
|||||||
|
|
||||||
[BIP 0174]:https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki#encoding
|
[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.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.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.28.0]: https://github.com/bitcoindevkit/bdk-ffi/compare/v0.27.1...v0.28.0
|
||||||
|
27
README.md
27
README.md
@ -8,7 +8,7 @@
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
## 🚨 Warning 🚨
|
## 🚨 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.
|
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.30.X`](https://github.com/bitcoindevkit/bdk-ffi/tree/release/0.30) releases.
|
||||||
|
|
||||||
## Readme
|
## Readme
|
||||||
The workspace in this repository creates the `libbdkffi` multi-language library for the Rust-based
|
The workspace in this repository creates the `libbdkffi` multi-language library for the Rust-based
|
||||||
@ -26,21 +26,22 @@ The below directories (a separate repository in the case of bdk-swift) include i
|
|||||||
| Swift | iOS, macOS | [bdk-swift (GitHub)] | [Readme bdk-swift] | |
|
| Swift | iOS, macOS | [bdk-swift (GitHub)] | [Readme bdk-swift] | |
|
||||||
| Python | linux, macOS, Windows | [bdk-python (PyPI)] | [Readme bdk-python] | |
|
| 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
|
|
||||||
```
|
|
||||||
|
|
||||||
## Minimum Supported Rust Version (MSRV)
|
## Minimum Supported Rust Version (MSRV)
|
||||||
This library should compile with any combination of features with Rust 1.77.1.
|
|
||||||
|
This library should compile with any combination of features with Rust 1.73.0.
|
||||||
|
|
||||||
## Contributing
|
## 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
|
## Goals
|
||||||
1. Language bindings should feel idiomatic in target languages/platforms
|
1. Language bindings should feel idiomatic in target languages/platforms
|
||||||
|
@ -13,6 +13,24 @@ dependencies {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
You may then import and use the `org.bitcoindevkit` library in your Kotlin code like so. Note that this example is for the `0.30.0` release. For examples of the 1.0 API in the alpha releases, take a look at the tests [here](https://github.com/bitcoindevkit/bdk-ffi/tree/master/bdk-android/lib/src/androidTest/kotlin/org/bitcoindevkit).
|
||||||
|
```kotlin
|
||||||
|
import org.bitcoindevkit.*
|
||||||
|
|
||||||
|
// ...
|
||||||
|
|
||||||
|
val externalDescriptor = Descriptor("wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)", Network.TESTNET)
|
||||||
|
val internalDescriptor = Descriptor("wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)", Network.TESTNET)
|
||||||
|
|
||||||
|
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.LastUnused)
|
||||||
|
```
|
||||||
|
|
||||||
### Snapshot releases
|
### 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:
|
To use a snapshot release, specify the snapshot repository url in the `repositories` block and use the snapshot version in the `dependencies` block:
|
||||||
```kotlin
|
```kotlin
|
||||||
@ -31,41 +49,36 @@ dependencies {
|
|||||||
* [Padawan Wallet](https://github.com/thunderbiscuit/padawan-wallet)
|
* [Padawan Wallet](https://github.com/thunderbiscuit/padawan-wallet)
|
||||||
|
|
||||||
### How to build
|
### 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.
|
1. Clone this repository.
|
||||||
```shell
|
```shell
|
||||||
git clone https://github.com/bitcoindevkit/bdk-ffi
|
git clone https://github.com/bitcoindevkit/bdk-ffi
|
||||||
```
|
```
|
||||||
2. Follow the "General" bdk-ffi ["Getting Started (Developer)"] instructions.
|
2. Follow the "General" bdk-ffi ["Getting Started (Developer)"] instructions.
|
||||||
3. Install Rust (note that we are currently building using Rust 1.77.1):
|
3. Install Rust (note that we are currently building using Rust 1.73.0):
|
||||||
```shell
|
```shell
|
||||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
|
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
|
||||||
rustup default 1.77.1
|
rustup default 1.73.0
|
||||||
```
|
```
|
||||||
4. Install required targets
|
4. Install required targets
|
||||||
```sh
|
```sh
|
||||||
rustup target add x86_64-linux-android aarch64-linux-android armv7-linux-androideabi
|
rustup target add x86_64-linux-android aarch64-linux-android armv7-linux-androideabi
|
||||||
```
|
```
|
||||||
5. Install Android SDK and Build-Tools for API level 30+
|
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:
|
6. Setup `$ANDROID_SDK_ROOT` and `$ANDROID_NDK_ROOT` path variables (which are required by the
|
||||||
|
build tool), for example (note that currently, NDK version 25.2.9519653 or above is required):
|
||||||
```shell
|
```shell
|
||||||
# macOS
|
export ANDROID_SDK_ROOT=~/Android/Sdk
|
||||||
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
|
export ANDROID_NDK_ROOT=$ANDROID_SDK_ROOT/ndk/25.2.9519653
|
||||||
```
|
```
|
||||||
|
|
||||||
7. Build kotlin bindings
|
7. Build kotlin bindings
|
||||||
```sh
|
```sh
|
||||||
# build Android library
|
# build Android library
|
||||||
cd bdk-android
|
cd bdk-android
|
||||||
./gradlew buildAndroidLib
|
./gradlew buildAndroidLib
|
||||||
```
|
```
|
||||||
1. Start android emulator and run tests
|
8. Start android emulator (must be x86_64) and run tests
|
||||||
```sh
|
```sh
|
||||||
./gradlew connectedAndroidTest
|
./gradlew connectedAndroidTest
|
||||||
```
|
```
|
||||||
@ -73,7 +86,7 @@ export ANDROID_NDK_ROOT=$ANDROID_SDK_ROOT/ndk/25.2.9519653
|
|||||||
## How to publish to your local Maven repo
|
## How to publish to your local Maven repo
|
||||||
```shell
|
```shell
|
||||||
cd bdk-android
|
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:
|
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,7 +95,7 @@ signing.gnupg.keyName=<YOUR_GNUPG_ID>
|
|||||||
signing.gnupg.passphrase=<YOUR_GNUPG_PASSPHRASE>
|
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
|
```shell
|
||||||
./gradlew publishToMavenLocal
|
./gradlew publishToMavenLocal
|
||||||
```
|
```
|
||||||
|
@ -1,10 +1,14 @@
|
|||||||
|
buildscript {
|
||||||
|
repositories {
|
||||||
|
google()
|
||||||
|
}
|
||||||
|
dependencies {
|
||||||
|
classpath("com.android.tools.build:gradle:7.1.2")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id("com.android.library").version("8.3.1").apply(false)
|
id("io.github.gradle-nexus.publish-plugin") version "1.1.0"
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// library version is defined in gradle.properties
|
// library version is defined in gradle.properties
|
||||||
|
@ -2,4 +2,4 @@ org.gradle.jvmargs=-Xmx1536m
|
|||||||
android.useAndroidX=true
|
android.useAndroidX=true
|
||||||
android.enableJetifier=true
|
android.enableJetifier=true
|
||||||
kotlin.code.style=official
|
kotlin.code.style=official
|
||||||
libraryVersion=1.0.0-alpha.12-SNAPSHOT
|
libraryVersion=1.0.0-alpha.2a
|
||||||
|
BIN
bdk-android/gradle/wrapper/gradle-wrapper.jar
vendored
BIN
bdk-android/gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
@ -1,7 +1,5 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-all.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.2-bin.zip
|
||||||
networkTimeout=10000
|
|
||||||
validateDistributionUrl=true
|
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
41
bdk-android/gradlew
vendored
41
bdk-android/gradlew
vendored
@ -55,7 +55,7 @@
|
|||||||
# Darwin, MinGW, and NonStop.
|
# Darwin, MinGW, and NonStop.
|
||||||
#
|
#
|
||||||
# (3) This script is generated from the Groovy template
|
# (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.
|
# within the Gradle project.
|
||||||
#
|
#
|
||||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||||
@ -80,11 +80,13 @@ do
|
|||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
|
|
||||||
# This is normally unused
|
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
||||||
# shellcheck disable=SC2034
|
|
||||||
|
APP_NAME="Gradle"
|
||||||
APP_BASE_NAME=${0##*/}
|
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.
|
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||||
MAX_FD=maximum
|
MAX_FD=maximum
|
||||||
@ -131,29 +133,22 @@ location of your Java installation."
|
|||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
JAVACMD=java
|
JAVACMD=java
|
||||||
if ! command -v java >/dev/null 2>&1
|
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
then
|
|
||||||
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
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
location of your Java installation."
|
location of your Java installation."
|
||||||
fi
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Increase the maximum file descriptors if we can.
|
# Increase the maximum file descriptors if we can.
|
||||||
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||||
case $MAX_FD in #(
|
case $MAX_FD in #(
|
||||||
max*)
|
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 ) ||
|
MAX_FD=$( ulimit -H -n ) ||
|
||||||
warn "Could not query maximum file descriptor limit"
|
warn "Could not query maximum file descriptor limit"
|
||||||
esac
|
esac
|
||||||
case $MAX_FD in #(
|
case $MAX_FD in #(
|
||||||
'' | soft) :;; #(
|
'' | 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" ||
|
ulimit -n "$MAX_FD" ||
|
||||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||||
esac
|
esac
|
||||||
@ -198,15 +193,11 @@ if "$cygwin" || "$msys" ; then
|
|||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Collect all arguments for the java command;
|
||||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
|
||||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
# shell script including quotes and variable substitutions, so put them in
|
||||||
|
# double quotes to make sure that they get re-expanded; and
|
||||||
# Collect all arguments for the java command:
|
# * put everything else in single quotes, so that it's not re-expanded.
|
||||||
# * 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.
|
|
||||||
|
|
||||||
set -- \
|
set -- \
|
||||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||||
@ -214,12 +205,6 @@ set -- \
|
|||||||
org.gradle.wrapper.GradleWrapperMain \
|
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.
|
# Use "xargs" to parse quoted args.
|
||||||
#
|
#
|
||||||
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||||
|
15
bdk-android/gradlew.bat
vendored
15
bdk-android/gradlew.bat
vendored
@ -14,7 +14,7 @@
|
|||||||
@rem limitations under the License.
|
@rem limitations under the License.
|
||||||
@rem
|
@rem
|
||||||
|
|
||||||
@if "%DEBUG%"=="" @echo off
|
@if "%DEBUG%" == "" @echo off
|
||||||
@rem ##########################################################################
|
@rem ##########################################################################
|
||||||
@rem
|
@rem
|
||||||
@rem Gradle startup script for Windows
|
@rem Gradle startup script for Windows
|
||||||
@ -25,8 +25,7 @@
|
|||||||
if "%OS%"=="Windows_NT" setlocal
|
if "%OS%"=="Windows_NT" setlocal
|
||||||
|
|
||||||
set DIRNAME=%~dp0
|
set DIRNAME=%~dp0
|
||||||
if "%DIRNAME%"=="" set DIRNAME=.
|
if "%DIRNAME%" == "" set DIRNAME=.
|
||||||
@rem This is normally unused
|
|
||||||
set APP_BASE_NAME=%~n0
|
set APP_BASE_NAME=%~n0
|
||||||
set APP_HOME=%DIRNAME%
|
set APP_HOME=%DIRNAME%
|
||||||
|
|
||||||
@ -41,7 +40,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome
|
|||||||
|
|
||||||
set JAVA_EXE=java.exe
|
set JAVA_EXE=java.exe
|
||||||
%JAVA_EXE% -version >NUL 2>&1
|
%JAVA_EXE% -version >NUL 2>&1
|
||||||
if %ERRORLEVEL% equ 0 goto execute
|
if "%ERRORLEVEL%" == "0" goto execute
|
||||||
|
|
||||||
echo.
|
echo.
|
||||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
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
|
:end
|
||||||
@rem End local scope for the variables with windows NT shell
|
@rem End local scope for the variables with windows NT shell
|
||||||
if %ERRORLEVEL% equ 0 goto mainEnd
|
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||||
|
|
||||||
:fail
|
:fail
|
||||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||||
rem the _cmd.exe /c_ return code!
|
rem the _cmd.exe /c_ return code!
|
||||||
set EXIT_CODE=%ERRORLEVEL%
|
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||||
if %EXIT_CODE% equ 0 set EXIT_CODE=1
|
exit /b 1
|
||||||
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
|
|
||||||
exit /b %EXIT_CODE%
|
|
||||||
|
|
||||||
:mainEnd
|
:mainEnd
|
||||||
if "%OS%"=="Windows_NT" endlocal
|
if "%OS%"=="Windows_NT" endlocal
|
||||||
|
@ -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}}
|
|
@ -5,21 +5,25 @@ val libraryVersion: String by project
|
|||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id("com.android.library")
|
id("com.android.library")
|
||||||
id("org.jetbrains.kotlin.android")
|
id("org.jetbrains.kotlin.android") version "1.6.10"
|
||||||
id("org.gradle.maven-publish")
|
id("maven-publish")
|
||||||
id("org.gradle.signing")
|
id("signing")
|
||||||
|
|
||||||
// Custom plugin to generate the native libs and bindings file
|
// Custom plugin to generate the native libs and bindings file
|
||||||
id("org.bitcoindevkit.plugins.generate-android-bindings")
|
id("org.bitcoindevkit.plugins.generate-android-bindings")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
google()
|
||||||
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
namespace = "org.bitcoindevkit"
|
compileSdk = 31
|
||||||
compileSdk = 34
|
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdk = 24
|
minSdk = 21
|
||||||
targetSdk = 34
|
targetSdk = 31
|
||||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
consumerProguardFiles("consumer-rules.pro")
|
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 {
|
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("org.jetbrains.kotlin:kotlin-stdlib-jdk7")
|
||||||
implementation("androidx.appcompat:appcompat:1.4.0")
|
implementation("androidx.appcompat:appcompat:1.4.0")
|
||||||
implementation("androidx.core:core-ktx:1.7.0")
|
implementation("androidx.core:core-ktx:1.7.0")
|
||||||
@ -106,22 +96,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 {
|
signing {
|
||||||
if (project.hasProperty("localBuild")) {
|
|
||||||
isRequired = false
|
|
||||||
}
|
|
||||||
|
|
||||||
val signingKeyId: String? by project
|
val signingKeyId: String? by project
|
||||||
val signingKey: String? by project
|
val signingKey: String? by project
|
||||||
val signingPassword: String? by project
|
val signingPassword: String? by project
|
||||||
@ -129,7 +106,8 @@ signing {
|
|||||||
sign(publishing.publications)
|
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> {
|
tasks.withType<KotlinCompile> {
|
||||||
dependsOn("buildAndroidLib")
|
dependsOn("buildAndroidLib")
|
||||||
}
|
}
|
||||||
|
@ -1,82 +1,30 @@
|
|||||||
package org.bitcoindevkit
|
package org.bitcoindevkit
|
||||||
|
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
|
||||||
import androidx.test.platform.app.InstrumentationRegistry
|
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
import java.io.File
|
|
||||||
import kotlin.test.AfterTest
|
|
||||||
import kotlin.test.assertTrue
|
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)
|
@RunWith(AndroidJUnit4::class)
|
||||||
class LiveTxBuilderTest {
|
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
|
@Test
|
||||||
fun testTxBuilder() {
|
fun testTxBuilder() {
|
||||||
val wallet = Wallet(descriptor, changeDescriptor, Network.SIGNET)
|
val descriptor = Descriptor("wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)", Network.TESTNET)
|
||||||
val esploraClient: EsploraClient = EsploraClient(SIGNET_ESPLORA_URL)
|
val wallet = Wallet.newNoPersist(descriptor, null, Network.TESTNET)
|
||||||
val fullScanRequest: FullScanRequest = wallet.startFullScan()
|
val esploraClient = EsploraClient("https://mempool.space/testnet/api")
|
||||||
val update = esploraClient.fullScan(fullScanRequest, 10uL, 1uL)
|
val update = esploraClient.scan(wallet, 10uL, 1uL)
|
||||||
wallet.applyUpdate(update)
|
wallet.applyUpdate(update)
|
||||||
println("Balance: ${wallet.balance().total.toSat()}")
|
println("Balance: ${wallet.getBalance().total()}")
|
||||||
|
|
||||||
assert(wallet.balance().total.toSat() > 0uL) {
|
assert(wallet.getBalance().total() > 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 recipient: Address = Address("tb1qrnfslnrve9uncz9pzpvf83k3ukz22ljgees989", Network.TESTNET)
|
||||||
val psbt: Psbt = TxBuilder()
|
val psbt: PartiallySignedTransaction = TxBuilder()
|
||||||
.addRecipient(recipient.scriptPubkey(), Amount.fromSat(4200uL))
|
.addRecipient(recipient.scriptPubkey(), 4200uL)
|
||||||
.feeRate(FeeRate.fromSatPerVb(2uL))
|
.feeRate(2.0f)
|
||||||
.finish(wallet)
|
.finish(wallet)
|
||||||
|
|
||||||
println(psbt.serialize())
|
println(psbt.serialize())
|
||||||
assertTrue(psbt.serialize().startsWith("cHNi"), "PSBT should start with 'cHNi'")
|
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'")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -2,73 +2,46 @@ package org.bitcoindevkit
|
|||||||
|
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
import androidx.test.platform.app.InstrumentationRegistry
|
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
import java.io.File
|
|
||||||
import kotlin.test.AfterTest
|
|
||||||
import kotlin.test.assertTrue
|
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)
|
@RunWith(AndroidJUnit4::class)
|
||||||
class LiveWalletTest {
|
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
|
@Test
|
||||||
fun testSyncedBalance() {
|
fun testSyncedBalance() {
|
||||||
val wallet: Wallet = Wallet(descriptor, changeDescriptor, Network.SIGNET)
|
val descriptor: Descriptor = Descriptor("wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)", Network.TESTNET)
|
||||||
val esploraClient: EsploraClient = EsploraClient(SIGNET_ESPLORA_URL)
|
val wallet: Wallet = Wallet.newNoPersist(descriptor, null, Network.TESTNET)
|
||||||
val fullScanRequest: FullScanRequest = wallet.startFullScan()
|
val esploraClient: EsploraClient = EsploraClient("https://mempool.space/testnet/api")
|
||||||
val update = esploraClient.fullScan(fullScanRequest, 10uL, 1uL)
|
// val esploraClient = EsploraClient("https://blockstream.info/testnet/api")
|
||||||
|
val update = esploraClient.scan(wallet, 10uL, 1uL)
|
||||||
wallet.applyUpdate(update)
|
wallet.applyUpdate(update)
|
||||||
println("Balance: ${wallet.balance().total.toSat()}")
|
println("Balance: ${wallet.getBalance().total()}")
|
||||||
val balance: Balance = wallet.balance()
|
val balance: Balance = wallet.getBalance()
|
||||||
println("Balance: $balance")
|
println("Balance: $balance")
|
||||||
|
|
||||||
assert(wallet.balance().total.toSat() > 0uL) {
|
assert(wallet.getBalance().total() > 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
|
@Test
|
||||||
fun testBroadcastTransaction() {
|
fun testBroadcastTransaction() {
|
||||||
val wallet = Wallet(descriptor, changeDescriptor, Network.SIGNET)
|
val descriptor = Descriptor("wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)", Network.TESTNET)
|
||||||
val esploraClient = EsploraClient(SIGNET_ESPLORA_URL)
|
val wallet = Wallet.newNoPersist(descriptor, null, Network.TESTNET)
|
||||||
val fullScanRequest: FullScanRequest = wallet.startFullScan()
|
val esploraClient = EsploraClient("https://mempool.space/testnet/api")
|
||||||
val update = esploraClient.fullScan(fullScanRequest, 10uL, 1uL)
|
val update = esploraClient.scan(wallet, 10uL, 1uL)
|
||||||
wallet.applyUpdate(update)
|
|
||||||
println("Balance: ${wallet.balance().total.toSat()}")
|
|
||||||
|
|
||||||
assert(wallet.balance().total.toSat() > 0uL) {
|
wallet.applyUpdate(update)
|
||||||
"Wallet balance must be greater than 0! Please send funds to ${wallet.revealNextAddress(KeychainKind.EXTERNAL).address} and try again."
|
println("Balance: ${wallet.getBalance().total()}")
|
||||||
|
println("New address: ${wallet.getAddress(AddressIndex.New).address}")
|
||||||
|
|
||||||
|
assert(wallet.getBalance().total() > 0uL) {
|
||||||
|
"Wallet balance must be greater than 0! Please send funds to ${wallet.getAddress(AddressIndex.New).address} and try again."
|
||||||
}
|
}
|
||||||
|
|
||||||
val recipient: Address = Address("tb1qrnfslnrve9uncz9pzpvf83k3ukz22ljgees989", Network.SIGNET)
|
val recipient: Address = Address("tb1qrnfslnrve9uncz9pzpvf83k3ukz22ljgees989", Network.TESTNET)
|
||||||
|
|
||||||
val psbt: Psbt = TxBuilder()
|
val psbt: PartiallySignedTransaction = TxBuilder()
|
||||||
.addRecipient(recipient.scriptPubkey(), Amount.fromSat(4200uL))
|
.addRecipient(recipient.scriptPubkey(), 4200uL)
|
||||||
.feeRate(FeeRate.fromSatPerVb(4uL))
|
.feeRate(4.0f)
|
||||||
.finish(wallet)
|
.finish(wallet)
|
||||||
|
|
||||||
println(psbt.serialize())
|
println(psbt.serialize())
|
||||||
@ -78,14 +51,8 @@ class LiveWalletTest {
|
|||||||
assertTrue(walletDidSign)
|
assertTrue(walletDidSign)
|
||||||
|
|
||||||
val tx: Transaction = psbt.extractTx()
|
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")
|
|
||||||
|
|
||||||
|
println("Txid is: ${tx.txid()}")
|
||||||
esploraClient.broadcast(tx)
|
esploraClient.broadcast(tx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ import kotlin.test.Test
|
|||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
class OfflineDescriptorTest {
|
class OfflineDescriptorTest {
|
||||||
@ -15,7 +16,7 @@ class OfflineDescriptorTest {
|
|||||||
|
|
||||||
assertEquals(
|
assertEquals(
|
||||||
expected = "tr([be1eec8f/86'/1'/0']tpubDCTtszwSxPx3tATqDrsSyqScPNnUChwQAVAkanuDUCJQESGBbkt68nXXKRDifYSDbeMa2Xg2euKbXaU3YphvGWftDE7ozRKPriT6vAo3xsc/0/*)#m7puekcx",
|
expected = "tr([be1eec8f/86'/1'/0']tpubDCTtszwSxPx3tATqDrsSyqScPNnUChwQAVAkanuDUCJQESGBbkt68nXXKRDifYSDbeMa2Xg2euKbXaU3YphvGWftDE7ozRKPriT6vAo3xsc/0/*)#m7puekcx",
|
||||||
actual = descriptor.toString()
|
actual = descriptor.asString()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,68 +3,54 @@ package org.bitcoindevkit
|
|||||||
import kotlin.test.Test
|
import kotlin.test.Test
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
import kotlin.test.assertTrue
|
import kotlin.test.assertTrue
|
||||||
import kotlin.test.assertFalse
|
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
import androidx.test.platform.app.InstrumentationRegistry
|
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
import java.io.File
|
|
||||||
import kotlin.test.AfterTest
|
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
class OfflineWalletTest {
|
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
|
@Test
|
||||||
fun testDescriptorBip86() {
|
fun testDescriptorBip86() {
|
||||||
val mnemonic: Mnemonic = Mnemonic(WordCount.WORDS12)
|
val mnemonic: Mnemonic = Mnemonic(WordCount.WORDS12)
|
||||||
val descriptorSecretKey: DescriptorSecretKey = DescriptorSecretKey(Network.TESTNET, mnemonic, null)
|
val descriptorSecretKey: DescriptorSecretKey = DescriptorSecretKey(Network.TESTNET, mnemonic, null)
|
||||||
val descriptor: Descriptor = Descriptor.newBip86(descriptorSecretKey, KeychainKind.EXTERNAL, Network.TESTNET)
|
val descriptor: Descriptor = Descriptor.newBip86(descriptorSecretKey, KeychainKind.EXTERNAL, Network.TESTNET)
|
||||||
|
|
||||||
assertTrue(descriptor.toString().startsWith("tr"), "Bip86 Descriptor does not start with 'tr'")
|
assertTrue(descriptor.asString().startsWith("tr"), "Bip86 Descriptor does not start with 'tr'")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testNewAddress() {
|
fun testNewAddress() {
|
||||||
val wallet: Wallet = Wallet(
|
val descriptor: Descriptor = Descriptor(
|
||||||
descriptor,
|
"wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)",
|
||||||
changeDescriptor,
|
|
||||||
Network.TESTNET
|
Network.TESTNET
|
||||||
)
|
)
|
||||||
val addressInfo: AddressInfo = wallet.revealNextAddress(KeychainKind.EXTERNAL)
|
val wallet: Wallet = Wallet.newNoPersist(
|
||||||
|
descriptor,
|
||||||
assertTrue(addressInfo.address.isValidForNetwork(Network.TESTNET), "Address is not valid for testnet network")
|
null,
|
||||||
assertTrue(addressInfo.address.isValidForNetwork(Network.SIGNET), "Address is not valid for signet network")
|
Network.TESTNET
|
||||||
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")
|
val addressInfo: AddressInfo = wallet.getAddress(AddressIndex.New)
|
||||||
|
|
||||||
assertEquals(
|
assertEquals(
|
||||||
expected = "tb1qrnfslnrve9uncz9pzpvf83k3ukz22ljgees989",
|
expected = "tb1qzg4mckdh50nwdm9hkzq06528rsu73hjxxzem3e",
|
||||||
actual = addressInfo.address.toString()
|
actual = addressInfo.address.asString()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testBalance() {
|
fun testBalance() {
|
||||||
val wallet: Wallet = Wallet(
|
val descriptor: Descriptor = Descriptor(
|
||||||
|
"wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)",
|
||||||
|
Network.TESTNET
|
||||||
|
)
|
||||||
|
val wallet: Wallet = Wallet.newNoPersist(
|
||||||
descriptor,
|
descriptor,
|
||||||
changeDescriptor,
|
null,
|
||||||
Network.TESTNET
|
Network.TESTNET
|
||||||
)
|
)
|
||||||
|
|
||||||
assertEquals(
|
assertEquals(
|
||||||
expected = 0uL,
|
expected = 0uL,
|
||||||
actual = wallet.balance().total.toSat()
|
actual = wallet.getBalance().total()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package org.bitcoindevkit.plugins
|
package org.bitcoindevkit.plugins
|
||||||
|
|
||||||
|
|
||||||
val operatingSystem: OS = when {
|
val operatingSystem: OS = when {
|
||||||
System.getProperty("os.name").contains("mac", ignoreCase = true) -> OS.MAC
|
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("linux", ignoreCase = true) -> OS.LINUX
|
||||||
|
@ -17,11 +17,6 @@ internal class UniFfiAndroidPlugin : Plugin<Project> {
|
|||||||
OS.OTHER -> throw Error("Cannot build Android library from current architecture")
|
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
|
// arm64-v8a is the most popular hardware architecture for Android
|
||||||
val buildAndroidAarch64Binary by tasks.register<Exec>("buildAndroidAarch64Binary") {
|
val buildAndroidAarch64Binary by tasks.register<Exec>("buildAndroidAarch64Binary") {
|
||||||
|
|
||||||
@ -31,13 +26,20 @@ internal class UniFfiAndroidPlugin : Plugin<Project> {
|
|||||||
executable("cargo")
|
executable("cargo")
|
||||||
args(cargoArgs)
|
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(
|
environment(
|
||||||
// add build toolchain to PATH
|
// add build toolchain to PATH
|
||||||
Pair("PATH", "${System.getenv("PATH")}:${System.getenv("ANDROID_NDK_ROOT")}/toolchains/llvm/prebuilt/$llvmArchPath/bin"),
|
Pair("PATH", "${System.getenv("PATH")}:${System.getenv("ANDROID_NDK_ROOT")}/toolchains/llvm/prebuilt/$llvmArchPath/bin"),
|
||||||
Pair("CFLAGS", "-D__ANDROID_MIN_SDK_VERSION__=24"),
|
Pair("CFLAGS", "-D__ANDROID_MIN_SDK_VERSION__=21"),
|
||||||
Pair("AR", "llvm-ar"),
|
Pair("AR", "llvm-ar"),
|
||||||
Pair("CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER", "aarch64-linux-android24-clang"),
|
Pair("CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER", "aarch64-linux-android21-clang"),
|
||||||
Pair("CC", "aarch64-linux-android24-clang")
|
Pair("CC", "aarch64-linux-android21-clang")
|
||||||
)
|
)
|
||||||
|
|
||||||
doLast {
|
doLast {
|
||||||
@ -54,13 +56,20 @@ internal class UniFfiAndroidPlugin : Plugin<Project> {
|
|||||||
executable("cargo")
|
executable("cargo")
|
||||||
args(cargoArgs)
|
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(
|
environment(
|
||||||
// add build toolchain to PATH
|
// add build toolchain to PATH
|
||||||
Pair("PATH", "${System.getenv("PATH")}:${System.getenv("ANDROID_NDK_ROOT")}/toolchains/llvm/prebuilt/$llvmArchPath/bin"),
|
Pair("PATH", "${System.getenv("PATH")}:${System.getenv("ANDROID_NDK_ROOT")}/toolchains/llvm/prebuilt/$llvmArchPath/bin"),
|
||||||
Pair("CFLAGS", "-D__ANDROID_MIN_SDK_VERSION__=24"),
|
Pair("CFLAGS", "-D__ANDROID_MIN_SDK_VERSION__=21"),
|
||||||
Pair("AR", "llvm-ar"),
|
Pair("AR", "llvm-ar"),
|
||||||
Pair("CARGO_TARGET_X86_64_LINUX_ANDROID_LINKER", "x86_64-linux-android24-clang"),
|
Pair("CARGO_TARGET_X86_64_LINUX_ANDROID_LINKER", "x86_64-linux-android21-clang"),
|
||||||
Pair("CC", "x86_64-linux-android24-clang")
|
Pair("CC", "x86_64-linux-android21-clang")
|
||||||
)
|
)
|
||||||
|
|
||||||
doLast {
|
doLast {
|
||||||
@ -77,13 +86,20 @@ internal class UniFfiAndroidPlugin : Plugin<Project> {
|
|||||||
executable("cargo")
|
executable("cargo")
|
||||||
args(cargoArgs)
|
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(
|
environment(
|
||||||
// add build toolchain to PATH
|
// add build toolchain to PATH
|
||||||
Pair("PATH", "${System.getenv("PATH")}:${System.getenv("ANDROID_NDK_ROOT")}/toolchains/llvm/prebuilt/$llvmArchPath/bin"),
|
Pair("PATH", "${System.getenv("PATH")}:${System.getenv("ANDROID_NDK_ROOT")}/toolchains/llvm/prebuilt/$llvmArchPath/bin"),
|
||||||
Pair("CFLAGS", "-D__ANDROID_MIN_SDK_VERSION__=24"),
|
Pair("CFLAGS", "-D__ANDROID_MIN_SDK_VERSION__=21"),
|
||||||
Pair("AR", "llvm-ar"),
|
Pair("AR", "llvm-ar"),
|
||||||
Pair("CARGO_TARGET_ARMV7_LINUX_ANDROIDEABI_LINKER", "armv7a-linux-androideabi24-clang"),
|
Pair("CARGO_TARGET_ARMV7_LINUX_ANDROIDEABI_LINKER", "armv7a-linux-androideabi21-clang"),
|
||||||
Pair("CC", "armv7a-linux-androideabi24-clang")
|
Pair("CC", "armv7a-linux-androideabi21-clang")
|
||||||
)
|
)
|
||||||
|
|
||||||
doLast {
|
doLast {
|
||||||
@ -97,8 +113,6 @@ internal class UniFfiAndroidPlugin : Plugin<Project> {
|
|||||||
val moveNativeAndroidLibs by tasks.register<Copy>("moveNativeAndroidLibs") {
|
val moveNativeAndroidLibs by tasks.register<Copy>("moveNativeAndroidLibs") {
|
||||||
|
|
||||||
dependsOn(buildAndroidAarch64Binary)
|
dependsOn(buildAndroidAarch64Binary)
|
||||||
dependsOn(buildAndroidArmv7Binary)
|
|
||||||
dependsOn(buildAndroidX86_64Binary)
|
|
||||||
|
|
||||||
into("${project.projectDir}/../lib/src/main/jniLibs/")
|
into("${project.projectDir}/../lib/src/main/jniLibs/")
|
||||||
|
|
||||||
@ -123,14 +137,14 @@ internal class UniFfiAndroidPlugin : Plugin<Project> {
|
|||||||
val generateAndroidBindings by tasks.register<Exec>("generateAndroidBindings") {
|
val generateAndroidBindings by tasks.register<Exec>("generateAndroidBindings") {
|
||||||
dependsOn(moveNativeAndroidLibs)
|
dependsOn(moveNativeAndroidLibs)
|
||||||
|
|
||||||
val libraryPath = "${project.projectDir}/../../bdk-ffi/target/aarch64-linux-android/release-smaller/libbdkffi.so"
|
// val libraryPath = "${project.projectDir}/../../bdk-ffi/target/aarch64-linux-android/release-smaller/libbdkffi.so"
|
||||||
workingDir("${project.projectDir}/../../bdk-ffi")
|
// 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")
|
// 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 above worked for uniffi 0.24.3 using the --library flag
|
||||||
// The code below works for uniffi 0.23.0
|
// The code below works for uniffi 0.23.0
|
||||||
// workingDir("${project.projectDir}/../../bdk-ffi")
|
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")
|
val cargoArgs: List<String> = listOf("run", "--bin", "uniffi-bindgen", "generate", "src/bdk.udl", "--language", "kotlin", "--out-dir", "../bdk-android/lib/src/main/kotlin", "--no-format")
|
||||||
|
|
||||||
executable("cargo")
|
executable("cargo")
|
||||||
args(cargoArgs)
|
args(cargoArgs)
|
||||||
|
@ -2,17 +2,3 @@ rootProject.name = "bdk-android"
|
|||||||
|
|
||||||
include(":lib")
|
include(":lib")
|
||||||
includeBuild("plugins")
|
includeBuild("plugins")
|
||||||
|
|
||||||
pluginManagement {
|
|
||||||
repositories {
|
|
||||||
gradlePluginPortal()
|
|
||||||
google()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencyResolutionManagement {
|
|
||||||
repositories {
|
|
||||||
mavenCentral()
|
|
||||||
google()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
1124
bdk-ffi/Cargo.lock
generated
1124
bdk-ffi/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "bdk-ffi"
|
name = "bdk-ffi"
|
||||||
version = "1.0.0-alpha.11"
|
version = "1.0.0-alpha.2"
|
||||||
homepage = "https://bitcoindevkit.org"
|
homepage = "https://bitcoindevkit.org"
|
||||||
repository = "https://github.com/bitcoindevkit/bdk"
|
repository = "https://github.com/bitcoindevkit/bdk"
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
@ -18,24 +18,24 @@ path = "uniffi-bindgen.rs"
|
|||||||
default = ["uniffi/cli"]
|
default = ["uniffi/cli"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
bdk_wallet = { version = "1.0.0-alpha.13", features = ["all-keys", "keys-bip39"] }
|
bdk = { version = "1.0.0-alpha.2", 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" }
|
# TODO 22: The bdk_esplora crate uses esplora_client which uses reqwest for async. By default it uses the system
|
||||||
thiserror = "1.0.58"
|
# openssl library, which is creating problems for cross-compilation. I'd rather use rustls, but it's hidden
|
||||||
|
# behind a feature flag. We need to look into whether openssl-sys is really required by bdk or if using rustls
|
||||||
|
# would work just as well. This here is a temporary workaround which removes the async feature on the bdk_esplora crate.
|
||||||
|
# See PR #1179 https://github.com/bitcoindevkit/bdk/pull/1179 for the fix in bdk.
|
||||||
|
# bdk = { git = "https://github.com/thunderbiscuit/bdk.git", branch = "test-rust-tls", version = "1.0.0-alpha.2", features = ["all-keys", "keys-bip39"] }
|
||||||
|
# bdk_esplora = { git = "https://github.com/thunderbiscuit/bdk.git", branch = "test-rust-tls", version = "0.4.0", package = "bdk_esplora", default-features = false, features = ["std", "blocking", "async-https-rustls"] }
|
||||||
|
|
||||||
|
bdk_esplora = { version = "0.4.0", default-features = false, features = ["std", "blocking"] }
|
||||||
|
uniffi = { version = "=0.25.1" }
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
uniffi = { version = "=0.28.0", features = ["build"] }
|
uniffi = { version = "=0.25.1", features = ["build"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
uniffi = { version = "=0.28.0", features = ["bindgen-tests"] }
|
uniffi = { version = "=0.25.1", features = ["bindgen-tests"] }
|
||||||
assert_matches = "1.5.0"
|
assert_matches = "1.5.0"
|
||||||
|
|
||||||
[profile.release-smaller]
|
[profile.release-smaller]
|
||||||
|
@ -1,12 +0,0 @@
|
|||||||
default:
|
|
||||||
just --list
|
|
||||||
|
|
||||||
build:
|
|
||||||
cargo build
|
|
||||||
|
|
||||||
check:
|
|
||||||
cargo fmt
|
|
||||||
cargo clippy
|
|
||||||
|
|
||||||
test:
|
|
||||||
cargo test --lib
|
|
@ -1,281 +1,7 @@
|
|||||||
namespace bdk {};
|
namespace bdk {};
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
// bdk crate - error module
|
// bdk crate - root module
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
|
|
||||||
[Error]
|
|
||||||
interface AddressParseError {
|
|
||||||
Base58();
|
|
||||||
Bech32();
|
|
||||||
WitnessVersion(string error_message);
|
|
||||||
WitnessProgram(string error_message);
|
|
||||||
UnknownHrp();
|
|
||||||
LegacyAddressTooLong();
|
|
||||||
InvalidBase58PayloadLength();
|
|
||||||
InvalidLegacyPrefix();
|
|
||||||
NetworkValidation();
|
|
||||||
OtherAddressParseErr();
|
|
||||||
};
|
|
||||||
|
|
||||||
[Error]
|
|
||||||
interface Bip32Error {
|
|
||||||
CannotDeriveFromHardenedKey();
|
|
||||||
Secp256k1(string error_message);
|
|
||||||
InvalidChildNumber(u32 child_number);
|
|
||||||
InvalidChildNumberFormat();
|
|
||||||
InvalidDerivationPathFormat();
|
|
||||||
UnknownVersion(string version);
|
|
||||||
WrongExtendedKeyLength(u32 length);
|
|
||||||
Base58(string error_message);
|
|
||||||
Hex(string error_message);
|
|
||||||
InvalidPublicKeyHexLength(u32 length);
|
|
||||||
UnknownError(string error_message);
|
|
||||||
};
|
|
||||||
|
|
||||||
[Error]
|
|
||||||
interface Bip39Error {
|
|
||||||
BadWordCount(u64 word_count);
|
|
||||||
UnknownWord(u64 index);
|
|
||||||
BadEntropyBitCount(u64 bit_count);
|
|
||||||
InvalidChecksum();
|
|
||||||
AmbiguousLanguages(string languages);
|
|
||||||
};
|
|
||||||
|
|
||||||
[Error]
|
|
||||||
interface CalculateFeeError {
|
|
||||||
MissingTxOut(sequence<OutPoint> out_points);
|
|
||||||
NegativeFee(string amount);
|
|
||||||
};
|
|
||||||
|
|
||||||
[Error]
|
|
||||||
interface CannotConnectError {
|
|
||||||
Include(u32 height);
|
|
||||||
};
|
|
||||||
|
|
||||||
[Error]
|
|
||||||
interface CreateTxError {
|
|
||||||
Descriptor(string error_message);
|
|
||||||
Policy(string error_message);
|
|
||||||
SpendingPolicyRequired(string kind);
|
|
||||||
Version0();
|
|
||||||
Version1Csv();
|
|
||||||
LockTime(string requested, string required);
|
|
||||||
RbfSequence();
|
|
||||||
RbfSequenceCsv(string rbf, string csv);
|
|
||||||
FeeTooLow(string required);
|
|
||||||
FeeRateTooLow(string required);
|
|
||||||
NoUtxosSelected();
|
|
||||||
OutputBelowDustLimit(u64 index);
|
|
||||||
ChangePolicyDescriptor();
|
|
||||||
CoinSelection(string error_message);
|
|
||||||
InsufficientFunds(u64 needed, u64 available);
|
|
||||||
NoRecipients();
|
|
||||||
Psbt(string error_message);
|
|
||||||
MissingKeyOrigin(string key);
|
|
||||||
UnknownUtxo(string outpoint);
|
|
||||||
MissingNonWitnessUtxo(string outpoint);
|
|
||||||
MiniscriptPsbt(string error_message);
|
|
||||||
};
|
|
||||||
|
|
||||||
[Error]
|
|
||||||
interface DescriptorError {
|
|
||||||
InvalidHdKeyPath();
|
|
||||||
InvalidDescriptorChecksum();
|
|
||||||
HardenedDerivationXpub();
|
|
||||||
MultiPath();
|
|
||||||
Key(string error_message);
|
|
||||||
Policy(string error_message);
|
|
||||||
InvalidDescriptorCharacter(string char);
|
|
||||||
Bip32(string error_message);
|
|
||||||
Base58(string error_message);
|
|
||||||
Pk(string error_message);
|
|
||||||
Miniscript(string error_message);
|
|
||||||
Hex(string error_message);
|
|
||||||
ExternalAndInternalAreTheSame();
|
|
||||||
};
|
|
||||||
|
|
||||||
[Error]
|
|
||||||
interface DescriptorKeyError {
|
|
||||||
Parse(string error_message);
|
|
||||||
InvalidKeyType();
|
|
||||||
Bip32(string error_message);
|
|
||||||
};
|
|
||||||
|
|
||||||
[Error]
|
|
||||||
interface ElectrumError {
|
|
||||||
IOError(string error_message);
|
|
||||||
Json(string error_message);
|
|
||||||
Hex(string error_message);
|
|
||||||
Protocol(string error_message);
|
|
||||||
Bitcoin(string error_message);
|
|
||||||
AlreadySubscribed();
|
|
||||||
NotSubscribed();
|
|
||||||
InvalidResponse(string error_message);
|
|
||||||
Message(string error_message);
|
|
||||||
InvalidDNSNameError(string domain);
|
|
||||||
MissingDomain();
|
|
||||||
AllAttemptsErrored();
|
|
||||||
SharedIOError(string error_message);
|
|
||||||
CouldntLockReader();
|
|
||||||
Mpsc();
|
|
||||||
CouldNotCreateConnection(string error_message);
|
|
||||||
RequestAlreadyConsumed();
|
|
||||||
};
|
|
||||||
|
|
||||||
[Error]
|
|
||||||
interface EsploraError {
|
|
||||||
Minreq(string error_message);
|
|
||||||
HttpResponse(u16 status, string error_message);
|
|
||||||
Parsing(string error_message);
|
|
||||||
StatusCode(string error_message);
|
|
||||||
BitcoinEncoding(string error_message);
|
|
||||||
HexToArray(string error_message);
|
|
||||||
HexToBytes(string error_message);
|
|
||||||
TransactionNotFound();
|
|
||||||
HeaderHeightNotFound(u32 height);
|
|
||||||
HeaderHashNotFound();
|
|
||||||
InvalidHttpHeaderName(string name);
|
|
||||||
InvalidHttpHeaderValue(string value);
|
|
||||||
RequestAlreadyConsumed();
|
|
||||||
};
|
|
||||||
|
|
||||||
[Error]
|
|
||||||
interface ExtractTxError {
|
|
||||||
AbsurdFeeRate(u64 fee_rate);
|
|
||||||
MissingInputValue();
|
|
||||||
SendingTooMuch();
|
|
||||||
OtherExtractTxErr();
|
|
||||||
};
|
|
||||||
|
|
||||||
[Error]
|
|
||||||
enum FeeRateError {
|
|
||||||
"ArithmeticOverflow"
|
|
||||||
};
|
|
||||||
|
|
||||||
[Error]
|
|
||||||
interface FromScriptError {
|
|
||||||
UnrecognizedScript();
|
|
||||||
WitnessProgram(string error_message);
|
|
||||||
WitnessVersion(string error_message);
|
|
||||||
OtherFromScriptErr();
|
|
||||||
};
|
|
||||||
|
|
||||||
[Error]
|
|
||||||
interface ParseAmountError {
|
|
||||||
OutOfRange();
|
|
||||||
TooPrecise();
|
|
||||||
MissingDigits();
|
|
||||||
InputTooLarge();
|
|
||||||
InvalidCharacter(string error_message);
|
|
||||||
OtherParseAmountErr();
|
|
||||||
};
|
|
||||||
|
|
||||||
[Error]
|
|
||||||
interface PersistenceError {
|
|
||||||
Write(string error_message);
|
|
||||||
};
|
|
||||||
|
|
||||||
[Error]
|
|
||||||
interface PsbtError {
|
|
||||||
InvalidMagic();
|
|
||||||
MissingUtxo();
|
|
||||||
InvalidSeparator();
|
|
||||||
PsbtUtxoOutOfBounds();
|
|
||||||
InvalidKey(string key);
|
|
||||||
InvalidProprietaryKey();
|
|
||||||
DuplicateKey(string key);
|
|
||||||
UnsignedTxHasScriptSigs();
|
|
||||||
UnsignedTxHasScriptWitnesses();
|
|
||||||
MustHaveUnsignedTx();
|
|
||||||
NoMorePairs();
|
|
||||||
UnexpectedUnsignedTx();
|
|
||||||
NonStandardSighashType(u32 sighash);
|
|
||||||
InvalidHash(string hash);
|
|
||||||
InvalidPreimageHashPair();
|
|
||||||
CombineInconsistentKeySources(string xpub);
|
|
||||||
ConsensusEncoding(string encoding_error);
|
|
||||||
NegativeFee();
|
|
||||||
FeeOverflow();
|
|
||||||
InvalidPublicKey(string error_message);
|
|
||||||
InvalidSecp256k1PublicKey(string secp256k1_error);
|
|
||||||
InvalidXOnlyPublicKey();
|
|
||||||
InvalidEcdsaSignature(string error_message);
|
|
||||||
InvalidTaprootSignature(string error_message);
|
|
||||||
InvalidControlBlock();
|
|
||||||
InvalidLeafVersion();
|
|
||||||
Taproot();
|
|
||||||
TapTree(string error_message);
|
|
||||||
XPubKey();
|
|
||||||
Version(string error_message);
|
|
||||||
PartialDataConsumption();
|
|
||||||
Io(string error_message);
|
|
||||||
OtherPsbtErr();
|
|
||||||
};
|
|
||||||
|
|
||||||
[Error]
|
|
||||||
interface PsbtParseError {
|
|
||||||
PsbtEncoding(string error_message);
|
|
||||||
Base64Encoding(string error_message);
|
|
||||||
};
|
|
||||||
|
|
||||||
[Error]
|
|
||||||
interface InspectError {
|
|
||||||
RequestAlreadyConsumed();
|
|
||||||
};
|
|
||||||
|
|
||||||
[Error]
|
|
||||||
interface SignerError {
|
|
||||||
MissingKey();
|
|
||||||
InvalidKey();
|
|
||||||
UserCanceled();
|
|
||||||
InputIndexOutOfRange();
|
|
||||||
MissingNonWitnessUtxo();
|
|
||||||
InvalidNonWitnessUtxo();
|
|
||||||
MissingWitnessUtxo();
|
|
||||||
MissingWitnessScript();
|
|
||||||
MissingHdKeypath();
|
|
||||||
NonStandardSighash();
|
|
||||||
InvalidSighash();
|
|
||||||
SighashP2wpkh(string error_message);
|
|
||||||
SighashTaproot(string error_message);
|
|
||||||
TxInputsIndexError(string error_message);
|
|
||||||
MiniscriptPsbt(string error_message);
|
|
||||||
External(string error_message);
|
|
||||||
};
|
|
||||||
|
|
||||||
[Error]
|
|
||||||
interface SqliteError {
|
|
||||||
InvalidNetwork(Network expected, Network given);
|
|
||||||
Sqlite(string rusqlite_error);
|
|
||||||
};
|
|
||||||
|
|
||||||
[Error]
|
|
||||||
interface TransactionError {
|
|
||||||
Io();
|
|
||||||
OversizedVectorAllocation();
|
|
||||||
InvalidChecksum(string expected, string actual);
|
|
||||||
NonMinimalVarInt();
|
|
||||||
ParseFailed();
|
|
||||||
UnsupportedSegwitFlag(u8 flag);
|
|
||||||
OtherTransactionErr();
|
|
||||||
};
|
|
||||||
|
|
||||||
[Error]
|
|
||||||
interface TxidParseError {
|
|
||||||
InvalidTxid(string txid);
|
|
||||||
};
|
|
||||||
|
|
||||||
[Error]
|
|
||||||
interface WalletCreationError {
|
|
||||||
Descriptor(string error_message);
|
|
||||||
LoadedGenesisDoesNotMatch(string expected, string got);
|
|
||||||
LoadedNetworkDoesNotMatch(Network expected, Network? got);
|
|
||||||
LoadedDescriptorDoesNotMatch(string got, KeychainKind keychain);
|
|
||||||
};
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// bdk_wallet crate - types module
|
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
enum KeychainKind {
|
enum KeychainKind {
|
||||||
@ -283,124 +9,92 @@ enum KeychainKind {
|
|||||||
"Internal",
|
"Internal",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// bdk crate - wallet module
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
[Error]
|
||||||
|
enum BdkError {
|
||||||
|
"Generic",
|
||||||
|
"NoRecipients",
|
||||||
|
"NoUtxosSelected",
|
||||||
|
"OutputBelowDustLimit",
|
||||||
|
"InsufficientFunds",
|
||||||
|
"BnBTotalTriesExceeded",
|
||||||
|
"BnBNoExactMatch",
|
||||||
|
"UnknownUtxo",
|
||||||
|
"TransactionNotFound",
|
||||||
|
"TransactionConfirmed",
|
||||||
|
"IrreplaceableTransaction",
|
||||||
|
"FeeRateTooLow",
|
||||||
|
"FeeTooLow",
|
||||||
|
"FeeRateUnavailable",
|
||||||
|
"MissingKeyOrigin",
|
||||||
|
"Key",
|
||||||
|
"ChecksumMismatch",
|
||||||
|
"SpendingPolicyRequired",
|
||||||
|
"InvalidPolicyPathError",
|
||||||
|
"Signer",
|
||||||
|
"InvalidOutpoint",
|
||||||
|
"Descriptor",
|
||||||
|
"Miniscript",
|
||||||
|
"MiniscriptPsbt",
|
||||||
|
"Bip32",
|
||||||
|
"Psbt",
|
||||||
|
};
|
||||||
|
|
||||||
|
enum ChangeSpendPolicy {
|
||||||
|
"ChangeAllowed",
|
||||||
|
"OnlyChange",
|
||||||
|
"ChangeForbidden"
|
||||||
|
};
|
||||||
|
|
||||||
|
interface Balance {
|
||||||
|
u64 immature();
|
||||||
|
|
||||||
|
u64 trusted_pending();
|
||||||
|
|
||||||
|
u64 untrusted_pending();
|
||||||
|
|
||||||
|
u64 confirmed();
|
||||||
|
|
||||||
|
u64 trusted_spendable();
|
||||||
|
|
||||||
|
u64 total();
|
||||||
|
};
|
||||||
|
|
||||||
dictionary AddressInfo {
|
dictionary AddressInfo {
|
||||||
u32 index;
|
u32 index;
|
||||||
Address address;
|
Address address;
|
||||||
KeychainKind keychain;
|
KeychainKind keychain;
|
||||||
};
|
};
|
||||||
|
|
||||||
dictionary Balance {
|
|
||||||
Amount immature;
|
|
||||||
|
|
||||||
Amount trusted_pending;
|
|
||||||
|
|
||||||
Amount untrusted_pending;
|
|
||||||
|
|
||||||
Amount confirmed;
|
|
||||||
|
|
||||||
Amount trusted_spendable;
|
|
||||||
|
|
||||||
Amount total;
|
|
||||||
};
|
|
||||||
|
|
||||||
dictionary LocalOutput {
|
|
||||||
OutPoint outpoint;
|
|
||||||
TxOut txout;
|
|
||||||
KeychainKind keychain;
|
|
||||||
boolean is_spent;
|
|
||||||
};
|
|
||||||
|
|
||||||
dictionary TxOut {
|
|
||||||
u64 value;
|
|
||||||
Script script_pubkey;
|
|
||||||
};
|
|
||||||
|
|
||||||
[Enum]
|
[Enum]
|
||||||
interface ChainPosition {
|
interface AddressIndex {
|
||||||
Confirmed(u32 height, u64 timestamp);
|
New();
|
||||||
Unconfirmed(u64 timestamp);
|
LastUnused();
|
||||||
};
|
Peek(u32 index);
|
||||||
|
|
||||||
dictionary CanonicalTx {
|
|
||||||
Transaction transaction;
|
|
||||||
ChainPosition chain_position;
|
|
||||||
};
|
|
||||||
|
|
||||||
interface FullScanRequest {
|
|
||||||
[Throws=InspectError]
|
|
||||||
FullScanRequest inspect_spks_for_all_keychains(FullScanScriptInspector inspector);
|
|
||||||
};
|
|
||||||
|
|
||||||
interface SyncRequest {
|
|
||||||
[Throws=InspectError]
|
|
||||||
SyncRequest inspect_spks(SyncScriptInspector inspector);
|
|
||||||
};
|
|
||||||
|
|
||||||
[Trait, WithForeign]
|
|
||||||
interface SyncScriptInspector {
|
|
||||||
void inspect(Script script, u64 total);
|
|
||||||
};
|
|
||||||
|
|
||||||
[Trait, WithForeign]
|
|
||||||
interface FullScanScriptInspector {
|
|
||||||
void inspect(KeychainKind keychain, u32 index, Script script);
|
|
||||||
};
|
|
||||||
|
|
||||||
interface ChangeSet {};
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// bdk_wallet crate - wallet module
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
|
|
||||||
enum ChangeSpendPolicy {
|
|
||||||
"ChangeAllowed",
|
|
||||||
"OnlyChange",
|
|
||||||
"ChangeForbidden"
|
|
||||||
};
|
};
|
||||||
|
|
||||||
interface Wallet {
|
interface Wallet {
|
||||||
[Throws=WalletCreationError]
|
[Name=new_no_persist, Throws=BdkError]
|
||||||
constructor(Descriptor descriptor, Descriptor change_descriptor, Network network);
|
constructor(Descriptor descriptor, Descriptor? change_descriptor, Network network);
|
||||||
|
|
||||||
[Name=new_or_load, Throws=WalletCreationError]
|
AddressInfo get_address(AddressIndex address_index);
|
||||||
constructor(Descriptor descriptor, Descriptor change_descriptor, ChangeSet? change_set, Network network);
|
|
||||||
|
|
||||||
AddressInfo reveal_next_address(KeychainKind keychain);
|
AddressInfo get_internal_address(AddressIndex address_index);
|
||||||
|
|
||||||
Network network();
|
Network network();
|
||||||
|
|
||||||
Balance balance();
|
Balance get_balance();
|
||||||
|
|
||||||
[Throws=CannotConnectError]
|
boolean is_mine(Script script);
|
||||||
|
|
||||||
|
[Throws=BdkError]
|
||||||
void apply_update(Update update);
|
void apply_update(Update update);
|
||||||
|
|
||||||
boolean is_mine([ByRef] Script script);
|
[Throws=BdkError]
|
||||||
|
boolean sign(PartiallySignedTransaction psbt);
|
||||||
[Throws=SignerError]
|
|
||||||
boolean sign(Psbt psbt);
|
|
||||||
|
|
||||||
SentAndReceivedValues sent_and_received([ByRef] Transaction tx);
|
|
||||||
|
|
||||||
sequence<CanonicalTx> transactions();
|
|
||||||
|
|
||||||
[Throws=TxidParseError]
|
|
||||||
CanonicalTx? get_tx(string txid);
|
|
||||||
|
|
||||||
[Throws=CalculateFeeError]
|
|
||||||
Amount calculate_fee([ByRef] Transaction tx);
|
|
||||||
|
|
||||||
[Throws=CalculateFeeError]
|
|
||||||
FeeRate calculate_fee_rate([ByRef] Transaction tx);
|
|
||||||
|
|
||||||
sequence<LocalOutput> list_unspent();
|
|
||||||
|
|
||||||
sequence<LocalOutput> list_output();
|
|
||||||
|
|
||||||
FullScanRequest start_full_scan();
|
|
||||||
|
|
||||||
SyncRequest start_sync_with_revealed_spks();
|
|
||||||
|
|
||||||
ChangeSet? take_staged();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
interface Update {};
|
interface Update {};
|
||||||
@ -408,14 +102,12 @@ interface Update {};
|
|||||||
interface TxBuilder {
|
interface TxBuilder {
|
||||||
constructor();
|
constructor();
|
||||||
|
|
||||||
TxBuilder add_recipient([ByRef] Script script, Amount amount);
|
TxBuilder add_recipient(Script script, u64 amount);
|
||||||
|
|
||||||
TxBuilder set_recipients(sequence<ScriptAmount> recipients);
|
TxBuilder set_recipients(sequence<ScriptAmount> script_amount);
|
||||||
|
|
||||||
TxBuilder add_unspendable(OutPoint unspendable);
|
TxBuilder add_unspendable(OutPoint unspendable);
|
||||||
|
|
||||||
TxBuilder unspendable(sequence<OutPoint> unspendable);
|
|
||||||
|
|
||||||
TxBuilder add_utxo(OutPoint outpoint);
|
TxBuilder add_utxo(OutPoint outpoint);
|
||||||
|
|
||||||
TxBuilder change_policy(ChangeSpendPolicy change_policy);
|
TxBuilder change_policy(ChangeSpendPolicy change_policy);
|
||||||
@ -426,79 +118,46 @@ interface TxBuilder {
|
|||||||
|
|
||||||
TxBuilder manually_selected_only();
|
TxBuilder manually_selected_only();
|
||||||
|
|
||||||
TxBuilder fee_rate([ByRef] FeeRate fee_rate);
|
TxBuilder fee_rate(float sat_per_vbyte);
|
||||||
|
|
||||||
TxBuilder fee_absolute(Amount fee);
|
|
||||||
|
|
||||||
TxBuilder drain_wallet();
|
TxBuilder drain_wallet();
|
||||||
|
|
||||||
TxBuilder drain_to([ByRef] Script script);
|
[Throws=BdkError]
|
||||||
|
PartiallySignedTransaction finish([ByRef] Wallet wallet);
|
||||||
TxBuilder enable_rbf();
|
|
||||||
|
|
||||||
TxBuilder enable_rbf_with_sequence(u32 nsequence);
|
|
||||||
|
|
||||||
[Throws=CreateTxError]
|
|
||||||
Psbt finish([ByRef] Wallet wallet);
|
|
||||||
};
|
|
||||||
|
|
||||||
interface BumpFeeTxBuilder {
|
|
||||||
constructor(string txid, FeeRate fee_rate);
|
|
||||||
|
|
||||||
BumpFeeTxBuilder enable_rbf();
|
|
||||||
|
|
||||||
BumpFeeTxBuilder enable_rbf_with_sequence(u32 nsequence);
|
|
||||||
|
|
||||||
[Throws=CreateTxError]
|
|
||||||
Psbt finish([ByRef] Wallet wallet);
|
|
||||||
};
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// bdk_sqlite crate
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
|
|
||||||
interface SqliteStore {
|
|
||||||
[Throws=SqliteError]
|
|
||||||
constructor(string path);
|
|
||||||
|
|
||||||
[Throws=SqliteError]
|
|
||||||
void write([ByRef] ChangeSet change_set);
|
|
||||||
|
|
||||||
[Throws=SqliteError]
|
|
||||||
ChangeSet? read();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
// bdk crate - descriptor module
|
// bdk crate - descriptor module
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
[Traits=(Display)]
|
|
||||||
interface Mnemonic {
|
interface Mnemonic {
|
||||||
constructor(WordCount word_count);
|
constructor(WordCount word_count);
|
||||||
|
|
||||||
[Name=from_string, Throws=Bip39Error]
|
[Name=from_string, Throws=BdkError]
|
||||||
constructor(string mnemonic);
|
constructor(string mnemonic);
|
||||||
|
|
||||||
[Name=from_entropy, Throws=Bip39Error]
|
[Name=from_entropy, Throws=BdkError]
|
||||||
constructor(sequence<u8> entropy);
|
constructor(sequence<u8> entropy);
|
||||||
|
|
||||||
|
string as_string();
|
||||||
};
|
};
|
||||||
|
|
||||||
interface DerivationPath {
|
interface DerivationPath {
|
||||||
[Throws=Bip32Error]
|
[Throws=BdkError]
|
||||||
constructor(string path);
|
constructor(string path);
|
||||||
};
|
};
|
||||||
|
|
||||||
interface DescriptorSecretKey {
|
interface DescriptorSecretKey {
|
||||||
constructor(Network network, [ByRef] Mnemonic mnemonic, string? password);
|
constructor(Network network, Mnemonic mnemonic, string? password);
|
||||||
|
|
||||||
[Name=from_string, Throws=DescriptorKeyError]
|
[Name=from_string, Throws=BdkError]
|
||||||
constructor(string secret_key);
|
constructor(string secret_key);
|
||||||
|
|
||||||
[Throws=DescriptorKeyError]
|
[Throws=BdkError]
|
||||||
DescriptorSecretKey derive([ByRef] DerivationPath path);
|
DescriptorSecretKey derive(DerivationPath path);
|
||||||
|
|
||||||
[Throws=DescriptorKeyError]
|
[Throws=BdkError]
|
||||||
DescriptorSecretKey extend([ByRef] DerivationPath path);
|
DescriptorSecretKey extend(DerivationPath path);
|
||||||
|
|
||||||
DescriptorPublicKey as_public();
|
DescriptorPublicKey as_public();
|
||||||
|
|
||||||
@ -508,48 +167,49 @@ interface DescriptorSecretKey {
|
|||||||
};
|
};
|
||||||
|
|
||||||
interface DescriptorPublicKey {
|
interface DescriptorPublicKey {
|
||||||
[Name=from_string, Throws=DescriptorKeyError]
|
[Name=from_string, Throws=BdkError]
|
||||||
constructor(string public_key);
|
constructor(string public_key);
|
||||||
|
|
||||||
[Throws=DescriptorKeyError]
|
[Throws=BdkError]
|
||||||
DescriptorPublicKey derive([ByRef] DerivationPath path);
|
DescriptorPublicKey derive(DerivationPath path);
|
||||||
|
|
||||||
[Throws=DescriptorKeyError]
|
[Throws=BdkError]
|
||||||
DescriptorPublicKey extend([ByRef] DerivationPath path);
|
DescriptorPublicKey extend(DerivationPath path);
|
||||||
|
|
||||||
string as_string();
|
string as_string();
|
||||||
};
|
};
|
||||||
|
|
||||||
[Traits=(Display)]
|
|
||||||
interface Descriptor {
|
interface Descriptor {
|
||||||
[Throws=DescriptorError]
|
[Throws=BdkError]
|
||||||
constructor(string descriptor, Network network);
|
constructor(string descriptor, Network network);
|
||||||
|
|
||||||
[Name=new_bip44]
|
[Name=new_bip44]
|
||||||
constructor([ByRef] DescriptorSecretKey secret_key, KeychainKind keychain, Network network);
|
constructor(DescriptorSecretKey secret_key, KeychainKind keychain, Network network);
|
||||||
|
|
||||||
[Name=new_bip44_public]
|
[Name=new_bip44_public]
|
||||||
constructor([ByRef] DescriptorPublicKey public_key, string fingerprint, KeychainKind keychain, Network network);
|
constructor(DescriptorPublicKey public_key, string fingerprint, KeychainKind keychain, Network network);
|
||||||
|
|
||||||
[Name=new_bip49]
|
[Name=new_bip49]
|
||||||
constructor([ByRef] DescriptorSecretKey secret_key, KeychainKind keychain, Network network);
|
constructor(DescriptorSecretKey secret_key, KeychainKind keychain, Network network);
|
||||||
|
|
||||||
[Name=new_bip49_public]
|
[Name=new_bip49_public]
|
||||||
constructor([ByRef] DescriptorPublicKey public_key, string fingerprint, KeychainKind keychain, Network network);
|
constructor(DescriptorPublicKey public_key, string fingerprint, KeychainKind keychain, Network network);
|
||||||
|
|
||||||
[Name=new_bip84]
|
[Name=new_bip84]
|
||||||
constructor([ByRef] DescriptorSecretKey secret_key, KeychainKind keychain, Network network);
|
constructor(DescriptorSecretKey secret_key, KeychainKind keychain, Network network);
|
||||||
|
|
||||||
[Name=new_bip84_public]
|
[Name=new_bip84_public]
|
||||||
constructor([ByRef] DescriptorPublicKey public_key, string fingerprint, KeychainKind keychain, Network network);
|
constructor(DescriptorPublicKey public_key, string fingerprint, KeychainKind keychain, Network network);
|
||||||
|
|
||||||
[Name=new_bip86]
|
[Name=new_bip86]
|
||||||
constructor([ByRef] DescriptorSecretKey secret_key, KeychainKind keychain, Network network);
|
constructor(DescriptorSecretKey secret_key, KeychainKind keychain, Network network);
|
||||||
|
|
||||||
[Name=new_bip86_public]
|
[Name=new_bip86_public]
|
||||||
constructor([ByRef] DescriptorPublicKey public_key, string fingerprint, KeychainKind keychain, Network network);
|
constructor(DescriptorPublicKey public_key, string fingerprint, KeychainKind keychain, Network network);
|
||||||
|
|
||||||
string to_string_with_secret();
|
string as_string();
|
||||||
|
|
||||||
|
string as_string_private();
|
||||||
};
|
};
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
@ -559,32 +219,11 @@ interface Descriptor {
|
|||||||
interface EsploraClient {
|
interface EsploraClient {
|
||||||
constructor(string url);
|
constructor(string url);
|
||||||
|
|
||||||
[Throws=EsploraError]
|
[Throws=BdkError]
|
||||||
Update full_scan(FullScanRequest full_scan_request, u64 stop_gap, u64 parallel_requests);
|
Update scan(Wallet wallet, u64 stop_gap, u64 parallel_requests);
|
||||||
|
|
||||||
[Throws=EsploraError]
|
[Throws=BdkError]
|
||||||
Update sync(SyncRequest sync_request, u64 parallel_requests);
|
void broadcast(Transaction transaction);
|
||||||
|
|
||||||
[Throws=EsploraError]
|
|
||||||
void broadcast([ByRef] Transaction transaction);
|
|
||||||
};
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// bdk_electrum crate
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
|
|
||||||
interface ElectrumClient {
|
|
||||||
[Throws=ElectrumError]
|
|
||||||
constructor(string url);
|
|
||||||
|
|
||||||
[Throws=ElectrumError]
|
|
||||||
Update full_scan(FullScanRequest full_scan_request, u64 stop_gap, u64 batch_size, boolean fetch_prev_txouts);
|
|
||||||
|
|
||||||
[Throws=ElectrumError]
|
|
||||||
Update sync(SyncRequest sync_request, u64 batch_size, boolean fetch_prev_txouts);
|
|
||||||
|
|
||||||
[Throws=ElectrumError]
|
|
||||||
string broadcast([ByRef] Transaction transaction);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
@ -593,16 +232,11 @@ interface ElectrumClient {
|
|||||||
|
|
||||||
dictionary ScriptAmount {
|
dictionary ScriptAmount {
|
||||||
Script script;
|
Script script;
|
||||||
Amount amount;
|
u64 amount;
|
||||||
};
|
|
||||||
|
|
||||||
dictionary SentAndReceivedValues {
|
|
||||||
Amount sent;
|
|
||||||
Amount received;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
// bdk_wallet crate - bitcoin re-exports
|
// bdk crate - bitcoin re-exports
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
interface Script {
|
interface Script {
|
||||||
@ -611,7 +245,6 @@ interface Script {
|
|||||||
sequence<u8> to_bytes();
|
sequence<u8> to_bytes();
|
||||||
};
|
};
|
||||||
|
|
||||||
[NonExhaustive]
|
|
||||||
enum Network {
|
enum Network {
|
||||||
"Bitcoin",
|
"Bitcoin",
|
||||||
"Testnet",
|
"Testnet",
|
||||||
@ -627,102 +260,48 @@ enum WordCount {
|
|||||||
"Words24",
|
"Words24",
|
||||||
};
|
};
|
||||||
|
|
||||||
[Traits=(Display)]
|
|
||||||
interface Address {
|
interface Address {
|
||||||
[Throws=AddressParseError]
|
[Throws=BdkError]
|
||||||
constructor(string address, Network network);
|
constructor(string address, Network network);
|
||||||
|
|
||||||
[Name=from_script, Throws=FromScriptError]
|
Network network();
|
||||||
constructor(Script script, Network network);
|
|
||||||
|
|
||||||
Script script_pubkey();
|
Script script_pubkey();
|
||||||
|
|
||||||
string to_qr_uri();
|
string to_qr_uri();
|
||||||
|
|
||||||
boolean is_valid_for_network(Network network);
|
string as_string();
|
||||||
};
|
};
|
||||||
|
|
||||||
interface Transaction {
|
interface Transaction {
|
||||||
[Throws=TransactionError]
|
[Throws=BdkError]
|
||||||
constructor(sequence<u8> transaction_bytes);
|
constructor(sequence<u8> transaction_bytes);
|
||||||
|
|
||||||
string compute_txid();
|
string txid();
|
||||||
|
|
||||||
u64 total_size();
|
u64 size();
|
||||||
|
|
||||||
u64 vsize();
|
u64 vsize();
|
||||||
|
|
||||||
boolean is_coinbase();
|
boolean is_coin_base();
|
||||||
|
|
||||||
boolean is_explicitly_rbf();
|
boolean is_explicitly_rbf();
|
||||||
|
|
||||||
boolean is_lock_time_enabled();
|
boolean is_lock_time_enabled();
|
||||||
|
|
||||||
i32 version();
|
i32 version();
|
||||||
|
|
||||||
sequence<u8> serialize();
|
|
||||||
|
|
||||||
u64 weight();
|
|
||||||
|
|
||||||
sequence<TxIn> input();
|
|
||||||
|
|
||||||
sequence<TxOut> output();
|
|
||||||
|
|
||||||
u32 lock_time();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
interface Psbt {
|
interface PartiallySignedTransaction {
|
||||||
[Throws=PsbtParseError]
|
[Throws=BdkError]
|
||||||
constructor(string psbt_base64);
|
constructor(string psbt_base64);
|
||||||
|
|
||||||
string serialize();
|
string serialize();
|
||||||
|
|
||||||
[Throws=ExtractTxError]
|
|
||||||
Transaction extract_tx();
|
Transaction extract_tx();
|
||||||
|
|
||||||
[Throws=PsbtError]
|
|
||||||
u64 fee();
|
|
||||||
|
|
||||||
[Throws=PsbtError]
|
|
||||||
Psbt combine(Psbt other);
|
|
||||||
|
|
||||||
string json_serialize();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
dictionary OutPoint {
|
dictionary OutPoint {
|
||||||
string txid;
|
string txid;
|
||||||
u32 vout;
|
u32 vout;
|
||||||
};
|
};
|
||||||
|
|
||||||
interface Amount {
|
|
||||||
[Name=from_sat]
|
|
||||||
constructor(u64 from_sat);
|
|
||||||
|
|
||||||
[Name=from_btc, Throws=ParseAmountError]
|
|
||||||
constructor(f64 from_btc);
|
|
||||||
|
|
||||||
u64 to_sat();
|
|
||||||
|
|
||||||
f64 to_btc();
|
|
||||||
};
|
|
||||||
|
|
||||||
interface FeeRate {
|
|
||||||
[Name=from_sat_per_vb, Throws=FeeRateError]
|
|
||||||
constructor(u64 sat_per_vb);
|
|
||||||
|
|
||||||
[Name=from_sat_per_kwu]
|
|
||||||
constructor(u64 sat_per_kwu);
|
|
||||||
|
|
||||||
u64 to_sat_per_vb_ceil();
|
|
||||||
|
|
||||||
u64 to_sat_per_vb_floor();
|
|
||||||
|
|
||||||
u64 to_sat_per_kwu();
|
|
||||||
};
|
|
||||||
|
|
||||||
dictionary TxIn {
|
|
||||||
OutPoint previous_output;
|
|
||||||
Script script_sig;
|
|
||||||
u32 sequence;
|
|
||||||
sequence<sequence<u8>> witness;
|
|
||||||
};
|
|
||||||
|
@ -1,65 +1,19 @@
|
|||||||
use crate::error::{
|
use bdk::bitcoin::address::{NetworkChecked, NetworkUnchecked};
|
||||||
AddressParseError, FeeRateError, FromScriptError, PsbtError, PsbtParseError, TransactionError,
|
use bdk::bitcoin::blockdata::script::ScriptBuf as BdkScriptBuf;
|
||||||
};
|
use bdk::bitcoin::consensus::Decodable;
|
||||||
|
use bdk::bitcoin::network::constants::Network as BdkNetwork;
|
||||||
|
use bdk::bitcoin::psbt::PartiallySignedTransaction as BdkPartiallySignedTransaction;
|
||||||
|
use bdk::bitcoin::Address as BdkAddress;
|
||||||
|
use bdk::bitcoin::OutPoint as BdkOutPoint;
|
||||||
|
use bdk::bitcoin::Transaction as BdkTransaction;
|
||||||
|
use bdk::bitcoin::Txid;
|
||||||
|
use bdk::Error as BdkError;
|
||||||
|
|
||||||
use bdk_bitcoind_rpc::bitcoincore_rpc::jsonrpc::serde_json;
|
use std::io::Cursor;
|
||||||
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::str::FromStr;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
/// A Bitcoin script.
|
||||||
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)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
pub struct Script(pub(crate) BdkScriptBuf);
|
pub struct Script(pub(crate) BdkScriptBuf);
|
||||||
|
|
||||||
@ -80,186 +34,270 @@ impl From<BdkScriptBuf> for Script {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
pub enum Network {
|
||||||
pub struct Address(BdkAddress<NetworkChecked>);
|
/// Mainnet Bitcoin.
|
||||||
|
Bitcoin,
|
||||||
|
/// Bitcoin's testnet network.
|
||||||
|
Testnet,
|
||||||
|
/// Bitcoin's signet network.
|
||||||
|
Signet,
|
||||||
|
/// Bitcoin's regtest network.
|
||||||
|
Regtest,
|
||||||
|
}
|
||||||
|
|
||||||
impl Address {
|
impl From<Network> for BdkNetwork {
|
||||||
pub fn new(address: String, network: Network) -> Result<Self, AddressParseError> {
|
fn from(network: Network) -> Self {
|
||||||
let parsed_address = address.parse::<bdk_wallet::bitcoin::Address<NetworkUnchecked>>()?;
|
match network {
|
||||||
let network_checked_address = parsed_address.require_network(network)?;
|
Network::Bitcoin => BdkNetwork::Bitcoin,
|
||||||
|
Network::Testnet => BdkNetwork::Testnet,
|
||||||
Ok(Address(network_checked_address))
|
Network::Signet => BdkNetwork::Signet,
|
||||||
}
|
Network::Regtest => BdkNetwork::Regtest,
|
||||||
|
|
||||||
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 {
|
impl From<BdkNetwork> for Network {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn from(network: BdkNetwork) -> Self {
|
||||||
write!(f, "{}", self.0)
|
match network {
|
||||||
|
BdkNetwork::Bitcoin => Network::Bitcoin,
|
||||||
|
BdkNetwork::Testnet => Network::Testnet,
|
||||||
|
BdkNetwork::Signet => Network::Signet,
|
||||||
|
BdkNetwork::Regtest => Network::Regtest,
|
||||||
|
_ => panic!("Network {} not supported", network),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A Bitcoin address.
|
||||||
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
pub struct Address {
|
||||||
|
inner: BdkAddress<NetworkChecked>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Address {
|
||||||
|
pub fn new(address: String, network: Network) -> Result<Self, BdkError> {
|
||||||
|
let parsed_address = address
|
||||||
|
.parse::<bdk::bitcoin::Address<NetworkUnchecked>>()
|
||||||
|
.map_err(|e| BdkError::Generic(e.to_string()))?;
|
||||||
|
|
||||||
|
let network_checked_address = parsed_address
|
||||||
|
.require_network(network.into())
|
||||||
|
.map_err(|e| BdkError::Generic(e.to_string()))?;
|
||||||
|
|
||||||
|
Ok(Address {
|
||||||
|
inner: network_checked_address,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// alternative constructor
|
||||||
|
// fn from_script(script: Arc<Script>, network: Network) -> Result<Self, BdkError> {
|
||||||
|
// BdkAddress::from_script(&script.inner, network)
|
||||||
|
// .map(|a| Address { inner: a })
|
||||||
|
// .map_err(|e| BdkError::Generic(e.to_string()))
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// fn payload(&self) -> Payload {
|
||||||
|
// match &self.inner.payload.clone() {
|
||||||
|
// BdkPayload::PubkeyHash(pubkey_hash) => Payload::PubkeyHash {
|
||||||
|
// pubkey_hash: pubkey_hash.to_vec(),
|
||||||
|
// },
|
||||||
|
// BdkPayload::ScriptHash(script_hash) => Payload::ScriptHash {
|
||||||
|
// script_hash: script_hash.to_vec(),
|
||||||
|
// },
|
||||||
|
// BdkPayload::WitnessProgram { version, program } => Payload::WitnessProgram {
|
||||||
|
// version: *version,
|
||||||
|
// program: program.clone(),
|
||||||
|
// },
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
pub fn network(&self) -> Network {
|
||||||
|
self.inner.network.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn script_pubkey(&self) -> Arc<Script> {
|
||||||
|
Arc::new(Script(self.inner.script_pubkey()))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_qr_uri(&self) -> String {
|
||||||
|
self.inner.to_qr_uri()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_string(&self) -> String {
|
||||||
|
self.inner.to_string()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Address> for BdkAddress {
|
impl From<Address> for BdkAddress {
|
||||||
fn from(address: Address) -> Self {
|
fn from(address: Address) -> Self {
|
||||||
address.0
|
address.inner
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<BdkAddress> for Address {
|
impl From<BdkAddress> for Address {
|
||||||
fn from(address: BdkAddress) -> Self {
|
fn from(address: BdkAddress) -> Self {
|
||||||
Address(address)
|
Address { inner: address }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A Bitcoin transaction.
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub struct Transaction(BdkTransaction);
|
pub struct Transaction {
|
||||||
|
inner: BdkTransaction,
|
||||||
|
}
|
||||||
|
|
||||||
impl Transaction {
|
impl Transaction {
|
||||||
pub fn new(transaction_bytes: Vec<u8>) -> Result<Self, TransactionError> {
|
pub fn new(transaction_bytes: Vec<u8>) -> Result<Self, BdkError> {
|
||||||
let mut decoder = Cursor::new(transaction_bytes);
|
let mut decoder = Cursor::new(transaction_bytes);
|
||||||
let tx: BdkTransaction = BdkTransaction::consensus_decode(&mut decoder)?;
|
let tx: BdkTransaction = BdkTransaction::consensus_decode(&mut decoder)
|
||||||
Ok(Transaction(tx))
|
.map_err(|e| BdkError::Generic(e.to_string()))?;
|
||||||
|
Ok(Transaction { inner: tx })
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn compute_txid(&self) -> String {
|
pub fn txid(&self) -> String {
|
||||||
self.0.compute_txid().to_string()
|
self.inner.txid().to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn weight(&self) -> u64 {
|
// fn weight(&self) -> u64 {
|
||||||
self.0.weight().to_wu()
|
// self.inner.weight() as u64
|
||||||
}
|
// }
|
||||||
|
|
||||||
pub fn total_size(&self) -> u64 {
|
pub fn size(&self) -> u64 {
|
||||||
self.0.total_size() as u64
|
self.inner.size() as u64
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn vsize(&self) -> u64 {
|
pub fn vsize(&self) -> u64 {
|
||||||
self.0.vsize() as u64
|
self.inner.vsize() as u64
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_coinbase(&self) -> bool {
|
// fn serialize(&self) -> Vec<u8> {
|
||||||
self.0.is_coinbase()
|
// self.inner.serialize()
|
||||||
|
// }
|
||||||
|
|
||||||
|
pub fn is_coin_base(&self) -> bool {
|
||||||
|
self.inner.is_coin_base()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_explicitly_rbf(&self) -> bool {
|
pub fn is_explicitly_rbf(&self) -> bool {
|
||||||
self.0.is_explicitly_rbf()
|
self.inner.is_explicitly_rbf()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_lock_time_enabled(&self) -> bool {
|
pub fn is_lock_time_enabled(&self) -> bool {
|
||||||
self.0.is_lock_time_enabled()
|
self.inner.is_lock_time_enabled()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn version(&self) -> i32 {
|
pub fn version(&self) -> i32 {
|
||||||
self.0.version.0
|
self.inner.version
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn serialize(&self) -> Vec<u8> {
|
// fn lock_time(&self) -> u32 {
|
||||||
serialize(&self.0)
|
// self.inner.lock_time.0
|
||||||
}
|
// }
|
||||||
|
|
||||||
pub fn input(&self) -> Vec<TxIn> {
|
// fn input(&self) -> Vec<TxIn> {
|
||||||
self.0.input.iter().map(|tx_in| tx_in.into()).collect()
|
// self.inner.input.iter().map(|x| x.into()).collect()
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
pub fn output(&self) -> Vec<TxOut> {
|
// fn output(&self) -> Vec<TxOut> {
|
||||||
self.0.output.iter().map(|tx_out| tx_out.into()).collect()
|
// self.inner.output.iter().map(|x| x.into()).collect()
|
||||||
}
|
// }
|
||||||
|
|
||||||
pub fn lock_time(&self) -> u32 {
|
|
||||||
self.0.lock_time.to_consensus_u32()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<BdkTransaction> for Transaction {
|
impl From<BdkTransaction> for Transaction {
|
||||||
fn from(tx: BdkTransaction) -> Self {
|
fn from(tx: BdkTransaction) -> Self {
|
||||||
Transaction(tx)
|
Transaction { inner: tx }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<&BdkTransaction> for Transaction {
|
impl From<Transaction> for BdkTransaction {
|
||||||
fn from(tx: &BdkTransaction) -> Self {
|
fn from(tx: Transaction) -> Self {
|
||||||
Transaction(tx.clone())
|
tx.inner
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<&Transaction> for BdkTransaction {
|
pub struct PartiallySignedTransaction {
|
||||||
fn from(tx: &Transaction) -> Self {
|
pub(crate) inner: Mutex<BdkPartiallySignedTransaction>,
|
||||||
tx.0.clone()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Psbt(pub(crate) Mutex<BdkPsbt>);
|
impl PartiallySignedTransaction {
|
||||||
|
pub(crate) fn new(psbt_base64: String) -> Result<Self, BdkError> {
|
||||||
|
let psbt: BdkPartiallySignedTransaction =
|
||||||
|
BdkPartiallySignedTransaction::from_str(&psbt_base64)
|
||||||
|
.map_err(|e| BdkError::Generic(e.to_string()))?;
|
||||||
|
|
||||||
impl Psbt {
|
Ok(PartiallySignedTransaction {
|
||||||
pub(crate) fn new(psbt_base64: String) -> Result<Self, PsbtParseError> {
|
inner: Mutex::new(psbt),
|
||||||
let psbt: BdkPsbt = BdkPsbt::from_str(&psbt_base64)?;
|
})
|
||||||
Ok(Psbt(Mutex::new(psbt)))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn serialize(&self) -> String {
|
pub(crate) fn serialize(&self) -> String {
|
||||||
let psbt = self.0.lock().unwrap().clone();
|
let psbt = self.inner.lock().unwrap().clone();
|
||||||
psbt.to_string()
|
psbt.to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn extract_tx(&self) -> Result<Arc<Transaction>, ExtractTxError> {
|
// pub(crate) fn txid(&self) -> String {
|
||||||
let tx: BdkTransaction = self.0.lock().unwrap().clone().extract_tx()?;
|
// let tx = self.inner.lock().unwrap().clone().extract_tx();
|
||||||
let transaction: Transaction = tx.into();
|
// let txid = tx.txid();
|
||||||
Ok(Arc::new(transaction))
|
// txid.to_hex()
|
||||||
|
// }
|
||||||
|
|
||||||
|
/// Return the transaction.
|
||||||
|
pub(crate) fn extract_tx(&self) -> Arc<Transaction> {
|
||||||
|
let tx = self.inner.lock().unwrap().clone().extract_tx();
|
||||||
|
Arc::new(tx.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn fee(&self) -> Result<u64, PsbtError> {
|
// /// Combines this PartiallySignedTransaction with other PSBT as described by BIP 174.
|
||||||
self.0
|
// ///
|
||||||
.lock()
|
// /// In accordance with BIP 174 this function is commutative i.e., `A.combine(B) == B.combine(A)`
|
||||||
.unwrap()
|
// pub(crate) fn combine(
|
||||||
.fee()
|
// &self,
|
||||||
.map(|fee| fee.to_sat())
|
// other: Arc<PartiallySignedTransaction>,
|
||||||
.map_err(PsbtError::from)
|
// ) -> Result<Arc<PartiallySignedTransaction>, BdkError> {
|
||||||
}
|
// let other_psbt = other.inner.lock().unwrap().clone();
|
||||||
|
// let mut original_psbt = self.inner.lock().unwrap().clone();
|
||||||
|
//
|
||||||
|
// original_psbt.combine(other_psbt)?;
|
||||||
|
// Ok(Arc::new(PartiallySignedTransaction {
|
||||||
|
// inner: Mutex::new(original_psbt),
|
||||||
|
// }))
|
||||||
|
// }
|
||||||
|
|
||||||
pub(crate) fn combine(&self, other: Arc<Psbt>) -> Result<Arc<Psbt>, PsbtError> {
|
// /// The total transaction fee amount, sum of input amounts minus sum of output amounts, in Sats.
|
||||||
let mut original_psbt = self.0.lock().unwrap().clone();
|
// /// If the PSBT is missing a TxOut for an input returns None.
|
||||||
let other_psbt = other.0.lock().unwrap().clone();
|
// pub(crate) fn fee_amount(&self) -> Option<u64> {
|
||||||
original_psbt.combine(other_psbt)?;
|
// self.inner.lock().unwrap().fee_amount()
|
||||||
Ok(Arc::new(Psbt(Mutex::new(original_psbt))))
|
// }
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn json_serialize(&self) -> String {
|
// /// The transaction's fee rate. This value will only be accurate if calculated AFTER the
|
||||||
let psbt = self.0.lock().unwrap();
|
// /// `PartiallySignedTransaction` is finalized and all witness/signature data is added to the
|
||||||
serde_json::to_string(psbt.deref()).unwrap()
|
// /// transaction.
|
||||||
}
|
// /// If the PSBT is missing a TxOut for an input returns None.
|
||||||
}
|
// pub(crate) fn fee_rate(&self) -> Option<Arc<FeeRate>> {
|
||||||
|
// self.inner.lock().unwrap().fee_rate().map(Arc::new)
|
||||||
impl From<BdkPsbt> for Psbt {
|
// }
|
||||||
fn from(psbt: BdkPsbt) -> Self {
|
|
||||||
Psbt(Mutex::new(psbt))
|
// /// Serialize the PSBT data structure as a String of JSON.
|
||||||
|
// pub(crate) fn json_serialize(&self) -> String {
|
||||||
|
// let psbt = self.inner.lock().unwrap();
|
||||||
|
// serde_json::to_string(psbt.deref()).unwrap()
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<BdkPartiallySignedTransaction> for PartiallySignedTransaction {
|
||||||
|
fn from(psbt: BdkPartiallySignedTransaction) -> Self {
|
||||||
|
PartiallySignedTransaction {
|
||||||
|
inner: Mutex::new(psbt),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A reference to a transaction output.
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
pub struct OutPoint {
|
pub struct OutPoint {
|
||||||
|
/// The referenced transaction's txid.
|
||||||
pub txid: String,
|
pub txid: String,
|
||||||
|
/// The index of the referenced output in its transaction's vout.
|
||||||
pub vout: u32,
|
pub vout: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -271,409 +309,3 @@ impl From<&OutPoint> for BdkOutPoint {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -1,21 +1,21 @@
|
|||||||
use crate::error::DescriptorError;
|
|
||||||
use crate::keys::DescriptorPublicKey;
|
use crate::keys::DescriptorPublicKey;
|
||||||
use crate::keys::DescriptorSecretKey;
|
use crate::keys::DescriptorSecretKey;
|
||||||
use std::fmt::Display;
|
use crate::Network;
|
||||||
|
|
||||||
use bdk_wallet::bitcoin::bip32::Fingerprint;
|
use bdk::bitcoin::bip32::Fingerprint;
|
||||||
use bdk_wallet::bitcoin::key::Secp256k1;
|
use bdk::bitcoin::key::Secp256k1;
|
||||||
use bdk_wallet::bitcoin::Network;
|
use bdk::descriptor::{ExtendedDescriptor, IntoWalletDescriptor};
|
||||||
use bdk_wallet::descriptor::{ExtendedDescriptor, IntoWalletDescriptor};
|
use bdk::keys::DescriptorPublicKey as BdkDescriptorPublicKey;
|
||||||
use bdk_wallet::keys::DescriptorPublicKey as BdkDescriptorPublicKey;
|
use bdk::keys::{DescriptorSecretKey as BdkDescriptorSecretKey, KeyMap};
|
||||||
use bdk_wallet::keys::{DescriptorSecretKey as BdkDescriptorSecretKey, KeyMap};
|
use bdk::template::{
|
||||||
use bdk_wallet::template::{
|
|
||||||
Bip44, Bip44Public, Bip49, Bip49Public, Bip84, Bip84Public, Bip86, Bip86Public,
|
Bip44, Bip44Public, Bip49, Bip49Public, Bip84, Bip84Public, Bip86, Bip86Public,
|
||||||
DescriptorTemplate,
|
DescriptorTemplate,
|
||||||
};
|
};
|
||||||
use bdk_wallet::KeychainKind;
|
use bdk::Error as BdkError;
|
||||||
|
use bdk::KeychainKind;
|
||||||
|
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Descriptor {
|
pub struct Descriptor {
|
||||||
@ -24,9 +24,10 @@ pub struct Descriptor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Descriptor {
|
impl Descriptor {
|
||||||
pub(crate) fn new(descriptor: String, network: Network) -> Result<Self, DescriptorError> {
|
pub(crate) fn new(descriptor: String, network: Network) -> Result<Self, BdkError> {
|
||||||
let secp = Secp256k1::new();
|
let secp = Secp256k1::new();
|
||||||
let (extended_descriptor, key_map) = descriptor.into_wallet_descriptor(&secp, network)?;
|
let (extended_descriptor, key_map) =
|
||||||
|
descriptor.into_wallet_descriptor(&secp, network.into())?;
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
extended_descriptor,
|
extended_descriptor,
|
||||||
key_map,
|
key_map,
|
||||||
@ -34,11 +35,11 @@ impl Descriptor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn new_bip44(
|
pub(crate) fn new_bip44(
|
||||||
secret_key: &DescriptorSecretKey,
|
secret_key: Arc<DescriptorSecretKey>,
|
||||||
keychain_kind: KeychainKind,
|
keychain_kind: KeychainKind,
|
||||||
network: Network,
|
network: Network,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let derivable_key = &secret_key.0;
|
let derivable_key = &secret_key.inner;
|
||||||
|
|
||||||
match derivable_key {
|
match derivable_key {
|
||||||
BdkDescriptorSecretKey::Single(_) => {
|
BdkDescriptorSecretKey::Single(_) => {
|
||||||
@ -46,8 +47,9 @@ impl Descriptor {
|
|||||||
}
|
}
|
||||||
BdkDescriptorSecretKey::XPrv(descriptor_x_key) => {
|
BdkDescriptorSecretKey::XPrv(descriptor_x_key) => {
|
||||||
let derivable_key = descriptor_x_key.xkey;
|
let derivable_key = descriptor_x_key.xkey;
|
||||||
let (extended_descriptor, key_map, _) =
|
let (extended_descriptor, key_map, _) = Bip44(derivable_key, keychain_kind)
|
||||||
Bip44(derivable_key, keychain_kind).build(network).unwrap();
|
.build(network.into())
|
||||||
|
.unwrap();
|
||||||
Self {
|
Self {
|
||||||
extended_descriptor,
|
extended_descriptor,
|
||||||
key_map,
|
key_map,
|
||||||
@ -60,13 +62,13 @@ impl Descriptor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn new_bip44_public(
|
pub(crate) fn new_bip44_public(
|
||||||
public_key: &DescriptorPublicKey,
|
public_key: Arc<DescriptorPublicKey>,
|
||||||
fingerprint: String,
|
fingerprint: String,
|
||||||
keychain_kind: KeychainKind,
|
keychain_kind: KeychainKind,
|
||||||
network: Network,
|
network: Network,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let fingerprint = Fingerprint::from_str(fingerprint.as_str()).unwrap();
|
let fingerprint = Fingerprint::from_str(fingerprint.as_str()).unwrap();
|
||||||
let derivable_key = &public_key.0;
|
let derivable_key = &public_key.inner;
|
||||||
|
|
||||||
match derivable_key {
|
match derivable_key {
|
||||||
BdkDescriptorPublicKey::Single(_) => {
|
BdkDescriptorPublicKey::Single(_) => {
|
||||||
@ -76,7 +78,7 @@ impl Descriptor {
|
|||||||
let derivable_key = descriptor_x_key.xkey;
|
let derivable_key = descriptor_x_key.xkey;
|
||||||
let (extended_descriptor, key_map, _) =
|
let (extended_descriptor, key_map, _) =
|
||||||
Bip44Public(derivable_key, fingerprint, keychain_kind)
|
Bip44Public(derivable_key, fingerprint, keychain_kind)
|
||||||
.build(network)
|
.build(network.into())
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
@ -91,11 +93,11 @@ impl Descriptor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn new_bip49(
|
pub(crate) fn new_bip49(
|
||||||
secret_key: &DescriptorSecretKey,
|
secret_key: Arc<DescriptorSecretKey>,
|
||||||
keychain_kind: KeychainKind,
|
keychain_kind: KeychainKind,
|
||||||
network: Network,
|
network: Network,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let derivable_key = &secret_key.0;
|
let derivable_key = &secret_key.inner;
|
||||||
|
|
||||||
match derivable_key {
|
match derivable_key {
|
||||||
BdkDescriptorSecretKey::Single(_) => {
|
BdkDescriptorSecretKey::Single(_) => {
|
||||||
@ -103,8 +105,9 @@ impl Descriptor {
|
|||||||
}
|
}
|
||||||
BdkDescriptorSecretKey::XPrv(descriptor_x_key) => {
|
BdkDescriptorSecretKey::XPrv(descriptor_x_key) => {
|
||||||
let derivable_key = descriptor_x_key.xkey;
|
let derivable_key = descriptor_x_key.xkey;
|
||||||
let (extended_descriptor, key_map, _) =
|
let (extended_descriptor, key_map, _) = Bip49(derivable_key, keychain_kind)
|
||||||
Bip49(derivable_key, keychain_kind).build(network).unwrap();
|
.build(network.into())
|
||||||
|
.unwrap();
|
||||||
Self {
|
Self {
|
||||||
extended_descriptor,
|
extended_descriptor,
|
||||||
key_map,
|
key_map,
|
||||||
@ -117,13 +120,13 @@ impl Descriptor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn new_bip49_public(
|
pub(crate) fn new_bip49_public(
|
||||||
public_key: &DescriptorPublicKey,
|
public_key: Arc<DescriptorPublicKey>,
|
||||||
fingerprint: String,
|
fingerprint: String,
|
||||||
keychain_kind: KeychainKind,
|
keychain_kind: KeychainKind,
|
||||||
network: Network,
|
network: Network,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let fingerprint = Fingerprint::from_str(fingerprint.as_str()).unwrap();
|
let fingerprint = Fingerprint::from_str(fingerprint.as_str()).unwrap();
|
||||||
let derivable_key = &public_key.0;
|
let derivable_key = &public_key.inner;
|
||||||
|
|
||||||
match derivable_key {
|
match derivable_key {
|
||||||
BdkDescriptorPublicKey::Single(_) => {
|
BdkDescriptorPublicKey::Single(_) => {
|
||||||
@ -133,7 +136,7 @@ impl Descriptor {
|
|||||||
let derivable_key = descriptor_x_key.xkey;
|
let derivable_key = descriptor_x_key.xkey;
|
||||||
let (extended_descriptor, key_map, _) =
|
let (extended_descriptor, key_map, _) =
|
||||||
Bip49Public(derivable_key, fingerprint, keychain_kind)
|
Bip49Public(derivable_key, fingerprint, keychain_kind)
|
||||||
.build(network)
|
.build(network.into())
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
@ -148,11 +151,11 @@ impl Descriptor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn new_bip84(
|
pub(crate) fn new_bip84(
|
||||||
secret_key: &DescriptorSecretKey,
|
secret_key: Arc<DescriptorSecretKey>,
|
||||||
keychain_kind: KeychainKind,
|
keychain_kind: KeychainKind,
|
||||||
network: Network,
|
network: Network,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let derivable_key = &secret_key.0;
|
let derivable_key = &secret_key.inner;
|
||||||
|
|
||||||
match derivable_key {
|
match derivable_key {
|
||||||
BdkDescriptorSecretKey::Single(_) => {
|
BdkDescriptorSecretKey::Single(_) => {
|
||||||
@ -160,8 +163,9 @@ impl Descriptor {
|
|||||||
}
|
}
|
||||||
BdkDescriptorSecretKey::XPrv(descriptor_x_key) => {
|
BdkDescriptorSecretKey::XPrv(descriptor_x_key) => {
|
||||||
let derivable_key = descriptor_x_key.xkey;
|
let derivable_key = descriptor_x_key.xkey;
|
||||||
let (extended_descriptor, key_map, _) =
|
let (extended_descriptor, key_map, _) = Bip84(derivable_key, keychain_kind)
|
||||||
Bip84(derivable_key, keychain_kind).build(network).unwrap();
|
.build(network.into())
|
||||||
|
.unwrap();
|
||||||
Self {
|
Self {
|
||||||
extended_descriptor,
|
extended_descriptor,
|
||||||
key_map,
|
key_map,
|
||||||
@ -174,13 +178,13 @@ impl Descriptor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn new_bip84_public(
|
pub(crate) fn new_bip84_public(
|
||||||
public_key: &DescriptorPublicKey,
|
public_key: Arc<DescriptorPublicKey>,
|
||||||
fingerprint: String,
|
fingerprint: String,
|
||||||
keychain_kind: KeychainKind,
|
keychain_kind: KeychainKind,
|
||||||
network: Network,
|
network: Network,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let fingerprint = Fingerprint::from_str(fingerprint.as_str()).unwrap();
|
let fingerprint = Fingerprint::from_str(fingerprint.as_str()).unwrap();
|
||||||
let derivable_key = &public_key.0;
|
let derivable_key = &public_key.inner;
|
||||||
|
|
||||||
match derivable_key {
|
match derivable_key {
|
||||||
BdkDescriptorPublicKey::Single(_) => {
|
BdkDescriptorPublicKey::Single(_) => {
|
||||||
@ -190,7 +194,7 @@ impl Descriptor {
|
|||||||
let derivable_key = descriptor_x_key.xkey;
|
let derivable_key = descriptor_x_key.xkey;
|
||||||
let (extended_descriptor, key_map, _) =
|
let (extended_descriptor, key_map, _) =
|
||||||
Bip84Public(derivable_key, fingerprint, keychain_kind)
|
Bip84Public(derivable_key, fingerprint, keychain_kind)
|
||||||
.build(network)
|
.build(network.into())
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
@ -205,11 +209,11 @@ impl Descriptor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn new_bip86(
|
pub(crate) fn new_bip86(
|
||||||
secret_key: &DescriptorSecretKey,
|
secret_key: Arc<DescriptorSecretKey>,
|
||||||
keychain_kind: KeychainKind,
|
keychain_kind: KeychainKind,
|
||||||
network: Network,
|
network: Network,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let derivable_key = &secret_key.0;
|
let derivable_key = &secret_key.inner;
|
||||||
|
|
||||||
match derivable_key {
|
match derivable_key {
|
||||||
BdkDescriptorSecretKey::Single(_) => {
|
BdkDescriptorSecretKey::Single(_) => {
|
||||||
@ -217,8 +221,9 @@ impl Descriptor {
|
|||||||
}
|
}
|
||||||
BdkDescriptorSecretKey::XPrv(descriptor_x_key) => {
|
BdkDescriptorSecretKey::XPrv(descriptor_x_key) => {
|
||||||
let derivable_key = descriptor_x_key.xkey;
|
let derivable_key = descriptor_x_key.xkey;
|
||||||
let (extended_descriptor, key_map, _) =
|
let (extended_descriptor, key_map, _) = Bip86(derivable_key, keychain_kind)
|
||||||
Bip86(derivable_key, keychain_kind).build(network).unwrap();
|
.build(network.into())
|
||||||
|
.unwrap();
|
||||||
Self {
|
Self {
|
||||||
extended_descriptor,
|
extended_descriptor,
|
||||||
key_map,
|
key_map,
|
||||||
@ -231,13 +236,13 @@ impl Descriptor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn new_bip86_public(
|
pub(crate) fn new_bip86_public(
|
||||||
public_key: &DescriptorPublicKey,
|
public_key: Arc<DescriptorPublicKey>,
|
||||||
fingerprint: String,
|
fingerprint: String,
|
||||||
keychain_kind: KeychainKind,
|
keychain_kind: KeychainKind,
|
||||||
network: Network,
|
network: Network,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let fingerprint = Fingerprint::from_str(fingerprint.as_str()).unwrap();
|
let fingerprint = Fingerprint::from_str(fingerprint.as_str()).unwrap();
|
||||||
let derivable_key = &public_key.0;
|
let derivable_key = &public_key.inner;
|
||||||
|
|
||||||
match derivable_key {
|
match derivable_key {
|
||||||
BdkDescriptorPublicKey::Single(_) => {
|
BdkDescriptorPublicKey::Single(_) => {
|
||||||
@ -247,7 +252,7 @@ impl Descriptor {
|
|||||||
let derivable_key = descriptor_x_key.xkey;
|
let derivable_key = descriptor_x_key.xkey;
|
||||||
let (extended_descriptor, key_map, _) =
|
let (extended_descriptor, key_map, _) =
|
||||||
Bip86Public(derivable_key, fingerprint, keychain_kind)
|
Bip86Public(derivable_key, fingerprint, keychain_kind)
|
||||||
.build(network)
|
.build(network.into())
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
@ -261,142 +266,154 @@ impl Descriptor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn to_string_with_secret(&self) -> String {
|
pub(crate) fn as_string_private(&self) -> String {
|
||||||
let descriptor = &self.extended_descriptor;
|
let descriptor = &self.extended_descriptor;
|
||||||
let key_map = &self.key_map;
|
let key_map = &self.key_map;
|
||||||
descriptor.to_string_with_secret(key_map)
|
descriptor.to_string_with_secret(key_map)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for Descriptor {
|
pub(crate) fn as_string(&self) -> String {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
self.extended_descriptor.to_string()
|
||||||
write!(f, "{}", self.extended_descriptor)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// // The goal of these tests to to ensure `bdk-ffi` intermediate code correctly calls `bdk` APIs.
|
||||||
|
// // These tests should not be used to verify `bdk` behavior that is already tested in the `bdk`
|
||||||
|
// // crate.
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use crate::*;
|
use crate::*;
|
||||||
use assert_matches::assert_matches;
|
use assert_matches::assert_matches;
|
||||||
use bdk_wallet::bitcoin::Network;
|
use bdk::descriptor::DescriptorError::Key;
|
||||||
use bdk_wallet::KeychainKind;
|
use bdk::keys::KeyError::InvalidNetwork;
|
||||||
|
|
||||||
fn get_descriptor_secret_key() -> DescriptorSecretKey {
|
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();
|
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)
|
DescriptorSecretKey::new(Network::Testnet, Arc::new(mnemonic), None)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_descriptor_templates() {
|
fn test_descriptor_templates() {
|
||||||
let master: DescriptorSecretKey = get_descriptor_secret_key();
|
let master: Arc<DescriptorSecretKey> = Arc::new(get_descriptor_secret_key());
|
||||||
println!("Master: {:?}", master.as_string());
|
println!("Master: {:?}", master.as_string());
|
||||||
// tprv8ZgxMBicQKsPdWuqM1t1CDRvQtQuBPyfL6GbhQwtxDKgUAVPbxmj71pRA8raTqLrec5LyTs5TqCxdABcZr77bt2KyWA5bizJHnC4g4ysm4h
|
// tprv8ZgxMBicQKsPdWuqM1t1CDRvQtQuBPyfL6GbhQwtxDKgUAVPbxmj71pRA8raTqLrec5LyTs5TqCxdABcZr77bt2KyWA5bizJHnC4g4ysm4h
|
||||||
let handmade_public_44 = master
|
let handmade_public_44 = master
|
||||||
.derive(&DerivationPath::new("m/44h/1h/0h".to_string()).unwrap())
|
.derive(Arc::new(
|
||||||
|
DerivationPath::new("m/44h/1h/0h".to_string()).unwrap(),
|
||||||
|
))
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.as_public();
|
.as_public();
|
||||||
println!("Public 44: {}", handmade_public_44.as_string());
|
println!("Public 44: {}", handmade_public_44.as_string());
|
||||||
// Public 44: [d1d04177/44'/1'/0']tpubDCoPjomfTqh1e7o1WgGpQtARWtkueXQAepTeNpWiitS3Sdv8RKJ1yvTrGHcwjDXp2SKyMrTEca4LoN7gEUiGCWboyWe2rz99Kf4jK4m2Zmx/*
|
// Public 44: [d1d04177/44'/1'/0']tpubDCoPjomfTqh1e7o1WgGpQtARWtkueXQAepTeNpWiitS3Sdv8RKJ1yvTrGHcwjDXp2SKyMrTEca4LoN7gEUiGCWboyWe2rz99Kf4jK4m2Zmx/*
|
||||||
let handmade_public_49 = master
|
let handmade_public_49 = master
|
||||||
.derive(&DerivationPath::new("m/49h/1h/0h".to_string()).unwrap())
|
.derive(Arc::new(
|
||||||
|
DerivationPath::new("m/49h/1h/0h".to_string()).unwrap(),
|
||||||
|
))
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.as_public();
|
.as_public();
|
||||||
println!("Public 49: {}", handmade_public_49.as_string());
|
println!("Public 49: {}", handmade_public_49.as_string());
|
||||||
// Public 49: [d1d04177/49'/1'/0']tpubDC65ZRvk1NDddHrVAUAZrUPJ772QXzooNYmPywYF9tMyNLYKf5wpKE7ZJvK9kvfG3FV7rCsHBNXy1LVKW95jrmC7c7z4hq7a27aD2sRrAhR/*
|
// Public 49: [d1d04177/49'/1'/0']tpubDC65ZRvk1NDddHrVAUAZrUPJ772QXzooNYmPywYF9tMyNLYKf5wpKE7ZJvK9kvfG3FV7rCsHBNXy1LVKW95jrmC7c7z4hq7a27aD2sRrAhR/*
|
||||||
let handmade_public_84 = master
|
let handmade_public_84 = master
|
||||||
.derive(&DerivationPath::new("m/84h/1h/0h".to_string()).unwrap())
|
.derive(Arc::new(
|
||||||
|
DerivationPath::new("m/84h/1h/0h".to_string()).unwrap(),
|
||||||
|
))
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.as_public();
|
.as_public();
|
||||||
println!("Public 84: {}", handmade_public_84.as_string());
|
println!("Public 84: {}", handmade_public_84.as_string());
|
||||||
// Public 84: [d1d04177/84'/1'/0']tpubDDNxbq17egjFk2edjv8oLnzxk52zny9aAYNv9CMqTzA4mQDiQq818sEkNe9Gzmd4QU8558zftqbfoVBDQorG3E4Wq26tB2JeE4KUoahLkx6/*
|
// Public 84: [d1d04177/84'/1'/0']tpubDDNxbq17egjFk2edjv8oLnzxk52zny9aAYNv9CMqTzA4mQDiQq818sEkNe9Gzmd4QU8558zftqbfoVBDQorG3E4Wq26tB2JeE4KUoahLkx6/*
|
||||||
let handmade_public_86 = master
|
let handmade_public_86 = master
|
||||||
.derive(&DerivationPath::new("m/86h/1h/0h".to_string()).unwrap())
|
.derive(Arc::new(
|
||||||
|
DerivationPath::new("m/86h/1h/0h".to_string()).unwrap(),
|
||||||
|
))
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.as_public();
|
.as_public();
|
||||||
println!("Public 86: {}", handmade_public_86.as_string());
|
println!("Public 86: {}", handmade_public_86.as_string());
|
||||||
// Public 86: [d1d04177/86'/1'/0']tpubDCJzjbcGbdEfXMWaL6QmgVmuSfXkrue7m2YNoacWwyc7a2XjXaKojRqNEbo41CFL3PyYmKdhwg2fkGpLX4SQCbQjCGxAkWHJTw9WEeenrJb/*
|
// Public 86: [d1d04177/86'/1'/0']tpubDCJzjbcGbdEfXMWaL6QmgVmuSfXkrue7m2YNoacWwyc7a2XjXaKojRqNEbo41CFL3PyYmKdhwg2fkGpLX4SQCbQjCGxAkWHJTw9WEeenrJb/*
|
||||||
let template_private_44 =
|
let template_private_44 =
|
||||||
Descriptor::new_bip44(&master, KeychainKind::External, Network::Testnet);
|
Descriptor::new_bip44(master.clone(), KeychainKind::External, Network::Testnet);
|
||||||
let template_private_49 =
|
let template_private_49 =
|
||||||
Descriptor::new_bip49(&master, KeychainKind::External, Network::Testnet);
|
Descriptor::new_bip49(master.clone(), KeychainKind::External, Network::Testnet);
|
||||||
let template_private_84 =
|
let template_private_84 =
|
||||||
Descriptor::new_bip84(&master, KeychainKind::External, Network::Testnet);
|
Descriptor::new_bip84(master.clone(), KeychainKind::External, Network::Testnet);
|
||||||
let template_private_86 =
|
let template_private_86 =
|
||||||
Descriptor::new_bip86(&master, KeychainKind::External, Network::Testnet);
|
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
|
// 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 49: {}", template_private_49.as_string());
|
||||||
println!("Template 44: {}", template_private_44);
|
println!("Template 44: {}", template_private_44.as_string());
|
||||||
println!("Template 84: {}", template_private_84);
|
println!("Template 84: {}", template_private_84.as_string());
|
||||||
println!("Template 86: {}", template_private_86);
|
println!("Template 86: {}", template_private_86.as_string());
|
||||||
let template_public_44 = Descriptor::new_bip44_public(
|
let template_public_44 = Descriptor::new_bip44_public(
|
||||||
&handmade_public_44,
|
handmade_public_44,
|
||||||
"d1d04177".to_string(),
|
"d1d04177".to_string(),
|
||||||
KeychainKind::External,
|
KeychainKind::External,
|
||||||
Network::Testnet,
|
Network::Testnet,
|
||||||
);
|
);
|
||||||
let template_public_49 = Descriptor::new_bip49_public(
|
let template_public_49 = Descriptor::new_bip49_public(
|
||||||
&handmade_public_49,
|
handmade_public_49,
|
||||||
"d1d04177".to_string(),
|
"d1d04177".to_string(),
|
||||||
KeychainKind::External,
|
KeychainKind::External,
|
||||||
Network::Testnet,
|
Network::Testnet,
|
||||||
);
|
);
|
||||||
let template_public_84 = Descriptor::new_bip84_public(
|
let template_public_84 = Descriptor::new_bip84_public(
|
||||||
&handmade_public_84,
|
handmade_public_84,
|
||||||
"d1d04177".to_string(),
|
"d1d04177".to_string(),
|
||||||
KeychainKind::External,
|
KeychainKind::External,
|
||||||
Network::Testnet,
|
Network::Testnet,
|
||||||
);
|
);
|
||||||
let template_public_86 = Descriptor::new_bip86_public(
|
let template_public_86 = Descriptor::new_bip86_public(
|
||||||
&handmade_public_86,
|
handmade_public_86,
|
||||||
"d1d04177".to_string(),
|
"d1d04177".to_string(),
|
||||||
KeychainKind::External,
|
KeychainKind::External,
|
||||||
Network::Testnet,
|
Network::Testnet,
|
||||||
);
|
);
|
||||||
println!("Template public 49: {}", template_public_49);
|
println!("Template public 49: {}", template_public_49.as_string());
|
||||||
println!("Template public 44: {}", template_public_44);
|
println!("Template public 44: {}", template_public_44.as_string());
|
||||||
println!("Template public 84: {}", template_public_84);
|
println!("Template public 84: {}", template_public_84.as_string());
|
||||||
println!("Template public 86: {}", template_public_86);
|
println!("Template public 86: {}", template_public_86.as_string());
|
||||||
// when using a public key, both to_string and as_string_private return the same string
|
// when using a public key, both as_string and as_string_private return the same string
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
template_public_44.to_string_with_secret(),
|
template_public_44.as_string_private(),
|
||||||
template_public_44.to_string()
|
template_public_44.as_string()
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
template_public_49.to_string_with_secret(),
|
template_public_49.as_string_private(),
|
||||||
template_public_49.to_string()
|
template_public_49.as_string()
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
template_public_84.to_string_with_secret(),
|
template_public_84.as_string_private(),
|
||||||
template_public_84.to_string()
|
template_public_84.as_string()
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
template_public_86.to_string_with_secret(),
|
template_public_86.as_string_private(),
|
||||||
template_public_86.to_string()
|
template_public_86.as_string()
|
||||||
);
|
);
|
||||||
// when using to_string on a private key, we get the same result as when using it on a public key
|
// when using as_string on a private key, we get the same result as when using it on a public key
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
template_private_44.to_string(),
|
template_private_44.as_string(),
|
||||||
template_public_44.to_string()
|
template_public_44.as_string()
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
template_private_49.to_string(),
|
template_private_49.as_string(),
|
||||||
template_public_49.to_string()
|
template_public_49.as_string()
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
template_private_84.to_string(),
|
template_private_84.as_string(),
|
||||||
template_public_84.to_string()
|
template_public_84.as_string()
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
template_private_86.to_string(),
|
template_private_86.as_string(),
|
||||||
template_public_86.to_string()
|
template_public_86.as_string()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
fn test_descriptor_from_string() {
|
fn test_descriptor_from_string() {
|
||||||
let descriptor1 = Descriptor::new("wpkh(tprv8hwWMmPE4BVNxGdVt3HhEERZhondQvodUY7Ajyseyhudr4WabJqWKWLr4Wi2r26CDaNCQhhxEftEaNzz7dPGhWuKFU4VULesmhEfZYyBXdE/0/*)".to_string(), Network::Testnet);
|
let descriptor1 = Descriptor::new("wpkh(tprv8hwWMmPE4BVNxGdVt3HhEERZhondQvodUY7Ajyseyhudr4WabJqWKWLr4Wi2r26CDaNCQhhxEftEaNzz7dPGhWuKFU4VULesmhEfZYyBXdE/0/*)".to_string(), Network::Testnet);
|
||||||
let descriptor2 = Descriptor::new("wpkh(tprv8hwWMmPE4BVNxGdVt3HhEERZhondQvodUY7Ajyseyhudr4WabJqWKWLr4Wi2r26CDaNCQhhxEftEaNzz7dPGhWuKFU4VULesmhEfZYyBXdE/0/*)".to_string(), Network::Bitcoin);
|
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
|
// Creating a Descriptor using an extended key that doesn't match the network provided will throw and InvalidNetwork Error
|
||||||
assert!(descriptor1.is_ok());
|
assert!(descriptor1.is_ok());
|
||||||
assert_matches!(descriptor2.unwrap_err(), DescriptorError::Key { .. });
|
assert_matches!(
|
||||||
|
descriptor2.unwrap_err(),
|
||||||
|
bdk::Error::Descriptor(Key(InvalidNetwork))
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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())
|
|
||||||
}
|
|
||||||
}
|
|
1960
bdk-ffi/src/error.rs
1960
bdk-ffi/src/error.rs
File diff suppressed because it is too large
Load Diff
@ -1,84 +1,68 @@
|
|||||||
use crate::bitcoin::Transaction;
|
use crate::wallet::{Update, Wallet};
|
||||||
use crate::error::EsploraError;
|
use std::ops::Deref;
|
||||||
use crate::types::{FullScanRequest, SyncRequest};
|
|
||||||
use crate::wallet::Update;
|
|
||||||
|
|
||||||
|
use bdk::wallet::Update as BdkUpdate;
|
||||||
|
use bdk::Error as BdkError;
|
||||||
use bdk_esplora::esplora_client::{BlockingClient, Builder};
|
use bdk_esplora::esplora_client::{BlockingClient, Builder};
|
||||||
use bdk_esplora::EsploraExt;
|
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;
|
use std::sync::Arc;
|
||||||
|
|
||||||
pub struct EsploraClient(BlockingClient);
|
pub struct EsploraClient(BlockingClient);
|
||||||
|
|
||||||
impl EsploraClient {
|
impl EsploraClient {
|
||||||
pub fn new(url: String) -> Self {
|
pub fn new(url: String) -> Self {
|
||||||
let client = Builder::new(url.as_str()).build_blocking();
|
let client = Builder::new(url.as_str()).build_blocking().unwrap();
|
||||||
Self(client)
|
Self(client)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn full_scan(
|
// This is a temporary solution for scanning. The long-term solution involves not passing
|
||||||
|
// the wallet to the client at all.
|
||||||
|
pub fn scan(
|
||||||
&self,
|
&self,
|
||||||
request: Arc<FullScanRequest>,
|
wallet: Arc<Wallet>,
|
||||||
stop_gap: u64,
|
stop_gap: u64,
|
||||||
parallel_requests: u64,
|
parallel_requests: u64,
|
||||||
) -> Result<Arc<Update>, EsploraError> {
|
) -> Result<Arc<Update>, BdkError> {
|
||||||
// using option and take is not ideal but the only way to take full ownership of the request
|
let wallet = wallet.get_wallet();
|
||||||
let request: BdkFullScanRequest<KeychainKind> = request
|
|
||||||
.0
|
|
||||||
.lock()
|
|
||||||
.unwrap()
|
|
||||||
.take()
|
|
||||||
.ok_or(EsploraError::RequestAlreadyConsumed)?;
|
|
||||||
|
|
||||||
let result: BdkFullScanResult<KeychainKind> =
|
let previous_tip = wallet.latest_checkpoint();
|
||||||
self.0
|
let keychain_spks = wallet.spks_of_all_keychains().into_iter().collect();
|
||||||
.full_scan(request, stop_gap as usize, parallel_requests as usize)?;
|
|
||||||
|
let (update_graph, last_active_indices) = self
|
||||||
|
.0
|
||||||
|
.scan_txs_with_keychains(
|
||||||
|
keychain_spks,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
stop_gap as usize,
|
||||||
|
parallel_requests as usize,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let missing_heights = update_graph.missing_heights(wallet.local_chain());
|
||||||
|
let chain_update = self
|
||||||
|
.0
|
||||||
|
.update_local_chain(previous_tip, missing_heights)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
let update = BdkUpdate {
|
let update = BdkUpdate {
|
||||||
last_active_indices: result.last_active_indices,
|
last_active_indices,
|
||||||
graph: result.graph_update,
|
graph: update_graph,
|
||||||
chain: Some(result.chain_update),
|
chain: Some(chain_update),
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(Arc::new(Update(update)))
|
Ok(Arc::new(Update(update)))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn sync(
|
// 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)?;
|
pub fn broadcast(&self, transaction: Arc<crate::bitcoin::Transaction>) -> Result<(), BdkError> {
|
||||||
|
let bdk_transaction: bdk::bitcoin::Transaction = transaction.deref().clone().into();
|
||||||
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
|
self.0
|
||||||
.broadcast(&bdk_transaction)
|
.broadcast(&bdk_transaction)
|
||||||
.map_err(EsploraError::from)
|
.map_err(|e| BdkError::Generic(e.to_string()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// pub fn estimate_fee();
|
||||||
}
|
}
|
||||||
|
@ -1,27 +1,31 @@
|
|||||||
use crate::error::{Bip32Error, Bip39Error, DescriptorKeyError};
|
use crate::Network;
|
||||||
use std::fmt::Display;
|
|
||||||
|
|
||||||
use bdk_wallet::bitcoin::bip32::DerivationPath as BdkDerivationPath;
|
use bdk::bitcoin::bip32::DerivationPath as BdkDerivationPath;
|
||||||
use bdk_wallet::bitcoin::key::Secp256k1;
|
use bdk::bitcoin::key::Secp256k1;
|
||||||
use bdk_wallet::bitcoin::secp256k1::rand;
|
use bdk::bitcoin::secp256k1::rand;
|
||||||
use bdk_wallet::bitcoin::secp256k1::rand::Rng;
|
use bdk::bitcoin::secp256k1::rand::Rng;
|
||||||
use bdk_wallet::bitcoin::Network;
|
use bdk::keys::bip39::WordCount;
|
||||||
use bdk_wallet::keys::bip39::WordCount;
|
use bdk::keys::bip39::{Language, Mnemonic as BdkMnemonic};
|
||||||
use bdk_wallet::keys::bip39::{Language, Mnemonic as BdkMnemonic};
|
use bdk::keys::{
|
||||||
use bdk_wallet::keys::{
|
|
||||||
DerivableKey, DescriptorPublicKey as BdkDescriptorPublicKey,
|
DerivableKey, DescriptorPublicKey as BdkDescriptorPublicKey,
|
||||||
DescriptorSecretKey as BdkDescriptorSecretKey, ExtendedKey, GeneratableKey, GeneratedKey,
|
DescriptorSecretKey as BdkDescriptorSecretKey, ExtendedKey, GeneratableKey, GeneratedKey,
|
||||||
};
|
};
|
||||||
use bdk_wallet::miniscript::descriptor::{DescriptorXKey, Wildcard};
|
use bdk::miniscript::descriptor::{DescriptorXKey, Wildcard};
|
||||||
use bdk_wallet::miniscript::BareCtx;
|
use bdk::miniscript::BareCtx;
|
||||||
|
use bdk::Error as BdkError;
|
||||||
|
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
pub(crate) struct Mnemonic(BdkMnemonic);
|
/// Mnemonic phrases are a human-readable version of the private keys.
|
||||||
|
/// Supported number of words are 12, 15, 18, 21 and 24.
|
||||||
|
pub(crate) struct Mnemonic {
|
||||||
|
inner: BdkMnemonic,
|
||||||
|
}
|
||||||
|
|
||||||
impl Mnemonic {
|
impl Mnemonic {
|
||||||
|
/// Generates Mnemonic with a random entropy
|
||||||
pub(crate) fn new(word_count: WordCount) -> Self {
|
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
|
// 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 rng = rand::thread_rng();
|
||||||
@ -31,25 +35,27 @@ impl Mnemonic {
|
|||||||
let generated_key: GeneratedKey<_, BareCtx> =
|
let generated_key: GeneratedKey<_, BareCtx> =
|
||||||
BdkMnemonic::generate_with_entropy((word_count, Language::English), entropy).unwrap();
|
BdkMnemonic::generate_with_entropy((word_count, Language::English), entropy).unwrap();
|
||||||
let mnemonic = BdkMnemonic::parse_in(Language::English, generated_key.to_string()).unwrap();
|
let mnemonic = BdkMnemonic::parse_in(Language::English, generated_key.to_string()).unwrap();
|
||||||
Mnemonic(mnemonic)
|
Mnemonic { inner: mnemonic }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn from_string(mnemonic: String) -> Result<Self, Bip39Error> {
|
/// Parse a Mnemonic with given string
|
||||||
|
pub(crate) fn from_string(mnemonic: String) -> Result<Self, BdkError> {
|
||||||
BdkMnemonic::from_str(&mnemonic)
|
BdkMnemonic::from_str(&mnemonic)
|
||||||
.map(Mnemonic)
|
.map(|m| Mnemonic { inner: m })
|
||||||
.map_err(Bip39Error::from)
|
.map_err(|e| BdkError::Generic(e.to_string()))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn from_entropy(entropy: Vec<u8>) -> Result<Self, Bip39Error> {
|
/// 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.
|
||||||
|
pub(crate) fn from_entropy(entropy: Vec<u8>) -> Result<Self, BdkError> {
|
||||||
BdkMnemonic::from_entropy(entropy.as_slice())
|
BdkMnemonic::from_entropy(entropy.as_slice())
|
||||||
.map(Mnemonic)
|
.map(|m| Mnemonic { inner: m })
|
||||||
.map_err(Bip39Error::from)
|
.map_err(|e| BdkError::Generic(e.to_string()))
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for Mnemonic {
|
/// Returns Mnemonic as string
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
pub(crate) fn as_string(&self) -> String {
|
||||||
write!(f, "{}", self.0)
|
self.inner.to_string()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -58,48 +64,53 @@ pub(crate) struct DerivationPath {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl DerivationPath {
|
impl DerivationPath {
|
||||||
pub(crate) fn new(path: String) -> Result<Self, Bip32Error> {
|
pub(crate) fn new(path: String) -> Result<Self, BdkError> {
|
||||||
BdkDerivationPath::from_str(&path)
|
BdkDerivationPath::from_str(&path)
|
||||||
.map(|x| DerivationPath {
|
.map(|x| DerivationPath {
|
||||||
inner_mutex: Mutex::new(x),
|
inner_mutex: Mutex::new(x),
|
||||||
})
|
})
|
||||||
.map_err(Bip32Error::from)
|
.map_err(|e| BdkError::Generic(e.to_string()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct DescriptorSecretKey(pub(crate) BdkDescriptorSecretKey);
|
pub struct DescriptorSecretKey {
|
||||||
|
pub(crate) inner: BdkDescriptorSecretKey,
|
||||||
|
}
|
||||||
|
|
||||||
impl DescriptorSecretKey {
|
impl DescriptorSecretKey {
|
||||||
pub(crate) fn new(network: Network, mnemonic: &Mnemonic, password: Option<String>) -> Self {
|
pub(crate) fn new(network: Network, mnemonic: Arc<Mnemonic>, password: Option<String>) -> Self {
|
||||||
let mnemonic = mnemonic.0.clone();
|
let mnemonic = mnemonic.inner.clone();
|
||||||
let xkey: ExtendedKey = (mnemonic, password).into_extended_key().unwrap();
|
let xkey: ExtendedKey = (mnemonic, password).into_extended_key().unwrap();
|
||||||
let descriptor_secret_key = BdkDescriptorSecretKey::XPrv(DescriptorXKey {
|
let descriptor_secret_key = BdkDescriptorSecretKey::XPrv(DescriptorXKey {
|
||||||
origin: None,
|
origin: None,
|
||||||
xkey: xkey.into_xprv(network).unwrap(),
|
xkey: xkey.into_xprv(network.into()).unwrap(),
|
||||||
derivation_path: BdkDerivationPath::master(),
|
derivation_path: BdkDerivationPath::master(),
|
||||||
wildcard: Wildcard::Unhardened,
|
wildcard: Wildcard::Unhardened,
|
||||||
});
|
});
|
||||||
Self(descriptor_secret_key)
|
Self {
|
||||||
|
inner: descriptor_secret_key,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn from_string(private_key: String) -> Result<Self, DescriptorKeyError> {
|
pub(crate) fn from_string(private_key: String) -> Result<Self, BdkError> {
|
||||||
let descriptor_secret_key = BdkDescriptorSecretKey::from_str(private_key.as_str())
|
let descriptor_secret_key = BdkDescriptorSecretKey::from_str(private_key.as_str())
|
||||||
.map_err(DescriptorKeyError::from)?;
|
.map_err(|e| BdkError::Generic(e.to_string()))?;
|
||||||
Ok(Self(descriptor_secret_key))
|
Ok(Self {
|
||||||
|
inner: descriptor_secret_key,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn derive(&self, path: &DerivationPath) -> Result<Arc<Self>, DescriptorKeyError> {
|
pub(crate) fn derive(&self, path: Arc<DerivationPath>) -> Result<Arc<Self>, BdkError> {
|
||||||
let secp = Secp256k1::new();
|
let secp = Secp256k1::new();
|
||||||
let descriptor_secret_key = &self.0;
|
let descriptor_secret_key = &self.inner;
|
||||||
let path = path.inner_mutex.lock().unwrap().deref().clone();
|
let path = path.inner_mutex.lock().unwrap().deref().clone();
|
||||||
match descriptor_secret_key {
|
match descriptor_secret_key {
|
||||||
BdkDescriptorSecretKey::Single(_) => Err(DescriptorKeyError::InvalidKeyType),
|
BdkDescriptorSecretKey::Single(_) => Err(BdkError::Generic(
|
||||||
|
"Cannot derive from a single key".to_string(),
|
||||||
|
)),
|
||||||
BdkDescriptorSecretKey::XPrv(descriptor_x_key) => {
|
BdkDescriptorSecretKey::XPrv(descriptor_x_key) => {
|
||||||
let derived_xprv = descriptor_x_key
|
let derived_xprv = descriptor_x_key.xkey.derive_priv(&secp, &path)?;
|
||||||
.xkey
|
|
||||||
.derive_priv(&secp, &path)
|
|
||||||
.map_err(DescriptorKeyError::from)?;
|
|
||||||
let key_source = match descriptor_x_key.origin.clone() {
|
let key_source = match descriptor_x_key.origin.clone() {
|
||||||
Some((fingerprint, origin_path)) => (fingerprint, origin_path.extend(path)),
|
Some((fingerprint, origin_path)) => (fingerprint, origin_path.extend(path)),
|
||||||
None => (descriptor_x_key.xkey.fingerprint(&secp), path),
|
None => (descriptor_x_key.xkey.fingerprint(&secp), path),
|
||||||
@ -110,17 +121,23 @@ impl DescriptorSecretKey {
|
|||||||
derivation_path: BdkDerivationPath::default(),
|
derivation_path: BdkDerivationPath::default(),
|
||||||
wildcard: descriptor_x_key.wildcard,
|
wildcard: descriptor_x_key.wildcard,
|
||||||
});
|
});
|
||||||
Ok(Arc::new(Self(derived_descriptor_secret_key)))
|
Ok(Arc::new(Self {
|
||||||
|
inner: derived_descriptor_secret_key,
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
BdkDescriptorSecretKey::MultiXPrv(_) => Err(DescriptorKeyError::InvalidKeyType),
|
BdkDescriptorSecretKey::MultiXPrv(_) => Err(BdkError::Generic(
|
||||||
|
"Cannot derive from a multi key".to_string(),
|
||||||
|
)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn extend(&self, path: &DerivationPath) -> Result<Arc<Self>, DescriptorKeyError> {
|
pub(crate) fn extend(&self, path: Arc<DerivationPath>) -> Result<Arc<Self>, BdkError> {
|
||||||
let descriptor_secret_key = &self.0;
|
let descriptor_secret_key = &self.inner;
|
||||||
let path = path.inner_mutex.lock().unwrap().deref().clone();
|
let path = path.inner_mutex.lock().unwrap().deref().clone();
|
||||||
match descriptor_secret_key {
|
match descriptor_secret_key {
|
||||||
BdkDescriptorSecretKey::Single(_) => Err(DescriptorKeyError::InvalidKeyType),
|
BdkDescriptorSecretKey::Single(_) => Err(BdkError::Generic(
|
||||||
|
"Cannot extend from a single key".to_string(),
|
||||||
|
)),
|
||||||
BdkDescriptorSecretKey::XPrv(descriptor_x_key) => {
|
BdkDescriptorSecretKey::XPrv(descriptor_x_key) => {
|
||||||
let extended_path = descriptor_x_key.derivation_path.extend(path);
|
let extended_path = descriptor_x_key.derivation_path.extend(path);
|
||||||
let extended_descriptor_secret_key = BdkDescriptorSecretKey::XPrv(DescriptorXKey {
|
let extended_descriptor_secret_key = BdkDescriptorSecretKey::XPrv(DescriptorXKey {
|
||||||
@ -129,20 +146,27 @@ impl DescriptorSecretKey {
|
|||||||
derivation_path: extended_path,
|
derivation_path: extended_path,
|
||||||
wildcard: descriptor_x_key.wildcard,
|
wildcard: descriptor_x_key.wildcard,
|
||||||
});
|
});
|
||||||
Ok(Arc::new(Self(extended_descriptor_secret_key)))
|
Ok(Arc::new(Self {
|
||||||
|
inner: extended_descriptor_secret_key,
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
BdkDescriptorSecretKey::MultiXPrv(_) => Err(DescriptorKeyError::InvalidKeyType),
|
BdkDescriptorSecretKey::MultiXPrv(_) => Err(BdkError::Generic(
|
||||||
|
"Cannot derive from a multi key".to_string(),
|
||||||
|
)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn as_public(&self) -> Arc<DescriptorPublicKey> {
|
pub(crate) fn as_public(&self) -> Arc<DescriptorPublicKey> {
|
||||||
let secp = Secp256k1::new();
|
let secp = Secp256k1::new();
|
||||||
let descriptor_public_key = self.0.to_public(&secp).unwrap();
|
let descriptor_public_key = self.inner.to_public(&secp).unwrap();
|
||||||
Arc::new(DescriptorPublicKey(descriptor_public_key))
|
Arc::new(DescriptorPublicKey {
|
||||||
|
inner: descriptor_public_key,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the private key as bytes.
|
||||||
pub(crate) fn secret_bytes(&self) -> Vec<u8> {
|
pub(crate) fn secret_bytes(&self) -> Vec<u8> {
|
||||||
let inner = &self.0;
|
let inner = &self.inner;
|
||||||
let secret_bytes: Vec<u8> = match inner {
|
let secret_bytes: Vec<u8> = match inner {
|
||||||
BdkDescriptorSecretKey::Single(_) => {
|
BdkDescriptorSecretKey::Single(_) => {
|
||||||
unreachable!()
|
unreachable!()
|
||||||
@ -159,32 +183,35 @@ impl DescriptorSecretKey {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn as_string(&self) -> String {
|
pub(crate) fn as_string(&self) -> String {
|
||||||
self.0.to_string()
|
self.inner.to_string()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct DescriptorPublicKey(pub(crate) BdkDescriptorPublicKey);
|
pub struct DescriptorPublicKey {
|
||||||
|
pub(crate) inner: BdkDescriptorPublicKey,
|
||||||
|
}
|
||||||
|
|
||||||
impl DescriptorPublicKey {
|
impl DescriptorPublicKey {
|
||||||
pub(crate) fn from_string(public_key: String) -> Result<Self, DescriptorKeyError> {
|
pub(crate) fn from_string(public_key: String) -> Result<Self, BdkError> {
|
||||||
let descriptor_public_key = BdkDescriptorPublicKey::from_str(public_key.as_str())
|
let descriptor_public_key = BdkDescriptorPublicKey::from_str(public_key.as_str())
|
||||||
.map_err(DescriptorKeyError::from)?;
|
.map_err(|e| BdkError::Generic(e.to_string()))?;
|
||||||
Ok(Self(descriptor_public_key))
|
Ok(Self {
|
||||||
|
inner: descriptor_public_key,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn derive(&self, path: &DerivationPath) -> Result<Arc<Self>, DescriptorKeyError> {
|
pub(crate) fn derive(&self, path: Arc<DerivationPath>) -> Result<Arc<Self>, BdkError> {
|
||||||
let secp = Secp256k1::new();
|
let secp = Secp256k1::new();
|
||||||
let descriptor_public_key = &self.0;
|
let descriptor_public_key = &self.inner;
|
||||||
let path = path.inner_mutex.lock().unwrap().deref().clone();
|
let path = path.inner_mutex.lock().unwrap().deref().clone();
|
||||||
|
|
||||||
match descriptor_public_key {
|
match descriptor_public_key {
|
||||||
BdkDescriptorPublicKey::Single(_) => Err(DescriptorKeyError::InvalidKeyType),
|
BdkDescriptorPublicKey::Single(_) => Err(BdkError::Generic(
|
||||||
|
"Cannot derive from a single key".to_string(),
|
||||||
|
)),
|
||||||
BdkDescriptorPublicKey::XPub(descriptor_x_key) => {
|
BdkDescriptorPublicKey::XPub(descriptor_x_key) => {
|
||||||
let derived_xpub = descriptor_x_key
|
let derived_xpub = descriptor_x_key.xkey.derive_pub(&secp, &path)?;
|
||||||
.xkey
|
|
||||||
.derive_pub(&secp, &path)
|
|
||||||
.map_err(DescriptorKeyError::from)?;
|
|
||||||
let key_source = match descriptor_x_key.origin.clone() {
|
let key_source = match descriptor_x_key.origin.clone() {
|
||||||
Some((fingerprint, origin_path)) => (fingerprint, origin_path.extend(path)),
|
Some((fingerprint, origin_path)) => (fingerprint, origin_path.extend(path)),
|
||||||
None => (descriptor_x_key.xkey.fingerprint(), path),
|
None => (descriptor_x_key.xkey.fingerprint(), path),
|
||||||
@ -195,17 +222,23 @@ impl DescriptorPublicKey {
|
|||||||
derivation_path: BdkDerivationPath::default(),
|
derivation_path: BdkDerivationPath::default(),
|
||||||
wildcard: descriptor_x_key.wildcard,
|
wildcard: descriptor_x_key.wildcard,
|
||||||
});
|
});
|
||||||
Ok(Arc::new(Self(derived_descriptor_public_key)))
|
Ok(Arc::new(Self {
|
||||||
|
inner: derived_descriptor_public_key,
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
BdkDescriptorPublicKey::MultiXPub(_) => Err(DescriptorKeyError::InvalidKeyType),
|
BdkDescriptorPublicKey::MultiXPub(_) => Err(BdkError::Generic(
|
||||||
|
"Cannot derive from a multi xpub".to_string(),
|
||||||
|
)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn extend(&self, path: &DerivationPath) -> Result<Arc<Self>, DescriptorKeyError> {
|
pub(crate) fn extend(&self, path: Arc<DerivationPath>) -> Result<Arc<Self>, BdkError> {
|
||||||
let descriptor_public_key = &self.0;
|
let descriptor_public_key = &self.inner;
|
||||||
let path = path.inner_mutex.lock().unwrap().deref().clone();
|
let path = path.inner_mutex.lock().unwrap().deref().clone();
|
||||||
match descriptor_public_key {
|
match descriptor_public_key {
|
||||||
BdkDescriptorPublicKey::Single(_) => Err(DescriptorKeyError::InvalidKeyType),
|
BdkDescriptorPublicKey::Single(_) => Err(BdkError::Generic(
|
||||||
|
"Cannot extend from a single key".to_string(),
|
||||||
|
)),
|
||||||
BdkDescriptorPublicKey::XPub(descriptor_x_key) => {
|
BdkDescriptorPublicKey::XPub(descriptor_x_key) => {
|
||||||
let extended_path = descriptor_x_key.derivation_path.extend(path);
|
let extended_path = descriptor_x_key.derivation_path.extend(path);
|
||||||
let extended_descriptor_public_key = BdkDescriptorPublicKey::XPub(DescriptorXKey {
|
let extended_descriptor_public_key = BdkDescriptorPublicKey::XPub(DescriptorXKey {
|
||||||
@ -214,59 +247,67 @@ impl DescriptorPublicKey {
|
|||||||
derivation_path: extended_path,
|
derivation_path: extended_path,
|
||||||
wildcard: descriptor_x_key.wildcard,
|
wildcard: descriptor_x_key.wildcard,
|
||||||
});
|
});
|
||||||
Ok(Arc::new(Self(extended_descriptor_public_key)))
|
Ok(Arc::new(Self {
|
||||||
|
inner: extended_descriptor_public_key,
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
BdkDescriptorPublicKey::MultiXPub(_) => Err(DescriptorKeyError::InvalidKeyType),
|
BdkDescriptorPublicKey::MultiXPub(_) => Err(BdkError::Generic(
|
||||||
|
"Cannot derive from a multi xpub".to_string(),
|
||||||
|
)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn as_string(&self) -> String {
|
pub(crate) fn as_string(&self) -> String {
|
||||||
self.0.to_string()
|
self.inner.to_string()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
//
|
||||||
|
// // The goal of these tests to to ensure `bdk-ffi` intermediate code correctly calls `bdk` APIs.
|
||||||
|
// // These tests should not be used to verify `bdk` behavior that is already tested in the `bdk`
|
||||||
|
// // crate.
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use crate::error::DescriptorKeyError;
|
|
||||||
use crate::keys::{DerivationPath, DescriptorPublicKey, DescriptorSecretKey, Mnemonic};
|
use crate::keys::{DerivationPath, DescriptorPublicKey, DescriptorSecretKey, Mnemonic};
|
||||||
use bdk_wallet::bitcoin::Network;
|
use crate::BdkError;
|
||||||
|
// use bdk::bitcoin::hashes::hex::ToHex;
|
||||||
|
use bdk::bitcoin::Network;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
fn get_inner() -> DescriptorSecretKey {
|
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();
|
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)
|
DescriptorSecretKey::new(Network::Testnet.into(), Arc::new(mnemonic), None)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn derive_dsk(
|
fn derive_dsk(
|
||||||
key: &DescriptorSecretKey,
|
key: &DescriptorSecretKey,
|
||||||
path: &str,
|
path: &str,
|
||||||
) -> Result<Arc<DescriptorSecretKey>, DescriptorKeyError> {
|
) -> Result<Arc<DescriptorSecretKey>, BdkError> {
|
||||||
let path = DerivationPath::new(path.to_string()).unwrap();
|
let path = Arc::new(DerivationPath::new(path.to_string()).unwrap());
|
||||||
key.derive(&path)
|
key.derive(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn extend_dsk(
|
fn extend_dsk(
|
||||||
key: &DescriptorSecretKey,
|
key: &DescriptorSecretKey,
|
||||||
path: &str,
|
path: &str,
|
||||||
) -> Result<Arc<DescriptorSecretKey>, DescriptorKeyError> {
|
) -> Result<Arc<DescriptorSecretKey>, BdkError> {
|
||||||
let path = DerivationPath::new(path.to_string()).unwrap();
|
let path = Arc::new(DerivationPath::new(path.to_string()).unwrap());
|
||||||
key.extend(&path)
|
key.extend(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn derive_dpk(
|
fn derive_dpk(
|
||||||
key: &DescriptorPublicKey,
|
key: &DescriptorPublicKey,
|
||||||
path: &str,
|
path: &str,
|
||||||
) -> Result<Arc<DescriptorPublicKey>, DescriptorKeyError> {
|
) -> Result<Arc<DescriptorPublicKey>, BdkError> {
|
||||||
let path = DerivationPath::new(path.to_string()).unwrap();
|
let path = Arc::new(DerivationPath::new(path.to_string()).unwrap());
|
||||||
key.derive(&path)
|
key.derive(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn extend_dpk(
|
fn extend_dpk(
|
||||||
key: &DescriptorPublicKey,
|
key: &DescriptorPublicKey,
|
||||||
path: &str,
|
path: &str,
|
||||||
) -> Result<Arc<DescriptorPublicKey>, DescriptorKeyError> {
|
) -> Result<Arc<DescriptorPublicKey>, BdkError> {
|
||||||
let path = DerivationPath::new(path.to_string()).unwrap();
|
let path = Arc::new(DerivationPath::new(path.to_string()).unwrap());
|
||||||
key.extend(&path)
|
key.extend(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -306,7 +347,7 @@ mod test {
|
|||||||
assert_eq!(extended_dpk.as_string(), "tpubD6NzVbkrYhZ4WywdEfYbbd62yuvqLjAZuPsNyvzCNV85JekAEMbKHWSHLF9h3j45SxewXDcLv328B1SEZrxg4iwGfmdt1pDFjZiTkGiFqGa/0/*");
|
assert_eq!(extended_dpk.as_string(), "tpubD6NzVbkrYhZ4WywdEfYbbd62yuvqLjAZuPsNyvzCNV85JekAEMbKHWSHLF9h3j45SxewXDcLv328B1SEZrxg4iwGfmdt1pDFjZiTkGiFqGa/0/*");
|
||||||
let wif = "L2wTu6hQrnDMiFNWA5na6jB12ErGQqtXwqpSL7aWquJaZG8Ai3ch";
|
let wif = "L2wTu6hQrnDMiFNWA5na6jB12ErGQqtXwqpSL7aWquJaZG8Ai3ch";
|
||||||
let extended_key = DescriptorSecretKey::from_string(wif.to_string()).unwrap();
|
let extended_key = DescriptorSecretKey::from_string(wif.to_string()).unwrap();
|
||||||
let result = extended_key.derive(&DerivationPath::new("m/0".to_string()).unwrap());
|
let result = extended_key.derive(Arc::new(DerivationPath::new("m/0".to_string()).unwrap()));
|
||||||
dbg!(&result);
|
dbg!(&result);
|
||||||
assert!(result.is_err());
|
assert!(result.is_err());
|
||||||
}
|
}
|
||||||
|
@ -1,73 +1,385 @@
|
|||||||
mod bitcoin;
|
mod bitcoin;
|
||||||
mod descriptor;
|
mod descriptor;
|
||||||
mod electrum;
|
|
||||||
mod error;
|
|
||||||
mod esplora;
|
mod esplora;
|
||||||
mod keys;
|
mod keys;
|
||||||
mod store;
|
|
||||||
mod types;
|
|
||||||
mod wallet;
|
mod wallet;
|
||||||
|
|
||||||
|
// TODO 6: Why are these imports required?
|
||||||
use crate::bitcoin::Address;
|
use crate::bitcoin::Address;
|
||||||
use crate::bitcoin::Amount;
|
use crate::bitcoin::Network;
|
||||||
use crate::bitcoin::FeeRate;
|
|
||||||
use crate::bitcoin::OutPoint;
|
use crate::bitcoin::OutPoint;
|
||||||
use crate::bitcoin::Psbt;
|
use crate::bitcoin::PartiallySignedTransaction;
|
||||||
use crate::bitcoin::Script;
|
use crate::bitcoin::Script;
|
||||||
use crate::bitcoin::Transaction;
|
use crate::bitcoin::Transaction;
|
||||||
use crate::bitcoin::TxIn;
|
|
||||||
use crate::bitcoin::TxOut;
|
|
||||||
use crate::descriptor::Descriptor;
|
use crate::descriptor::Descriptor;
|
||||||
use crate::electrum::ElectrumClient;
|
|
||||||
use crate::error::AddressParseError;
|
|
||||||
use crate::error::Bip32Error;
|
|
||||||
use crate::error::Bip39Error;
|
|
||||||
use crate::error::CalculateFeeError;
|
|
||||||
use crate::error::CannotConnectError;
|
|
||||||
use crate::error::CreateTxError;
|
|
||||||
use crate::error::DescriptorError;
|
|
||||||
use crate::error::DescriptorKeyError;
|
|
||||||
use crate::error::ElectrumError;
|
|
||||||
use crate::error::EsploraError;
|
|
||||||
use crate::error::ExtractTxError;
|
|
||||||
use crate::error::FeeRateError;
|
|
||||||
use crate::error::FromScriptError;
|
|
||||||
use crate::error::InspectError;
|
|
||||||
use crate::error::ParseAmountError;
|
|
||||||
use crate::error::PersistenceError;
|
|
||||||
use crate::error::PsbtError;
|
|
||||||
use crate::error::PsbtParseError;
|
|
||||||
use crate::error::SignerError;
|
|
||||||
use crate::error::SqliteError;
|
|
||||||
use crate::error::TransactionError;
|
|
||||||
use crate::error::TxidParseError;
|
|
||||||
use crate::error::WalletCreationError;
|
|
||||||
use crate::esplora::EsploraClient;
|
use crate::esplora::EsploraClient;
|
||||||
use crate::keys::DerivationPath;
|
use crate::keys::DerivationPath;
|
||||||
use crate::keys::DescriptorPublicKey;
|
use crate::keys::DescriptorPublicKey;
|
||||||
use crate::keys::DescriptorSecretKey;
|
use crate::keys::DescriptorSecretKey;
|
||||||
use crate::keys::Mnemonic;
|
use crate::keys::Mnemonic;
|
||||||
use crate::store::SqliteStore;
|
|
||||||
use crate::types::AddressInfo;
|
|
||||||
use crate::types::Balance;
|
|
||||||
use crate::types::CanonicalTx;
|
|
||||||
use crate::types::ChainPosition;
|
|
||||||
use crate::types::ChangeSet;
|
|
||||||
use crate::types::FullScanRequest;
|
|
||||||
use crate::types::FullScanScriptInspector;
|
|
||||||
use crate::types::LocalOutput;
|
|
||||||
use crate::types::ScriptAmount;
|
|
||||||
use crate::types::SyncRequest;
|
|
||||||
use crate::types::SyncScriptInspector;
|
|
||||||
use crate::wallet::BumpFeeTxBuilder;
|
|
||||||
use crate::wallet::SentAndReceivedValues;
|
|
||||||
use crate::wallet::TxBuilder;
|
use crate::wallet::TxBuilder;
|
||||||
use crate::wallet::Update;
|
use crate::wallet::Update;
|
||||||
use crate::wallet::Wallet;
|
use crate::wallet::Wallet;
|
||||||
|
|
||||||
use bdk_wallet::bitcoin::Network;
|
use bdk::keys::bip39::WordCount;
|
||||||
use bdk_wallet::keys::bip39::WordCount;
|
use bdk::wallet::tx_builder::ChangeSpendPolicy;
|
||||||
use bdk_wallet::wallet::tx_builder::ChangeSpendPolicy;
|
use bdk::wallet::AddressIndex as BdkAddressIndex;
|
||||||
use bdk_wallet::KeychainKind;
|
use bdk::wallet::AddressInfo as BdkAddressInfo;
|
||||||
|
use bdk::wallet::Balance as BdkBalance;
|
||||||
|
use bdk::Error as BdkError;
|
||||||
|
use bdk::KeychainKind;
|
||||||
|
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
uniffi::include_scaffolding!("bdk");
|
uniffi::include_scaffolding!("bdk");
|
||||||
|
|
||||||
|
/// A output script and an amount of satoshis.
|
||||||
|
pub struct ScriptAmount {
|
||||||
|
pub script: Arc<Script>,
|
||||||
|
pub amount: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A derived address and the index it was found at.
|
||||||
|
pub struct AddressInfo {
|
||||||
|
/// Child index of this address.
|
||||||
|
pub index: u32,
|
||||||
|
/// Address.
|
||||||
|
pub address: Arc<Address>,
|
||||||
|
/// Type of keychain.
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The address index selection strategy to use to derived an address from the wallet's external
|
||||||
|
/// descriptor.
|
||||||
|
pub enum 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.
|
||||||
|
LastUnused,
|
||||||
|
/// Return the address for a specific descriptor index. Does not change the current descriptor
|
||||||
|
/// index used by `AddressIndex::New` and `AddressIndex::LastUsed`.
|
||||||
|
/// Use with caution, if an index is given that is less than the current descriptor index
|
||||||
|
/// then the returned address may have already been used.
|
||||||
|
Peek { index: u32 },
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<AddressIndex> for BdkAddressIndex {
|
||||||
|
fn from(address_index: AddressIndex) -> Self {
|
||||||
|
match address_index {
|
||||||
|
AddressIndex::New => BdkAddressIndex::New,
|
||||||
|
AddressIndex::LastUnused => BdkAddressIndex::LastUnused,
|
||||||
|
AddressIndex::Peek { index } => BdkAddressIndex::Peek(index),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO 9: Peek is not correctly implemented
|
||||||
|
impl From<&AddressIndex> for BdkAddressIndex {
|
||||||
|
fn from(address_index: &AddressIndex) -> Self {
|
||||||
|
match address_index {
|
||||||
|
AddressIndex::New => BdkAddressIndex::New,
|
||||||
|
AddressIndex::LastUnused => BdkAddressIndex::LastUnused,
|
||||||
|
AddressIndex::Peek { index } => BdkAddressIndex::Peek(*index),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<BdkAddressIndex> for AddressIndex {
|
||||||
|
fn from(address_index: BdkAddressIndex) -> Self {
|
||||||
|
match address_index {
|
||||||
|
BdkAddressIndex::New => AddressIndex::New,
|
||||||
|
BdkAddressIndex::LastUnused => AddressIndex::LastUnused,
|
||||||
|
_ => panic!("Mmmm not working"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&BdkAddressIndex> for AddressIndex {
|
||||||
|
fn from(address_index: &BdkAddressIndex) -> Self {
|
||||||
|
match address_index {
|
||||||
|
BdkAddressIndex::New => AddressIndex::New,
|
||||||
|
BdkAddressIndex::LastUnused => AddressIndex::LastUnused,
|
||||||
|
_ => panic!("Mmmm not working"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// /// A wallet transaction
|
||||||
|
// #[derive(Debug, Clone, PartialEq, Eq, Default)]
|
||||||
|
// pub struct TransactionDetails {
|
||||||
|
// pub transaction: Option<Arc<Transaction>>,
|
||||||
|
// /// Transaction id.
|
||||||
|
// pub txid: String,
|
||||||
|
// /// Received value (sats)
|
||||||
|
// /// Sum of owned outputs of this transaction.
|
||||||
|
// pub received: u64,
|
||||||
|
// /// Sent value (sats)
|
||||||
|
// /// Sum of owned inputs of this transaction.
|
||||||
|
// pub sent: u64,
|
||||||
|
// /// Fee value (sats) if confirmed.
|
||||||
|
// /// The availability of the fee depends on the backend. It's never None with an Electrum
|
||||||
|
// /// Server backend, but it could be None with a Bitcoin RPC node without txindex that receive
|
||||||
|
// /// funds while offline.
|
||||||
|
// pub fee: Option<u64>,
|
||||||
|
// /// If the transaction is confirmed, contains height and timestamp of the block containing the
|
||||||
|
// /// transaction, unconfirmed transaction contains `None`.
|
||||||
|
// pub confirmation_time: Option<BlockTime>,
|
||||||
|
// }
|
||||||
|
|
||||||
|
//
|
||||||
|
// impl From<BdkTransactionDetails> for TransactionDetails {
|
||||||
|
// fn from(tx_details: BdkTransactionDetails) -> Self {
|
||||||
|
// let optional_tx: Option<Arc<Transaction>> =
|
||||||
|
// tx_details.transaction.map(|tx| Arc::new(tx.into()));
|
||||||
|
//
|
||||||
|
// TransactionDetails {
|
||||||
|
// transaction: optional_tx,
|
||||||
|
// fee: tx_details.fee,
|
||||||
|
// txid: tx_details.txid.to_string(),
|
||||||
|
// received: tx_details.received,
|
||||||
|
// sent: tx_details.sent,
|
||||||
|
// confirmation_time: tx_details.confirmation_time,
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// /// A reference to a transaction output.
|
||||||
|
// #[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
|
// pub struct OutPoint {
|
||||||
|
// /// The referenced transaction's txid.
|
||||||
|
// txid: String,
|
||||||
|
// /// The index of the referenced output in its transaction's vout.
|
||||||
|
// vout: u32,
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// impl From<&OutPoint> for BdkOutPoint {
|
||||||
|
// fn from(outpoint: &OutPoint) -> Self {
|
||||||
|
// BdkOutPoint {
|
||||||
|
// txid: Txid::from_str(&outpoint.txid).unwrap(),
|
||||||
|
// vout: outpoint.vout,
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
pub struct Balance {
|
||||||
|
pub inner: BdkBalance,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Balance {
|
||||||
|
/// All coinbase outputs not yet matured.
|
||||||
|
fn immature(&self) -> u64 {
|
||||||
|
self.inner.immature
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Unconfirmed UTXOs generated by a wallet tx.
|
||||||
|
fn trusted_pending(&self) -> u64 {
|
||||||
|
self.inner.trusted_pending
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Unconfirmed UTXOs received from an external wallet.
|
||||||
|
fn untrusted_pending(&self) -> u64 {
|
||||||
|
self.inner.untrusted_pending
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Confirmed and immediately spendable balance.
|
||||||
|
fn confirmed(&self) -> u64 {
|
||||||
|
self.inner.confirmed
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get sum of trusted_pending and confirmed coins.
|
||||||
|
fn trusted_spendable(&self) -> u64 {
|
||||||
|
self.inner.trusted_spendable()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the whole balance visible to the wallet.
|
||||||
|
fn total(&self) -> u64 {
|
||||||
|
self.inner.total()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// impl From<BdkBalance> for Balance {
|
||||||
|
// fn from(bdk_balance: BdkBalance) -> Self {
|
||||||
|
// Balance { inner: bdk_balance }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// /// A transaction output, which defines new coins to be created from old ones.
|
||||||
|
// #[derive(Debug, Clone)]
|
||||||
|
// pub struct TxOut {
|
||||||
|
// /// The value of the output, in satoshis.
|
||||||
|
// value: u64,
|
||||||
|
// /// The address of the output.
|
||||||
|
// script_pubkey: Arc<Script>,
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// impl From<&BdkTxOut> for TxOut {
|
||||||
|
// fn from(tx_out: &BdkTxOut) -> Self {
|
||||||
|
// TxOut {
|
||||||
|
// value: tx_out.value,
|
||||||
|
// script_pubkey: Arc::new(Script {
|
||||||
|
// inner: tx_out.script_pubkey.clone(),
|
||||||
|
// }),
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// pub struct LocalUtxo {
|
||||||
|
// outpoint: OutPoint,
|
||||||
|
// txout: TxOut,
|
||||||
|
// keychain: KeychainKind,
|
||||||
|
// is_spent: bool,
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// impl From<BdkLocalUtxo> for LocalUtxo {
|
||||||
|
// fn from(local_utxo: BdkLocalUtxo) -> Self {
|
||||||
|
// LocalUtxo {
|
||||||
|
// outpoint: OutPoint {
|
||||||
|
// txid: local_utxo.outpoint.txid.to_string(),
|
||||||
|
// vout: local_utxo.outpoint.vout,
|
||||||
|
// },
|
||||||
|
// txout: TxOut {
|
||||||
|
// value: local_utxo.txout.value,
|
||||||
|
// script_pubkey: Arc::new(Script {
|
||||||
|
// inner: local_utxo.txout.script_pubkey,
|
||||||
|
// }),
|
||||||
|
// },
|
||||||
|
// keychain: local_utxo.keychain,
|
||||||
|
// is_spent: local_utxo.is_spent,
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// /// Trait that logs at level INFO every update received (if any).
|
||||||
|
// pub trait Progress: Send + Sync + 'static {
|
||||||
|
// /// 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.
|
||||||
|
// fn update(&self, progress: f32, message: Option<String>);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// struct ProgressHolder {
|
||||||
|
// progress: Box<dyn Progress>,
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// impl BdkProgress for ProgressHolder {
|
||||||
|
// fn update(&self, progress: f32, message: Option<String>) -> Result<(), BdkError> {
|
||||||
|
// self.progress.update(progress, message);
|
||||||
|
// Ok(())
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// impl Debug for ProgressHolder {
|
||||||
|
// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
// f.debug_struct("ProgressHolder").finish_non_exhaustive()
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// #[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 {
|
||||||
|
// inner: tx_in.script_sig.clone(),
|
||||||
|
// }),
|
||||||
|
// sequence: tx_in.sequence.0,
|
||||||
|
// witness: tx_in.witness.to_vec(),
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// /// The method used to produce an address.
|
||||||
|
// #[derive(Debug)]
|
||||||
|
// pub enum Payload {
|
||||||
|
// /// P2PKH address.
|
||||||
|
// PubkeyHash { pubkey_hash: Vec<u8> },
|
||||||
|
// /// P2SH address.
|
||||||
|
// ScriptHash { script_hash: Vec<u8> },
|
||||||
|
// /// Segwit address.
|
||||||
|
// WitnessProgram {
|
||||||
|
// /// The witness program version.
|
||||||
|
// version: WitnessVersion,
|
||||||
|
// /// The witness program.
|
||||||
|
// program: Vec<u8>,
|
||||||
|
// },
|
||||||
|
// }
|
||||||
|
|
||||||
|
// impl From<BdkScript> for Script {
|
||||||
|
// fn from(bdk_script: BdkScript) -> Self {
|
||||||
|
// Script { inner: bdk_script }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// #[derive(Clone, Debug)]
|
||||||
|
// enum RbfValue {
|
||||||
|
// Default,
|
||||||
|
// Value(u32),
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// /// The result after calling the TxBuilder finish() function. Contains unsigned PSBT and
|
||||||
|
// /// transaction details.
|
||||||
|
// pub struct TxBuilderResult {
|
||||||
|
// pub(crate) psbt: Arc<PartiallySignedTransaction>,
|
||||||
|
// pub transaction_details: TransactionDetails,
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// uniffi::deps::static_assertions::assert_impl_all!(Wallet: Sync, Send);
|
||||||
|
//
|
||||||
|
// // The goal of these tests to to ensure `bdk-ffi` intermediate code correctly calls `bdk` APIs.
|
||||||
|
// // These tests should not be used to verify `bdk` behavior that is already tested in the `bdk`
|
||||||
|
// // crate.
|
||||||
|
// #[cfg(test)]
|
||||||
|
// mod test {
|
||||||
|
// use super::Transaction;
|
||||||
|
// use crate::Network::Regtest;
|
||||||
|
// use crate::{Address, Payload};
|
||||||
|
// use assert_matches::assert_matches;
|
||||||
|
// use bdk::bitcoin::hashes::hex::FromHex;
|
||||||
|
// use bdk::bitcoin::util::address::WitnessVersion;
|
||||||
|
//
|
||||||
|
// // Verify that bdk-ffi Transaction can be created from valid bytes and serialized back into the same bytes.
|
||||||
|
// #[test]
|
||||||
|
// fn test_transaction_serde() {
|
||||||
|
// let test_tx_bytes = Vec::from_hex("020000000001031cfbc8f54fbfa4a33a30068841371f80dbfe166211242213188428f437445c91000000006a47304402206fbcec8d2d2e740d824d3d36cc345b37d9f65d665a99f5bd5c9e8d42270a03a8022013959632492332200c2908459547bf8dbf97c65ab1a28dec377d6f1d41d3d63e012103d7279dfb90ce17fe139ba60a7c41ddf605b25e1c07a4ddcb9dfef4e7d6710f48feffffff476222484f5e35b3f0e43f65fc76e21d8be7818dd6a989c160b1e5039b7835fc00000000171600140914414d3c94af70ac7e25407b0689e0baa10c77feffffffa83d954a62568bbc99cc644c62eb7383d7c2a2563041a0aeb891a6a4055895570000000017160014795d04cc2d4f31480d9a3710993fbd80d04301dffeffffff06fef72f000000000017a91476fd7035cd26f1a32a5ab979e056713aac25796887a5000f00000000001976a914b8332d502a529571c6af4be66399cd33379071c588ac3fda0500000000001976a914fc1d692f8de10ae33295f090bea5fe49527d975c88ac522e1b00000000001976a914808406b54d1044c429ac54c0e189b0d8061667e088ac6eb68501000000001976a914dfab6085f3a8fb3e6710206a5a959313c5618f4d88acbba20000000000001976a914eb3026552d7e3f3073457d0bee5d4757de48160d88ac0002483045022100bee24b63212939d33d513e767bc79300051f7a0d433c3fcf1e0e3bf03b9eb1d70220588dc45a9ce3a939103b4459ce47500b64e23ab118dfc03c9caa7d6bfc32b9c601210354fd80328da0f9ae6eef2b3a81f74f9a6f66761fadf96f1d1d22b1fd6845876402483045022100e29c7e3a5efc10da6269e5fc20b6a1cb8beb92130cc52c67e46ef40aaa5cac5f0220644dd1b049727d991aece98a105563416e10a5ac4221abac7d16931842d5c322012103960b87412d6e169f30e12106bdf70122aabb9eb61f455518322a18b920a4dfa887d30700").unwrap();
|
||||||
|
// let new_tx_from_bytes = Transaction::new(test_tx_bytes.clone()).unwrap();
|
||||||
|
// let serialized_tx_to_bytes = new_tx_from_bytes.serialize();
|
||||||
|
// assert_eq!(test_tx_bytes, serialized_tx_to_bytes);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // Verify that bdk-ffi Address.payload includes expected WitnessProgram variant, version and program bytes.
|
||||||
|
// #[test]
|
||||||
|
// fn test_address_witness_program() {
|
||||||
|
// let address =
|
||||||
|
// Address::new("bcrt1qqjn9gky9mkrm3c28e5e87t5akd3twg6xezp0tv".to_string()).unwrap();
|
||||||
|
// let payload = address.payload();
|
||||||
|
// assert_matches!(payload, Payload::WitnessProgram { version, program } => {
|
||||||
|
// assert_eq!(version,WitnessVersion::V0);
|
||||||
|
// assert_eq!(program, Vec::from_hex("04a6545885dd87b8e147cd327f2e9db362b72346").unwrap());
|
||||||
|
// });
|
||||||
|
// assert_eq!(address.network(), Regtest);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
@ -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))
|
|
||||||
}
|
|
||||||
}
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,185 +1,297 @@
|
|||||||
use crate::bitcoin::Amount;
|
use crate::bitcoin::{OutPoint, PartiallySignedTransaction};
|
||||||
use crate::bitcoin::{FeeRate, OutPoint, Psbt, Script, Transaction};
|
|
||||||
use crate::descriptor::Descriptor;
|
use crate::descriptor::Descriptor;
|
||||||
use crate::error::{
|
use crate::{AddressIndex, AddressInfo, Network, ScriptAmount};
|
||||||
CalculateFeeError, CannotConnectError, CreateTxError, SignerError, TxidParseError,
|
use crate::{Balance, Script};
|
||||||
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::collections::HashSet;
|
||||||
use std::str::FromStr;
|
|
||||||
|
use bdk::bitcoin::blockdata::script::ScriptBuf as BdkScriptBuf;
|
||||||
|
use bdk::bitcoin::OutPoint as BdkOutPoint;
|
||||||
|
use bdk::wallet::Update as BdkUpdate;
|
||||||
|
use bdk::{Error as BdkError, FeeRate};
|
||||||
|
use bdk::{SignOptions, Wallet as BdkWallet};
|
||||||
|
|
||||||
|
use bdk::wallet::tx_builder::ChangeSpendPolicy;
|
||||||
use std::sync::{Arc, Mutex, MutexGuard};
|
use std::sync::{Arc, Mutex, MutexGuard};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct Wallet {
|
pub struct Wallet {
|
||||||
|
// TODO 8: Do we really need the mutex on the wallet? Could this be an Arc?
|
||||||
inner_mutex: Mutex<BdkWallet>,
|
inner_mutex: Mutex<BdkWallet>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Wallet {
|
impl Wallet {
|
||||||
pub fn new(
|
pub fn new_no_persist(
|
||||||
descriptor: Arc<Descriptor>,
|
descriptor: Arc<Descriptor>,
|
||||||
change_descriptor: Arc<Descriptor>,
|
change_descriptor: Option<Arc<Descriptor>>,
|
||||||
network: Network,
|
network: Network,
|
||||||
) -> Result<Self, WalletCreationError> {
|
) -> Result<Self, BdkError> {
|
||||||
let descriptor = descriptor.to_string_with_secret();
|
let descriptor = descriptor.as_string_private();
|
||||||
let change_descriptor = change_descriptor.to_string_with_secret();
|
let change_descriptor = change_descriptor.map(|d| d.as_string_private());
|
||||||
let wallet: BdkWallet = BdkWallet::new(&descriptor, &change_descriptor, network)?;
|
|
||||||
|
let wallet =
|
||||||
Ok(Wallet {
|
BdkWallet::new_no_persist(&descriptor, change_descriptor.as_ref(), network.into())?;
|
||||||
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 {
|
Ok(Wallet {
|
||||||
inner_mutex: Mutex::new(wallet),
|
inner_mutex: Mutex::new(wallet),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO 10: Do we need this mutex
|
||||||
pub(crate) fn get_wallet(&self) -> MutexGuard<BdkWallet> {
|
pub(crate) fn get_wallet(&self) -> MutexGuard<BdkWallet> {
|
||||||
self.inner_mutex.lock().expect("wallet")
|
self.inner_mutex.lock().expect("wallet")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn reveal_next_address(&self, keychain_kind: KeychainKind) -> AddressInfo {
|
pub fn get_address(&self, address_index: AddressIndex) -> AddressInfo {
|
||||||
self.get_wallet().reveal_next_address(keychain_kind).into()
|
self.get_wallet().get_address(address_index.into()).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 {
|
pub fn network(&self) -> Network {
|
||||||
self.get_wallet().network()
|
self.get_wallet().network().into()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn balance(&self) -> Balance {
|
pub fn get_internal_address(&self, address_index: AddressIndex) -> AddressInfo {
|
||||||
let bdk_balance = self.get_wallet().balance();
|
self.get_wallet()
|
||||||
Balance::from(bdk_balance)
|
.get_internal_address(address_index.into())
|
||||||
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_mine(&self, script: &Script) -> bool {
|
// TODO 16: Why is the Arc required here?
|
||||||
|
pub fn get_balance(&self) -> Arc<Balance> {
|
||||||
|
let bdk_balance = self.get_wallet().get_balance();
|
||||||
|
let balance = Balance { inner: bdk_balance };
|
||||||
|
Arc::new(balance)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn apply_update(&self, update: Arc<Update>) -> Result<(), BdkError> {
|
||||||
|
self.get_wallet()
|
||||||
|
.apply_update(update.0.clone())
|
||||||
|
.map_err(|e| BdkError::Generic(e.to_string()))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_mine(&self, script: Arc<Script>) -> bool {
|
||||||
|
// TODO: Both of the following lines work. Which is better?
|
||||||
self.get_wallet().is_mine(&script.0)
|
self.get_wallet().is_mine(&script.0)
|
||||||
|
// self.get_wallet().is_mine(script.0.clone().as_script())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sign a transaction with all the wallet's signers, in the order specified by every signer's
|
||||||
|
/// [`SignerOrdering`]. This function returns the `Result` type with an encapsulated `bool` that
|
||||||
|
/// has the value true if the PSBT was finalized, or false otherwise.
|
||||||
|
///
|
||||||
|
/// The [`SignOptions`] can be used to tweak the behavior of the software signers, and the way
|
||||||
|
/// the transaction is finalized at the end. Note that it can't be guaranteed that *every*
|
||||||
|
/// signers will follow the options, but the "software signers" (WIF keys and `xprv`) defined
|
||||||
|
/// in this library will.
|
||||||
pub(crate) fn sign(
|
pub(crate) fn sign(
|
||||||
&self,
|
&self,
|
||||||
psbt: Arc<Psbt>,
|
psbt: Arc<PartiallySignedTransaction>,
|
||||||
// sign_options: Option<SignOptions>,
|
// sign_options: Option<SignOptions>,
|
||||||
) -> Result<bool, SignerError> {
|
) -> Result<bool, BdkError> {
|
||||||
let mut psbt = psbt.0.lock().unwrap();
|
let mut psbt = psbt.inner.lock().unwrap();
|
||||||
self.get_wallet()
|
self.get_wallet()
|
||||||
.sign(&mut psbt, SignOptions::default())
|
.sign(&mut psbt, SignOptions::default())
|
||||||
.map_err(SignerError::from)
|
.map_err(|e| BdkError::Generic(e.to_string()))
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
pub struct Update(pub(crate) BdkUpdate);
|
||||||
|
|
||||||
|
// /// 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.
|
||||||
|
// impl Wallet {
|
||||||
|
// pub fn new(
|
||||||
|
// descriptor: Arc<Descriptor>,
|
||||||
|
// change_descriptor: Option<Arc<Descriptor>>,
|
||||||
|
// network: Network,
|
||||||
|
// ) -> Result<Self, BdkError> {
|
||||||
|
// let wallet = BdkWallet::new_no_persist()?;
|
||||||
|
// Ok(Wallet {
|
||||||
|
// inner: wallet,
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// /// Return whether or not a script is part of this wallet (either internal or external).
|
||||||
|
// pub(crate) fn is_mine(&self, script: Arc<Script>) -> bool {
|
||||||
|
// self.inner.is_mine(&script.inner)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// /// Sync the internal database with the blockchain.
|
||||||
|
// // pub(crate) fn sync(
|
||||||
|
// // &self,
|
||||||
|
// // blockchain: &Blockchain,
|
||||||
|
// // progress: Option<Box<dyn Progress>>,
|
||||||
|
// // ) -> Result<(), BdkError> {
|
||||||
|
// // let bdk_sync_opts = BdkSyncOptions {
|
||||||
|
// // progress: progress.map(|p| {
|
||||||
|
// // Box::new(ProgressHolder { progress: p })
|
||||||
|
// // as Box<(dyn bdk::blockchain::Progress + 'static)>
|
||||||
|
// // }),
|
||||||
|
// // };
|
||||||
|
// //
|
||||||
|
// // let blockchain = blockchain.get_blockchain();
|
||||||
|
// // self.get_wallet().sync(blockchain.deref(), bdk_sync_opts)
|
||||||
|
// // }
|
||||||
|
//
|
||||||
|
// /// 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.
|
||||||
|
// /// MIGRATION 1.0: The wallet needs to be mutated for this method to work... does that mean I should bring back the Mutex?
|
||||||
|
// /// Is this thread-safe?
|
||||||
|
// pub(crate) fn get_address(&mut self, address_index: AddressIndex) -> AddressInfo {
|
||||||
|
// AddressInfo::from(self.inner.get_address(address_index.into()))
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// /// Return a derived address using the internal (change) descriptor.
|
||||||
|
// ///
|
||||||
|
// /// If the wallet doesn't have an internal descriptor it will use the external descriptor.
|
||||||
|
// ///
|
||||||
|
// /// see [`AddressIndex`] for available address index selection strategies. If none of the keys
|
||||||
|
// /// in the descriptor are derivable (i.e. does not end with /*) then the same address will always
|
||||||
|
// /// be returned for any [`AddressIndex`].
|
||||||
|
// pub(crate) fn get_internal_address(&mut self, address_index: AddressIndex, ) -> AddressInfo {
|
||||||
|
// AddressInfo::from(self.inner.get_internal_address(address_index.into()))
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// /// Return the balance, meaning the sum of this wallet’s unspent outputs’ values. Note that this method only operates
|
||||||
|
// /// on the internal database, which first needs to be Wallet.sync manually.
|
||||||
|
// pub(crate) fn get_balance(&self) -> Balance {
|
||||||
|
// Balance::from(self.inner.get_balance())
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// /// Sign a transaction with all the wallet's signers, in the order specified by every signer's
|
||||||
|
// /// [`SignerOrdering`]. This function returns the `Result` type with an encapsulated `bool` that
|
||||||
|
// /// has the value true if the PSBT was finalized, or false otherwise.
|
||||||
|
// ///
|
||||||
|
// /// The [`SignOptions`] can be used to tweak the behavior of the software signers, and the way
|
||||||
|
// /// the transaction is finalized at the end. Note that it can't be guaranteed that *every*
|
||||||
|
// /// signers will follow the options, but the "software signers" (WIF keys and `xprv`) defined
|
||||||
|
// /// in this library will.
|
||||||
|
// pub(crate) fn sign(
|
||||||
|
// &self,
|
||||||
|
// psbt: &PartiallySignedTransaction,
|
||||||
|
// sign_options: Option<SignOptions>,
|
||||||
|
// ) -> Result<bool, BdkError> {
|
||||||
|
// let mut psbt = psbt.inner.lock().unwrap();
|
||||||
|
// self.inner.sign(
|
||||||
|
// &mut psbt,
|
||||||
|
// sign_options.map(SignOptions::into).unwrap_or_default(),
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// /// Return the list of transactions made and received by the wallet. Note that this method only operate on the internal database, which first needs to be [Wallet.sync] manually.
|
||||||
|
// pub(crate) fn list_transactions(
|
||||||
|
// &self,
|
||||||
|
// include_raw: bool,
|
||||||
|
// ) -> Result<Vec<TransactionDetails>, BdkError> {
|
||||||
|
// let transaction_details = self.inner.list_transactions(include_raw)?;
|
||||||
|
// Ok(transaction_details
|
||||||
|
// .into_iter()
|
||||||
|
// .map(TransactionDetails::from)
|
||||||
|
// .collect())
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// /// 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.
|
||||||
|
// pub(crate) fn list_unspent(&self) -> Result<Vec<LocalUtxo>, BdkError> {
|
||||||
|
// let unspents: Vec<BdkLocalUtxo> = self.inner.list_unspent()?;
|
||||||
|
// Ok(unspents.into_iter().map(LocalUtxo::from).collect())
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// /// Options for a software signer
|
||||||
|
// ///
|
||||||
|
// /// Adjust the behavior of our software signers and the way a transaction is finalized
|
||||||
|
// #[derive(Debug, Clone, Default)]
|
||||||
|
// pub struct SignOptions {
|
||||||
|
// /// Whether the signer should trust the `witness_utxo`, if the `non_witness_utxo` hasn't been
|
||||||
|
// /// provided
|
||||||
|
// ///
|
||||||
|
// /// Defaults to `false` to mitigate the "SegWit bug" which should trick the wallet into
|
||||||
|
// /// paying a fee larger than expected.
|
||||||
|
// ///
|
||||||
|
// /// Some wallets, especially if relatively old, might not provide the `non_witness_utxo` for
|
||||||
|
// /// SegWit transactions in the PSBT they generate: in those cases setting this to `true`
|
||||||
|
// /// should correctly produce a signature, at the expense of an increased trust in the creator
|
||||||
|
// /// of the PSBT.
|
||||||
|
// ///
|
||||||
|
// /// For more details see: <https://blog.trezor.io/details-of-firmware-updates-for-trezor-one-version-1-9-1-and-trezor-model-t-version-2-3-1-1eba8f60f2dd>
|
||||||
|
// pub trust_witness_utxo: bool,
|
||||||
|
//
|
||||||
|
// /// Whether the wallet should assume a specific height has been reached when trying to finalize
|
||||||
|
// /// a transaction
|
||||||
|
// ///
|
||||||
|
// /// The wallet will only "use" a timelock to satisfy the spending policy of an input if the
|
||||||
|
// /// timelock height has already been reached. This option allows overriding the "current height" to let the
|
||||||
|
// /// wallet use timelocks in the future to spend a coin.
|
||||||
|
// pub assume_height: Option<u32>,
|
||||||
|
//
|
||||||
|
// /// Whether the signer should use the `sighash_type` set in the PSBT when signing, no matter
|
||||||
|
// /// what its value is
|
||||||
|
// ///
|
||||||
|
// /// Defaults to `false` which will only allow signing using `SIGHASH_ALL`.
|
||||||
|
// pub allow_all_sighashes: bool,
|
||||||
|
//
|
||||||
|
// /// Whether to remove partial signatures from the PSBT inputs while finalizing PSBT.
|
||||||
|
// ///
|
||||||
|
// /// Defaults to `true` which will remove partial signatures during finalization.
|
||||||
|
// pub remove_partial_sigs: bool,
|
||||||
|
//
|
||||||
|
// /// Whether to try finalizing the PSBT after the inputs are signed.
|
||||||
|
// ///
|
||||||
|
// /// Defaults to `true` which will try finalizing PSBT after inputs are signed.
|
||||||
|
// pub try_finalize: bool,
|
||||||
|
//
|
||||||
|
// // Specifies which Taproot script-spend leaves we should sign for. This option is
|
||||||
|
// // ignored if we're signing a non-taproot PSBT.
|
||||||
|
// //
|
||||||
|
// // Defaults to All, i.e., the wallet will sign all the leaves it has a key for.
|
||||||
|
// // TODO pub tap_leaves_options: TapLeavesOptions,
|
||||||
|
// /// Whether we should try to sign a taproot transaction with the taproot internal key
|
||||||
|
// /// or not. This option is ignored if we're signing a non-taproot PSBT.
|
||||||
|
// ///
|
||||||
|
// /// Defaults to `true`, i.e., we always try to sign with the taproot internal key.
|
||||||
|
// pub sign_with_tap_internal_key: bool,
|
||||||
|
//
|
||||||
|
// /// Whether we should grind ECDSA signature to ensure signing with low r
|
||||||
|
// /// or not.
|
||||||
|
// /// Defaults to `true`, i.e., we always grind ECDSA signature to sign with low r.
|
||||||
|
// pub allow_grinding: bool,
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// impl From<SignOptions> for BdkSignOptions {
|
||||||
|
// fn from(sign_options: SignOptions) -> Self {
|
||||||
|
// BdkSignOptions {
|
||||||
|
// trust_witness_utxo: sign_options.trust_witness_utxo,
|
||||||
|
// assume_height: sign_options.assume_height,
|
||||||
|
// allow_all_sighashes: sign_options.allow_all_sighashes,
|
||||||
|
// remove_partial_sigs: sign_options.remove_partial_sigs,
|
||||||
|
// try_finalize: sign_options.try_finalize,
|
||||||
|
// tap_leaves_options: Default::default(),
|
||||||
|
// sign_with_tap_internal_key: sign_options.sign_with_tap_internal_key,
|
||||||
|
// allow_grinding: sign_options.allow_grinding,
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
/// A transaction builder.
|
||||||
|
/// After creating the TxBuilder, you set options on it until finally calling finish to consume the builder and generate the transaction.
|
||||||
|
/// Each method on the TxBuilder returns an instance of a new TxBuilder with the option set/added.
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct TxBuilder {
|
pub struct TxBuilder {
|
||||||
pub(crate) recipients: Vec<(BdkScriptBuf, BdkAmount)>,
|
pub(crate) recipients: Vec<(BdkScriptBuf, u64)>,
|
||||||
pub(crate) utxos: Vec<OutPoint>,
|
pub(crate) utxos: Vec<OutPoint>,
|
||||||
pub(crate) unspendable: HashSet<OutPoint>,
|
pub(crate) unspendable: HashSet<OutPoint>,
|
||||||
pub(crate) change_policy: ChangeSpendPolicy,
|
pub(crate) change_policy: ChangeSpendPolicy,
|
||||||
pub(crate) manually_selected_only: bool,
|
pub(crate) manually_selected_only: bool,
|
||||||
pub(crate) fee_rate: Option<FeeRate>,
|
pub(crate) fee_rate: Option<f32>,
|
||||||
pub(crate) fee_absolute: Option<Arc<Amount>>,
|
// pub(crate) fee_absolute: Option<u64>,
|
||||||
pub(crate) drain_wallet: bool,
|
pub(crate) drain_wallet: bool,
|
||||||
pub(crate) drain_to: Option<BdkScriptBuf>,
|
// pub(crate) drain_to: Option<BdkScript>,
|
||||||
pub(crate) rbf: Option<RbfValue>,
|
// pub(crate) rbf: Option<RbfValue>,
|
||||||
// pub(crate) data: Vec<u8>,
|
// pub(crate) data: Vec<u8>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -192,17 +304,18 @@ impl TxBuilder {
|
|||||||
change_policy: ChangeSpendPolicy::ChangeAllowed,
|
change_policy: ChangeSpendPolicy::ChangeAllowed,
|
||||||
manually_selected_only: false,
|
manually_selected_only: false,
|
||||||
fee_rate: None,
|
fee_rate: None,
|
||||||
fee_absolute: None,
|
// fee_absolute: None,
|
||||||
drain_wallet: false,
|
drain_wallet: false,
|
||||||
drain_to: None,
|
// drain_to: None,
|
||||||
rbf: None,
|
// rbf: None,
|
||||||
// data: Vec::new(),
|
// data: Vec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn add_recipient(&self, script: &Script, amount: Arc<Amount>) -> Arc<Self> {
|
/// Add a recipient to the internal list.
|
||||||
let mut recipients: Vec<(BdkScriptBuf, BdkAmount)> = self.recipients.clone();
|
pub(crate) fn add_recipient(&self, script: Arc<Script>, amount: u64) -> Arc<Self> {
|
||||||
recipients.append(&mut vec![(script.0.clone(), amount.0)]);
|
let mut recipients: Vec<(BdkScriptBuf, u64)> = self.recipients.clone();
|
||||||
|
recipients.append(&mut vec![(script.0.clone(), amount)]);
|
||||||
|
|
||||||
Arc::new(TxBuilder {
|
Arc::new(TxBuilder {
|
||||||
recipients,
|
recipients,
|
||||||
@ -213,7 +326,7 @@ impl TxBuilder {
|
|||||||
pub(crate) fn set_recipients(&self, recipients: Vec<ScriptAmount>) -> Arc<Self> {
|
pub(crate) fn set_recipients(&self, recipients: Vec<ScriptAmount>) -> Arc<Self> {
|
||||||
let recipients = recipients
|
let recipients = recipients
|
||||||
.iter()
|
.iter()
|
||||||
.map(|script_amount| (script_amount.script.0.clone(), script_amount.amount.0)) //;
|
.map(|script_amount| (script_amount.script.0.clone(), script_amount.amount))
|
||||||
.collect();
|
.collect();
|
||||||
Arc::new(TxBuilder {
|
Arc::new(TxBuilder {
|
||||||
recipients,
|
recipients,
|
||||||
@ -221,6 +334,8 @@ impl TxBuilder {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Add a utxo to the internal list of unspendable utxos. It’s 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.
|
||||||
pub(crate) fn add_unspendable(&self, unspendable: OutPoint) -> Arc<Self> {
|
pub(crate) fn add_unspendable(&self, unspendable: OutPoint) -> Arc<Self> {
|
||||||
let mut unspendable_hash_set = self.unspendable.clone();
|
let mut unspendable_hash_set = self.unspendable.clone();
|
||||||
unspendable_hash_set.insert(unspendable);
|
unspendable_hash_set.insert(unspendable);
|
||||||
@ -230,17 +345,15 @@ impl TxBuilder {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn unspendable(&self, unspendable: Vec<OutPoint>) -> Arc<Self> {
|
/// Add an outpoint to the internal list of UTXOs that must be spent. These have priority over the "unspendable"
|
||||||
Arc::new(TxBuilder {
|
/// utxos, meaning that if a utxo is present both in the "utxos" and the "unspendable" list, it will be spent.
|
||||||
unspendable: unspendable.into_iter().collect(),
|
|
||||||
..self.clone()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn add_utxo(&self, outpoint: OutPoint) -> Arc<Self> {
|
pub(crate) fn add_utxo(&self, outpoint: OutPoint) -> Arc<Self> {
|
||||||
self.add_utxos(vec![outpoint])
|
self.add_utxos(vec![outpoint])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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.
|
||||||
pub(crate) fn add_utxos(&self, mut outpoints: Vec<OutPoint>) -> Arc<Self> {
|
pub(crate) fn add_utxos(&self, mut outpoints: Vec<OutPoint>) -> Arc<Self> {
|
||||||
let mut utxos = self.utxos.to_vec();
|
let mut utxos = self.utxos.to_vec();
|
||||||
utxos.append(&mut outpoints);
|
utxos.append(&mut outpoints);
|
||||||
@ -257,6 +370,7 @@ impl TxBuilder {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Do not spend change outputs. This effectively adds all the change outputs to the "unspendable" list. See TxBuilder.unspendable.
|
||||||
pub(crate) fn do_not_spend_change(&self) -> Arc<Self> {
|
pub(crate) fn do_not_spend_change(&self) -> Arc<Self> {
|
||||||
Arc::new(TxBuilder {
|
Arc::new(TxBuilder {
|
||||||
change_policy: ChangeSpendPolicy::ChangeForbidden,
|
change_policy: ChangeSpendPolicy::ChangeForbidden,
|
||||||
@ -264,6 +378,7 @@ impl TxBuilder {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Only spend change outputs. This effectively adds all the non-change outputs to the "unspendable" list. See TxBuilder.unspendable.
|
||||||
pub(crate) fn only_spend_change(&self) -> Arc<Self> {
|
pub(crate) fn only_spend_change(&self) -> Arc<Self> {
|
||||||
Arc::new(TxBuilder {
|
Arc::new(TxBuilder {
|
||||||
change_policy: ChangeSpendPolicy::OnlyChange,
|
change_policy: ChangeSpendPolicy::OnlyChange,
|
||||||
@ -271,6 +386,8 @@ impl 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.
|
||||||
pub(crate) fn manually_selected_only(&self) -> Arc<Self> {
|
pub(crate) fn manually_selected_only(&self) -> Arc<Self> {
|
||||||
Arc::new(TxBuilder {
|
Arc::new(TxBuilder {
|
||||||
manually_selected_only: true,
|
manually_selected_only: true,
|
||||||
@ -278,20 +395,32 @@ impl TxBuilder {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn fee_rate(&self, fee_rate: &FeeRate) -> Arc<Self> {
|
// /// Replace the internal list of unspendable utxos with a new list. It’s 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.
|
||||||
|
// pub(crate) fn unspendable(&self, unspendable: Vec<OutPoint>) -> Arc<Self> {
|
||||||
|
// Arc::new(TxBuilder {
|
||||||
|
// unspendable: unspendable.into_iter().collect(),
|
||||||
|
// ..self.clone()
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
|
||||||
|
/// Set a custom fee rate.
|
||||||
|
pub(crate) fn fee_rate(&self, sat_per_vb: f32) -> Arc<Self> {
|
||||||
Arc::new(TxBuilder {
|
Arc::new(TxBuilder {
|
||||||
fee_rate: Some(fee_rate.clone()),
|
fee_rate: Some(sat_per_vb),
|
||||||
..self.clone()
|
..self.clone()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn fee_absolute(&self, fee_amount: Arc<Amount>) -> Arc<Self> {
|
// /// Set an absolute fee.
|
||||||
Arc::new(TxBuilder {
|
// pub(crate) fn fee_absolute(&self, fee_amount: u64) -> Arc<Self> {
|
||||||
fee_absolute: Some(fee_amount),
|
// Arc::new(TxBuilder {
|
||||||
..self.clone()
|
// fee_absolute: Some(fee_amount),
|
||||||
})
|
// ..self.clone()
|
||||||
}
|
// })
|
||||||
|
// }
|
||||||
|
|
||||||
|
/// Spend all the available inputs. This respects filters like TxBuilder.unspendable and the change policy.
|
||||||
pub(crate) fn drain_wallet(&self) -> Arc<Self> {
|
pub(crate) fn drain_wallet(&self) -> Arc<Self> {
|
||||||
Arc::new(TxBuilder {
|
Arc::new(TxBuilder {
|
||||||
drain_wallet: true,
|
drain_wallet: true,
|
||||||
@ -299,28 +428,52 @@ impl TxBuilder {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn drain_to(&self, script: &Script) -> Arc<Self> {
|
// /// Sets the address to drain excess coins to. Usually, when there are excess coins they are sent to a change address
|
||||||
Arc::new(TxBuilder {
|
// /// generated by the wallet. This option replaces the usual change address with an arbitrary ScriptPubKey of your choosing.
|
||||||
drain_to: Some(script.0.clone()),
|
// /// Just as with a change output, if the drain output is not needed (the excess coins are too small) it will not be included
|
||||||
..self.clone()
|
// /// in the resulting transaction. The only difference is that it is valid to use drain_to without setting any ordinary recipients
|
||||||
})
|
// /// with add_recipient (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 add_utxos, or set drain_wallet to spend all of them.
|
||||||
|
// /// When bumping the fees of a transaction made with this option, you probably want to use BumpFeeTxBuilder.allow_shrinking
|
||||||
|
// /// to allow this output to be reduced to pay for the extra fees.
|
||||||
|
// pub(crate) fn drain_to(&self, script: Arc<Script>) -> Arc<Self> {
|
||||||
|
// Arc::new(TxBuilder {
|
||||||
|
// drain_to: Some(script.inner.clone()),
|
||||||
|
// ..self.clone()
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// /// Enable signaling RBF. This will use the default `nsequence` value of `0xFFFFFFFD`.
|
||||||
|
// pub(crate) fn enable_rbf(&self) -> Arc<Self> {
|
||||||
|
// Arc::new(TxBuilder {
|
||||||
|
// rbf: Some(RbfValue::Default),
|
||||||
|
// ..self.clone()
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// /// 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.
|
||||||
|
// 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 enable_rbf(&self) -> Arc<Self> {
|
/// Add data as an output using OP_RETURN.
|
||||||
Arc::new(TxBuilder {
|
// pub(crate) fn add_data(&self, data: Vec<u8>) -> Arc<Self> {
|
||||||
rbf: Some(RbfValue::Default),
|
// Arc::new(TxBuilder {
|
||||||
..self.clone()
|
// data,
|
||||||
})
|
// ..self.clone()
|
||||||
}
|
// })
|
||||||
|
// }
|
||||||
|
|
||||||
pub(crate) fn enable_rbf_with_sequence(&self, nsequence: u32) -> Arc<Self> {
|
/// Finish building the transaction. Returns the BIP174 PSBT.
|
||||||
Arc::new(TxBuilder {
|
pub(crate) fn finish(
|
||||||
rbf: Some(RbfValue::Value(nsequence)),
|
&self,
|
||||||
..self.clone()
|
wallet: &Wallet,
|
||||||
})
|
) -> Result<Arc<PartiallySignedTransaction>, BdkError> {
|
||||||
}
|
|
||||||
|
|
||||||
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?
|
// 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 wallet = wallet.get_wallet();
|
||||||
let mut tx_builder = wallet.build_tx();
|
let mut tx_builder = wallet.build_tx();
|
||||||
@ -330,106 +483,137 @@ impl TxBuilder {
|
|||||||
tx_builder.change_policy(self.change_policy);
|
tx_builder.change_policy(self.change_policy);
|
||||||
if !self.utxos.is_empty() {
|
if !self.utxos.is_empty() {
|
||||||
let bdk_utxos: Vec<BdkOutPoint> = self.utxos.iter().map(BdkOutPoint::from).collect();
|
let bdk_utxos: Vec<BdkOutPoint> = self.utxos.iter().map(BdkOutPoint::from).collect();
|
||||||
tx_builder
|
let utxos: &[BdkOutPoint] = &bdk_utxos;
|
||||||
.add_utxos(&bdk_utxos)
|
tx_builder.add_utxos(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.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 {
|
if self.manually_selected_only {
|
||||||
tx_builder.manually_selected_only();
|
tx_builder.manually_selected_only();
|
||||||
}
|
}
|
||||||
if let Some(fee_rate) = &self.fee_rate {
|
if let Some(sat_per_vb) = self.fee_rate {
|
||||||
tx_builder.fee_rate(fee_rate.0);
|
tx_builder.fee_rate(FeeRate::from_sat_per_vb(sat_per_vb));
|
||||||
}
|
|
||||||
if let Some(fee_amount) = &self.fee_absolute {
|
|
||||||
tx_builder.fee_absolute(fee_amount.0);
|
|
||||||
}
|
}
|
||||||
|
// if let Some(fee_amount) = self.fee_absolute {
|
||||||
|
// tx_builder.fee_absolute(fee_amount);
|
||||||
|
// }
|
||||||
if self.drain_wallet {
|
if self.drain_wallet {
|
||||||
tx_builder.drain_wallet();
|
tx_builder.drain_wallet();
|
||||||
}
|
}
|
||||||
if let Some(script) = &self.drain_to {
|
// if let Some(script) = &self.drain_to {
|
||||||
tx_builder.drain_to(script.clone());
|
// tx_builder.drain_to(script.clone());
|
||||||
}
|
// }
|
||||||
if let Some(rbf) = &self.rbf {
|
// if let Some(rbf) = &self.rbf {
|
||||||
match *rbf {
|
// match *rbf {
|
||||||
RbfValue::Default => {
|
// RbfValue::Default => {
|
||||||
tx_builder.enable_rbf();
|
// tx_builder.enable_rbf();
|
||||||
}
|
// }
|
||||||
RbfValue::Value(nsequence) => {
|
// RbfValue::Value(nsequence) => {
|
||||||
tx_builder.enable_rbf_with_sequence(Sequence(nsequence));
|
// tx_builder.enable_rbf_with_sequence(Sequence(nsequence));
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
// if !&self.data.is_empty() {
|
||||||
|
// tx_builder.add_data(self.data.as_slice());
|
||||||
|
// }
|
||||||
|
|
||||||
let psbt = tx_builder.finish().map_err(CreateTxError::from)?;
|
// tx_builder.finish().map(|psbt| psbt.serialize_hex())
|
||||||
|
// tx_builder.finish().into()
|
||||||
|
let psbt = tx_builder.finish()?;
|
||||||
|
|
||||||
Ok(Arc::new(psbt.into()))
|
Ok(Arc::new(psbt.into()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
// /// The BumpFeeTxBuilder is used to bump the fee on a transaction that has been broadcast and has its RBF flag set to true.
|
||||||
pub(crate) struct BumpFeeTxBuilder {
|
// #[derive(Clone)]
|
||||||
pub(crate) txid: String,
|
// pub(crate) struct BumpFeeTxBuilder {
|
||||||
pub(crate) fee_rate: Arc<FeeRate>,
|
// pub(crate) txid: String,
|
||||||
pub(crate) rbf: Option<RbfValue>,
|
// pub(crate) fee_rate: f32,
|
||||||
}
|
// pub(crate) allow_shrinking: Option<String>,
|
||||||
|
// pub(crate) rbf: Option<RbfValue>,
|
||||||
impl BumpFeeTxBuilder {
|
// }
|
||||||
pub(crate) fn new(txid: String, fee_rate: Arc<FeeRate>) -> Self {
|
//
|
||||||
Self {
|
// impl BumpFeeTxBuilder {
|
||||||
txid,
|
// pub(crate) fn new(txid: String, fee_rate: f32) -> Self {
|
||||||
fee_rate,
|
// Self {
|
||||||
rbf: None,
|
// txid,
|
||||||
}
|
// fee_rate,
|
||||||
}
|
// allow_shrinking: None,
|
||||||
|
// rbf: None,
|
||||||
pub(crate) fn enable_rbf(&self) -> Arc<Self> {
|
// }
|
||||||
Arc::new(Self {
|
// }
|
||||||
rbf: Some(RbfValue::Default),
|
//
|
||||||
..self.clone()
|
// /// Explicitly tells the wallet that it is allowed to reduce the amount of the output matching this script_pubkey
|
||||||
})
|
// /// 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 script_pubkey
|
||||||
pub(crate) fn enable_rbf_with_sequence(&self, nsequence: u32) -> Arc<Self> {
|
// /// can’t be found among the recipients of the transaction we are bumping.
|
||||||
Arc::new(Self {
|
// pub(crate) fn allow_shrinking(&self, address: String) -> Arc<Self> {
|
||||||
rbf: Some(RbfValue::Value(nsequence)),
|
// Arc::new(Self {
|
||||||
..self.clone()
|
// allow_shrinking: Some(address),
|
||||||
})
|
// ..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 {
|
// /// Enable signaling RBF. This will use the default `nsequence` value of `0xFFFFFFFD`.
|
||||||
outpoint: self.txid.clone(),
|
// pub(crate) fn enable_rbf(&self) -> Arc<Self> {
|
||||||
})?;
|
// Arc::new(Self {
|
||||||
let mut wallet = wallet.get_wallet();
|
// rbf: Some(RbfValue::Default),
|
||||||
let mut tx_builder = wallet.build_fee_bump(txid).map_err(CreateTxError::from)?;
|
// ..self.clone()
|
||||||
tx_builder.fee_rate(self.fee_rate.0);
|
// })
|
||||||
if let Some(rbf) = &self.rbf {
|
// }
|
||||||
match *rbf {
|
//
|
||||||
RbfValue::Default => {
|
// /// Enable signaling RBF with a specific nSequence value. This can cause conflicts if the wallet's descriptors contain an
|
||||||
tx_builder.enable_rbf();
|
// /// "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.
|
||||||
RbfValue::Value(nsequence) => {
|
// pub(crate) fn enable_rbf_with_sequence(&self, nsequence: u32) -> Arc<Self> {
|
||||||
tx_builder.enable_rbf_with_sequence(Sequence(nsequence));
|
// Arc::new(Self {
|
||||||
}
|
// rbf: Some(RbfValue::Value(nsequence)),
|
||||||
}
|
// ..self.clone()
|
||||||
}
|
// })
|
||||||
let psbt: BdkPsbt = tx_builder.finish()?;
|
// }
|
||||||
|
//
|
||||||
Ok(Arc::new(psbt.into()))
|
// /// Finish building the transaction. Returns the BIP174 PSBT.
|
||||||
}
|
// pub(crate) fn finish(
|
||||||
}
|
// &self,
|
||||||
|
// wallet: &Wallet,
|
||||||
#[derive(Clone, Debug)]
|
// ) -> Result<Arc<PartiallySignedTransaction>, BdkError> {
|
||||||
pub enum RbfValue {
|
// let wallet = wallet.get_wallet();
|
||||||
Default,
|
// let txid = Txid::from_str(self.txid.as_str())?;
|
||||||
Value(u32),
|
// let mut tx_builder = wallet.build_fee_bump(txid)?;
|
||||||
}
|
// tx_builder.fee_rate(FeeRate::from_sat_per_vb(self.fee_rate));
|
||||||
|
// if let Some(allow_shrinking) = &self.allow_shrinking {
|
||||||
|
// let address = BdkAddress::from_str(allow_shrinking)
|
||||||
|
// .map_err(|e| BdkError::Generic(e.to_string()))?;
|
||||||
|
// let script = address.script_pubkey();
|
||||||
|
// tx_builder.allow_shrinking(script)?;
|
||||||
|
// }
|
||||||
|
// 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));
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// tx_builder
|
||||||
|
// .finish()
|
||||||
|
// .map(|(psbt, _)| PartiallySignedTransaction {
|
||||||
|
// inner: Mutex::new(psbt),
|
||||||
|
// })
|
||||||
|
// .map(Arc::new)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // The goal of these tests to to ensure `bdk-ffi` intermediate code correctly calls `bdk` APIs.
|
||||||
|
// // These tests should not be used to verify `bdk` behavior that is already tested in the `bdk`
|
||||||
|
// // crate.
|
||||||
// #[cfg(test)]
|
// #[cfg(test)]
|
||||||
// mod test {
|
// mod test {
|
||||||
// use crate::database::DatabaseConfig;
|
// use crate::database::DatabaseConfig;
|
||||||
|
@ -5,7 +5,7 @@ supported bindings languages.
|
|||||||
|
|
||||||
To skip integration tests and only run unit tests use `cargo test --lib`.
|
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:
|
Before running integration tests you must install the following development tools:
|
||||||
|
|
||||||
|
@ -4,6 +4,6 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import BitcoinDevKit
|
import bdk
|
||||||
|
|
||||||
let network = Network.testnet
|
let network = Network.testnet
|
||||||
|
Binary file not shown.
BIN
bdk-ffi/tests/jna/jna-5.8.0.jar
Normal file
BIN
bdk-ffi/tests/jna/jna-5.8.0.jar
Normal file
Binary file not shown.
@ -1,3 +0,0 @@
|
|||||||
[bindings.kotlin]
|
|
||||||
android = true
|
|
||||||
android_cleaner = true
|
|
@ -5,6 +5,8 @@ cdylib_name = "bdkffi"
|
|||||||
[bindings.python]
|
[bindings.python]
|
||||||
cdylib_name = "bdkffi"
|
cdylib_name = "bdkffi"
|
||||||
|
|
||||||
[bindings.swift]
|
[bindings.ruby]
|
||||||
module_name = "BitcoinDevKit"
|
cdylib_name = "bdkffi"
|
||||||
|
|
||||||
|
[bindings.swift]
|
||||||
cdylib_name = "bdkffi"
|
cdylib_name = "bdkffi"
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
# bdk-jvm
|
# 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.
|
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
|
## How to Use
|
||||||
To use the Kotlin language bindings for [`bdk`] in your JVM project add the following to your gradle dependencies:
|
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 like so. Note that this example is for the `0.30.0` release. For examples of the 1.0 API in the alpha releases, take a look at the tests [here](https://github.com/bitcoindevkit/bdk-ffi/tree/master/bdk-jvm/lib/src/test/kotlin/org/bitcoindevkit).
|
||||||
|
```kotlin
|
||||||
|
import org.bitcoindevkit.*
|
||||||
|
|
||||||
|
// ...
|
||||||
|
|
||||||
|
val externalDescriptor = Descriptor("wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)", Network.TESTNET)
|
||||||
|
val internalDescriptor = Descriptor("wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)", Network.TESTNET)
|
||||||
|
|
||||||
|
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.LastUnused)
|
||||||
|
```
|
||||||
|
|
||||||
### Snapshot releases
|
### 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:
|
To use a snapshot release, specify the snapshot repository url in the `repositories` block and use the snapshot version in the `dependencies` block:
|
||||||
```kotlin
|
```kotlin
|
||||||
@ -29,17 +47,17 @@ dependencies {
|
|||||||
* [Tatooine Faucet](https://github.com/thunderbiscuit/tatooine)
|
* [Tatooine Faucet](https://github.com/thunderbiscuit/tatooine)
|
||||||
|
|
||||||
## How to build
|
## 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. Install JDK 17. For example, with SDKMAN!:
|
1. Install JDK 11. It must be version 11 (not 17), otherwise it won't build. For example, with SDKMAN!:
|
||||||
```shell
|
```shell
|
||||||
curl -s "https://get.sdkman.io" | bash
|
curl -s "https://get.sdkman.io" | bash
|
||||||
source "$HOME/.sdkman/bin/sdkman-init.sh"
|
source "$HOME/.sdkman/bin/sdkman-init.sh"
|
||||||
sdk install java 17.0.2-tem
|
sdk install java 11.0.19-tem
|
||||||
```
|
```
|
||||||
2. Install Rust (note that we are currently building using Rust 1.77.1):
|
2. Install Rust (note that we are currently building using Rust 1.73.0):
|
||||||
```shell
|
```shell
|
||||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
|
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
|
||||||
rustup default 1.77.1
|
rustup default 1.73.0
|
||||||
```
|
```
|
||||||
3. Clone this repository.
|
3. Clone this repository.
|
||||||
```shell
|
```shell
|
||||||
@ -57,7 +75,7 @@ rustup target add x86_64-apple-darwin aarch64-apple-darwin
|
|||||||
## How to publish to your local Maven repo
|
## How to publish to your local Maven repo
|
||||||
```shell
|
```shell
|
||||||
cd bdk-jvm
|
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:
|
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,7 +84,7 @@ signing.gnupg.keyName=<YOUR_GNUPG_ID>
|
|||||||
signing.gnupg.passphrase=<YOUR_GNUPG_PASSPHRASE>
|
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
|
```shell
|
||||||
./gradlew publishToMavenLocal
|
./gradlew publishToMavenLocal
|
||||||
```
|
```
|
||||||
|
@ -1,9 +1,4 @@
|
|||||||
plugins {
|
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"
|
id("io.github.gradle-nexus.publish-plugin") version "1.1.0"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
org.gradle.jvmargs=-Xmx1536m
|
org.gradle.jvmargs=-Xmx1536m
|
||||||
android.enableJetifier=true
|
android.enableJetifier=true
|
||||||
kotlin.code.style=official
|
kotlin.code.style=official
|
||||||
libraryVersion=1.0.0-alpha.12-SNAPSHOT
|
libraryVersion=1.0.0-alpha.2a
|
||||||
|
BIN
bdk-jvm/gradle/wrapper/gradle-wrapper.jar
vendored
BIN
bdk-jvm/gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
@ -1,7 +1,5 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-all.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.2-bin.zip
|
||||||
networkTimeout=10000
|
|
||||||
validateDistributionUrl=true
|
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
31
bdk-jvm/gradlew
vendored
31
bdk-jvm/gradlew
vendored
@ -55,7 +55,7 @@
|
|||||||
# Darwin, MinGW, and NonStop.
|
# Darwin, MinGW, and NonStop.
|
||||||
#
|
#
|
||||||
# (3) This script is generated from the Groovy template
|
# (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.
|
# within the Gradle project.
|
||||||
#
|
#
|
||||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||||
@ -80,11 +80,13 @@ do
|
|||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
|
|
||||||
# This is normally unused
|
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
||||||
# shellcheck disable=SC2034
|
|
||||||
|
APP_NAME="Gradle"
|
||||||
APP_BASE_NAME=${0##*/}
|
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.
|
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||||
MAX_FD=maximum
|
MAX_FD=maximum
|
||||||
@ -131,29 +133,22 @@ location of your Java installation."
|
|||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
JAVACMD=java
|
JAVACMD=java
|
||||||
if ! command -v java >/dev/null 2>&1
|
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
then
|
|
||||||
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
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
location of your Java installation."
|
location of your Java installation."
|
||||||
fi
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Increase the maximum file descriptors if we can.
|
# Increase the maximum file descriptors if we can.
|
||||||
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||||
case $MAX_FD in #(
|
case $MAX_FD in #(
|
||||||
max*)
|
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 ) ||
|
MAX_FD=$( ulimit -H -n ) ||
|
||||||
warn "Could not query maximum file descriptor limit"
|
warn "Could not query maximum file descriptor limit"
|
||||||
esac
|
esac
|
||||||
case $MAX_FD in #(
|
case $MAX_FD in #(
|
||||||
'' | soft) :;; #(
|
'' | 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" ||
|
ulimit -n "$MAX_FD" ||
|
||||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||||
esac
|
esac
|
||||||
@ -198,10 +193,6 @@ if "$cygwin" || "$msys" ; then
|
|||||||
done
|
done
|
||||||
fi
|
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;
|
# Collect all arguments for the java command;
|
||||||
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
|
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
|
||||||
# shell script including quotes and variable substitutions, so put them in
|
# shell script including quotes and variable substitutions, so put them in
|
||||||
@ -214,12 +205,6 @@ set -- \
|
|||||||
org.gradle.wrapper.GradleWrapperMain \
|
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.
|
# Use "xargs" to parse quoted args.
|
||||||
#
|
#
|
||||||
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||||
|
15
bdk-jvm/gradlew.bat
vendored
15
bdk-jvm/gradlew.bat
vendored
@ -14,7 +14,7 @@
|
|||||||
@rem limitations under the License.
|
@rem limitations under the License.
|
||||||
@rem
|
@rem
|
||||||
|
|
||||||
@if "%DEBUG%"=="" @echo off
|
@if "%DEBUG%" == "" @echo off
|
||||||
@rem ##########################################################################
|
@rem ##########################################################################
|
||||||
@rem
|
@rem
|
||||||
@rem Gradle startup script for Windows
|
@rem Gradle startup script for Windows
|
||||||
@ -25,8 +25,7 @@
|
|||||||
if "%OS%"=="Windows_NT" setlocal
|
if "%OS%"=="Windows_NT" setlocal
|
||||||
|
|
||||||
set DIRNAME=%~dp0
|
set DIRNAME=%~dp0
|
||||||
if "%DIRNAME%"=="" set DIRNAME=.
|
if "%DIRNAME%" == "" set DIRNAME=.
|
||||||
@rem This is normally unused
|
|
||||||
set APP_BASE_NAME=%~n0
|
set APP_BASE_NAME=%~n0
|
||||||
set APP_HOME=%DIRNAME%
|
set APP_HOME=%DIRNAME%
|
||||||
|
|
||||||
@ -41,7 +40,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome
|
|||||||
|
|
||||||
set JAVA_EXE=java.exe
|
set JAVA_EXE=java.exe
|
||||||
%JAVA_EXE% -version >NUL 2>&1
|
%JAVA_EXE% -version >NUL 2>&1
|
||||||
if %ERRORLEVEL% equ 0 goto execute
|
if "%ERRORLEVEL%" == "0" goto execute
|
||||||
|
|
||||||
echo.
|
echo.
|
||||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
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
|
:end
|
||||||
@rem End local scope for the variables with windows NT shell
|
@rem End local scope for the variables with windows NT shell
|
||||||
if %ERRORLEVEL% equ 0 goto mainEnd
|
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||||
|
|
||||||
:fail
|
:fail
|
||||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||||
rem the _cmd.exe /c_ return code!
|
rem the _cmd.exe /c_ return code!
|
||||||
set EXIT_CODE=%ERRORLEVEL%
|
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||||
if %EXIT_CODE% equ 0 set EXIT_CODE=1
|
exit /b 1
|
||||||
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
|
|
||||||
exit /b %EXIT_CODE%
|
|
||||||
|
|
||||||
:mainEnd
|
:mainEnd
|
||||||
if "%OS%"=="Windows_NT" endlocal
|
if "%OS%"=="Windows_NT" endlocal
|
||||||
|
@ -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}}
|
|
@ -6,15 +6,19 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
|||||||
val libraryVersion: String by project
|
val libraryVersion: String by project
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id("org.jetbrains.kotlin.jvm")
|
id("org.jetbrains.kotlin.jvm") version "1.6.10"
|
||||||
id("org.gradle.java-library")
|
id("java-library")
|
||||||
id("org.gradle.maven-publish")
|
id("maven-publish")
|
||||||
id("org.gradle.signing")
|
id("signing")
|
||||||
|
|
||||||
// Custom plugin to generate the native libs and bindings file
|
// Custom plugin to generate the native libs and bindings file
|
||||||
id("org.bitcoindevkit.plugins.generate-jvm-bindings")
|
id("org.bitcoindevkit.plugins.generate-jvm-bindings")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
|
||||||
java {
|
java {
|
||||||
sourceCompatibility = JavaVersion.VERSION_11
|
sourceCompatibility = JavaVersion.VERSION_11
|
||||||
targetCompatibility = JavaVersion.VERSION_11
|
targetCompatibility = JavaVersion.VERSION_11
|
||||||
@ -24,23 +28,20 @@ java {
|
|||||||
|
|
||||||
// This block ensures that the tests that require access to a blockchain are not
|
// This block ensures that the tests that require access to a blockchain are not
|
||||||
// run if the -P excludeConnectedTests flag is passed to gradle.
|
// 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 ensures our CI runs are not fickle by not requiring access to testnet.
|
||||||
// This is a workaround until we have a proper regtest setup for the CI.
|
// 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
|
// Note that the command in the CI is ./gradlew test -P excludeConnectedTests
|
||||||
tasks.test {
|
tasks.test {
|
||||||
if (project.hasProperty("excludeConnectedTests")) {
|
if (project.hasProperty("excludeConnectedTests")) {
|
||||||
exclude("**/LiveElectrumClientTest.class")
|
|
||||||
exclude("**/LiveMemoryWalletTest.class")
|
|
||||||
exclude("**/LiveTransactionTest.class")
|
|
||||||
exclude("**/LiveTxBuilderTest.class")
|
|
||||||
exclude("**/LiveWalletTest.class")
|
exclude("**/LiveWalletTest.class")
|
||||||
|
exclude("**/LiveTxBuilderTest.class")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
testing {
|
testing {
|
||||||
suites {
|
suites {
|
||||||
val test by getting(JvmTestSuite::class) {
|
val test by getting(JvmTestSuite::class) {
|
||||||
useKotlinTest("1.9.23")
|
useKotlinTest("1.6.10")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -58,12 +59,10 @@ tasks.withType<Test> {
|
|||||||
dependencies {
|
dependencies {
|
||||||
implementation(platform("org.jetbrains.kotlin:kotlin-bom"))
|
implementation(platform("org.jetbrains.kotlin:kotlin-bom"))
|
||||||
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk7")
|
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")
|
api("org.slf4j:slf4j-api:1.7.30")
|
||||||
|
testImplementation("junit:junit:4.13.2")
|
||||||
// testImplementation("org.junit.jupiter:junit-jupiter-api:5.10.1")
|
testRuntimeOnly("org.junit.vintage:junit-vintage-engine:5.8.2")
|
||||||
// testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.10.1")
|
|
||||||
// testRuntimeOnly("org.junit.vintage:junit-vintage-engine:5.8.2")
|
|
||||||
testImplementation("ch.qos.logback:logback-classic:1.2.3")
|
testImplementation("ch.qos.logback:logback-classic:1.2.3")
|
||||||
testImplementation("ch.qos.logback:logback-core:1.2.3")
|
testImplementation("ch.qos.logback:logback-core:1.2.3")
|
||||||
}
|
}
|
||||||
@ -110,10 +109,6 @@ afterEvaluate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
signing {
|
signing {
|
||||||
if (project.hasProperty("localBuild")) {
|
|
||||||
isRequired = false
|
|
||||||
}
|
|
||||||
|
|
||||||
val signingKeyId: String? by project
|
val signingKeyId: String? by project
|
||||||
val signingKey: String? by project
|
val signingKey: String? by project
|
||||||
val signingPassword: String? by project
|
val signingPassword: String? by project
|
||||||
|
@ -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()}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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")
|
|
||||||
}
|
|
||||||
}
|
|
@ -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()}")
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,88 +1,29 @@
|
|||||||
package org.bitcoindevkit
|
package org.bitcoindevkit
|
||||||
|
|
||||||
import java.io.File
|
|
||||||
import kotlin.test.AfterTest
|
|
||||||
import kotlin.test.Test
|
import kotlin.test.Test
|
||||||
|
import kotlin.test.assertEquals
|
||||||
import kotlin.test.assertTrue
|
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 {
|
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
|
@Test
|
||||||
fun testTxBuilder() {
|
fun testTxBuilder() {
|
||||||
val descriptor = Descriptor("wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)", Network.TESTNET)
|
val descriptor = Descriptor("wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)", Network.TESTNET)
|
||||||
val wallet = Wallet(descriptor, changeDescriptor, Network.SIGNET)
|
val wallet = Wallet.newNoPersist(descriptor, null, Network.TESTNET)
|
||||||
val esploraClient = EsploraClient(SIGNET_ESPLORA_URL)
|
val esploraClient = EsploraClient("https://mempool.space/testnet/api")
|
||||||
val fullScanRequest: FullScanRequest = wallet.startFullScan()
|
val update = esploraClient.scan(wallet, 10uL, 1uL)
|
||||||
val update = esploraClient.fullScan(fullScanRequest, 10uL, 1uL)
|
|
||||||
wallet.applyUpdate(update)
|
wallet.applyUpdate(update)
|
||||||
println("Balance: ${wallet.balance().total.toSat()}")
|
println("Balance: ${wallet.getBalance().total()}")
|
||||||
|
|
||||||
assert(wallet.balance().total.toSat() > 0uL) {
|
assert(wallet.getBalance().total() > 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 recipient: Address = Address("tb1qrnfslnrve9uncz9pzpvf83k3ukz22ljgees989", Network.TESTNET)
|
||||||
val psbt: Psbt = TxBuilder()
|
val psbt: PartiallySignedTransaction = TxBuilder()
|
||||||
.addRecipient(recipient.scriptPubkey(), Amount.fromSat(4200uL))
|
.addRecipient(recipient.scriptPubkey(), 4200uL)
|
||||||
.feeRate(FeeRate.fromSatPerVb(2uL))
|
.feeRate(2.0f)
|
||||||
.finish(wallet)
|
.finish(wallet)
|
||||||
|
|
||||||
println(psbt.serialize())
|
println(psbt.serialize())
|
||||||
|
|
||||||
assertTrue(psbt.serialize().startsWith("cHNi"), "PSBT should start with 'cHNi'")
|
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'")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,77 +1,42 @@
|
|||||||
package org.bitcoindevkit
|
package org.bitcoindevkit
|
||||||
|
|
||||||
import java.io.File
|
|
||||||
import kotlin.test.AfterTest
|
|
||||||
import kotlin.test.Test
|
import kotlin.test.Test
|
||||||
import kotlin.test.assertTrue
|
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 {
|
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
|
@Test
|
||||||
fun testSyncedBalance() {
|
fun testSyncedBalance() {
|
||||||
val wallet: Wallet = Wallet(descriptor, changeDescriptor, Network.SIGNET)
|
val descriptor: Descriptor = Descriptor("wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)", Network.TESTNET)
|
||||||
val esploraClient: EsploraClient = EsploraClient(SIGNET_ESPLORA_URL)
|
val wallet: Wallet = Wallet.newNoPersist(descriptor, null, Network.TESTNET)
|
||||||
val fullScanRequest: FullScanRequest = wallet.startFullScan()
|
val esploraClient: EsploraClient = EsploraClient("https://mempool.space/testnet/api")
|
||||||
val update = esploraClient.fullScan(fullScanRequest, 10uL, 1uL)
|
// val esploraClient = EsploraClient("https://blockstream.info/testnet/api")
|
||||||
|
val update = esploraClient.scan(wallet, 10uL, 1uL)
|
||||||
wallet.applyUpdate(update)
|
wallet.applyUpdate(update)
|
||||||
println("Balance: ${wallet.balance().total.toSat()}")
|
println("Balance: ${wallet.getBalance().total()}")
|
||||||
|
|
||||||
assert(wallet.balance().total.toSat() > 0uL) {
|
assert(wallet.getBalance().total() > 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
|
@Test
|
||||||
fun testBroadcastTransaction() {
|
fun testBroadcastTransaction() {
|
||||||
val descriptor = Descriptor("wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)", Network.SIGNET)
|
val descriptor = Descriptor("wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)", Network.TESTNET)
|
||||||
val wallet: Wallet = Wallet(descriptor, changeDescriptor, Network.SIGNET)
|
val wallet = Wallet.newNoPersist(descriptor, null, Network.TESTNET)
|
||||||
val esploraClient = EsploraClient(SIGNET_ESPLORA_URL)
|
val esploraClient = EsploraClient("https://mempool.space/testnet/api")
|
||||||
val fullScanRequest: FullScanRequest = wallet.startFullScan()
|
val update = esploraClient.scan(wallet, 10uL, 1uL)
|
||||||
val update = esploraClient.fullScan(fullScanRequest, 10uL, 1uL)
|
|
||||||
wallet.applyUpdate(update)
|
|
||||||
println("Balance: ${wallet.balance().total.toSat()}")
|
|
||||||
|
|
||||||
assert(wallet.balance().total.toSat() > 0uL) {
|
wallet.applyUpdate(update)
|
||||||
"Wallet balance must be greater than 0! Please send funds to ${wallet.revealNextAddress(KeychainKind.EXTERNAL).address} and try again."
|
println("Balance: ${wallet.getBalance().total()}")
|
||||||
|
println("New address: ${wallet.getAddress(AddressIndex.New).address.asString()}")
|
||||||
|
|
||||||
|
assert(wallet.getBalance().total() > 0uL) {
|
||||||
|
"Wallet balance must be greater than 0! Please send funds to ${wallet.getAddress(AddressIndex.New).address} and try again."
|
||||||
}
|
}
|
||||||
|
|
||||||
val recipient: Address = Address("tb1qrnfslnrve9uncz9pzpvf83k3ukz22ljgees989", Network.SIGNET)
|
val recipient: Address = Address("tb1qrnfslnrve9uncz9pzpvf83k3ukz22ljgees989", Network.TESTNET)
|
||||||
|
|
||||||
val psbt: Psbt = TxBuilder()
|
val psbt: PartiallySignedTransaction = TxBuilder()
|
||||||
.addRecipient(recipient.scriptPubkey(), Amount.fromSat(4200uL))
|
.addRecipient(recipient.scriptPubkey(), 4200uL)
|
||||||
.feeRate(FeeRate.fromSatPerVb(2uL))
|
.feeRate(2.0f)
|
||||||
.finish(wallet)
|
.finish(wallet)
|
||||||
|
|
||||||
println(psbt.serialize())
|
println(psbt.serialize())
|
||||||
@ -81,14 +46,8 @@ class LiveWalletTest {
|
|||||||
assertTrue(walletDidSign)
|
assertTrue(walletDidSign)
|
||||||
|
|
||||||
val tx: Transaction = psbt.extractTx()
|
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")
|
|
||||||
|
|
||||||
|
println("Txid is: ${tx.txid()}")
|
||||||
esploraClient.broadcast(tx)
|
esploraClient.broadcast(tx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,7 @@ class OfflineDescriptorTest {
|
|||||||
|
|
||||||
assertEquals(
|
assertEquals(
|
||||||
expected = "tr([be1eec8f/86'/1'/0']tpubDCTtszwSxPx3tATqDrsSyqScPNnUChwQAVAkanuDUCJQESGBbkt68nXXKRDifYSDbeMa2Xg2euKbXaU3YphvGWftDE7ozRKPriT6vAo3xsc/0/*)#m7puekcx",
|
expected = "tr([be1eec8f/86'/1'/0']tpubDCTtszwSxPx3tATqDrsSyqScPNnUChwQAVAkanuDUCJQESGBbkt68nXXKRDifYSDbeMa2Xg2euKbXaU3YphvGWftDE7ozRKPriT6vAo3xsc/0/*)#m7puekcx",
|
||||||
actual = descriptor.toString()
|
actual = descriptor.asString()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,74 +1,53 @@
|
|||||||
package org.bitcoindevkit
|
package org.bitcoindevkit
|
||||||
|
|
||||||
import java.io.File
|
|
||||||
import kotlin.test.AfterTest
|
|
||||||
import kotlin.test.Test
|
import kotlin.test.Test
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
import kotlin.test.assertTrue
|
import kotlin.test.assertTrue
|
||||||
import kotlin.test.assertFalse
|
|
||||||
|
|
||||||
class OfflineWalletTest {
|
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
|
@Test
|
||||||
fun testDescriptorBip86() {
|
fun testDescriptorBip86() {
|
||||||
val mnemonic: Mnemonic = Mnemonic(WordCount.WORDS12)
|
val mnemonic: Mnemonic = Mnemonic(WordCount.WORDS12)
|
||||||
val descriptorSecretKey: DescriptorSecretKey = DescriptorSecretKey(Network.TESTNET, mnemonic, null)
|
val descriptorSecretKey: DescriptorSecretKey = DescriptorSecretKey(Network.TESTNET, mnemonic, null)
|
||||||
val descriptor: Descriptor = Descriptor.newBip86(descriptorSecretKey, KeychainKind.EXTERNAL, Network.TESTNET)
|
val descriptor: Descriptor = Descriptor.newBip86(descriptorSecretKey, KeychainKind.EXTERNAL, Network.TESTNET)
|
||||||
|
|
||||||
assertTrue(descriptor.toString().startsWith("tr"), "Bip86 Descriptor does not start with 'tr'")
|
assertTrue(descriptor.asString().startsWith("tr"), "Bip86 Descriptor does not start with 'tr'")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testNewAddress() {
|
fun testNewAddress() {
|
||||||
val wallet: Wallet = Wallet(
|
val descriptor: Descriptor = Descriptor(
|
||||||
descriptor,
|
"wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)",
|
||||||
changeDescriptor,
|
|
||||||
Network.TESTNET
|
Network.TESTNET
|
||||||
)
|
)
|
||||||
val addressInfo: AddressInfo = wallet.revealNextAddress(KeychainKind.EXTERNAL)
|
val wallet: Wallet = Wallet.newNoPersist(
|
||||||
|
descriptor,
|
||||||
assertTrue(addressInfo.address.isValidForNetwork(Network.TESTNET), "Address is not valid for testnet network")
|
null,
|
||||||
assertTrue(addressInfo.address.isValidForNetwork(Network.SIGNET), "Address is not valid for signet network")
|
Network.TESTNET
|
||||||
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")
|
val addressInfo: AddressInfo = wallet.getAddress(AddressIndex.New)
|
||||||
|
|
||||||
assertEquals(
|
assertEquals(
|
||||||
expected = "tb1qrnfslnrve9uncz9pzpvf83k3ukz22ljgees989",
|
expected = "tb1qzg4mckdh50nwdm9hkzq06528rsu73hjxxzem3e",
|
||||||
actual = addressInfo.address.toString()
|
actual = addressInfo.address.asString()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testBalance() {
|
fun testBalance() {
|
||||||
val wallet: Wallet = Wallet(
|
val descriptor: Descriptor = Descriptor(
|
||||||
|
"wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)",
|
||||||
|
Network.TESTNET
|
||||||
|
)
|
||||||
|
val wallet: Wallet = Wallet.newNoPersist(
|
||||||
descriptor,
|
descriptor,
|
||||||
changeDescriptor,
|
null,
|
||||||
Network.TESTNET
|
Network.TESTNET
|
||||||
)
|
)
|
||||||
|
|
||||||
assertEquals(
|
assertEquals(
|
||||||
expected = 0uL,
|
expected = 0uL,
|
||||||
actual = wallet.balance().total.toSat()
|
actual = wallet.getBalance().total()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Binary file not shown.
@ -112,20 +112,20 @@ internal class UniFfiJvmPlugin : Plugin<Project> {
|
|||||||
|
|
||||||
// TODO 2: Is the Windows name the correct one?
|
// TODO 2: Is the Windows name the correct one?
|
||||||
// TODO 3: This will not work on mac Intel (x86_64 architecture)
|
// TODO 3: This will not work on mac Intel (x86_64 architecture)
|
||||||
val libraryPath = when (operatingSystem) {
|
// val libraryPath = when (operatingSystem) {
|
||||||
OS.LINUX -> "./target/x86_64-unknown-linux-gnu/release-smaller/libbdkffi.so"
|
// OS.LINUX -> "./target/x86_64-unknown-linux-gnu/release-smaller/libbdkffi.so"
|
||||||
OS.MAC -> "./target/aarch64-apple-darwin/release-smaller/libbdkffi.dylib"
|
// OS.MAC -> "./target/aarch64-apple-darwin/release-smaller/libbdkffi.dylib"
|
||||||
OS.WINDOWS -> "./target/x86_64-pc-windows-msvc/release-smaller/bdkffi.dll"
|
// OS.WINDOWS -> "./target/x86_64-pc-windows-msvc/release-smaller/bdkffi.dll"
|
||||||
else -> throw Exception("Unsupported OS")
|
// else -> throw Exception("Unsupported OS")
|
||||||
}
|
// }
|
||||||
|
|
||||||
workingDir("${project.projectDir}/../../bdk-ffi/")
|
// 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")
|
// 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 above was for the migration to uniffi 0.24.3 using the --library flag
|
||||||
// The code below works with uniffi 0.23.0
|
// The code below works with uniffi 0.23.0
|
||||||
// workingDir("${project.projectDir}/../../bdk-ffi/")
|
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")
|
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")
|
||||||
executable("cargo")
|
executable("cargo")
|
||||||
args(cargoArgs)
|
args(cargoArgs)
|
||||||
|
|
||||||
|
@ -2,15 +2,3 @@ rootProject.name = "bdk-jvm"
|
|||||||
|
|
||||||
include(":lib")
|
include(":lib")
|
||||||
includeBuild("plugins")
|
includeBuild("plugins")
|
||||||
|
|
||||||
pluginManagement {
|
|
||||||
repositories {
|
|
||||||
gradlePluginPortal()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencyResolutionManagement {
|
|
||||||
repositories {
|
|
||||||
mavenCentral()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -1,14 +0,0 @@
|
|||||||
default:
|
|
||||||
just --list
|
|
||||||
|
|
||||||
build-local-mac:
|
|
||||||
bash ./scripts/generate-macos-arm64.sh && python3 setup.py bdist_wheel --verbose
|
|
||||||
|
|
||||||
clean:
|
|
||||||
rm -rf ../bdk-ffi/target/
|
|
||||||
rm -rf ./bdkpython.egg-info/
|
|
||||||
rm -rf ./build/
|
|
||||||
rm -rf ./dist/
|
|
||||||
|
|
||||||
test:
|
|
||||||
python3 -m unittest --verbose
|
|
@ -4,15 +4,14 @@ set -euo pipefail
|
|||||||
${PYBIN}/python --version
|
${PYBIN}/python --version
|
||||||
${PYBIN}/pip install -r requirements.txt
|
${PYBIN}/pip install -r requirements.txt
|
||||||
|
|
||||||
|
echo "Generating bdk.py..."
|
||||||
cd ../bdk-ffi/
|
cd ../bdk-ffi/
|
||||||
rustup default 1.77.1
|
cargo run --bin uniffi-bindgen generate src/bdk.udl --language python --out-dir ../bdk-python/src/bdkpython/ --no-format
|
||||||
|
|
||||||
echo "Generating native binaries..."
|
echo "Generating native binaries..."
|
||||||
|
rustup default 1.73.0
|
||||||
cargo build --profile release-smaller
|
cargo build --profile release-smaller
|
||||||
|
|
||||||
echo "Generating bdk.py..."
|
|
||||||
cargo run --bin uniffi-bindgen generate --library ./target/release-smaller/libbdkffi.so --language python --out-dir ../bdk-python/src/bdkpython/ --no-format
|
|
||||||
|
|
||||||
echo "Copying linux libbdkffi.so..."
|
echo "Copying linux libbdkffi.so..."
|
||||||
cp ./target/release-smaller/libbdkffi.so ../bdk-python/src/bdkpython/libbdkffi.so
|
cp ./target/release-smaller/libbdkffi.so ../bdk-python/src/bdkpython/libbdkffi.so
|
||||||
|
|
||||||
|
@ -2,17 +2,16 @@
|
|||||||
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
python3 --version
|
python3 --version
|
||||||
pip install -r requirements.txt
|
pip install --user -r requirements.txt
|
||||||
|
|
||||||
cd ../bdk-ffi/
|
|
||||||
rustup default 1.77.1
|
|
||||||
rustup target add aarch64-apple-darwin
|
|
||||||
|
|
||||||
echo "Generating native binaries..."
|
|
||||||
cargo build --profile release-smaller --target aarch64-apple-darwin
|
|
||||||
|
|
||||||
echo "Generating bdk.py..."
|
echo "Generating bdk.py..."
|
||||||
cargo run --bin uniffi-bindgen generate --library ./target/aarch64-apple-darwin/release-smaller/libbdkffi.dylib --language python --out-dir ../bdk-python/src/bdkpython/ --no-format
|
cd ../bdk-ffi/
|
||||||
|
cargo run --bin uniffi-bindgen generate src/bdk.udl --language python --out-dir ../bdk-python/src/bdkpython/ --no-format
|
||||||
|
|
||||||
|
echo "Generating native binaries..."
|
||||||
|
rustup default 1.73.0
|
||||||
|
rustup target add aarch64-apple-darwin
|
||||||
|
cargo build --profile release-smaller --target aarch64-apple-darwin
|
||||||
|
|
||||||
echo "Copying libraries libbdkffi.dylib..."
|
echo "Copying libraries libbdkffi.dylib..."
|
||||||
cp ./target/aarch64-apple-darwin/release-smaller/libbdkffi.dylib ../bdk-python/src/bdkpython/libbdkffi.dylib
|
cp ./target/aarch64-apple-darwin/release-smaller/libbdkffi.dylib ../bdk-python/src/bdkpython/libbdkffi.dylib
|
||||||
|
@ -2,17 +2,16 @@
|
|||||||
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
python3 --version
|
python3 --version
|
||||||
pip install -r requirements.txt
|
pip install --user -r requirements.txt
|
||||||
|
|
||||||
cd ../bdk-ffi/
|
|
||||||
rustup default 1.77.1
|
|
||||||
rustup target add x86_64-apple-darwin
|
|
||||||
|
|
||||||
echo "Generating native binaries..."
|
|
||||||
cargo build --profile release-smaller --target x86_64-apple-darwin
|
|
||||||
|
|
||||||
echo "Generating bdk.py..."
|
echo "Generating bdk.py..."
|
||||||
cargo run --bin uniffi-bindgen generate --library ./target/x86_64-apple-darwin/release-smaller/libbdkffi.dylib --language python --out-dir ../bdk-python/src/bdkpython/ --no-format
|
cd ../bdk-ffi/
|
||||||
|
cargo run --bin uniffi-bindgen generate src/bdk.udl --language python --out-dir ../bdk-python/src/bdkpython/ --no-format
|
||||||
|
|
||||||
|
echo "Generating native binaries..."
|
||||||
|
rustup default 1.73.0
|
||||||
|
rustup target add x86_64-apple-darwin
|
||||||
|
cargo build --profile release-smaller --target x86_64-apple-darwin
|
||||||
|
|
||||||
echo "Copying libraries libbdkffi.dylib..."
|
echo "Copying libraries libbdkffi.dylib..."
|
||||||
cp ./target/x86_64-apple-darwin/release-smaller/libbdkffi.dylib ../bdk-python/src/bdkpython/libbdkffi.dylib
|
cp ./target/x86_64-apple-darwin/release-smaller/libbdkffi.dylib ../bdk-python/src/bdkpython/libbdkffi.dylib
|
||||||
|
@ -2,17 +2,16 @@
|
|||||||
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
python3 --version
|
python3 --version
|
||||||
pip install -r requirements.txt
|
pip install --user -r requirements.txt
|
||||||
|
|
||||||
cd ../bdk-ffi/
|
|
||||||
rustup default 1.77.1
|
|
||||||
rustup target add x86_64-pc-windows-msvc
|
|
||||||
|
|
||||||
echo "Generating native binaries..."
|
|
||||||
cargo build --profile release-smaller --target x86_64-pc-windows-msvc
|
|
||||||
|
|
||||||
echo "Generating bdk.py..."
|
echo "Generating bdk.py..."
|
||||||
cargo run --bin uniffi-bindgen generate --library ./target/x86_64-pc-windows-msvc/release-smaller/bdkffi.dll --language python --out-dir ../bdk-python/src/bdkpython/ --no-format
|
cd ../bdk-ffi/
|
||||||
|
cargo run --bin uniffi-bindgen generate src/bdk.udl --language python --out-dir ../bdk-python/src/bdkpython/ --no-format
|
||||||
|
|
||||||
|
echo "Generating native binaries..."
|
||||||
|
rustup default 1.73.0
|
||||||
|
rustup target add x86_64-pc-windows-msvc
|
||||||
|
cargo build --profile release-smaller --target x86_64-pc-windows-msvc
|
||||||
|
|
||||||
echo "Copying libraries bdkffi.dll..."
|
echo "Copying libraries bdkffi.dll..."
|
||||||
cp ./target/x86_64-pc-windows-msvc/release-smaller/bdkffi.dll ../bdk-python/src/bdkpython/bdkffi.dll
|
cp ./target/x86_64-pc-windows-msvc/release-smaller/bdkffi.dll ../bdk-python/src/bdkpython/bdkffi.dll
|
||||||
|
@ -13,12 +13,11 @@ pip install bdkpython
|
|||||||
## Simple example
|
## Simple example
|
||||||
```python
|
```python
|
||||||
import bdkpython as bdk
|
import bdkpython as bdk
|
||||||
```
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
setup(
|
setup(
|
||||||
name="bdkpython",
|
name="bdkpython",
|
||||||
version="1.0.0a12.dev",
|
version="1.0.0a2.dev0",
|
||||||
description="The Python language bindings for the Bitcoin Development Kit",
|
description="The Python language bindings for the Bitcoin Development Kit",
|
||||||
long_description=LONG_DESCRIPTION,
|
long_description=LONG_DESCRIPTION,
|
||||||
long_description_content_type="text/markdown",
|
long_description_content_type="text/markdown",
|
||||||
|
@ -1,98 +1,38 @@
|
|||||||
import bdkpython as bdk
|
import bdkpython as bdk
|
||||||
import unittest
|
import unittest
|
||||||
import os
|
|
||||||
|
|
||||||
SIGNET_ESPLORA_URL = "http://signet.bitcoindevkit.net"
|
class TestLiveTxBuilder(unittest.TestCase):
|
||||||
TESTNET_ESPLORA_URL = "https://esplora.testnet.kuutamo.cloud"
|
|
||||||
|
|
||||||
descriptor: bdk.Descriptor = bdk.Descriptor(
|
|
||||||
"wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)",
|
|
||||||
bdk.Network.TESTNET
|
|
||||||
)
|
|
||||||
change_descriptor: bdk.Descriptor = bdk.Descriptor(
|
|
||||||
"wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/1/*)",
|
|
||||||
bdk.Network.TESTNET
|
|
||||||
)
|
|
||||||
|
|
||||||
class LiveTxBuilderTest(unittest.TestCase):
|
|
||||||
|
|
||||||
def tearDown(self) -> None:
|
|
||||||
if os.path.exists("./bdk_persistence.sqlite"):
|
|
||||||
os.remove("./bdk_persistence.sqlite")
|
|
||||||
|
|
||||||
def test_tx_builder(self):
|
def test_tx_builder(self):
|
||||||
descriptor: bdk.Descriptor = bdk.Descriptor(
|
descriptor: bdk.Descriptor = bdk.Descriptor(
|
||||||
"wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)",
|
"wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)",
|
||||||
bdk.Network.SIGNET
|
bdk.Network.TESTNET
|
||||||
)
|
)
|
||||||
wallet: bdk.Wallet = bdk.Wallet(
|
wallet: bdk.Wallet = bdk.Wallet.new_no_persist(
|
||||||
descriptor,
|
descriptor,
|
||||||
change_descriptor,
|
None,
|
||||||
bdk.Network.SIGNET
|
bdk.Network.TESTNET
|
||||||
)
|
)
|
||||||
esplora_client: bdk.EsploraClient = bdk.EsploraClient(url = SIGNET_ESPLORA_URL)
|
esploraClient: bdk.EsploraClient = bdk.EsploraClient(url = "https://mempool.space/testnet/api")
|
||||||
full_scan_request: bdk.FullScanRequest = wallet.start_full_scan()
|
update = esploraClient.scan(
|
||||||
update = esplora_client.full_scan(
|
wallet = wallet,
|
||||||
full_scan_request=full_scan_request,
|
stop_gap = 10,
|
||||||
stop_gap=10,
|
parallel_requests = 1
|
||||||
parallel_requests=1
|
|
||||||
)
|
)
|
||||||
wallet.apply_update(update)
|
wallet.apply_update(update)
|
||||||
|
|
||||||
self.assertGreater(
|
self.assertGreater(wallet.get_balance().total(), 0)
|
||||||
wallet.balance().total.to_sat(),
|
|
||||||
0,
|
|
||||||
f"Wallet balance must be greater than 0! Please send funds to {wallet.reveal_next_address(bdk.KeychainKind.EXTERNAL).address} and try again."
|
|
||||||
)
|
|
||||||
|
|
||||||
recipient = bdk.Address(
|
recipient = bdk.Address(
|
||||||
address="tb1qrnfslnrve9uncz9pzpvf83k3ukz22ljgees989",
|
address = "tb1qrnfslnrve9uncz9pzpvf83k3ukz22ljgees989",
|
||||||
network=bdk.Network.SIGNET
|
network = bdk.Network.TESTNET
|
||||||
)
|
)
|
||||||
|
|
||||||
psbt = bdk.TxBuilder().add_recipient(script=recipient.script_pubkey(), amount=bdk.Amount.from_sat(4200)).fee_rate(fee_rate=bdk.FeeRate.from_sat_per_vb(2)).finish(wallet)
|
psbt = bdk.TxBuilder().add_recipient(script=recipient.script_pubkey(), amount=4200).fee_rate(2.0).finish(wallet)
|
||||||
|
# print(psbt.serialize())
|
||||||
|
|
||||||
self.assertTrue(psbt.serialize().startswith("cHNi"), "The PSBT should start with cHNi")
|
self.assertTrue(psbt.serialize().startswith("cHNi"), "The PSBT should start with cHNi")
|
||||||
|
|
||||||
def complex_tx_builder(self):
|
|
||||||
wallet: bdk.Wallet = bdk.Wallet(
|
|
||||||
descriptor,
|
|
||||||
change_descriptor,
|
|
||||||
bdk.Network.SIGNET
|
|
||||||
)
|
|
||||||
esplora_client: bdk.EsploraClient = bdk.EsploraClient(url = SIGNET_ESPLORA_URL)
|
|
||||||
full_scan_request: bdk.FullScanRequest = wallet.start_full_scan()
|
|
||||||
update = esplora_client.full_scan(
|
|
||||||
full_scan_request=full_scan_request,
|
|
||||||
stop_gap=10,
|
|
||||||
parallel_requests=1
|
|
||||||
)
|
|
||||||
wallet.apply_update(update)
|
|
||||||
|
|
||||||
self.assertGreater(
|
|
||||||
wallet.balance().total.to_sat(),
|
|
||||||
0,
|
|
||||||
f"Wallet balance must be greater than 0! Please send funds to {wallet.reveal_next_address(bdk.KeychainKind.EXTERNAL).address} and try again."
|
|
||||||
)
|
|
||||||
|
|
||||||
recipient1 = bdk.Address(
|
|
||||||
address="tb1qrnfslnrve9uncz9pzpvf83k3ukz22ljgees989",
|
|
||||||
network=bdk.Network.SIGNET
|
|
||||||
)
|
|
||||||
recipient2 = bdk.Address(
|
|
||||||
address="tb1qw2c3lxufxqe2x9s4rdzh65tpf4d7fssjgh8nv6",
|
|
||||||
network=bdk.Network.SIGNET
|
|
||||||
)
|
|
||||||
all_recipients = list(
|
|
||||||
bdk.ScriptAmount(recipient1.script_pubkey, 4200),
|
|
||||||
bdk.ScriptAmount(recipient2.script_pubkey, 4200)
|
|
||||||
)
|
|
||||||
|
|
||||||
psbt: bdk.Psbt = bdk.TxBuilder().set_recipients(all_recipients).fee_rate(fee_rate=bdk.FeeRate.from_sat_per_vb(2)).enable_rbf().finish(wallet)
|
|
||||||
wallet.sign(psbt)
|
|
||||||
|
|
||||||
self.assertTrue(psbt.serialize().startswith("cHNi"), "The PSBT should start with cHNi")
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
@ -1,94 +1,62 @@
|
|||||||
import bdkpython as bdk
|
import bdkpython as bdk
|
||||||
import unittest
|
import unittest
|
||||||
import os
|
|
||||||
|
|
||||||
SIGNET_ESPLORA_URL = "http://signet.bitcoindevkit.net"
|
class TestLiveWallet(unittest.TestCase):
|
||||||
TESTNET_ESPLORA_URL = "https://esplora.testnet.kuutamo.cloud"
|
|
||||||
|
|
||||||
descriptor: bdk.Descriptor = bdk.Descriptor(
|
|
||||||
"wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)",
|
|
||||||
bdk.Network.TESTNET
|
|
||||||
)
|
|
||||||
change_descriptor: bdk.Descriptor = bdk.Descriptor(
|
|
||||||
"wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/1/*)",
|
|
||||||
bdk.Network.TESTNET
|
|
||||||
)
|
|
||||||
|
|
||||||
class LiveWalletTest(unittest.TestCase):
|
|
||||||
|
|
||||||
def tearDown(self) -> None:
|
|
||||||
if os.path.exists("./bdk_persistence.sqlite"):
|
|
||||||
os.remove("./bdk_persistence.sqlite")
|
|
||||||
|
|
||||||
def test_synced_balance(self):
|
def test_synced_balance(self):
|
||||||
wallet: bdk.Wallet = bdk.Wallet(
|
descriptor: bdk.Descriptor = bdk.Descriptor(
|
||||||
descriptor,
|
"wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)",
|
||||||
change_descriptor,
|
bdk.Network.TESTNET
|
||||||
bdk.Network.SIGNET
|
|
||||||
)
|
)
|
||||||
esplora_client: bdk.EsploraClient = bdk.EsploraClient(url = SIGNET_ESPLORA_URL)
|
wallet: bdk.Wallet = bdk.Wallet.new_no_persist(
|
||||||
full_scan_request: bdk.FullScanRequest = wallet.start_full_scan()
|
descriptor,
|
||||||
update = esplora_client.full_scan(
|
None,
|
||||||
full_scan_request=full_scan_request,
|
bdk.Network.TESTNET
|
||||||
stop_gap=10,
|
)
|
||||||
parallel_requests=1
|
esploraClient: bdk.EsploraClient = bdk.EsploraClient(url = "https://mempool.space/testnet/api")
|
||||||
|
update = esploraClient.scan(
|
||||||
|
wallet = wallet,
|
||||||
|
stop_gap = 10,
|
||||||
|
parallel_requests = 1
|
||||||
)
|
)
|
||||||
wallet.apply_update(update)
|
wallet.apply_update(update)
|
||||||
|
|
||||||
self.assertGreater(
|
|
||||||
wallet.balance().total.to_sat(),
|
|
||||||
0,
|
|
||||||
f"Wallet balance must be greater than 0! Please send funds to {wallet.reveal_next_address(bdk.KeychainKind.EXTERNAL).address} and try again."
|
|
||||||
)
|
|
||||||
|
|
||||||
print(f"Transactions count: {len(wallet.transactions())}")
|
|
||||||
transactions = wallet.transactions()[:3]
|
|
||||||
for tx in transactions:
|
|
||||||
sent_and_received = wallet.sent_and_received(tx.transaction)
|
|
||||||
print(f"Transaction: {tx.transaction.compute_txid()}")
|
|
||||||
print(f"Sent {sent_and_received.sent.to_sat()}")
|
|
||||||
print(f"Received {sent_and_received.received.to_sat()}")
|
|
||||||
|
|
||||||
|
self.assertGreater(wallet.get_balance().total(), 0)
|
||||||
|
|
||||||
def test_broadcast_transaction(self):
|
def test_broadcast_transaction(self):
|
||||||
wallet: bdk.Wallet = bdk.Wallet(
|
descriptor: bdk.Descriptor = bdk.Descriptor(
|
||||||
descriptor,
|
"wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)",
|
||||||
change_descriptor,
|
bdk.Network.TESTNET
|
||||||
bdk.Network.SIGNET
|
|
||||||
)
|
)
|
||||||
esplora_client: bdk.EsploraClient = bdk.EsploraClient(url = SIGNET_ESPLORA_URL)
|
wallet: bdk.Wallet = bdk.Wallet.new_no_persist(
|
||||||
full_scan_request: bdk.FullScanRequest = wallet.start_full_scan()
|
descriptor,
|
||||||
update = esplora_client.full_scan(
|
None,
|
||||||
full_scan_request=full_scan_request,
|
bdk.Network.TESTNET
|
||||||
stop_gap=10,
|
)
|
||||||
parallel_requests=1
|
esploraClient: bdk.EsploraClient = bdk.EsploraClient(url = "https://mempool.space/testnet/api")
|
||||||
|
update = esploraClient.scan(
|
||||||
|
wallet = wallet,
|
||||||
|
stop_gap = 10,
|
||||||
|
parallel_requests = 1
|
||||||
)
|
)
|
||||||
wallet.apply_update(update)
|
wallet.apply_update(update)
|
||||||
|
|
||||||
self.assertGreater(
|
self.assertGreater(wallet.get_balance().total(), 0)
|
||||||
wallet.balance().total.to_sat(),
|
|
||||||
0,
|
|
||||||
f"Wallet balance must be greater than 0! Please send funds to {wallet.reveal_next_address(bdk.KeychainKind.EXTERNAL).address} and try again."
|
|
||||||
)
|
|
||||||
|
|
||||||
recipient = bdk.Address(
|
recipient = bdk.Address(
|
||||||
address="tb1qrnfslnrve9uncz9pzpvf83k3ukz22ljgees989",
|
address = "tb1qrnfslnrve9uncz9pzpvf83k3ukz22ljgees989",
|
||||||
network=bdk.Network.SIGNET
|
network = bdk.Network.TESTNET
|
||||||
)
|
)
|
||||||
|
|
||||||
psbt: bdk.Psbt = bdk.TxBuilder().add_recipient(script=recipient.script_pubkey(), amount=bdk.Amount.from_sat(4200)).fee_rate(fee_rate=bdk.FeeRate.from_sat_per_vb(2)).finish(wallet)
|
psbt = bdk.TxBuilder().add_recipient(script=recipient.script_pubkey(), amount=4200).fee_rate(2.0).finish(wallet)
|
||||||
|
# print(psbt.serialize())
|
||||||
self.assertTrue(psbt.serialize().startswith("cHNi"), "The PSBT should start with cHNi")
|
self.assertTrue(psbt.serialize().startswith("cHNi"), "The PSBT should start with cHNi")
|
||||||
|
|
||||||
walletDidSign = wallet.sign(psbt)
|
walletDidSign = wallet.sign(psbt)
|
||||||
self.assertTrue(walletDidSign)
|
self.assertTrue(walletDidSign)
|
||||||
tx = psbt.extract_tx()
|
tx = psbt.extract_tx()
|
||||||
print(f"Transaction Id: {tx.compute_txid()}")
|
|
||||||
fee = wallet.calculate_fee(tx)
|
|
||||||
print(f"Transaction Fee: {fee.to_sat()}")
|
|
||||||
fee_rate = wallet.calculate_fee_rate(tx)
|
|
||||||
print(f"Transaction Fee Rate: {fee_rate.to_sat_per_vb_ceil()} sat/vB")
|
|
||||||
|
|
||||||
esplora_client.broadcast(tx)
|
esploraClient.broadcast(tx)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import bdkpython as bdk
|
import bdkpython as bdk
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
class OfflineDescriptorTest(unittest.TestCase):
|
class TestSimpleWallet(unittest.TestCase):
|
||||||
|
|
||||||
def test_descriptor_bip86(self):
|
def test_descriptor_bip86(self):
|
||||||
mnemonic: bdk.Mnemonic = bdk.Mnemonic.from_string("space echo position wrist orient erupt relief museum myself grain wisdom tumble")
|
mnemonic: bdk.Mnemonic = bdk.Mnemonic.from_string("space echo position wrist orient erupt relief museum myself grain wisdom tumble")
|
||||||
@ -10,7 +10,7 @@ class OfflineDescriptorTest(unittest.TestCase):
|
|||||||
|
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
"tr([be1eec8f/86'/1'/0']tpubDCTtszwSxPx3tATqDrsSyqScPNnUChwQAVAkanuDUCJQESGBbkt68nXXKRDifYSDbeMa2Xg2euKbXaU3YphvGWftDE7ozRKPriT6vAo3xsc/0/*)#m7puekcx",
|
"tr([be1eec8f/86'/1'/0']tpubDCTtszwSxPx3tATqDrsSyqScPNnUChwQAVAkanuDUCJQESGBbkt68nXXKRDifYSDbeMa2Xg2euKbXaU3YphvGWftDE7ozRKPriT6vAo3xsc/0/*)#m7puekcx",
|
||||||
descriptor.__str__()
|
descriptor.as_string()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,45 +1,34 @@
|
|||||||
import bdkpython as bdk
|
import bdkpython as bdk
|
||||||
import unittest
|
import unittest
|
||||||
import os
|
|
||||||
|
|
||||||
descriptor: bdk.Descriptor = bdk.Descriptor(
|
class TestSimpleWallet(unittest.TestCase):
|
||||||
"wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)",
|
|
||||||
bdk.Network.TESTNET
|
|
||||||
)
|
|
||||||
change_descriptor: bdk.Descriptor = bdk.Descriptor(
|
|
||||||
"wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/1/*)",
|
|
||||||
bdk.Network.TESTNET
|
|
||||||
)
|
|
||||||
|
|
||||||
class OfflineWalletTest(unittest.TestCase):
|
|
||||||
|
|
||||||
def tearDown(self) -> None:
|
|
||||||
if os.path.exists("./bdk_persistence.sqlite"):
|
|
||||||
os.remove("./bdk_persistence.sqlite")
|
|
||||||
|
|
||||||
def test_new_address(self):
|
def test_new_address(self):
|
||||||
wallet: Wallet = bdk.Wallet(
|
descriptor: bdk.Descriptor = bdk.Descriptor(
|
||||||
descriptor,
|
"wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)",
|
||||||
change_descriptor,
|
|
||||||
bdk.Network.TESTNET
|
bdk.Network.TESTNET
|
||||||
)
|
)
|
||||||
address_info: bdk.AddressInfo = wallet.reveal_next_address(bdk.KeychainKind.EXTERNAL)
|
wallet: Wallet = bdk.Wallet.new_no_persist(
|
||||||
|
descriptor,
|
||||||
self.assertTrue(address_info.address.is_valid_for_network(bdk.Network.TESTNET), "Address is not valid for testnet network")
|
None,
|
||||||
self.assertTrue(address_info.address.is_valid_for_network(bdk.Network.SIGNET), "Address is not valid for signet network")
|
bdk.Network.TESTNET
|
||||||
self.assertFalse(address_info.address.is_valid_for_network(bdk.Network.REGTEST), "Address is valid for regtest network, but it shouldn't be")
|
)
|
||||||
self.assertFalse(address_info.address.is_valid_for_network(bdk.Network.BITCOIN), "Address is valid for bitcoin network, but it shouldn't be")
|
address_info: bdk.AddressInfo = wallet.get_address(bdk.AddressIndex.NEW())
|
||||||
|
|
||||||
self.assertEqual("tb1qrnfslnrve9uncz9pzpvf83k3ukz22ljgees989", address_info.address.__str__())
|
self.assertEqual("tb1qzg4mckdh50nwdm9hkzq06528rsu73hjxxzem3e", address_info.address.as_string())
|
||||||
|
|
||||||
def test_balance(self):
|
def test_balance(self):
|
||||||
wallet: bdk.Wallet = bdk.Wallet(
|
descriptor: bdk.Descriptor = bdk.Descriptor(
|
||||||
descriptor,
|
"wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)",
|
||||||
change_descriptor,
|
|
||||||
bdk.Network.TESTNET
|
bdk.Network.TESTNET
|
||||||
)
|
)
|
||||||
|
wallet: bdk.Wallet = bdk.Wallet.new_no_persist(
|
||||||
self.assertEqual(wallet.balance().total.to_sat(), 0)
|
descriptor,
|
||||||
|
None,
|
||||||
|
bdk.Network.TESTNET
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(wallet.get_balance().total(), 0)
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
@ -29,14 +29,11 @@ let package = Package(
|
|||||||
.binaryTarget(name: "bdkFFI", path: "./bdkFFI.xcframework"),
|
.binaryTarget(name: "bdkFFI", path: "./bdkFFI.xcframework"),
|
||||||
.target(
|
.target(
|
||||||
name: "BitcoinDevKit",
|
name: "BitcoinDevKit",
|
||||||
dependencies: ["bdkFFI"]
|
dependencies: ["bdkFFI"],
|
||||||
|
swiftSettings: [.unsafeFlags(["-suppress-warnings"])]
|
||||||
),
|
),
|
||||||
.testTarget(
|
.testTarget(
|
||||||
name: "BitcoinDevKitTests",
|
name: "BitcoinDevKitTests",
|
||||||
dependencies: ["BitcoinDevKit"],
|
dependencies: ["BitcoinDevKit"]),
|
||||||
resources: [
|
|
||||||
.copy("Resources/pre_existing_wallet_persistence_test.sqlite")
|
|
||||||
]
|
|
||||||
),
|
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
@ -25,7 +25,7 @@ import BitcoinDevKit
|
|||||||
|
|
||||||
Swift Package Manager releases for `bdk-swift` are published to a separate repository (https://github.com/bitcoindevkit/bdk-swift), and that is where the releases are created for it.
|
Swift Package Manager releases for `bdk-swift` are published to a separate repository (https://github.com/bitcoindevkit/bdk-swift), and that is where the releases are created for it.
|
||||||
|
|
||||||
The `bdk-swift/build-xcframework.sh` script can be used instead to create a version of the project for local testing.
|
The `bdk-swift/build-local-swift.sh` script can be used instead to create a version of the project for local testing.
|
||||||
|
|
||||||
### How to test
|
### How to test
|
||||||
|
|
||||||
|
@ -1,48 +0,0 @@
|
|||||||
import XCTest
|
|
||||||
@testable import BitcoinDevKit
|
|
||||||
|
|
||||||
private let SIGNET_ELECTRUM_URL = "ssl://mempool.space:60602"
|
|
||||||
|
|
||||||
final class LiveElectrumClientTests: XCTestCase {
|
|
||||||
private let descriptor = try! Descriptor(
|
|
||||||
descriptor: "wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)",
|
|
||||||
network: Network.signet
|
|
||||||
)
|
|
||||||
private let changeDescriptor = try! Descriptor(
|
|
||||||
descriptor: "wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/1/*)",
|
|
||||||
network: Network.signet
|
|
||||||
)
|
|
||||||
|
|
||||||
func testSyncedBalance() throws {
|
|
||||||
let wallet = try Wallet(
|
|
||||||
descriptor: descriptor,
|
|
||||||
changeDescriptor: changeDescriptor,
|
|
||||||
network: Network.signet
|
|
||||||
)
|
|
||||||
let electrumClient: ElectrumClient = try ElectrumClient(url: SIGNET_ELECTRUM_URL)
|
|
||||||
let fullScanRequest: FullScanRequest = wallet.startFullScan()
|
|
||||||
let update = try electrumClient.fullScan(
|
|
||||||
fullScanRequest: fullScanRequest,
|
|
||||||
stopGap: 10,
|
|
||||||
batchSize: 10,
|
|
||||||
fetchPrevTxouts: false
|
|
||||||
)
|
|
||||||
try wallet.applyUpdate(update: update)
|
|
||||||
let address = wallet.revealNextAddress(keychain: KeychainKind.external).address
|
|
||||||
|
|
||||||
XCTAssertGreaterThan(
|
|
||||||
wallet.balance().total.toSat(),
|
|
||||||
UInt64(0),
|
|
||||||
"Wallet must have positive balance, please send funds to \(address)"
|
|
||||||
)
|
|
||||||
|
|
||||||
print("Transactions count: \(wallet.transactions().count)")
|
|
||||||
let transactions = wallet.transactions().prefix(3)
|
|
||||||
for tx in transactions {
|
|
||||||
let sentAndReceived = wallet.sentAndReceived(tx: tx.transaction)
|
|
||||||
print("Transaction: \(tx.transaction.computeTxid())")
|
|
||||||
print("Sent \(sentAndReceived.sent.toSat())")
|
|
||||||
print("Received \(sentAndReceived.received.toSat())")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,80 +0,0 @@
|
|||||||
import XCTest
|
|
||||||
@testable import BitcoinDevKit
|
|
||||||
|
|
||||||
private let SIGNET_ESPLORA_URL = "http://signet.bitcoindevkit.net"
|
|
||||||
private let TESTNET_ESPLORA_URL = "https://esplora.testnet.kuutamo.cloud"
|
|
||||||
|
|
||||||
final class LiveMemoryWalletTests: XCTestCase {
|
|
||||||
private let descriptor = try! Descriptor(
|
|
||||||
descriptor: "wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)",
|
|
||||||
network: Network.signet
|
|
||||||
)
|
|
||||||
private let changeDescriptor = try! Descriptor(
|
|
||||||
descriptor: "wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/1/*)",
|
|
||||||
network: Network.signet
|
|
||||||
)
|
|
||||||
|
|
||||||
func testSyncedBalance() throws {
|
|
||||||
let wallet = try Wallet(
|
|
||||||
descriptor: descriptor,
|
|
||||||
changeDescriptor: changeDescriptor,
|
|
||||||
network: .signet
|
|
||||||
)
|
|
||||||
let esploraClient = EsploraClient(url: SIGNET_ESPLORA_URL)
|
|
||||||
let fullScanRequest: FullScanRequest = wallet.startFullScan()
|
|
||||||
let update = try esploraClient.fullScan(
|
|
||||||
fullScanRequest: fullScanRequest,
|
|
||||||
stopGap: 10,
|
|
||||||
parallelRequests: 1
|
|
||||||
)
|
|
||||||
try wallet.applyUpdate(update: update)
|
|
||||||
let address = wallet.revealNextAddress(keychain: KeychainKind.external).address.description
|
|
||||||
|
|
||||||
XCTAssertGreaterThan(
|
|
||||||
wallet.balance().total.toSat(),
|
|
||||||
UInt64(0),
|
|
||||||
"Wallet must have positive balance, please send funds to \(address)"
|
|
||||||
)
|
|
||||||
|
|
||||||
print("Transactions count: \(wallet.transactions().count)")
|
|
||||||
let transactions = wallet.transactions().prefix(3)
|
|
||||||
for tx in transactions {
|
|
||||||
let sentAndReceived = wallet.sentAndReceived(tx: tx.transaction)
|
|
||||||
print("Transaction: \(tx.transaction.computeTxid())")
|
|
||||||
print("Sent \(sentAndReceived.sent.toSat())")
|
|
||||||
print("Received \(sentAndReceived.received.toSat())")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func testScriptInspector() throws {
|
|
||||||
let wallet = try Wallet(
|
|
||||||
descriptor: descriptor,
|
|
||||||
changeDescriptor: changeDescriptor,
|
|
||||||
network: .signet
|
|
||||||
)
|
|
||||||
let esploraClient = EsploraClient(url: SIGNET_ESPLORA_URL)
|
|
||||||
let scriptInspector = FullScriptInspector()
|
|
||||||
let fullScanRequest = try wallet.startFullScan().inspectSpksForAllKeychains(inspector: scriptInspector)
|
|
||||||
|
|
||||||
let update = try esploraClient.fullScan(
|
|
||||||
fullScanRequest: fullScanRequest,
|
|
||||||
stopGap: 21,
|
|
||||||
parallelRequests: 1
|
|
||||||
)
|
|
||||||
try wallet.applyUpdate(update: update)
|
|
||||||
let address = wallet.revealNextAddress(keychain: KeychainKind.external).address.description
|
|
||||||
|
|
||||||
XCTAssertGreaterThan(
|
|
||||||
wallet.balance().total.toSat(),
|
|
||||||
UInt64(0),
|
|
||||||
"Wallet must have positive balance, please send funds to \(address)"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
class FullScriptInspector: FullScanScriptInspector {
|
|
||||||
func inspect(keychain: KeychainKind, index: UInt32, script: Script) {
|
|
||||||
print("Inspecting index \(index) for keychain \(keychain)")
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,56 +0,0 @@
|
|||||||
import XCTest
|
|
||||||
@testable import BitcoinDevKit
|
|
||||||
|
|
||||||
private let SIGNET_ESPLORA_URL = "http://signet.bitcoindevkit.net"
|
|
||||||
private let TESTNET_ESPLORA_URL = "https://esplora.testnet.kuutamo.cloud"
|
|
||||||
|
|
||||||
|
|
||||||
final class LiveTransactionTests: XCTestCase {
|
|
||||||
private let descriptor = try! Descriptor(
|
|
||||||
descriptor: "wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)",
|
|
||||||
network: Network.signet
|
|
||||||
)
|
|
||||||
private let changeDescriptor = try! Descriptor(
|
|
||||||
descriptor: "wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/1/*)",
|
|
||||||
network: Network.signet
|
|
||||||
)
|
|
||||||
|
|
||||||
func testSyncedBalance() throws {
|
|
||||||
let wallet = try Wallet(
|
|
||||||
descriptor: descriptor,
|
|
||||||
changeDescriptor: changeDescriptor,
|
|
||||||
network: .signet
|
|
||||||
)
|
|
||||||
let esploraClient = EsploraClient(url: SIGNET_ESPLORA_URL)
|
|
||||||
let fullScanRequest: FullScanRequest = wallet.startFullScan()
|
|
||||||
let update = try esploraClient.fullScan(
|
|
||||||
fullScanRequest: fullScanRequest,
|
|
||||||
stopGap: 10,
|
|
||||||
parallelRequests: 1
|
|
||||||
)
|
|
||||||
try wallet.applyUpdate(update: update)
|
|
||||||
let address = wallet.revealNextAddress(keychain: KeychainKind.external).address.description
|
|
||||||
|
|
||||||
XCTAssertGreaterThan(
|
|
||||||
wallet.balance().total.toSat(),
|
|
||||||
UInt64(0),
|
|
||||||
"Wallet must have positive balance, please send funds to \(address)"
|
|
||||||
)
|
|
||||||
|
|
||||||
guard let transaction = wallet.transactions().first?.transaction else {
|
|
||||||
print("No transactions found")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
print("First transaction:")
|
|
||||||
print("Txid: \(transaction.computeTxid())")
|
|
||||||
print("Version: \(transaction.version())")
|
|
||||||
print("Total size: \(transaction.totalSize())")
|
|
||||||
print("Vsize: \(transaction.vsize())")
|
|
||||||
print("Weight: \(transaction.weight())")
|
|
||||||
print("Coinbase transaction: \(transaction.isCoinbase())")
|
|
||||||
print("Is explicitly RBF: \(transaction.isExplicitlyRbf())")
|
|
||||||
print("Inputs: \(transaction.input())")
|
|
||||||
print("Outputs: \(transaction.output())")
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,117 +1,34 @@
|
|||||||
import XCTest
|
import XCTest
|
||||||
@testable import BitcoinDevKit
|
@testable import BitcoinDevKit
|
||||||
|
|
||||||
private let SIGNET_ESPLORA_URL = "http://signet.bitcoindevkit.net"
|
|
||||||
private let TESTNET_ESPLORA_URL = "https://esplora.testnet.kuutamo.cloud"
|
|
||||||
|
|
||||||
final class LiveTxBuilderTests: XCTestCase {
|
final class LiveTxBuilderTests: XCTestCase {
|
||||||
private let descriptor = try! Descriptor(
|
|
||||||
descriptor: "wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)",
|
|
||||||
network: Network.signet
|
|
||||||
)
|
|
||||||
private let changeDescriptor = try! Descriptor(
|
|
||||||
descriptor: "wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/1/*)",
|
|
||||||
network: Network.signet
|
|
||||||
)
|
|
||||||
var dbFilePath: URL!
|
|
||||||
|
|
||||||
override func setUpWithError() throws {
|
|
||||||
super.setUp()
|
|
||||||
let fileManager = FileManager.default
|
|
||||||
let documentDirectory = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first!
|
|
||||||
let uniqueDbFileName = "bdk_persistence_\(UUID().uuidString).sqlite"
|
|
||||||
dbFilePath = documentDirectory.appendingPathComponent(uniqueDbFileName)
|
|
||||||
|
|
||||||
if fileManager.fileExists(atPath: dbFilePath.path) {
|
|
||||||
try fileManager.removeItem(at: dbFilePath)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override func tearDownWithError() throws {
|
|
||||||
let fileManager = FileManager.default
|
|
||||||
if fileManager.fileExists(atPath: dbFilePath.path) {
|
|
||||||
try fileManager.removeItem(at: dbFilePath)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func testTxBuilder() throws {
|
func testTxBuilder() throws {
|
||||||
let wallet = try Wallet(
|
let descriptor = try Descriptor(
|
||||||
descriptor: descriptor,
|
descriptor: "wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)",
|
||||||
changeDescriptor: changeDescriptor,
|
network: Network.testnet
|
||||||
network: .signet
|
|
||||||
)
|
)
|
||||||
let esploraClient = EsploraClient(url: SIGNET_ESPLORA_URL)
|
let wallet = try Wallet.newNoPersist(
|
||||||
let fullScanRequest: FullScanRequest = wallet.startFullScan()
|
descriptor: descriptor,
|
||||||
let update = try esploraClient.fullScan(
|
changeDescriptor: nil,
|
||||||
fullScanRequest: fullScanRequest,
|
network: .testnet
|
||||||
|
)
|
||||||
|
let esploraClient = EsploraClient(url: "https://mempool.space/testnet/api")
|
||||||
|
let update = try esploraClient.scan(
|
||||||
|
wallet: wallet,
|
||||||
stopGap: 10,
|
stopGap: 10,
|
||||||
parallelRequests: 1
|
parallelRequests: 1
|
||||||
)
|
)
|
||||||
try wallet.applyUpdate(update: update)
|
try wallet.applyUpdate(update: update)
|
||||||
let address = wallet.revealNextAddress(keychain: KeychainKind.external).address.description
|
|
||||||
|
|
||||||
XCTAssertGreaterThan(
|
XCTAssertGreaterThan(wallet.getBalance().total(), UInt64(0), "Wallet must have positive balance, please add funds")
|
||||||
wallet.balance().total.toSat(),
|
|
||||||
UInt64(0),
|
|
||||||
"Wallet must have positive balance, please send funds to \(address)"
|
|
||||||
)
|
|
||||||
|
|
||||||
let recipient: Address = try Address(address: "tb1qrnfslnrve9uncz9pzpvf83k3ukz22ljgees989", network: .signet)
|
let recipient: Address = try Address(address: "tb1qrnfslnrve9uncz9pzpvf83k3ukz22ljgees989", network: .testnet)
|
||||||
let psbt: Psbt = try TxBuilder()
|
let psbt: PartiallySignedTransaction = try TxBuilder()
|
||||||
.addRecipient(script: recipient.scriptPubkey(), amount: Amount.fromSat(fromSat: 4200))
|
.addRecipient(script: recipient.scriptPubkey(), amount: 4200)
|
||||||
.feeRate(feeRate: FeeRate.fromSatPerVb(satPerVb: 2))
|
.feeRate(satPerVbyte: 2.0)
|
||||||
.finish(wallet: wallet)
|
.finish(wallet: wallet)
|
||||||
|
|
||||||
print(psbt.serialize())
|
print(psbt.serialize())
|
||||||
XCTAssertTrue(psbt.serialize().hasPrefix("cHNi"), "PSBT should start with cHNI")
|
XCTAssertTrue(psbt.serialize().hasPrefix("cHNi"), "PSBT should start with cHNI")
|
||||||
}
|
}
|
||||||
|
|
||||||
func testComplexTxBuilder() throws {
|
|
||||||
let descriptor = try Descriptor(
|
|
||||||
descriptor: "wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)",
|
|
||||||
network: Network.signet
|
|
||||||
)
|
|
||||||
let changeDescriptor = try Descriptor(
|
|
||||||
descriptor: "wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/1/*)",
|
|
||||||
network: Network.signet
|
|
||||||
)
|
|
||||||
let wallet = try Wallet(
|
|
||||||
descriptor: descriptor,
|
|
||||||
changeDescriptor: changeDescriptor,
|
|
||||||
network: .signet
|
|
||||||
)
|
|
||||||
let esploraClient = EsploraClient(url: SIGNET_ESPLORA_URL)
|
|
||||||
let fullScanRequest: FullScanRequest = wallet.startFullScan()
|
|
||||||
let update = try esploraClient.fullScan(
|
|
||||||
fullScanRequest: fullScanRequest,
|
|
||||||
stopGap: 10,
|
|
||||||
parallelRequests: 1
|
|
||||||
)
|
|
||||||
try wallet.applyUpdate(update: update)
|
|
||||||
let address = wallet.revealNextAddress(keychain: KeychainKind.external).address.description
|
|
||||||
|
|
||||||
XCTAssertGreaterThan(
|
|
||||||
wallet.balance().total.toSat(),
|
|
||||||
UInt64(0),
|
|
||||||
"Wallet must have positive balance, please send funds to \(address)"
|
|
||||||
)
|
|
||||||
|
|
||||||
let recipient1: Address = try Address(address: "tb1qrnfslnrve9uncz9pzpvf83k3ukz22ljgees989", network: .signet)
|
|
||||||
let recipient2: Address = try Address(address: "tb1qw2c3lxufxqe2x9s4rdzh65tpf4d7fssjgh8nv6", network: .signet)
|
|
||||||
let allRecipients: [ScriptAmount] = [
|
|
||||||
ScriptAmount(script: recipient1.scriptPubkey(), amount: Amount.fromSat(fromSat: 4200)),
|
|
||||||
ScriptAmount(script: recipient2.scriptPubkey(), amount: Amount.fromSat(fromSat: 4200))
|
|
||||||
]
|
|
||||||
|
|
||||||
let psbt: Psbt = try TxBuilder()
|
|
||||||
.setRecipients(recipients: allRecipients)
|
|
||||||
.feeRate(feeRate: FeeRate.fromSatPerVb(satPerVb: 4))
|
|
||||||
.changePolicy(changePolicy: ChangeSpendPolicy.changeForbidden)
|
|
||||||
.enableRbf()
|
|
||||||
.finish(wallet: wallet)
|
|
||||||
|
|
||||||
let _ = try! wallet.sign(psbt: psbt)
|
|
||||||
|
|
||||||
XCTAssertTrue(psbt.serialize().hasPrefix("cHNi"), "PSBT should start with cHNI")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,115 +1,65 @@
|
|||||||
import XCTest
|
import XCTest
|
||||||
@testable import BitcoinDevKit
|
@testable import BitcoinDevKit
|
||||||
|
|
||||||
private let SIGNET_ESPLORA_URL = "http://signet.bitcoindevkit.net"
|
|
||||||
private let TESTNET_ESPLORA_URL = "https://esplora.testnet.kuutamo.cloud"
|
|
||||||
|
|
||||||
final class LiveWalletTests: XCTestCase {
|
final class LiveWalletTests: XCTestCase {
|
||||||
private let descriptor = try! Descriptor(
|
|
||||||
descriptor: "wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)",
|
|
||||||
network: Network.signet
|
|
||||||
)
|
|
||||||
private let changeDescriptor = try! Descriptor(
|
|
||||||
descriptor: "wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/1/*)",
|
|
||||||
network: Network.signet
|
|
||||||
)
|
|
||||||
var dbFilePath: URL!
|
|
||||||
|
|
||||||
override func setUpWithError() throws {
|
|
||||||
super.setUp()
|
|
||||||
let fileManager = FileManager.default
|
|
||||||
let documentDirectory = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first!
|
|
||||||
let uniqueDbFileName = "bdk_persistence_\(UUID().uuidString).sqlite"
|
|
||||||
dbFilePath = documentDirectory.appendingPathComponent(uniqueDbFileName)
|
|
||||||
|
|
||||||
if fileManager.fileExists(atPath: dbFilePath.path) {
|
|
||||||
try fileManager.removeItem(at: dbFilePath)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override func tearDownWithError() throws {
|
|
||||||
let fileManager = FileManager.default
|
|
||||||
if fileManager.fileExists(atPath: dbFilePath.path) {
|
|
||||||
try fileManager.removeItem(at: dbFilePath)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func testSyncedBalance() throws {
|
func testSyncedBalance() throws {
|
||||||
let wallet = try Wallet(
|
let descriptor = try Descriptor(
|
||||||
descriptor: descriptor,
|
descriptor: "wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)",
|
||||||
changeDescriptor: changeDescriptor,
|
network: Network.testnet
|
||||||
network: .signet
|
|
||||||
)
|
)
|
||||||
let esploraClient = EsploraClient(url: SIGNET_ESPLORA_URL)
|
let wallet = try Wallet.newNoPersist(
|
||||||
let fullScanRequest: FullScanRequest = wallet.startFullScan()
|
descriptor: descriptor,
|
||||||
let update = try esploraClient.fullScan(
|
changeDescriptor: nil,
|
||||||
fullScanRequest: fullScanRequest,
|
network: .testnet
|
||||||
|
)
|
||||||
|
let esploraClient = EsploraClient(url: "https://mempool.space/testnet/api")
|
||||||
|
let update = try esploraClient.scan(
|
||||||
|
wallet: wallet,
|
||||||
stopGap: 10,
|
stopGap: 10,
|
||||||
parallelRequests: 1
|
parallelRequests: 1
|
||||||
)
|
)
|
||||||
try wallet.applyUpdate(update: update)
|
try wallet.applyUpdate(update: update)
|
||||||
let address = wallet.revealNextAddress(keychain: KeychainKind.external).address.description
|
|
||||||
|
|
||||||
XCTAssertGreaterThan(
|
XCTAssertGreaterThan(wallet.getBalance().total(), UInt64(0))
|
||||||
wallet.balance().total.toSat(),
|
|
||||||
UInt64(0),
|
|
||||||
"Wallet must have positive balance, please send funds to \(address)"
|
|
||||||
)
|
|
||||||
|
|
||||||
print("Transactions count: \(wallet.transactions().count)")
|
|
||||||
let transactions = wallet.transactions().prefix(3)
|
|
||||||
for tx in transactions {
|
|
||||||
let sentAndReceived = wallet.sentAndReceived(tx: tx.transaction)
|
|
||||||
print("Transaction: \(tx.transaction.computeTxid())")
|
|
||||||
print("Sent \(sentAndReceived.sent.toSat())")
|
|
||||||
print("Received \(sentAndReceived.received.toSat())")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func testBroadcastTransaction() throws {
|
func testBroadcastTransaction() throws {
|
||||||
let wallet = try Wallet(
|
let descriptor = try Descriptor(
|
||||||
descriptor: descriptor,
|
descriptor: "wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)",
|
||||||
changeDescriptor: changeDescriptor,
|
network: Network.testnet
|
||||||
network: .signet
|
|
||||||
)
|
)
|
||||||
let esploraClient = EsploraClient(url: SIGNET_ESPLORA_URL)
|
let wallet = try Wallet.newNoPersist(
|
||||||
let fullScanRequest: FullScanRequest = wallet.startFullScan()
|
descriptor: descriptor,
|
||||||
let update = try esploraClient.fullScan(
|
changeDescriptor: nil,
|
||||||
fullScanRequest: fullScanRequest,
|
network: .testnet
|
||||||
|
)
|
||||||
|
let esploraClient = EsploraClient(url: "https://mempool.space/testnet/api")
|
||||||
|
let update = try esploraClient.scan(
|
||||||
|
wallet: wallet,
|
||||||
stopGap: 10,
|
stopGap: 10,
|
||||||
parallelRequests: 1
|
parallelRequests: 1
|
||||||
)
|
)
|
||||||
try wallet.applyUpdate(update: update)
|
try wallet.applyUpdate(update: update)
|
||||||
let address = wallet.revealNextAddress(keychain: KeychainKind.external).address.description
|
|
||||||
|
XCTAssertGreaterThan(wallet.getBalance().total(), UInt64(0), "Wallet must have positive balance, please add funds")
|
||||||
|
|
||||||
XCTAssertGreaterThan(
|
print("Balance: \(wallet.getBalance().total())")
|
||||||
wallet.balance().total.toSat(),
|
|
||||||
UInt64(0),
|
|
||||||
"Wallet must have positive balance, please send funds to \(address)"
|
|
||||||
)
|
|
||||||
|
|
||||||
print("Balance: \(wallet.balance().total)")
|
let recipient: Address = try Address(address: "tb1qrnfslnrve9uncz9pzpvf83k3ukz22ljgees989", network: .testnet)
|
||||||
|
let psbt: PartiallySignedTransaction = try
|
||||||
let recipient: Address = try Address(address: "tb1qrnfslnrve9uncz9pzpvf83k3ukz22ljgees989", network: .signet)
|
|
||||||
let psbt: Psbt = try
|
|
||||||
TxBuilder()
|
TxBuilder()
|
||||||
.addRecipient(script: recipient.scriptPubkey(), amount: Amount.fromSat(fromSat: 4200))
|
.addRecipient(script: recipient.scriptPubkey(), amount: 4200)
|
||||||
.feeRate(feeRate: FeeRate.fromSatPerVb(satPerVb: 2))
|
.feeRate(satPerVbyte: 2.0)
|
||||||
.finish(wallet: wallet)
|
.finish(wallet: wallet)
|
||||||
|
|
||||||
print(psbt.serialize())
|
print(psbt.serialize())
|
||||||
XCTAssertTrue(psbt.serialize().hasPrefix("cHNi"), "PSBT should start with cHNI")
|
XCTAssertTrue(psbt.serialize().hasPrefix("cHNi"), "PSBT should start with cHNI")
|
||||||
|
|
||||||
let walletDidSign: Bool = try wallet.sign(psbt: psbt)
|
let walletDidSign: Bool = try wallet.sign(psbt: psbt)
|
||||||
XCTAssertTrue(walletDidSign, "Wallet did not sign transaction")
|
XCTAssertTrue(walletDidSign, "Wallet did not sign transaction")
|
||||||
|
|
||||||
let tx: Transaction = try! psbt.extractTx()
|
let tx: Transaction = psbt.extractTx()
|
||||||
print(tx.computeTxid())
|
print(tx.txid())
|
||||||
let fee: Amount = try wallet.calculateFee(tx: tx)
|
|
||||||
print("Transaction Fee: \(fee)")
|
|
||||||
let feeRate: FeeRate = try wallet.calculateFeeRate(tx: tx)
|
|
||||||
print("Transaction Fee Rate: \(feeRate.toSatPerVbCeil()) sat/vB")
|
|
||||||
|
|
||||||
try esploraClient.broadcast(transaction: tx)
|
try esploraClient.broadcast(transaction: tx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,6 @@ final class OfflineDescriptorTests: XCTestCase {
|
|||||||
network: Network.testnet
|
network: Network.testnet
|
||||||
)
|
)
|
||||||
|
|
||||||
XCTAssertEqual(descriptor.description, "tr([be1eec8f/86'/1'/0']tpubDCTtszwSxPx3tATqDrsSyqScPNnUChwQAVAkanuDUCJQESGBbkt68nXXKRDifYSDbeMa2Xg2euKbXaU3YphvGWftDE7ozRKPriT6vAo3xsc/0/*)#m7puekcx")
|
XCTAssertEqual(descriptor.asString(), "tr([be1eec8f/86'/1'/0']tpubDCTtszwSxPx3tATqDrsSyqScPNnUChwQAVAkanuDUCJQESGBbkt68nXXKRDifYSDbeMa2Xg2euKbXaU3YphvGWftDE7ozRKPriT6vAo3xsc/0/*)#m7puekcx")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,43 +0,0 @@
|
|||||||
import XCTest
|
|
||||||
@testable import BitcoinDevKit
|
|
||||||
|
|
||||||
final class OfflinePersistenceTests: XCTestCase {
|
|
||||||
private let descriptor = try! Descriptor(
|
|
||||||
descriptor: "wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)",
|
|
||||||
network: Network.signet
|
|
||||||
)
|
|
||||||
private let changeDescriptor = try! Descriptor(
|
|
||||||
descriptor: "wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/1/*)",
|
|
||||||
network: Network.signet
|
|
||||||
)
|
|
||||||
var dbFilePath: URL!
|
|
||||||
|
|
||||||
override func setUpWithError() throws {
|
|
||||||
super.setUp()
|
|
||||||
|
|
||||||
guard let resourceUrl = Bundle.module.url(
|
|
||||||
forResource: "pre_existing_wallet_persistence_test",
|
|
||||||
withExtension: "sqlite"
|
|
||||||
) else {
|
|
||||||
print("error finding resourceURL")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
dbFilePath = resourceUrl
|
|
||||||
}
|
|
||||||
|
|
||||||
func testPersistence() throws {
|
|
||||||
let sqliteStore = try! SqliteStore(path: dbFilePath.path)
|
|
||||||
let initialChangeSet = try! sqliteStore.read()
|
|
||||||
let wallet = try Wallet.newOrLoad(
|
|
||||||
descriptor: descriptor,
|
|
||||||
changeDescriptor: changeDescriptor,
|
|
||||||
changeSet: initialChangeSet,
|
|
||||||
network: .signet
|
|
||||||
)
|
|
||||||
let nextAddress: AddressInfo = wallet.revealNextAddress(keychain: KeychainKind.external)
|
|
||||||
print("Address: \(nextAddress)")
|
|
||||||
|
|
||||||
XCTAssertTrue(nextAddress.address.description == "tb1qan3lldunh37ma6c0afeywgjyjgnyc8uz975zl2")
|
|
||||||
XCTAssertTrue(nextAddress.index == 7)
|
|
||||||
}
|
|
||||||
}
|
|
@ -2,62 +2,32 @@ import XCTest
|
|||||||
@testable import BitcoinDevKit
|
@testable import BitcoinDevKit
|
||||||
|
|
||||||
final class OfflineWalletTests: XCTestCase {
|
final class OfflineWalletTests: XCTestCase {
|
||||||
private let descriptor = try! Descriptor(
|
|
||||||
descriptor: "wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)",
|
|
||||||
network: Network.signet
|
|
||||||
)
|
|
||||||
private let changeDescriptor = try! Descriptor(
|
|
||||||
descriptor: "wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/1/*)",
|
|
||||||
network: Network.signet
|
|
||||||
)
|
|
||||||
var dbFilePath: URL!
|
|
||||||
|
|
||||||
override func setUpWithError() throws {
|
|
||||||
super.setUp()
|
|
||||||
let fileManager = FileManager.default
|
|
||||||
let documentDirectory = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first!
|
|
||||||
let uniqueDbFileName = "bdk_persistence_\(UUID().uuidString).sqlite"
|
|
||||||
dbFilePath = documentDirectory.appendingPathComponent(uniqueDbFileName)
|
|
||||||
|
|
||||||
if fileManager.fileExists(atPath: dbFilePath.path) {
|
|
||||||
try fileManager.removeItem(at: dbFilePath)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override func tearDownWithError() throws {
|
|
||||||
let fileManager = FileManager.default
|
|
||||||
if fileManager.fileExists(atPath: dbFilePath.path) {
|
|
||||||
try fileManager.removeItem(at: dbFilePath)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func testNewAddress() throws {
|
func testNewAddress() throws {
|
||||||
let wallet = try Wallet(
|
let descriptor: Descriptor = try Descriptor(
|
||||||
|
descriptor: "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)",
|
||||||
|
network: Network.testnet
|
||||||
|
)
|
||||||
|
let wallet: Wallet = try Wallet.newNoPersist(
|
||||||
descriptor: descriptor,
|
descriptor: descriptor,
|
||||||
changeDescriptor: changeDescriptor,
|
changeDescriptor: nil,
|
||||||
network: .testnet
|
network: .testnet
|
||||||
)
|
)
|
||||||
let addressInfo: AddressInfo = wallet.revealNextAddress(keychain: KeychainKind.external)
|
let addressInfo: AddressInfo = wallet.getAddress(addressIndex: AddressIndex.new)
|
||||||
|
|
||||||
XCTAssertTrue(addressInfo.address.isValidForNetwork(network: Network.testnet),
|
XCTAssertEqual(addressInfo.address.asString(), "tb1qzg4mckdh50nwdm9hkzq06528rsu73hjxxzem3e")
|
||||||
"Address is not valid for testnet network")
|
|
||||||
XCTAssertTrue(addressInfo.address.isValidForNetwork(network: Network.signet),
|
|
||||||
"Address is not valid for signet network")
|
|
||||||
XCTAssertFalse(addressInfo.address.isValidForNetwork(network: Network.regtest),
|
|
||||||
"Address is valid for regtest network, but it shouldn't be")
|
|
||||||
XCTAssertFalse(addressInfo.address.isValidForNetwork(network: Network.bitcoin),
|
|
||||||
"Address is valid for bitcoin network, but it shouldn't be")
|
|
||||||
|
|
||||||
XCTAssertEqual(addressInfo.address.description, "tb1qrnfslnrve9uncz9pzpvf83k3ukz22ljgees989")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func testBalance() throws {
|
func testBalance() throws {
|
||||||
let wallet = try Wallet(
|
let descriptor: Descriptor = try Descriptor(
|
||||||
|
descriptor: "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)",
|
||||||
|
network: Network.testnet
|
||||||
|
)
|
||||||
|
let wallet: Wallet = try Wallet.newNoPersist(
|
||||||
descriptor: descriptor,
|
descriptor: descriptor,
|
||||||
changeDescriptor: changeDescriptor,
|
changeDescriptor: nil,
|
||||||
network: .testnet
|
network: .testnet
|
||||||
)
|
)
|
||||||
|
|
||||||
XCTAssertEqual(wallet.balance().total.toSat(), 0)
|
XCTAssertEqual(wallet.getBalance().total(), 0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Binary file not shown.
53
bdk-swift/bdkFFI.xcframework/Info.plist
Normal file
53
bdk-swift/bdkFFI.xcframework/Info.plist
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>AvailableLibraries</key>
|
||||||
|
<array>
|
||||||
|
<dict>
|
||||||
|
<key>LibraryIdentifier</key>
|
||||||
|
<string>macos-arm64_x86_64</string>
|
||||||
|
<key>LibraryPath</key>
|
||||||
|
<string>bdkFFI.framework</string>
|
||||||
|
<key>SupportedArchitectures</key>
|
||||||
|
<array>
|
||||||
|
<string>arm64</string>
|
||||||
|
<string>x86_64</string>
|
||||||
|
</array>
|
||||||
|
<key>SupportedPlatform</key>
|
||||||
|
<string>macos</string>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>LibraryIdentifier</key>
|
||||||
|
<string>ios-arm64_x86_64-simulator</string>
|
||||||
|
<key>LibraryPath</key>
|
||||||
|
<string>bdkFFI.framework</string>
|
||||||
|
<key>SupportedArchitectures</key>
|
||||||
|
<array>
|
||||||
|
<string>arm64</string>
|
||||||
|
<string>x86_64</string>
|
||||||
|
</array>
|
||||||
|
<key>SupportedPlatform</key>
|
||||||
|
<string>ios</string>
|
||||||
|
<key>SupportedPlatformVariant</key>
|
||||||
|
<string>simulator</string>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>LibraryIdentifier</key>
|
||||||
|
<string>ios-arm64</string>
|
||||||
|
<key>LibraryPath</key>
|
||||||
|
<string>bdkFFI.framework</string>
|
||||||
|
<key>SupportedArchitectures</key>
|
||||||
|
<array>
|
||||||
|
<string>arm64</string>
|
||||||
|
</array>
|
||||||
|
<key>SupportedPlatform</key>
|
||||||
|
<string>ios</string>
|
||||||
|
</dict>
|
||||||
|
</array>
|
||||||
|
<key>CFBundlePackageType</key>
|
||||||
|
<string>XFWK</string>
|
||||||
|
<key>XCFrameworkFormatVersion</key>
|
||||||
|
<string>1.0</string>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
@ -0,0 +1,4 @@
|
|||||||
|
// This is the "umbrella header" for our combined Rust code library.
|
||||||
|
// It needs to import all of the individual headers.
|
||||||
|
|
||||||
|
#import "bdkFFI.h"
|
@ -0,0 +1,6 @@
|
|||||||
|
framework module bdkFFI {
|
||||||
|
umbrella header "bdkFFI-umbrella.h"
|
||||||
|
|
||||||
|
export *
|
||||||
|
module * { export * }
|
||||||
|
}
|
@ -0,0 +1,4 @@
|
|||||||
|
// This is the "umbrella header" for our combined Rust code library.
|
||||||
|
// It needs to import all of the individual headers.
|
||||||
|
|
||||||
|
#import "bdkFFI.h"
|
@ -0,0 +1,6 @@
|
|||||||
|
framework module bdkFFI {
|
||||||
|
umbrella header "bdkFFI-umbrella.h"
|
||||||
|
|
||||||
|
export *
|
||||||
|
module * { export * }
|
||||||
|
}
|
@ -0,0 +1,4 @@
|
|||||||
|
// This is the "umbrella header" for our combined Rust code library.
|
||||||
|
// It needs to import all of the individual headers.
|
||||||
|
|
||||||
|
#import "bdkFFI.h"
|
@ -0,0 +1,6 @@
|
|||||||
|
framework module bdkFFI {
|
||||||
|
umbrella header "bdkFFI-umbrella.h"
|
||||||
|
|
||||||
|
export *
|
||||||
|
module * { export * }
|
||||||
|
}
|
41
bdk-swift/build-local-swift.sh
Executable file
41
bdk-swift/build-local-swift.sh
Executable file
@ -0,0 +1,41 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# This script builds local swift-bdk Swift language bindings and corresponding bdkFFI.xcframework.
|
||||||
|
# The results of this script can be used for locally testing your SPM package adding a local package
|
||||||
|
# to your application pointing at the bdk-swift directory.
|
||||||
|
|
||||||
|
# Run the script from the repo root directory, ie: ./bdk-swift/build-local-swift.sh
|
||||||
|
|
||||||
|
rustup install 1.73.0
|
||||||
|
rustup component add rust-src
|
||||||
|
rustup target add aarch64-apple-ios x86_64-apple-ios
|
||||||
|
rustup target add aarch64-apple-ios-sim
|
||||||
|
rustup target add aarch64-apple-darwin x86_64-apple-darwin
|
||||||
|
|
||||||
|
pushd bdk-ffi
|
||||||
|
mkdir -p Sources/BitcoinDevKit
|
||||||
|
cargo run --bin uniffi-bindgen generate src/bdk.udl --language swift --out-dir ../bdk-swift/Sources/BitcoinDevKit --no-format
|
||||||
|
|
||||||
|
cargo build --package bdk-ffi --profile release-smaller --target x86_64-apple-darwin
|
||||||
|
cargo build --package bdk-ffi --profile release-smaller --target aarch64-apple-darwin
|
||||||
|
cargo build --package bdk-ffi --profile release-smaller --target x86_64-apple-ios
|
||||||
|
cargo build --package bdk-ffi --profile release-smaller --target aarch64-apple-ios
|
||||||
|
cargo build --package bdk-ffi --profile release-smaller --target aarch64-apple-ios-sim
|
||||||
|
|
||||||
|
mkdir -p target/lipo-ios-sim/release-smaller
|
||||||
|
lipo target/aarch64-apple-ios-sim/release-smaller/libbdkffi.a target/x86_64-apple-ios/release-smaller/libbdkffi.a -create -output target/lipo-ios-sim/release-smaller/libbdkffi.a
|
||||||
|
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
|
||||||
|
|
||||||
|
popd
|
||||||
|
pushd bdk-swift
|
||||||
|
mv Sources/BitcoinDevKit/bdk.swift Sources/BitcoinDevKit/BitcoinDevKit.swift
|
||||||
|
cp Sources/BitcoinDevKit/bdkFFI.h bdkFFI.xcframework/ios-arm64/bdkFFI.framework/Headers
|
||||||
|
cp Sources/BitcoinDevKit/bdkFFI.h bdkFFI.xcframework/ios-arm64_x86_64-simulator/bdkFFI.framework/Headers
|
||||||
|
cp Sources/BitcoinDevKit/bdkFFI.h bdkFFI.xcframework/macos-arm64_x86_64/bdkFFI.framework/Headers
|
||||||
|
cp ../bdk-ffi/target/aarch64-apple-ios/release-smaller/libbdkffi.a bdkFFI.xcframework/ios-arm64/bdkFFI.framework/bdkFFI
|
||||||
|
cp ../bdk-ffi/target/lipo-ios-sim/release-smaller/libbdkffi.a bdkFFI.xcframework/ios-arm64_x86_64-simulator/bdkFFI.framework/bdkFFI
|
||||||
|
cp ../bdk-ffi/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
|
||||||
|
#rm bdkFFI.xcframework.zip || true
|
||||||
|
#zip -9 -r bdkFFI.xcframework.zip bdkFFI.xcframework
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user