Compare commits
132 Commits
release/al
...
fix/live-t
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1b0b50a954 | ||
|
|
6e207802b2 | ||
|
|
ac15ed7380 | ||
|
|
e9a76287c8 | ||
|
|
72b5bfd4c9 | ||
|
|
19723240b7 | ||
|
|
7d951578d0 | ||
|
|
b7fe91b003 | ||
|
|
330dc96b8a | ||
|
|
5557bb94ea | ||
|
|
9b5b96710e | ||
|
|
75d155c67a | ||
|
|
6522dfdd26 | ||
|
|
ebaa6fda2f | ||
|
|
5e8271e158 | ||
|
|
431ab90f04 | ||
|
|
d4736a64d1 | ||
|
|
f31678bf37 | ||
|
|
8130a419f2 | ||
|
|
262704751c | ||
|
|
00a8e1ba8b | ||
|
|
d0514f678e | ||
|
|
f6cc63539d | ||
|
|
df64a96dd2 | ||
|
|
4cd6a80ce0 | ||
|
|
e48af63fe6 | ||
|
|
eff4abcbfb | ||
|
|
e1a93379ce | ||
|
|
e609b57bff | ||
|
|
282fcfce0a | ||
|
|
4dd4e91ccd | ||
|
|
4f8b7f4ba5 | ||
|
|
4d737d3393 | ||
|
|
e14124b454 | ||
|
|
0a75fc1279 | ||
|
|
6ac386c8df | ||
|
|
126bc61df6 | ||
|
|
c63e7ad392 | ||
|
|
edea8e8c80 | ||
|
|
6bc974ed2e | ||
|
|
2f7652b979 | ||
|
|
fa17a862ce | ||
|
|
77cfee718c | ||
|
|
aa1c0de244 | ||
|
|
aee84f9634 | ||
|
|
a35fdaee61 | ||
|
|
dccc85d8ff | ||
|
|
f1744d192f | ||
|
|
cacb78f4dc | ||
|
|
54f3235254 | ||
|
|
806e29b93c | ||
|
|
97a104fd5f | ||
|
|
0e617bc986 | ||
|
|
ab2e97e782 | ||
|
|
84f2497aeb | ||
|
|
ab87355f9d | ||
|
|
a40702ebd9 | ||
|
|
0eadff1327 | ||
|
|
78ef936369 | ||
|
|
7b9e71714f | ||
|
|
f698d46392 | ||
|
|
ab9763bb58 | ||
|
|
aa035588a0 | ||
|
|
e774c94998 | ||
|
|
5e41275f29 | ||
|
|
15ac8c8ffb | ||
|
|
358b43c31e | ||
|
|
08f1957fe0 | ||
|
|
a8128e5056 | ||
|
|
b879bf4a50 | ||
|
|
43ff1fb394 | ||
|
|
a8541ecd40 | ||
|
|
c666bb247a | ||
|
|
d066007d9c | ||
|
|
c6d17b77ec | ||
|
|
9a996b1a94 | ||
|
|
c68c13fbfa | ||
|
|
0b851d441f | ||
|
|
267685bd58 | ||
|
|
896303f487 | ||
|
|
9ea9e9384c | ||
|
|
2e0cd058f4 | ||
|
|
c3313fb922 | ||
|
|
5fc30c6c26 | ||
|
|
38ccbb17be | ||
|
|
89f36a6c88 | ||
|
|
6ea0518a54 | ||
|
|
738f53e991 | ||
|
|
61e58240fc | ||
|
|
06fa9d751b | ||
|
|
145652beaa | ||
|
|
2dd6ee33f1 | ||
|
|
b249dae875 | ||
|
|
43c1ca66b8 | ||
|
|
68a9eb693d | ||
|
|
6022a703c6 | ||
|
|
99a3d74a4a | ||
|
|
50f102bbd3 | ||
|
|
141705e2ed | ||
|
|
619884eaed | ||
|
|
794f2bc995 | ||
|
|
991de09219 | ||
|
|
6f4b4c9155 | ||
|
|
34c76feb71 | ||
|
|
8e9d2ddc14 | ||
|
|
7319aea562 | ||
|
|
54beb23f13 | ||
|
|
ccf5fbda6e | ||
|
|
4f6c198168 | ||
|
|
3789c1dcd6 | ||
|
|
fc25cd709a | ||
|
|
cdec63efa3 | ||
|
|
bcb77c45f4 | ||
|
|
a1a45996fc | ||
|
|
bbc6e1a43c | ||
|
|
252bcaa444 | ||
|
|
643d254ab6 | ||
|
|
c79a39914e | ||
|
|
3df92527e9 | ||
|
|
fe65c70915 | ||
|
|
ce219e4ac4 | ||
|
|
c9019e9fcf | ||
|
|
53b59d5100 | ||
|
|
4a3cf49f24 | ||
|
|
40940c38af | ||
|
|
46e0324f28 | ||
|
|
6b177f0893 | ||
|
|
05ce7dad31 | ||
|
|
e5ded1a726 | ||
|
|
022e29faaa | ||
|
|
0c0dfd5f42 | ||
|
|
24e09a40cb |
87
.github/ISSUE_TEMPLATE/minor_release.md
vendored
87
.github/ISSUE_TEMPLATE/minor_release.md
vendored
@@ -1,87 +0,0 @@
|
||||
---
|
||||
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
Normal file
95
.github/ISSUE_TEMPLATE/release.md
vendored
Normal file
@@ -0,0 +1,95 @@
|
||||
---
|
||||
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:
|
||||
matrix:
|
||||
rust:
|
||||
- version: 1.73.0
|
||||
- version: 1.77.1
|
||||
clippy: true
|
||||
steps:
|
||||
- name: "Checkout"
|
||||
@@ -56,7 +56,7 @@ jobs:
|
||||
run: cargo clippy --all-targets --features "uniffi/bindgen-tests" -- -D warnings
|
||||
|
||||
- name: "Test"
|
||||
run: CLASSPATH=./tests/jna/jna-5.8.0.jar cargo test --features uniffi/bindgen-tests
|
||||
run: CLASSPATH=./tests/jna/jna-5.14.0.jar cargo test --features uniffi/bindgen-tests
|
||||
|
||||
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
|
||||
java-version: 11
|
||||
|
||||
- name: "Set default Rust version to 1.73.0"
|
||||
run: rustup default 1.73.0
|
||||
- name: "Set default Rust version to 1.77.1"
|
||||
run: rustup default 1.77.1
|
||||
|
||||
- name: "Build bdk-jvm library"
|
||||
run: |
|
||||
@@ -48,7 +48,8 @@ jobs:
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: "Build Swift package"
|
||||
run: bash ./bdk-swift/build-local-swift.sh
|
||||
working-directory: bdk-swift
|
||||
run: bash ./build-local-swift.sh
|
||||
|
||||
- name: "Run live Swift tests"
|
||||
working-directory: bdk-swift
|
||||
@@ -74,9 +75,11 @@ jobs:
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: true
|
||||
- uses: actions-rs/toolchain@v1
|
||||
|
||||
- name: "Install Rust 1.77.1"
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
toolchain: 1.77.1
|
||||
|
||||
- name: "Generate bdk.py and binaries"
|
||||
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
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: 11
|
||||
java-version: 17
|
||||
|
||||
- name: "Set default Rust version to 1.73.0"
|
||||
run: rustup default 1.73.0
|
||||
- name: "Set default Rust version to 1.77.1"
|
||||
run: rustup default 1.77.1
|
||||
|
||||
- name: "Install Rust Android targets"
|
||||
run: rustup target add x86_64-linux-android aarch64-linux-android armv7-linux-androideabi
|
||||
@@ -36,7 +36,7 @@ jobs:
|
||||
cd bdk-android
|
||||
./gradlew buildAndroidLib
|
||||
|
||||
- name: "Publish to Maven Local and Maven Central"
|
||||
- name: "Publish to Maven Central"
|
||||
env:
|
||||
ORG_GRADLE_PROJECT_signingKeyId: ${{ secrets.PGP_KEY_ID }}
|
||||
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
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: 11
|
||||
java-version: 17
|
||||
|
||||
- name: "Set default Rust version to 1.73.0"
|
||||
run: rustup default 1.73.0
|
||||
- name: "Set default Rust version to 1.77.1"
|
||||
run: rustup default 1.77.1
|
||||
|
||||
- name: "Install aarch64 Rust target"
|
||||
run: rustup target add aarch64-apple-darwin
|
||||
@@ -52,10 +52,10 @@ jobs:
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: 11
|
||||
java-version: 17
|
||||
|
||||
- name: "Set default Rust version to 1.73.0"
|
||||
run: rustup default 1.73.0
|
||||
- name: "Set default Rust version to 1.77.1"
|
||||
run: rustup default 1.77.1
|
||||
|
||||
- name: "Install x86_64-pc-windows-msvc Rust target"
|
||||
run: rustup target add x86_64-pc-windows-msvc
|
||||
@@ -92,10 +92,10 @@ jobs:
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: 11
|
||||
java-version: 17
|
||||
|
||||
- name: "Set default Rust version to 1.73.0"
|
||||
run: rustup default 1.73.0
|
||||
- name: "Set default Rust version to 1.77.1"
|
||||
run: rustup default 1.77.1
|
||||
|
||||
- name: "Build bdk-jvm library"
|
||||
run: |
|
||||
|
||||
17
.github/workflows/publish-python.yaml
vendored
17
.github/workflows/publish-python.yaml
vendored
@@ -24,16 +24,19 @@ jobs:
|
||||
- cp38-cp38
|
||||
- cp39-cp39
|
||||
- cp310-cp310
|
||||
- cp311-cp311
|
||||
- cp312-cp312
|
||||
steps:
|
||||
- name: "Checkout"
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: true
|
||||
# TODO 2: Other CI workflows use explicit Rust compiler versions, I think we should do the same here
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
|
||||
- name: "Install Rust 1.77.1"
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: 1.77.1
|
||||
|
||||
- name: "Generate bdk.py and binaries"
|
||||
run: bash ./scripts/generate-linux.sh
|
||||
|
||||
@@ -59,6 +62,8 @@ jobs:
|
||||
- "3.8"
|
||||
- "3.9"
|
||||
- "3.10"
|
||||
- "3.11"
|
||||
- "3.12"
|
||||
steps:
|
||||
- name: "Checkout"
|
||||
uses: actions/checkout@v3
|
||||
@@ -96,6 +101,8 @@ jobs:
|
||||
- "3.8"
|
||||
- "3.9"
|
||||
- "3.10"
|
||||
- "3.11"
|
||||
- "3.12"
|
||||
steps:
|
||||
- name: "Checkout"
|
||||
uses: actions/checkout@v3
|
||||
@@ -132,6 +139,8 @@ jobs:
|
||||
- "3.8"
|
||||
- "3.9"
|
||||
- "3.10"
|
||||
- "3.11"
|
||||
- "3.12"
|
||||
steps:
|
||||
- name: "Checkout"
|
||||
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
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: 11
|
||||
java-version: 17
|
||||
|
||||
- name: "Set default Rust version to 1.73.0"
|
||||
run: rustup default 1.73.0
|
||||
- name: "Set default Rust version to 1.77.1"
|
||||
run: rustup default 1.77.1
|
||||
|
||||
- name: "Install Rust Android targets"
|
||||
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
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: 11
|
||||
java-version: 17
|
||||
|
||||
- name: "Set default Rust version to 1.73.0"
|
||||
run: rustup default 1.73.0
|
||||
- name: "Set default Rust version to 1.77.1"
|
||||
run: rustup default 1.77.1
|
||||
|
||||
- name: "Run JVM tests"
|
||||
run: |
|
||||
|
||||
16
.github/workflows/test-python.yaml
vendored
16
.github/workflows/test-python.yaml
vendored
@@ -33,15 +33,19 @@ jobs:
|
||||
- cp38-cp38
|
||||
- cp39-cp39
|
||||
- cp310-cp310
|
||||
- cp311-cp311
|
||||
- cp312-cp312
|
||||
steps:
|
||||
- name: "Checkout"
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: true
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
|
||||
- name: "Install Rust 1.77.1"
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: 1.77.1
|
||||
|
||||
- name: "Generate bdk.py and binaries"
|
||||
run: bash ./scripts/generate-linux.sh
|
||||
|
||||
@@ -74,6 +78,8 @@ jobs:
|
||||
- "3.8"
|
||||
- "3.9"
|
||||
- "3.10"
|
||||
- "3.11"
|
||||
- "3.12"
|
||||
steps:
|
||||
- name: "Checkout"
|
||||
uses: actions/checkout@v3
|
||||
@@ -117,6 +123,8 @@ jobs:
|
||||
- "3.8"
|
||||
- "3.9"
|
||||
- "3.10"
|
||||
- "3.11"
|
||||
- "3.12"
|
||||
steps:
|
||||
- name: "Checkout"
|
||||
uses: actions/checkout@v3
|
||||
@@ -158,6 +166,8 @@ jobs:
|
||||
- "3.8"
|
||||
- "3.9"
|
||||
- "3.10"
|
||||
- "3.11"
|
||||
- "3.12"
|
||||
steps:
|
||||
- name: "Checkout"
|
||||
uses: actions/checkout@v3
|
||||
|
||||
3
.github/workflows/test-swift.yaml
vendored
3
.github/workflows/test-swift.yaml
vendored
@@ -19,7 +19,8 @@ jobs:
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: "Build Swift package"
|
||||
run: bash ./bdk-swift/build-local-swift.sh
|
||||
working-directory: bdk-swift
|
||||
run: bash ./build-local-swift.sh
|
||||
|
||||
- name: "Run Swift tests"
|
||||
working-directory: bdk-swift
|
||||
|
||||
29
CHANGELOG.md
29
CHANGELOG.md
@@ -1,8 +1,30 @@
|
||||
# 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.
|
||||
|
||||
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).
|
||||
|
||||
## [1.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.
|
||||
|
||||
## [1.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
|
||||
|
||||
## [0.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
|
||||
|
||||
## [0.30.0]
|
||||
This release has a new API and a few internal optimizations and refactorings.
|
||||
@@ -223,6 +245,9 @@ Changelog
|
||||
|
||||
[BIP 0174]:https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki#encoding
|
||||
|
||||
[1.0.0-alpha.7]: https://github.com/bitcoindevkit/bdk-ffi/compare/v1.0.0-alpha.2a...v1.0.0-alpha.7
|
||||
[1.0.0-alpha.2a]: https://github.com/bitcoindevkit/bdk-ffi/compare/v0.31.0...v1.0.0-alpha.2a
|
||||
[v0.31.0]: https://github.com/bitcoindevkit/bdk-ffi/compare/v0.30.0...v0.31.0
|
||||
[v0.30.0]: https://github.com/bitcoindevkit/bdk-ffi/compare/v0.29.0...v0.30.0
|
||||
[v0.29.0]: https://github.com/bitcoindevkit/bdk-ffi/compare/v0.28.0...v0.29.0
|
||||
[v0.28.0]: https://github.com/bitcoindevkit/bdk-ffi/compare/v0.27.1...v0.28.0
|
||||
|
||||
27
README.md
27
README.md
@@ -8,7 +8,7 @@
|
||||
</p>
|
||||
|
||||
## 🚨 Warning 🚨
|
||||
The `master` branch of this repository is being migrated to the [bdk 1.0 API](https://github.com/bitcoindevkit/bdk) and is incomplete. For production-ready libraries, use the [`0.30.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.31.X`](https://github.com/bitcoindevkit/bdk-ffi/tree/release/0.30) releases.
|
||||
|
||||
## Readme
|
||||
The workspace in this repository creates the `libbdkffi` multi-language library for the Rust-based
|
||||
@@ -26,22 +26,21 @@ The below directories (a separate repository in the case of bdk-swift) include i
|
||||
| Swift | iOS, macOS | [bdk-swift (GitHub)] | [Readme bdk-swift] | |
|
||||
| Python | linux, macOS, Windows | [bdk-python (PyPI)] | [Readme bdk-python] | |
|
||||
|
||||
## Minimum Supported Rust Version (MSRV)
|
||||
## 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
|
||||
|
||||
This library should compile with any combination of features with Rust 1.73.0.
|
||||
just build
|
||||
just offlinetests
|
||||
just publishlocal
|
||||
```
|
||||
|
||||
## Minimum Supported Rust Version (MSRV)
|
||||
This library should compile with any combination of features with Rust 1.77.1.
|
||||
|
||||
## Contributing
|
||||
|
||||
### 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`
|
||||
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.
|
||||
|
||||
## Goals
|
||||
1. Language bindings should feel idiomatic in target languages/platforms
|
||||
|
||||
@@ -13,24 +13,6 @@ 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
|
||||
To use a snapshot release, specify the snapshot repository url in the `repositories` block and use the snapshot version in the `dependencies` block:
|
||||
```kotlin
|
||||
@@ -49,36 +31,41 @@ dependencies {
|
||||
* [Padawan Wallet](https://github.com/thunderbiscuit/padawan-wallet)
|
||||
|
||||
### How to build
|
||||
_Note that Kotlin version `1.6.10` or later is required to build the library._
|
||||
_Note that Kotlin version `1.9.23` or later is required to build the library._
|
||||
|
||||
1. Clone this repository.
|
||||
```shell
|
||||
git clone https://github.com/bitcoindevkit/bdk-ffi
|
||||
```
|
||||
2. Follow the "General" bdk-ffi ["Getting Started (Developer)"] instructions.
|
||||
3. Install Rust (note that we are currently building using Rust 1.73.0):
|
||||
3. Install Rust (note that we are currently building using Rust 1.77.1):
|
||||
```shell
|
||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
|
||||
rustup default 1.73.0
|
||||
rustup default 1.77.1
|
||||
```
|
||||
4. Install required targets
|
||||
```sh
|
||||
rustup target add x86_64-linux-android aarch64-linux-android armv7-linux-androideabi
|
||||
```
|
||||
5. Install Android SDK and Build-Tools for API level 30+
|
||||
6. Setup `$ANDROID_SDK_ROOT` and `$ANDROID_NDK_ROOT` path variables (which are required by the
|
||||
build tool), for example (note that currently, NDK version 25.2.9519653 or above is required):
|
||||
6. Setup `ANDROID_SDK_ROOT` and `ANDROID_NDK_ROOT` path variables which are required by the build tool. Note that currently, NDK version 25.2.9519653 or above is required. For example:
|
||||
```shell
|
||||
export ANDROID_SDK_ROOT=~/Android/Sdk
|
||||
# macOS
|
||||
export ANDROID_SDK_ROOT=~/Library/Android/sdk
|
||||
export ANDROID_NDK_ROOT=$ANDROID_SDK_ROOT/ndk/25.2.9519653
|
||||
|
||||
# linux
|
||||
export ANDROID_SDK_ROOT=/usr/local/lib/android/sdk
|
||||
export ANDROID_NDK_ROOT=$ANDROID_SDK_ROOT/ndk/25.2.9519653
|
||||
```
|
||||
|
||||
7. Build kotlin bindings
|
||||
```sh
|
||||
# build Android library
|
||||
cd bdk-android
|
||||
./gradlew buildAndroidLib
|
||||
```
|
||||
8. Start android emulator (must be x86_64) and run tests
|
||||
1. Start android emulator and run tests
|
||||
```sh
|
||||
./gradlew connectedAndroidTest
|
||||
```
|
||||
@@ -86,7 +73,7 @@ export ANDROID_NDK_ROOT=$ANDROID_SDK_ROOT/ndk/25.2.9519653
|
||||
## How to publish to your local Maven repo
|
||||
```shell
|
||||
cd bdk-android
|
||||
./gradlew publishToMavenLocal --exclude-task signMavenPublication
|
||||
./gradlew publishToMavenLocal -P localBuild
|
||||
```
|
||||
|
||||
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:
|
||||
@@ -95,7 +82,7 @@ signing.gnupg.keyName=<YOUR_GNUPG_ID>
|
||||
signing.gnupg.passphrase=<YOUR_GNUPG_PASSPHRASE>
|
||||
```
|
||||
|
||||
and use the `publishToMavenLocal` task without excluding the signing task:
|
||||
and use the `publishToMavenLocal` task without the `localBuild` flag:
|
||||
```shell
|
||||
./gradlew publishToMavenLocal
|
||||
```
|
||||
|
||||
@@ -1,14 +1,10 @@
|
||||
buildscript {
|
||||
repositories {
|
||||
google()
|
||||
}
|
||||
dependencies {
|
||||
classpath("com.android.tools.build:gradle:7.1.2")
|
||||
}
|
||||
}
|
||||
|
||||
plugins {
|
||||
id("io.github.gradle-nexus.publish-plugin") version "1.1.0"
|
||||
id("com.android.library").version("8.3.1").apply(false)
|
||||
id("org.jetbrains.kotlin.android").version("1.9.23").apply(false)
|
||||
id("org.gradle.maven-publish")
|
||||
id("org.gradle.signing")
|
||||
id("org.bitcoindevkit.plugins.generate-android-bindings").apply(false)
|
||||
id("io.github.gradle-nexus.publish-plugin").version("1.1.0").apply(true)
|
||||
}
|
||||
|
||||
// library version is defined in gradle.properties
|
||||
|
||||
@@ -2,4 +2,4 @@ org.gradle.jvmargs=-Xmx1536m
|
||||
android.useAndroidX=true
|
||||
android.enableJetifier=true
|
||||
kotlin.code.style=official
|
||||
libraryVersion=0.31.0-SNAPSHOT
|
||||
libraryVersion=1.0.0-alpha.10-SNAPSHOT
|
||||
|
||||
BIN
bdk-android/gradle/wrapper/gradle-wrapper.jar
vendored
BIN
bdk-android/gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
@@ -1,5 +1,7 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.2-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-all.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
||||
41
bdk-android/gradlew
vendored
41
bdk-android/gradlew
vendored
@@ -55,7 +55,7 @@
|
||||
# Darwin, MinGW, and NonStop.
|
||||
#
|
||||
# (3) This script is generated from the Groovy template
|
||||
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# within the Gradle project.
|
||||
#
|
||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||
@@ -80,13 +80,11 @@ do
|
||||
esac
|
||||
done
|
||||
|
||||
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
||||
|
||||
APP_NAME="Gradle"
|
||||
# This is normally unused
|
||||
# shellcheck disable=SC2034
|
||||
APP_BASE_NAME=${0##*/}
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
# 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
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD=maximum
|
||||
@@ -133,22 +131,29 @@ location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD=java
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
if ! command -v java >/dev/null 2>&1
|
||||
then
|
||||
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||
case $MAX_FD in #(
|
||||
max*)
|
||||
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC2039,SC3045
|
||||
MAX_FD=$( ulimit -H -n ) ||
|
||||
warn "Could not query maximum file descriptor limit"
|
||||
esac
|
||||
case $MAX_FD in #(
|
||||
'' | soft) :;; #(
|
||||
*)
|
||||
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC2039,SC3045
|
||||
ulimit -n "$MAX_FD" ||
|
||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||
esac
|
||||
@@ -193,11 +198,15 @@ if "$cygwin" || "$msys" ; then
|
||||
done
|
||||
fi
|
||||
|
||||
# Collect all arguments for the java command;
|
||||
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
|
||||
# shell script including quotes and variable substitutions, so put them in
|
||||
# double quotes to make sure that they get re-expanded; and
|
||||
# * put everything else in single quotes, so that it's not re-expanded.
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Collect all arguments for the java command:
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
||||
# and any embedded shellness will be escaped.
|
||||
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
|
||||
# treated as '${Hostname}' itself on the command line.
|
||||
|
||||
set -- \
|
||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||
@@ -205,6 +214,12 @@ set -- \
|
||||
org.gradle.wrapper.GradleWrapperMain \
|
||||
"$@"
|
||||
|
||||
# Stop when "xargs" is not available.
|
||||
if ! command -v xargs >/dev/null 2>&1
|
||||
then
|
||||
die "xargs is not available"
|
||||
fi
|
||||
|
||||
# Use "xargs" to parse quoted args.
|
||||
#
|
||||
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||
|
||||
15
bdk-android/gradlew.bat
vendored
15
bdk-android/gradlew.bat
vendored
@@ -14,7 +14,7 @@
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%" == "" @echo off
|
||||
@if "%DEBUG%"=="" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@@ -25,7 +25,8 @@
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
if "%DIRNAME%"=="" set DIRNAME=.
|
||||
@rem This is normally unused
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@@ -40,7 +41,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto execute
|
||||
if %ERRORLEVEL% equ 0 goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
@@ -75,13 +76,15 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||
if %ERRORLEVEL% equ 0 goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||
exit /b 1
|
||||
set EXIT_CODE=%ERRORLEVEL%
|
||||
if %EXIT_CODE% equ 0 set EXIT_CODE=1
|
||||
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
|
||||
exit /b %EXIT_CODE%
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
20
bdk-android/justfile
Normal file
20
bdk-android/justfile
Normal file
@@ -0,0 +1,20 @@
|
||||
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,25 +5,21 @@ val libraryVersion: String by project
|
||||
|
||||
plugins {
|
||||
id("com.android.library")
|
||||
id("org.jetbrains.kotlin.android") version "1.6.10"
|
||||
id("maven-publish")
|
||||
id("signing")
|
||||
id("org.jetbrains.kotlin.android")
|
||||
id("org.gradle.maven-publish")
|
||||
id("org.gradle.signing")
|
||||
|
||||
// Custom plugin to generate the native libs and bindings file
|
||||
id("org.bitcoindevkit.plugins.generate-android-bindings")
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
google()
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdk = 31
|
||||
namespace = "org.bitcoindevkit"
|
||||
compileSdk = 34
|
||||
|
||||
defaultConfig {
|
||||
minSdk = 21
|
||||
targetSdk = 31
|
||||
targetSdk = 34
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
consumerProguardFiles("consumer-rules.pro")
|
||||
}
|
||||
@@ -43,8 +39,22 @@ android {
|
||||
}
|
||||
}
|
||||
|
||||
kotlin {
|
||||
tasks.withType<KotlinCompile>().configureEach {
|
||||
kotlinOptions {
|
||||
jvmTarget = "17"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
java {
|
||||
toolchain {
|
||||
languageVersion.set(JavaLanguageVersion.of(17))
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("net.java.dev.jna:jna:5.8.0@aar")
|
||||
implementation("net.java.dev.jna:jna:5.14.0@aar")
|
||||
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk7")
|
||||
implementation("androidx.appcompat:appcompat:1.4.0")
|
||||
implementation("androidx.core:core-ktx:1.7.0")
|
||||
@@ -80,6 +90,13 @@ afterEvaluate {
|
||||
url.set("https://github.com/bitcoindevkit/bdk/blob/master/LICENSE-MIT")
|
||||
}
|
||||
}
|
||||
developers {
|
||||
developer {
|
||||
id.set("bdkdevelopers")
|
||||
name.set("Bitcoin Dev Kit Developers")
|
||||
email.set("dev@bitcoindevkit.org")
|
||||
}
|
||||
}
|
||||
scm {
|
||||
connection.set("scm:git:github.com/bitcoindevkit/bdk-ffi.git")
|
||||
developerConnection.set("scm:git:ssh://github.com/bitcoindevkit/bdk-ffi.git")
|
||||
@@ -89,9 +106,22 @@ afterEvaluate {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This is required because we must ensure the moveNativeAndroidLibs task is executed after
|
||||
// the mergeReleaseJniLibFolders (hard requirement introduced by our upgrade to Gradle 8.7)
|
||||
tasks.named("mergeReleaseJniLibFolders") {
|
||||
dependsOn(":lib:moveNativeAndroidLibs")
|
||||
}
|
||||
tasks.named("mergeDebugJniLibFolders") {
|
||||
dependsOn(":lib:moveNativeAndroidLibs")
|
||||
}
|
||||
}
|
||||
|
||||
signing {
|
||||
if (project.hasProperty("localBuild")) {
|
||||
isRequired = false
|
||||
}
|
||||
|
||||
val signingKeyId: String? by project
|
||||
val signingKey: String? by project
|
||||
val signingPassword: String? by project
|
||||
@@ -99,8 +129,7 @@ signing {
|
||||
sign(publishing.publications)
|
||||
}
|
||||
|
||||
// This task dependency ensures that we build the bindings
|
||||
// binaries before running the tests
|
||||
// This task dependency ensures that we build the bindings binaries before running the tests
|
||||
tasks.withType<KotlinCompile> {
|
||||
dependsOn("buildAndroidLib")
|
||||
}
|
||||
|
||||
@@ -1,30 +1,81 @@
|
||||
package org.bitcoindevkit
|
||||
|
||||
import org.junit.Test
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import java.io.File
|
||||
import kotlin.test.AfterTest
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
private const val SIGNET_ESPLORA_URL = "http://signet.bitcoindevkit.net"
|
||||
private const val TESTNET_ESPLORA_URL = "https://esplora.testnet.kuutamo.cloud"
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class LiveTxBuilderTest {
|
||||
private val persistenceFilePath = InstrumentationRegistry
|
||||
.getInstrumentation().targetContext.filesDir.path + "/bdk_persistence.db"
|
||||
|
||||
@AfterTest
|
||||
fun cleanup() {
|
||||
val file = File(persistenceFilePath)
|
||||
if (file.exists()) {
|
||||
file.delete()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testTxBuilder() {
|
||||
val descriptor = Descriptor("wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)", Network.TESTNET)
|
||||
val wallet = Wallet.newNoPersist(descriptor, null, Network.TESTNET)
|
||||
val esploraClient = EsploraClient("https://mempool.space/testnet/api")
|
||||
val update = esploraClient.scan(wallet, 10uL, 1uL)
|
||||
val descriptor = Descriptor("wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)", Network.SIGNET)
|
||||
val wallet = Wallet(descriptor, null, persistenceFilePath, 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.getBalance().total()}")
|
||||
wallet.commit()
|
||||
println("Balance: ${wallet.getBalance().total}")
|
||||
|
||||
assert(wallet.getBalance().total() > 0uL)
|
||||
assert(wallet.getBalance().total > 0uL)
|
||||
|
||||
val recipient: Address = Address("tb1qrnfslnrve9uncz9pzpvf83k3ukz22ljgees989", Network.TESTNET)
|
||||
val psbt: PartiallySignedTransaction = TxBuilder()
|
||||
val recipient: Address = Address("tb1qrnfslnrve9uncz9pzpvf83k3ukz22ljgees989", Network.SIGNET)
|
||||
val psbt: Psbt = TxBuilder()
|
||||
.addRecipient(recipient.scriptPubkey(), 4200uL)
|
||||
.feeRate(2.0f)
|
||||
.feeRate(FeeRate.fromSatPerVb(2uL))
|
||||
.finish(wallet)
|
||||
|
||||
println(psbt.serialize())
|
||||
assertTrue(psbt.serialize().startsWith("cHNi"), "PSBT should start with 'cHNi'")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun complexTxBuilder() {
|
||||
val externalDescriptor = Descriptor("wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)", Network.SIGNET)
|
||||
val changeDescriptor = Descriptor("wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/1/*)", Network.SIGNET)
|
||||
val wallet = Wallet(externalDescriptor, changeDescriptor, persistenceFilePath, Network.TESTNET)
|
||||
val esploraClient = EsploraClient(SIGNET_ESPLORA_URL)
|
||||
val fullScanRequest: FullScanRequest = wallet.startFullScan()
|
||||
val update = esploraClient.fullScan(fullScanRequest, 10uL, 1uL)
|
||||
wallet.applyUpdate(update)
|
||||
wallet.commit()
|
||||
println("Balance: ${wallet.getBalance().total}")
|
||||
|
||||
assert(wallet.getBalance().total > 0uL)
|
||||
|
||||
val recipient1: Address = Address("tb1qrnfslnrve9uncz9pzpvf83k3ukz22ljgees989", Network.SIGNET)
|
||||
val recipient2: Address = Address("tb1qw2c3lxufxqe2x9s4rdzh65tpf4d7fssjgh8nv6", Network.SIGNET)
|
||||
val allRecipients: List<ScriptAmount> = listOf(
|
||||
ScriptAmount(recipient1.scriptPubkey(), 4200uL),
|
||||
ScriptAmount(recipient2.scriptPubkey(), 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,46 +2,74 @@ package org.bitcoindevkit
|
||||
|
||||
import org.junit.Test
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import org.junit.runner.RunWith
|
||||
import java.io.File
|
||||
import kotlin.test.AfterTest
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
private const val SIGNET_ESPLORA_URL = "http://signet.bitcoindevkit.net"
|
||||
private const val TESTNET_ESPLORA_URL = "https://esplora.testnet.kuutamo.cloud"
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class LiveWalletTest {
|
||||
private val persistenceFilePath = InstrumentationRegistry
|
||||
.getInstrumentation().targetContext.filesDir.path + "/bdk_persistence.db"
|
||||
|
||||
@AfterTest
|
||||
fun cleanup() {
|
||||
val file = File(persistenceFilePath)
|
||||
if (file.exists()) {
|
||||
file.delete()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSyncedBalance() {
|
||||
val descriptor: Descriptor = Descriptor("wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)", Network.TESTNET)
|
||||
val wallet: Wallet = Wallet.newNoPersist(descriptor, null, Network.TESTNET)
|
||||
val esploraClient: EsploraClient = EsploraClient("https://mempool.space/testnet/api")
|
||||
// val esploraClient = EsploraClient("https://blockstream.info/testnet/api")
|
||||
val update = esploraClient.scan(wallet, 10uL, 1uL)
|
||||
val descriptor: Descriptor = Descriptor("wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)", Network.SIGNET)
|
||||
val wallet: Wallet = Wallet(descriptor, null, persistenceFilePath, 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.getBalance().total()}")
|
||||
wallet.commit()
|
||||
println("Balance: ${wallet.getBalance().total}")
|
||||
val balance: Balance = wallet.getBalance()
|
||||
println("Balance: $balance")
|
||||
|
||||
assert(wallet.getBalance().total() > 0uL)
|
||||
assert(wallet.getBalance().total > 0uL)
|
||||
|
||||
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.txid()}")
|
||||
println("Sent ${sentAndReceived.sent}")
|
||||
println("Received ${sentAndReceived.received}")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testBroadcastTransaction() {
|
||||
val descriptor = Descriptor("wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)", Network.TESTNET)
|
||||
val wallet = Wallet.newNoPersist(descriptor, null, Network.TESTNET)
|
||||
val esploraClient = EsploraClient("https://mempool.space/testnet/api")
|
||||
val update = esploraClient.scan(wallet, 10uL, 1uL)
|
||||
|
||||
val descriptor = Descriptor("wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)", Network.SIGNET)
|
||||
val wallet = Wallet(descriptor, null, persistenceFilePath, 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.getBalance().total()}")
|
||||
println("New address: ${wallet.getAddress(AddressIndex.New).address}")
|
||||
wallet.commit()
|
||||
println("Balance: ${wallet.getBalance().total}")
|
||||
println("New address: ${wallet.revealNextAddress(KeychainKind.EXTERNAL).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."
|
||||
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.TESTNET)
|
||||
val recipient: Address = Address("tb1qrnfslnrve9uncz9pzpvf83k3ukz22ljgees989", Network.SIGNET)
|
||||
|
||||
val psbt: PartiallySignedTransaction = TxBuilder()
|
||||
val psbt: Psbt = TxBuilder()
|
||||
.addRecipient(recipient.scriptPubkey(), 4200uL)
|
||||
.feeRate(4.0f)
|
||||
.feeRate(FeeRate.fromSatPerVb(4uL))
|
||||
.finish(wallet)
|
||||
|
||||
println(psbt.serialize())
|
||||
@@ -51,8 +79,14 @@ class LiveWalletTest {
|
||||
assertTrue(walletDidSign)
|
||||
|
||||
val tx: Transaction = psbt.extractTx()
|
||||
|
||||
println("Txid is: ${tx.txid()}")
|
||||
|
||||
val txFee: ULong = wallet.calculateFee(tx)
|
||||
println("Tx fee is: ${txFee}")
|
||||
|
||||
val feeRate: FeeRate = wallet.calculateFeeRate(tx)
|
||||
println("Tx fee rate is: ${feeRate.toSatPerVbCeil()} sat/vB")
|
||||
|
||||
esploraClient.broadcast(tx)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import org.junit.runner.RunWith
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class OfflineDescriptorTest {
|
||||
|
||||
@@ -3,11 +3,26 @@ package org.bitcoindevkit
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertTrue
|
||||
import kotlin.test.assertFalse
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import org.junit.runner.RunWith
|
||||
import java.io.File
|
||||
import kotlin.test.AfterTest
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class OfflineWalletTest {
|
||||
private val persistenceFilePath = InstrumentationRegistry
|
||||
.getInstrumentation().targetContext.filesDir.path + "/bdk_persistence.db"
|
||||
|
||||
@AfterTest
|
||||
fun cleanup() {
|
||||
val file = File(persistenceFilePath)
|
||||
if (file.exists()) {
|
||||
file.delete()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testDescriptorBip86() {
|
||||
val mnemonic: Mnemonic = Mnemonic(WordCount.WORDS12)
|
||||
@@ -23,12 +38,18 @@ class OfflineWalletTest {
|
||||
"wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)",
|
||||
Network.TESTNET
|
||||
)
|
||||
val wallet: Wallet = Wallet.newNoPersist(
|
||||
val wallet: Wallet = Wallet(
|
||||
descriptor,
|
||||
null,
|
||||
persistenceFilePath,
|
||||
Network.TESTNET
|
||||
)
|
||||
val addressInfo: AddressInfo = wallet.getAddress(AddressIndex.New)
|
||||
val addressInfo: AddressInfo = wallet.revealNextAddress(KeychainKind.EXTERNAL)
|
||||
|
||||
assertTrue(addressInfo.address.isValidForNetwork(Network.TESTNET), "Address is not valid for testnet network")
|
||||
assertTrue(addressInfo.address.isValidForNetwork(Network.SIGNET), "Address is not valid for signet network")
|
||||
assertFalse(addressInfo.address.isValidForNetwork(Network.REGTEST), "Address is valid for regtest network, but it shouldn't be")
|
||||
assertFalse(addressInfo.address.isValidForNetwork(Network.BITCOIN), "Address is valid for bitcoin network, but it shouldn't be")
|
||||
|
||||
assertEquals(
|
||||
expected = "tb1qzg4mckdh50nwdm9hkzq06528rsu73hjxxzem3e",
|
||||
@@ -42,15 +63,16 @@ class OfflineWalletTest {
|
||||
"wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)",
|
||||
Network.TESTNET
|
||||
)
|
||||
val wallet: Wallet = Wallet.newNoPersist(
|
||||
val wallet: Wallet = Wallet(
|
||||
descriptor,
|
||||
null,
|
||||
persistenceFilePath,
|
||||
Network.TESTNET
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
expected = 0uL,
|
||||
actual = wallet.getBalance().total()
|
||||
actual = wallet.getBalance().total
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="org.bitcoindevkit">
|
||||
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
|
||||
</manifest>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package org.bitcoindevkit.plugins
|
||||
|
||||
|
||||
val operatingSystem: OS = when {
|
||||
System.getProperty("os.name").contains("mac", ignoreCase = true) -> OS.MAC
|
||||
System.getProperty("os.name").contains("linux", ignoreCase = true) -> OS.LINUX
|
||||
|
||||
@@ -17,6 +17,11 @@ internal class UniFfiAndroidPlugin : Plugin<Project> {
|
||||
OS.OTHER -> throw Error("Cannot build Android library from current architecture")
|
||||
}
|
||||
|
||||
// if ANDROID_NDK_ROOT is not set, stop build
|
||||
if (System.getenv("ANDROID_NDK_ROOT") == null) {
|
||||
throw IllegalStateException("ANDROID_NDK_ROOT environment variable is not set; cannot build library")
|
||||
}
|
||||
|
||||
// arm64-v8a is the most popular hardware architecture for Android
|
||||
val buildAndroidAarch64Binary by tasks.register<Exec>("buildAndroidAarch64Binary") {
|
||||
|
||||
@@ -26,13 +31,6 @@ internal class UniFfiAndroidPlugin : Plugin<Project> {
|
||||
executable("cargo")
|
||||
args(cargoArgs)
|
||||
|
||||
// if ANDROID_NDK_ROOT is not set then set it to github actions default
|
||||
if (System.getenv("ANDROID_NDK_ROOT") == null) {
|
||||
environment(
|
||||
Pair("ANDROID_NDK_ROOT", "${System.getenv("ANDROID_SDK_ROOT")}/ndk-bundle")
|
||||
)
|
||||
}
|
||||
|
||||
environment(
|
||||
// add build toolchain to PATH
|
||||
Pair("PATH", "${System.getenv("PATH")}:${System.getenv("ANDROID_NDK_ROOT")}/toolchains/llvm/prebuilt/$llvmArchPath/bin"),
|
||||
@@ -56,13 +54,6 @@ internal class UniFfiAndroidPlugin : Plugin<Project> {
|
||||
executable("cargo")
|
||||
args(cargoArgs)
|
||||
|
||||
// if ANDROID_NDK_ROOT is not set then set it to github actions default
|
||||
if (System.getenv("ANDROID_NDK_ROOT") == null) {
|
||||
environment(
|
||||
Pair("ANDROID_NDK_ROOT", "${System.getenv("ANDROID_SDK_ROOT")}/ndk-bundle")
|
||||
)
|
||||
}
|
||||
|
||||
environment(
|
||||
// add build toolchain to PATH
|
||||
Pair("PATH", "${System.getenv("PATH")}:${System.getenv("ANDROID_NDK_ROOT")}/toolchains/llvm/prebuilt/$llvmArchPath/bin"),
|
||||
@@ -86,13 +77,6 @@ internal class UniFfiAndroidPlugin : Plugin<Project> {
|
||||
executable("cargo")
|
||||
args(cargoArgs)
|
||||
|
||||
// if ANDROID_NDK_ROOT is not set then set it to github actions default
|
||||
if (System.getenv("ANDROID_NDK_ROOT") == null) {
|
||||
environment(
|
||||
Pair("ANDROID_NDK_ROOT", "${System.getenv("ANDROID_SDK_ROOT")}/ndk-bundle")
|
||||
)
|
||||
}
|
||||
|
||||
environment(
|
||||
// add build toolchain to PATH
|
||||
Pair("PATH", "${System.getenv("PATH")}:${System.getenv("ANDROID_NDK_ROOT")}/toolchains/llvm/prebuilt/$llvmArchPath/bin"),
|
||||
@@ -113,6 +97,8 @@ internal class UniFfiAndroidPlugin : Plugin<Project> {
|
||||
val moveNativeAndroidLibs by tasks.register<Copy>("moveNativeAndroidLibs") {
|
||||
|
||||
dependsOn(buildAndroidAarch64Binary)
|
||||
dependsOn(buildAndroidArmv7Binary)
|
||||
dependsOn(buildAndroidX86_64Binary)
|
||||
|
||||
into("${project.projectDir}/../lib/src/main/jniLibs/")
|
||||
|
||||
@@ -137,14 +123,14 @@ internal class UniFfiAndroidPlugin : Plugin<Project> {
|
||||
val generateAndroidBindings by tasks.register<Exec>("generateAndroidBindings") {
|
||||
dependsOn(moveNativeAndroidLibs)
|
||||
|
||||
// val libraryPath = "${project.projectDir}/../../bdk-ffi/target/aarch64-linux-android/release-smaller/libbdkffi.so"
|
||||
// workingDir("${project.projectDir}/../../bdk-ffi")
|
||||
// val cargoArgs: List<String> = listOf("run", "--bin", "uniffi-bindgen", "generate", "--library", libraryPath, "--language", "kotlin", "--out-dir", "../bdk-android/lib/src/main/kotlin", "--no-format")
|
||||
val libraryPath = "${project.projectDir}/../../bdk-ffi/target/aarch64-linux-android/release-smaller/libbdkffi.so"
|
||||
workingDir("${project.projectDir}/../../bdk-ffi")
|
||||
val cargoArgs: List<String> = listOf("run", "--bin", "uniffi-bindgen", "generate", "--library", libraryPath, "--language", "kotlin", "--out-dir", "../bdk-android/lib/src/main/kotlin", "--no-format")
|
||||
|
||||
// The code above worked for uniffi 0.24.3 using the --library flag
|
||||
// The code below works for uniffi 0.23.0
|
||||
workingDir("${project.projectDir}/../../bdk-ffi")
|
||||
val cargoArgs: List<String> = listOf("run", "--bin", "uniffi-bindgen", "generate", "src/bdk.udl", "--language", "kotlin", "--out-dir", "../bdk-android/lib/src/main/kotlin", "--no-format")
|
||||
// workingDir("${project.projectDir}/../../bdk-ffi")
|
||||
// val cargoArgs: List<String> = listOf("run", "--bin", "uniffi-bindgen", "generate", "src/bdk.udl", "--language", "kotlin", "--config", "uniffi-android.toml", "--out-dir", "../bdk-android/lib/src/main/kotlin", "--no-format")
|
||||
|
||||
executable("cargo")
|
||||
args(cargoArgs)
|
||||
|
||||
@@ -2,3 +2,17 @@ rootProject.name = "bdk-android"
|
||||
|
||||
include(":lib")
|
||||
includeBuild("plugins")
|
||||
|
||||
pluginManagement {
|
||||
repositories {
|
||||
gradlePluginPortal()
|
||||
google()
|
||||
}
|
||||
}
|
||||
|
||||
dependencyResolutionManagement {
|
||||
repositories {
|
||||
mavenCentral()
|
||||
google()
|
||||
}
|
||||
}
|
||||
|
||||
921
bdk-ffi/Cargo.lock
generated
921
bdk-ffi/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "bdk-ffi"
|
||||
version = "1.0.0-alpha.2"
|
||||
version = "1.0.0-alpha.10"
|
||||
homepage = "https://bitcoindevkit.org"
|
||||
repository = "https://github.com/bitcoindevkit/bdk"
|
||||
edition = "2018"
|
||||
@@ -18,24 +18,19 @@ path = "uniffi-bindgen.rs"
|
||||
default = ["uniffi/cli"]
|
||||
|
||||
[dependencies]
|
||||
bdk = { version = "1.0.0-alpha.2", features = ["all-keys", "keys-bip39"] }
|
||||
bdk = { version = "1.0.0-alpha.10", features = ["all-keys", "keys-bip39"] }
|
||||
bdk_esplora = { version = "0.12.0", default-features = false, features = ["std", "blocking", "blocking-https-rustls"] }
|
||||
bdk_file_store = { version = "0.10.0" }
|
||||
|
||||
# TODO 22: The bdk_esplora crate uses esplora_client which uses reqwest for async. By default it uses the system
|
||||
# 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" }
|
||||
uniffi = { version = "=0.26.1" }
|
||||
bitcoin-internals = { version = "0.2.0", features = ["alloc"] }
|
||||
thiserror = "1.0.58"
|
||||
|
||||
[build-dependencies]
|
||||
uniffi = { version = "=0.25.1", features = ["build"] }
|
||||
uniffi = { version = "=0.26.1", features = ["build"] }
|
||||
|
||||
[dev-dependencies]
|
||||
uniffi = { version = "=0.25.1", features = ["bindgen-tests"] }
|
||||
uniffi = { version = "=0.26.1", features = ["bindgen-tests"] }
|
||||
assert_matches = "1.5.0"
|
||||
|
||||
[profile.release-smaller]
|
||||
|
||||
12
bdk-ffi/justfile
Normal file
12
bdk-ffi/justfile
Normal file
@@ -0,0 +1,12 @@
|
||||
default:
|
||||
just --list
|
||||
|
||||
build:
|
||||
cargo build
|
||||
|
||||
check:
|
||||
cargo fmt
|
||||
cargo clippy
|
||||
|
||||
test:
|
||||
cargo test --lib
|
||||
@@ -1,7 +1,194 @@
|
||||
namespace bdk {};
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// bdk crate - root module
|
||||
// bdk crate - error module
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
[Error]
|
||||
interface AddressError {
|
||||
Base58();
|
||||
Bech32();
|
||||
WitnessVersion(string error_message);
|
||||
WitnessProgram(string error_message);
|
||||
UncompressedPubkey();
|
||||
ExcessiveScriptSize();
|
||||
UnrecognizedScript();
|
||||
NetworkValidation(Network required, Network found, string address);
|
||||
OtherAddressErr();
|
||||
};
|
||||
|
||||
[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(i64 fee);
|
||||
};
|
||||
|
||||
[Error]
|
||||
interface CannotConnectError {
|
||||
Include(u32 height);
|
||||
};
|
||||
|
||||
[Error]
|
||||
interface CreateTxError {
|
||||
Descriptor(string error_message);
|
||||
Persist(string error_message);
|
||||
Policy(string error_message);
|
||||
SpendingPolicyRequired(string kind);
|
||||
Version0();
|
||||
Version1Csv();
|
||||
LockTime(string requested, string required);
|
||||
RbfSequence();
|
||||
RbfSequenceCsv(string rbf, string csv);
|
||||
FeeTooLow(u64 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);
|
||||
};
|
||||
|
||||
[Error]
|
||||
interface DescriptorKeyError {
|
||||
Parse(string error_message);
|
||||
InvalidKeyType();
|
||||
Bip32(string error_message);
|
||||
};
|
||||
|
||||
[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 PersistenceError {
|
||||
Write(string error_message);
|
||||
};
|
||||
|
||||
[Error]
|
||||
interface PsbtParseError {
|
||||
PsbtEncoding(string error_message);
|
||||
Base64Encoding(string error_message);
|
||||
};
|
||||
|
||||
[Error]
|
||||
interface SignerError {
|
||||
MissingKey();
|
||||
InvalidKey();
|
||||
UserCanceled();
|
||||
InputIndexOutOfRange();
|
||||
MissingNonWitnessUtxo();
|
||||
InvalidNonWitnessUtxo();
|
||||
MissingWitnessUtxo();
|
||||
MissingWitnessScript();
|
||||
MissingHdKeypath();
|
||||
NonStandardSighash();
|
||||
InvalidSighash();
|
||||
SighashError(string error_message);
|
||||
MiniscriptPsbt(string error_message);
|
||||
External(string error_message);
|
||||
};
|
||||
|
||||
[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 {
|
||||
Io(string error_message);
|
||||
InvalidMagicBytes(sequence<u8> got, sequence<u8> expected);
|
||||
Descriptor();
|
||||
Persist(string error_message);
|
||||
NotInitialized();
|
||||
LoadedGenesisDoesNotMatch(string expected, string got);
|
||||
LoadedNetworkDoesNotMatch(Network expected, Network? got);
|
||||
};
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// bdk crate - types module
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
enum KeychainKind {
|
||||
@@ -9,92 +196,105 @@ enum KeychainKind {
|
||||
"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 {
|
||||
u32 index;
|
||||
Address address;
|
||||
KeychainKind keychain;
|
||||
};
|
||||
|
||||
dictionary Balance {
|
||||
u64 immature;
|
||||
|
||||
u64 trusted_pending;
|
||||
|
||||
u64 untrusted_pending;
|
||||
|
||||
u64 confirmed;
|
||||
|
||||
u64 trusted_spendable;
|
||||
|
||||
u64 total;
|
||||
};
|
||||
|
||||
dictionary LocalOutput {
|
||||
OutPoint outpoint;
|
||||
TxOut txout;
|
||||
KeychainKind keychain;
|
||||
boolean is_spent;
|
||||
};
|
||||
|
||||
dictionary TxOut {
|
||||
u64 value;
|
||||
Script script_pubkey;
|
||||
};
|
||||
|
||||
[Enum]
|
||||
interface AddressIndex {
|
||||
New();
|
||||
LastUnused();
|
||||
Peek(u32 index);
|
||||
interface ChainPosition {
|
||||
Confirmed(u32 height, u64 timestamp);
|
||||
Unconfirmed(u64 timestamp);
|
||||
};
|
||||
|
||||
dictionary CanonicalTx {
|
||||
Transaction transaction;
|
||||
ChainPosition chain_position;
|
||||
};
|
||||
|
||||
interface FullScanRequest {};
|
||||
|
||||
interface SyncRequest {};
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// bdk crate - wallet module
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
enum ChangeSpendPolicy {
|
||||
"ChangeAllowed",
|
||||
"OnlyChange",
|
||||
"ChangeForbidden"
|
||||
};
|
||||
|
||||
interface Wallet {
|
||||
[Name=new_no_persist, Throws=BdkError]
|
||||
constructor(Descriptor descriptor, Descriptor? change_descriptor, Network network);
|
||||
[Throws=WalletCreationError]
|
||||
constructor(Descriptor descriptor, Descriptor? change_descriptor, string persistence_backend_path, Network network);
|
||||
|
||||
AddressInfo get_address(AddressIndex address_index);
|
||||
|
||||
AddressInfo get_internal_address(AddressIndex address_index);
|
||||
[Throws=PersistenceError]
|
||||
AddressInfo reveal_next_address(KeychainKind keychain);
|
||||
|
||||
Network network();
|
||||
|
||||
Balance get_balance();
|
||||
|
||||
boolean is_mine(Script script);
|
||||
|
||||
[Throws=BdkError]
|
||||
[Throws=CannotConnectError]
|
||||
void apply_update(Update update);
|
||||
|
||||
[Throws=BdkError]
|
||||
boolean sign(PartiallySignedTransaction psbt);
|
||||
[Throws=PersistenceError]
|
||||
boolean commit();
|
||||
|
||||
boolean is_mine([ByRef] Script script);
|
||||
|
||||
[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]
|
||||
u64 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();
|
||||
};
|
||||
|
||||
interface Update {};
|
||||
@@ -102,12 +302,14 @@ interface Update {};
|
||||
interface TxBuilder {
|
||||
constructor();
|
||||
|
||||
TxBuilder add_recipient(Script script, u64 amount);
|
||||
TxBuilder add_recipient([ByRef] Script script, u64 amount);
|
||||
|
||||
TxBuilder set_recipients(sequence<ScriptAmount> script_amount);
|
||||
TxBuilder set_recipients(sequence<ScriptAmount> recipients);
|
||||
|
||||
TxBuilder add_unspendable(OutPoint unspendable);
|
||||
|
||||
TxBuilder unspendable(sequence<OutPoint> unspendable);
|
||||
|
||||
TxBuilder add_utxo(OutPoint outpoint);
|
||||
|
||||
TxBuilder change_policy(ChangeSpendPolicy change_policy);
|
||||
@@ -118,12 +320,31 @@ interface TxBuilder {
|
||||
|
||||
TxBuilder manually_selected_only();
|
||||
|
||||
TxBuilder fee_rate(float sat_per_vbyte);
|
||||
TxBuilder fee_rate([ByRef] FeeRate fee_rate);
|
||||
|
||||
TxBuilder fee_absolute(u64 fee);
|
||||
|
||||
TxBuilder drain_wallet();
|
||||
|
||||
[Throws=BdkError]
|
||||
PartiallySignedTransaction finish([ByRef] Wallet wallet);
|
||||
TxBuilder drain_to([ByRef] Script script);
|
||||
|
||||
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);
|
||||
};
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
@@ -133,31 +354,31 @@ interface TxBuilder {
|
||||
interface Mnemonic {
|
||||
constructor(WordCount word_count);
|
||||
|
||||
[Name=from_string, Throws=BdkError]
|
||||
[Name=from_string, Throws=Bip39Error]
|
||||
constructor(string mnemonic);
|
||||
|
||||
[Name=from_entropy, Throws=BdkError]
|
||||
[Name=from_entropy, Throws=Bip39Error]
|
||||
constructor(sequence<u8> entropy);
|
||||
|
||||
string as_string();
|
||||
};
|
||||
|
||||
interface DerivationPath {
|
||||
[Throws=BdkError]
|
||||
[Throws=Bip32Error]
|
||||
constructor(string path);
|
||||
};
|
||||
|
||||
interface DescriptorSecretKey {
|
||||
constructor(Network network, Mnemonic mnemonic, string? password);
|
||||
constructor(Network network, [ByRef] Mnemonic mnemonic, string? password);
|
||||
|
||||
[Name=from_string, Throws=BdkError]
|
||||
[Name=from_string, Throws=DescriptorKeyError]
|
||||
constructor(string secret_key);
|
||||
|
||||
[Throws=BdkError]
|
||||
DescriptorSecretKey derive(DerivationPath path);
|
||||
[Throws=DescriptorKeyError]
|
||||
DescriptorSecretKey derive([ByRef] DerivationPath path);
|
||||
|
||||
[Throws=BdkError]
|
||||
DescriptorSecretKey extend(DerivationPath path);
|
||||
[Throws=DescriptorKeyError]
|
||||
DescriptorSecretKey extend([ByRef] DerivationPath path);
|
||||
|
||||
DescriptorPublicKey as_public();
|
||||
|
||||
@@ -167,45 +388,45 @@ interface DescriptorSecretKey {
|
||||
};
|
||||
|
||||
interface DescriptorPublicKey {
|
||||
[Name=from_string, Throws=BdkError]
|
||||
[Name=from_string, Throws=DescriptorKeyError]
|
||||
constructor(string public_key);
|
||||
|
||||
[Throws=BdkError]
|
||||
DescriptorPublicKey derive(DerivationPath path);
|
||||
[Throws=DescriptorKeyError]
|
||||
DescriptorPublicKey derive([ByRef] DerivationPath path);
|
||||
|
||||
[Throws=BdkError]
|
||||
DescriptorPublicKey extend(DerivationPath path);
|
||||
[Throws=DescriptorKeyError]
|
||||
DescriptorPublicKey extend([ByRef] DerivationPath path);
|
||||
|
||||
string as_string();
|
||||
};
|
||||
|
||||
interface Descriptor {
|
||||
[Throws=BdkError]
|
||||
[Throws=DescriptorError]
|
||||
constructor(string descriptor, Network network);
|
||||
|
||||
[Name=new_bip44]
|
||||
constructor(DescriptorSecretKey secret_key, KeychainKind keychain, Network network);
|
||||
constructor([ByRef] DescriptorSecretKey secret_key, KeychainKind keychain, Network network);
|
||||
|
||||
[Name=new_bip44_public]
|
||||
constructor(DescriptorPublicKey public_key, string fingerprint, KeychainKind keychain, Network network);
|
||||
constructor([ByRef] DescriptorPublicKey public_key, string fingerprint, KeychainKind keychain, Network network);
|
||||
|
||||
[Name=new_bip49]
|
||||
constructor(DescriptorSecretKey secret_key, KeychainKind keychain, Network network);
|
||||
constructor([ByRef] DescriptorSecretKey secret_key, KeychainKind keychain, Network network);
|
||||
|
||||
[Name=new_bip49_public]
|
||||
constructor(DescriptorPublicKey public_key, string fingerprint, KeychainKind keychain, Network network);
|
||||
constructor([ByRef] DescriptorPublicKey public_key, string fingerprint, KeychainKind keychain, Network network);
|
||||
|
||||
[Name=new_bip84]
|
||||
constructor(DescriptorSecretKey secret_key, KeychainKind keychain, Network network);
|
||||
constructor([ByRef] DescriptorSecretKey secret_key, KeychainKind keychain, Network network);
|
||||
|
||||
[Name=new_bip84_public]
|
||||
constructor(DescriptorPublicKey public_key, string fingerprint, KeychainKind keychain, Network network);
|
||||
constructor([ByRef] DescriptorPublicKey public_key, string fingerprint, KeychainKind keychain, Network network);
|
||||
|
||||
[Name=new_bip86]
|
||||
constructor(DescriptorSecretKey secret_key, KeychainKind keychain, Network network);
|
||||
constructor([ByRef] DescriptorSecretKey secret_key, KeychainKind keychain, Network network);
|
||||
|
||||
[Name=new_bip86_public]
|
||||
constructor(DescriptorPublicKey public_key, string fingerprint, KeychainKind keychain, Network network);
|
||||
constructor([ByRef] DescriptorPublicKey public_key, string fingerprint, KeychainKind keychain, Network network);
|
||||
|
||||
string as_string();
|
||||
|
||||
@@ -219,11 +440,14 @@ interface Descriptor {
|
||||
interface EsploraClient {
|
||||
constructor(string url);
|
||||
|
||||
[Throws=BdkError]
|
||||
Update scan(Wallet wallet, u64 stop_gap, u64 parallel_requests);
|
||||
[Throws=EsploraError]
|
||||
Update full_scan(FullScanRequest full_scan_request, u64 stop_gap, u64 parallel_requests);
|
||||
|
||||
[Throws=BdkError]
|
||||
void broadcast(Transaction transaction);
|
||||
[Throws=EsploraError]
|
||||
Update sync(SyncRequest sync_request, u64 parallel_requests);
|
||||
|
||||
[Throws=EsploraError]
|
||||
void broadcast([ByRef] Transaction transaction);
|
||||
};
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
@@ -235,6 +459,11 @@ dictionary ScriptAmount {
|
||||
u64 amount;
|
||||
};
|
||||
|
||||
dictionary SentAndReceivedValues {
|
||||
u64 sent;
|
||||
u64 received;
|
||||
};
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// bdk crate - bitcoin re-exports
|
||||
// ------------------------------------------------------------------------
|
||||
@@ -245,6 +474,7 @@ interface Script {
|
||||
sequence<u8> to_bytes();
|
||||
};
|
||||
|
||||
[NonExhaustive]
|
||||
enum Network {
|
||||
"Bitcoin",
|
||||
"Testnet",
|
||||
@@ -261,7 +491,7 @@ enum WordCount {
|
||||
};
|
||||
|
||||
interface Address {
|
||||
[Throws=BdkError]
|
||||
[Throws=AddressError]
|
||||
constructor(string address, Network network);
|
||||
|
||||
Network network();
|
||||
@@ -271,33 +501,40 @@ interface Address {
|
||||
string to_qr_uri();
|
||||
|
||||
string as_string();
|
||||
|
||||
boolean is_valid_for_network(Network network);
|
||||
};
|
||||
|
||||
interface Transaction {
|
||||
[Throws=BdkError]
|
||||
[Throws=TransactionError]
|
||||
constructor(sequence<u8> transaction_bytes);
|
||||
|
||||
string txid();
|
||||
|
||||
u64 size();
|
||||
u64 total_size();
|
||||
|
||||
u64 vsize();
|
||||
|
||||
boolean is_coin_base();
|
||||
boolean is_coinbase();
|
||||
|
||||
boolean is_explicitly_rbf();
|
||||
|
||||
boolean is_lock_time_enabled();
|
||||
|
||||
i32 version();
|
||||
|
||||
sequence<u8> serialize();
|
||||
|
||||
u64 weight();
|
||||
};
|
||||
|
||||
interface PartiallySignedTransaction {
|
||||
[Throws=BdkError]
|
||||
interface Psbt {
|
||||
[Throws=PsbtParseError]
|
||||
constructor(string psbt_base64);
|
||||
|
||||
string serialize();
|
||||
|
||||
[Throws=ExtractTxError]
|
||||
Transaction extract_tx();
|
||||
};
|
||||
|
||||
@@ -305,3 +542,17 @@ dictionary OutPoint {
|
||||
string txid;
|
||||
u32 vout;
|
||||
};
|
||||
|
||||
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();
|
||||
};
|
||||
|
||||
@@ -1,19 +1,23 @@
|
||||
use crate::error::{AddressError, FeeRateError, PsbtParseError, TransactionError};
|
||||
|
||||
use bdk::bitcoin::address::{NetworkChecked, NetworkUnchecked};
|
||||
use bdk::bitcoin::blockdata::script::ScriptBuf as BdkScriptBuf;
|
||||
use bdk::bitcoin::blockdata::transaction::TxOut as BdkTxOut;
|
||||
use bdk::bitcoin::consensus::encode::serialize;
|
||||
use bdk::bitcoin::consensus::Decodable;
|
||||
use bdk::bitcoin::network::constants::Network as BdkNetwork;
|
||||
use bdk::bitcoin::psbt::PartiallySignedTransaction as BdkPartiallySignedTransaction;
|
||||
use bdk::bitcoin::psbt::ExtractTxError;
|
||||
use bdk::bitcoin::Address as BdkAddress;
|
||||
use bdk::bitcoin::FeeRate as BdkFeeRate;
|
||||
use bdk::bitcoin::Network;
|
||||
use bdk::bitcoin::OutPoint as BdkOutPoint;
|
||||
use bdk::bitcoin::Psbt as BdkPsbt;
|
||||
use bdk::bitcoin::Transaction as BdkTransaction;
|
||||
use bdk::bitcoin::Txid;
|
||||
use bdk::Error as BdkError;
|
||||
|
||||
use std::io::Cursor;
|
||||
use std::str::FromStr;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
/// A Bitcoin script.
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct Script(pub(crate) BdkScriptBuf);
|
||||
|
||||
@@ -34,270 +38,149 @@ impl From<BdkScriptBuf> for Script {
|
||||
}
|
||||
}
|
||||
|
||||
pub enum Network {
|
||||
/// Mainnet Bitcoin.
|
||||
Bitcoin,
|
||||
/// Bitcoin's testnet network.
|
||||
Testnet,
|
||||
/// Bitcoin's signet network.
|
||||
Signet,
|
||||
/// Bitcoin's regtest network.
|
||||
Regtest,
|
||||
}
|
||||
|
||||
impl From<Network> for BdkNetwork {
|
||||
fn from(network: Network) -> Self {
|
||||
match network {
|
||||
Network::Bitcoin => BdkNetwork::Bitcoin,
|
||||
Network::Testnet => BdkNetwork::Testnet,
|
||||
Network::Signet => BdkNetwork::Signet,
|
||||
Network::Regtest => BdkNetwork::Regtest,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<BdkNetwork> for Network {
|
||||
fn from(network: BdkNetwork) -> Self {
|
||||
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>,
|
||||
}
|
||||
pub struct Address(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()))?;
|
||||
pub fn new(address: String, network: Network) -> Result<Self, AddressError> {
|
||||
let parsed_address = address.parse::<bdk::bitcoin::Address<NetworkUnchecked>>()?;
|
||||
let network_checked_address = parsed_address.require_network(network)?;
|
||||
|
||||
let network_checked_address = parsed_address
|
||||
.require_network(network.into())
|
||||
.map_err(|e| BdkError::Generic(e.to_string()))?;
|
||||
|
||||
Ok(Address {
|
||||
inner: network_checked_address,
|
||||
})
|
||||
Ok(Address(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()
|
||||
*self.0.network()
|
||||
}
|
||||
|
||||
pub fn script_pubkey(&self) -> Arc<Script> {
|
||||
Arc::new(Script(self.inner.script_pubkey()))
|
||||
Arc::new(Script(self.0.script_pubkey()))
|
||||
}
|
||||
|
||||
pub fn to_qr_uri(&self) -> String {
|
||||
self.inner.to_qr_uri()
|
||||
self.0.to_qr_uri()
|
||||
}
|
||||
|
||||
pub fn as_string(&self) -> String {
|
||||
self.inner.to_string()
|
||||
self.0.to_string()
|
||||
}
|
||||
|
||||
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 From<Address> for BdkAddress {
|
||||
fn from(address: Address) -> Self {
|
||||
address.inner
|
||||
address.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<BdkAddress> for Address {
|
||||
fn from(address: BdkAddress) -> Self {
|
||||
Address { inner: address }
|
||||
Address(address)
|
||||
}
|
||||
}
|
||||
|
||||
/// A Bitcoin transaction.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Transaction {
|
||||
inner: BdkTransaction,
|
||||
}
|
||||
pub struct Transaction(BdkTransaction);
|
||||
|
||||
impl Transaction {
|
||||
pub fn new(transaction_bytes: Vec<u8>) -> Result<Self, BdkError> {
|
||||
pub fn new(transaction_bytes: Vec<u8>) -> Result<Self, TransactionError> {
|
||||
let mut decoder = Cursor::new(transaction_bytes);
|
||||
let tx: BdkTransaction = BdkTransaction::consensus_decode(&mut decoder)
|
||||
.map_err(|e| BdkError::Generic(e.to_string()))?;
|
||||
Ok(Transaction { inner: tx })
|
||||
let tx: BdkTransaction = BdkTransaction::consensus_decode(&mut decoder)?;
|
||||
Ok(Transaction(tx))
|
||||
}
|
||||
|
||||
pub fn txid(&self) -> String {
|
||||
self.inner.txid().to_string()
|
||||
self.0.txid().to_string()
|
||||
}
|
||||
|
||||
// fn weight(&self) -> u64 {
|
||||
// self.inner.weight() as u64
|
||||
// }
|
||||
pub fn weight(&self) -> u64 {
|
||||
self.0.weight().to_wu()
|
||||
}
|
||||
|
||||
pub fn size(&self) -> u64 {
|
||||
self.inner.size() as u64
|
||||
pub fn total_size(&self) -> u64 {
|
||||
self.0.total_size() as u64
|
||||
}
|
||||
|
||||
pub fn vsize(&self) -> u64 {
|
||||
self.inner.vsize() as u64
|
||||
self.0.vsize() as u64
|
||||
}
|
||||
|
||||
// fn serialize(&self) -> Vec<u8> {
|
||||
// self.inner.serialize()
|
||||
// }
|
||||
|
||||
pub fn is_coin_base(&self) -> bool {
|
||||
self.inner.is_coin_base()
|
||||
pub fn is_coinbase(&self) -> bool {
|
||||
self.0.is_coinbase()
|
||||
}
|
||||
|
||||
pub fn is_explicitly_rbf(&self) -> bool {
|
||||
self.inner.is_explicitly_rbf()
|
||||
self.0.is_explicitly_rbf()
|
||||
}
|
||||
|
||||
pub fn is_lock_time_enabled(&self) -> bool {
|
||||
self.inner.is_lock_time_enabled()
|
||||
self.0.is_lock_time_enabled()
|
||||
}
|
||||
|
||||
pub fn version(&self) -> i32 {
|
||||
self.inner.version
|
||||
self.0.version.0
|
||||
}
|
||||
|
||||
// fn lock_time(&self) -> u32 {
|
||||
// self.inner.lock_time.0
|
||||
// }
|
||||
|
||||
// fn input(&self) -> Vec<TxIn> {
|
||||
// self.inner.input.iter().map(|x| x.into()).collect()
|
||||
// }
|
||||
//
|
||||
// fn output(&self) -> Vec<TxOut> {
|
||||
// self.inner.output.iter().map(|x| x.into()).collect()
|
||||
// }
|
||||
pub fn serialize(&self) -> Vec<u8> {
|
||||
serialize(&self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<BdkTransaction> for Transaction {
|
||||
fn from(tx: BdkTransaction) -> Self {
|
||||
Transaction { inner: tx }
|
||||
Transaction(tx)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Transaction> for BdkTransaction {
|
||||
fn from(tx: Transaction) -> Self {
|
||||
tx.inner
|
||||
impl From<&BdkTransaction> for Transaction {
|
||||
fn from(tx: &BdkTransaction) -> Self {
|
||||
Transaction(tx.clone())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PartiallySignedTransaction {
|
||||
pub(crate) inner: Mutex<BdkPartiallySignedTransaction>,
|
||||
impl From<&Transaction> for BdkTransaction {
|
||||
fn from(tx: &Transaction) -> Self {
|
||||
tx.0.clone()
|
||||
}
|
||||
}
|
||||
|
||||
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()))?;
|
||||
pub struct Psbt(pub(crate) Mutex<BdkPsbt>);
|
||||
|
||||
Ok(PartiallySignedTransaction {
|
||||
inner: Mutex::new(psbt),
|
||||
})
|
||||
impl Psbt {
|
||||
pub(crate) fn new(psbt_base64: String) -> Result<Self, PsbtParseError> {
|
||||
let psbt: BdkPsbt = BdkPsbt::from_str(&psbt_base64)?;
|
||||
Ok(Psbt(Mutex::new(psbt)))
|
||||
}
|
||||
|
||||
pub(crate) fn serialize(&self) -> String {
|
||||
let psbt = self.inner.lock().unwrap().clone();
|
||||
let psbt = self.0.lock().unwrap().clone();
|
||||
psbt.to_string()
|
||||
}
|
||||
|
||||
// pub(crate) fn txid(&self) -> String {
|
||||
// let tx = self.inner.lock().unwrap().clone().extract_tx();
|
||||
// let txid = tx.txid();
|
||||
// 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())
|
||||
}
|
||||
|
||||
// /// Combines this PartiallySignedTransaction with other PSBT as described by BIP 174.
|
||||
// ///
|
||||
// /// In accordance with BIP 174 this function is commutative i.e., `A.combine(B) == B.combine(A)`
|
||||
// pub(crate) fn combine(
|
||||
// &self,
|
||||
// other: Arc<PartiallySignedTransaction>,
|
||||
// ) -> 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),
|
||||
// }))
|
||||
// }
|
||||
|
||||
// /// The total transaction fee amount, sum of input amounts minus sum of output amounts, in Sats.
|
||||
// /// If the PSBT is missing a TxOut for an input returns None.
|
||||
// pub(crate) fn fee_amount(&self) -> Option<u64> {
|
||||
// self.inner.lock().unwrap().fee_amount()
|
||||
// }
|
||||
|
||||
// /// The transaction's fee rate. This value will only be accurate if calculated AFTER the
|
||||
// /// `PartiallySignedTransaction` is finalized and all witness/signature data is added to the
|
||||
// /// transaction.
|
||||
// /// If the PSBT is missing a TxOut for an input returns None.
|
||||
// pub(crate) fn fee_rate(&self) -> Option<Arc<FeeRate>> {
|
||||
// self.inner.lock().unwrap().fee_rate().map(Arc::new)
|
||||
// }
|
||||
|
||||
// /// 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),
|
||||
}
|
||||
pub(crate) fn extract_tx(&self) -> Result<Arc<Transaction>, ExtractTxError> {
|
||||
let tx: BdkTransaction = self.0.lock().unwrap().clone().extract_tx()?;
|
||||
let transaction: Transaction = tx.into();
|
||||
Ok(Arc::new(transaction))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<BdkPsbt> for Psbt {
|
||||
fn from(psbt: BdkPsbt) -> Self {
|
||||
Psbt(Mutex::new(psbt))
|
||||
}
|
||||
}
|
||||
|
||||
/// A reference to a transaction output.
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct OutPoint {
|
||||
/// The referenced transaction's txid.
|
||||
pub txid: String,
|
||||
/// The index of the referenced output in its transaction's vout.
|
||||
pub vout: u32,
|
||||
}
|
||||
|
||||
@@ -309,3 +192,407 @@ 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 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 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"
|
||||
);
|
||||
assert_ne!(
|
||||
docs_address_testnet.network(),
|
||||
Network::Bitcoin,
|
||||
"Address should not be parsed as Bitcoin"
|
||||
);
|
||||
|
||||
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"
|
||||
);
|
||||
assert_ne!(
|
||||
docs_address_mainnet.network(),
|
||||
Network::Testnet,
|
||||
"Address should not be valid for Testnet"
|
||||
);
|
||||
assert_ne!(
|
||||
docs_address_mainnet.network(),
|
||||
Network::Signet,
|
||||
"Address should not be valid for Signet"
|
||||
);
|
||||
assert_ne!(
|
||||
docs_address_mainnet.network(),
|
||||
Network::Regtest,
|
||||
"Address should not be valid for Regtest"
|
||||
);
|
||||
|
||||
// ====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,9 +1,10 @@
|
||||
use crate::error::DescriptorError;
|
||||
use crate::keys::DescriptorPublicKey;
|
||||
use crate::keys::DescriptorSecretKey;
|
||||
use crate::Network;
|
||||
|
||||
use bdk::bitcoin::bip32::Fingerprint;
|
||||
use bdk::bitcoin::key::Secp256k1;
|
||||
use bdk::bitcoin::Network;
|
||||
use bdk::descriptor::{ExtendedDescriptor, IntoWalletDescriptor};
|
||||
use bdk::keys::DescriptorPublicKey as BdkDescriptorPublicKey;
|
||||
use bdk::keys::{DescriptorSecretKey as BdkDescriptorSecretKey, KeyMap};
|
||||
@@ -11,11 +12,9 @@ use bdk::template::{
|
||||
Bip44, Bip44Public, Bip49, Bip49Public, Bip84, Bip84Public, Bip86, Bip86Public,
|
||||
DescriptorTemplate,
|
||||
};
|
||||
use bdk::Error as BdkError;
|
||||
use bdk::KeychainKind;
|
||||
|
||||
use std::str::FromStr;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Descriptor {
|
||||
@@ -24,10 +23,9 @@ pub struct Descriptor {
|
||||
}
|
||||
|
||||
impl Descriptor {
|
||||
pub(crate) fn new(descriptor: String, network: Network) -> Result<Self, BdkError> {
|
||||
pub(crate) fn new(descriptor: String, network: Network) -> Result<Self, DescriptorError> {
|
||||
let secp = Secp256k1::new();
|
||||
let (extended_descriptor, key_map) =
|
||||
descriptor.into_wallet_descriptor(&secp, network.into())?;
|
||||
let (extended_descriptor, key_map) = descriptor.into_wallet_descriptor(&secp, network)?;
|
||||
Ok(Self {
|
||||
extended_descriptor,
|
||||
key_map,
|
||||
@@ -35,11 +33,11 @@ impl Descriptor {
|
||||
}
|
||||
|
||||
pub(crate) fn new_bip44(
|
||||
secret_key: Arc<DescriptorSecretKey>,
|
||||
secret_key: &DescriptorSecretKey,
|
||||
keychain_kind: KeychainKind,
|
||||
network: Network,
|
||||
) -> Self {
|
||||
let derivable_key = &secret_key.inner;
|
||||
let derivable_key = &secret_key.0;
|
||||
|
||||
match derivable_key {
|
||||
BdkDescriptorSecretKey::Single(_) => {
|
||||
@@ -47,9 +45,8 @@ impl Descriptor {
|
||||
}
|
||||
BdkDescriptorSecretKey::XPrv(descriptor_x_key) => {
|
||||
let derivable_key = descriptor_x_key.xkey;
|
||||
let (extended_descriptor, key_map, _) = Bip44(derivable_key, keychain_kind)
|
||||
.build(network.into())
|
||||
.unwrap();
|
||||
let (extended_descriptor, key_map, _) =
|
||||
Bip44(derivable_key, keychain_kind).build(network).unwrap();
|
||||
Self {
|
||||
extended_descriptor,
|
||||
key_map,
|
||||
@@ -62,13 +59,13 @@ impl Descriptor {
|
||||
}
|
||||
|
||||
pub(crate) fn new_bip44_public(
|
||||
public_key: Arc<DescriptorPublicKey>,
|
||||
public_key: &DescriptorPublicKey,
|
||||
fingerprint: String,
|
||||
keychain_kind: KeychainKind,
|
||||
network: Network,
|
||||
) -> Self {
|
||||
let fingerprint = Fingerprint::from_str(fingerprint.as_str()).unwrap();
|
||||
let derivable_key = &public_key.inner;
|
||||
let derivable_key = &public_key.0;
|
||||
|
||||
match derivable_key {
|
||||
BdkDescriptorPublicKey::Single(_) => {
|
||||
@@ -78,7 +75,7 @@ impl Descriptor {
|
||||
let derivable_key = descriptor_x_key.xkey;
|
||||
let (extended_descriptor, key_map, _) =
|
||||
Bip44Public(derivable_key, fingerprint, keychain_kind)
|
||||
.build(network.into())
|
||||
.build(network)
|
||||
.unwrap();
|
||||
|
||||
Self {
|
||||
@@ -93,11 +90,11 @@ impl Descriptor {
|
||||
}
|
||||
|
||||
pub(crate) fn new_bip49(
|
||||
secret_key: Arc<DescriptorSecretKey>,
|
||||
secret_key: &DescriptorSecretKey,
|
||||
keychain_kind: KeychainKind,
|
||||
network: Network,
|
||||
) -> Self {
|
||||
let derivable_key = &secret_key.inner;
|
||||
let derivable_key = &secret_key.0;
|
||||
|
||||
match derivable_key {
|
||||
BdkDescriptorSecretKey::Single(_) => {
|
||||
@@ -105,9 +102,8 @@ impl Descriptor {
|
||||
}
|
||||
BdkDescriptorSecretKey::XPrv(descriptor_x_key) => {
|
||||
let derivable_key = descriptor_x_key.xkey;
|
||||
let (extended_descriptor, key_map, _) = Bip49(derivable_key, keychain_kind)
|
||||
.build(network.into())
|
||||
.unwrap();
|
||||
let (extended_descriptor, key_map, _) =
|
||||
Bip49(derivable_key, keychain_kind).build(network).unwrap();
|
||||
Self {
|
||||
extended_descriptor,
|
||||
key_map,
|
||||
@@ -120,13 +116,13 @@ impl Descriptor {
|
||||
}
|
||||
|
||||
pub(crate) fn new_bip49_public(
|
||||
public_key: Arc<DescriptorPublicKey>,
|
||||
public_key: &DescriptorPublicKey,
|
||||
fingerprint: String,
|
||||
keychain_kind: KeychainKind,
|
||||
network: Network,
|
||||
) -> Self {
|
||||
let fingerprint = Fingerprint::from_str(fingerprint.as_str()).unwrap();
|
||||
let derivable_key = &public_key.inner;
|
||||
let derivable_key = &public_key.0;
|
||||
|
||||
match derivable_key {
|
||||
BdkDescriptorPublicKey::Single(_) => {
|
||||
@@ -136,7 +132,7 @@ impl Descriptor {
|
||||
let derivable_key = descriptor_x_key.xkey;
|
||||
let (extended_descriptor, key_map, _) =
|
||||
Bip49Public(derivable_key, fingerprint, keychain_kind)
|
||||
.build(network.into())
|
||||
.build(network)
|
||||
.unwrap();
|
||||
|
||||
Self {
|
||||
@@ -151,11 +147,11 @@ impl Descriptor {
|
||||
}
|
||||
|
||||
pub(crate) fn new_bip84(
|
||||
secret_key: Arc<DescriptorSecretKey>,
|
||||
secret_key: &DescriptorSecretKey,
|
||||
keychain_kind: KeychainKind,
|
||||
network: Network,
|
||||
) -> Self {
|
||||
let derivable_key = &secret_key.inner;
|
||||
let derivable_key = &secret_key.0;
|
||||
|
||||
match derivable_key {
|
||||
BdkDescriptorSecretKey::Single(_) => {
|
||||
@@ -163,9 +159,8 @@ impl Descriptor {
|
||||
}
|
||||
BdkDescriptorSecretKey::XPrv(descriptor_x_key) => {
|
||||
let derivable_key = descriptor_x_key.xkey;
|
||||
let (extended_descriptor, key_map, _) = Bip84(derivable_key, keychain_kind)
|
||||
.build(network.into())
|
||||
.unwrap();
|
||||
let (extended_descriptor, key_map, _) =
|
||||
Bip84(derivable_key, keychain_kind).build(network).unwrap();
|
||||
Self {
|
||||
extended_descriptor,
|
||||
key_map,
|
||||
@@ -178,13 +173,13 @@ impl Descriptor {
|
||||
}
|
||||
|
||||
pub(crate) fn new_bip84_public(
|
||||
public_key: Arc<DescriptorPublicKey>,
|
||||
public_key: &DescriptorPublicKey,
|
||||
fingerprint: String,
|
||||
keychain_kind: KeychainKind,
|
||||
network: Network,
|
||||
) -> Self {
|
||||
let fingerprint = Fingerprint::from_str(fingerprint.as_str()).unwrap();
|
||||
let derivable_key = &public_key.inner;
|
||||
let derivable_key = &public_key.0;
|
||||
|
||||
match derivable_key {
|
||||
BdkDescriptorPublicKey::Single(_) => {
|
||||
@@ -194,7 +189,7 @@ impl Descriptor {
|
||||
let derivable_key = descriptor_x_key.xkey;
|
||||
let (extended_descriptor, key_map, _) =
|
||||
Bip84Public(derivable_key, fingerprint, keychain_kind)
|
||||
.build(network.into())
|
||||
.build(network)
|
||||
.unwrap();
|
||||
|
||||
Self {
|
||||
@@ -209,11 +204,11 @@ impl Descriptor {
|
||||
}
|
||||
|
||||
pub(crate) fn new_bip86(
|
||||
secret_key: Arc<DescriptorSecretKey>,
|
||||
secret_key: &DescriptorSecretKey,
|
||||
keychain_kind: KeychainKind,
|
||||
network: Network,
|
||||
) -> Self {
|
||||
let derivable_key = &secret_key.inner;
|
||||
let derivable_key = &secret_key.0;
|
||||
|
||||
match derivable_key {
|
||||
BdkDescriptorSecretKey::Single(_) => {
|
||||
@@ -221,9 +216,8 @@ impl Descriptor {
|
||||
}
|
||||
BdkDescriptorSecretKey::XPrv(descriptor_x_key) => {
|
||||
let derivable_key = descriptor_x_key.xkey;
|
||||
let (extended_descriptor, key_map, _) = Bip86(derivable_key, keychain_kind)
|
||||
.build(network.into())
|
||||
.unwrap();
|
||||
let (extended_descriptor, key_map, _) =
|
||||
Bip86(derivable_key, keychain_kind).build(network).unwrap();
|
||||
Self {
|
||||
extended_descriptor,
|
||||
key_map,
|
||||
@@ -236,13 +230,13 @@ impl Descriptor {
|
||||
}
|
||||
|
||||
pub(crate) fn new_bip86_public(
|
||||
public_key: Arc<DescriptorPublicKey>,
|
||||
public_key: &DescriptorPublicKey,
|
||||
fingerprint: String,
|
||||
keychain_kind: KeychainKind,
|
||||
network: Network,
|
||||
) -> Self {
|
||||
let fingerprint = Fingerprint::from_str(fingerprint.as_str()).unwrap();
|
||||
let derivable_key = &public_key.inner;
|
||||
let derivable_key = &public_key.0;
|
||||
|
||||
match derivable_key {
|
||||
BdkDescriptorPublicKey::Single(_) => {
|
||||
@@ -252,7 +246,7 @@ impl Descriptor {
|
||||
let derivable_key = descriptor_x_key.xkey;
|
||||
let (extended_descriptor, key_map, _) =
|
||||
Bip86Public(derivable_key, fingerprint, keychain_kind)
|
||||
.build(network.into())
|
||||
.build(network)
|
||||
.unwrap();
|
||||
|
||||
Self {
|
||||
@@ -277,91 +271,78 @@ impl 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)]
|
||||
mod test {
|
||||
use crate::*;
|
||||
use assert_matches::assert_matches;
|
||||
use bdk::descriptor::DescriptorError::Key;
|
||||
use bdk::keys::KeyError::InvalidNetwork;
|
||||
|
||||
fn get_descriptor_secret_key() -> DescriptorSecretKey {
|
||||
let mnemonic = Mnemonic::from_string("chaos fabric time speed sponsor all flat solution wisdom trophy crack object robot pave observe combine where aware bench orient secret primary cable detect".to_string()).unwrap();
|
||||
DescriptorSecretKey::new(Network::Testnet, Arc::new(mnemonic), None)
|
||||
DescriptorSecretKey::new(Network::Testnet, &mnemonic, None)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_descriptor_templates() {
|
||||
let master: Arc<DescriptorSecretKey> = Arc::new(get_descriptor_secret_key());
|
||||
let master: DescriptorSecretKey = get_descriptor_secret_key();
|
||||
println!("Master: {:?}", master.as_string());
|
||||
// tprv8ZgxMBicQKsPdWuqM1t1CDRvQtQuBPyfL6GbhQwtxDKgUAVPbxmj71pRA8raTqLrec5LyTs5TqCxdABcZr77bt2KyWA5bizJHnC4g4ysm4h
|
||||
let handmade_public_44 = master
|
||||
.derive(Arc::new(
|
||||
DerivationPath::new("m/44h/1h/0h".to_string()).unwrap(),
|
||||
))
|
||||
.derive(&DerivationPath::new("m/44h/1h/0h".to_string()).unwrap())
|
||||
.unwrap()
|
||||
.as_public();
|
||||
println!("Public 44: {}", handmade_public_44.as_string());
|
||||
// Public 44: [d1d04177/44'/1'/0']tpubDCoPjomfTqh1e7o1WgGpQtARWtkueXQAepTeNpWiitS3Sdv8RKJ1yvTrGHcwjDXp2SKyMrTEca4LoN7gEUiGCWboyWe2rz99Kf4jK4m2Zmx/*
|
||||
let handmade_public_49 = master
|
||||
.derive(Arc::new(
|
||||
DerivationPath::new("m/49h/1h/0h".to_string()).unwrap(),
|
||||
))
|
||||
.derive(&DerivationPath::new("m/49h/1h/0h".to_string()).unwrap())
|
||||
.unwrap()
|
||||
.as_public();
|
||||
println!("Public 49: {}", handmade_public_49.as_string());
|
||||
// Public 49: [d1d04177/49'/1'/0']tpubDC65ZRvk1NDddHrVAUAZrUPJ772QXzooNYmPywYF9tMyNLYKf5wpKE7ZJvK9kvfG3FV7rCsHBNXy1LVKW95jrmC7c7z4hq7a27aD2sRrAhR/*
|
||||
let handmade_public_84 = master
|
||||
.derive(Arc::new(
|
||||
DerivationPath::new("m/84h/1h/0h".to_string()).unwrap(),
|
||||
))
|
||||
.derive(&DerivationPath::new("m/84h/1h/0h".to_string()).unwrap())
|
||||
.unwrap()
|
||||
.as_public();
|
||||
println!("Public 84: {}", handmade_public_84.as_string());
|
||||
// Public 84: [d1d04177/84'/1'/0']tpubDDNxbq17egjFk2edjv8oLnzxk52zny9aAYNv9CMqTzA4mQDiQq818sEkNe9Gzmd4QU8558zftqbfoVBDQorG3E4Wq26tB2JeE4KUoahLkx6/*
|
||||
let handmade_public_86 = master
|
||||
.derive(Arc::new(
|
||||
DerivationPath::new("m/86h/1h/0h".to_string()).unwrap(),
|
||||
))
|
||||
.derive(&DerivationPath::new("m/86h/1h/0h".to_string()).unwrap())
|
||||
.unwrap()
|
||||
.as_public();
|
||||
println!("Public 86: {}", handmade_public_86.as_string());
|
||||
// Public 86: [d1d04177/86'/1'/0']tpubDCJzjbcGbdEfXMWaL6QmgVmuSfXkrue7m2YNoacWwyc7a2XjXaKojRqNEbo41CFL3PyYmKdhwg2fkGpLX4SQCbQjCGxAkWHJTw9WEeenrJb/*
|
||||
let template_private_44 =
|
||||
Descriptor::new_bip44(master.clone(), KeychainKind::External, Network::Testnet);
|
||||
Descriptor::new_bip44(&master, KeychainKind::External, Network::Testnet);
|
||||
let template_private_49 =
|
||||
Descriptor::new_bip49(master.clone(), KeychainKind::External, Network::Testnet);
|
||||
Descriptor::new_bip49(&master, KeychainKind::External, Network::Testnet);
|
||||
let template_private_84 =
|
||||
Descriptor::new_bip84(master.clone(), KeychainKind::External, Network::Testnet);
|
||||
Descriptor::new_bip84(&master, KeychainKind::External, Network::Testnet);
|
||||
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
|
||||
println!("Template 49: {}", template_private_49.as_string());
|
||||
println!("Template 44: {}", template_private_44.as_string());
|
||||
println!("Template 84: {}", template_private_84.as_string());
|
||||
println!("Template 86: {}", template_private_86.as_string());
|
||||
let template_public_44 = Descriptor::new_bip44_public(
|
||||
handmade_public_44,
|
||||
&handmade_public_44,
|
||||
"d1d04177".to_string(),
|
||||
KeychainKind::External,
|
||||
Network::Testnet,
|
||||
);
|
||||
let template_public_49 = Descriptor::new_bip49_public(
|
||||
handmade_public_49,
|
||||
&handmade_public_49,
|
||||
"d1d04177".to_string(),
|
||||
KeychainKind::External,
|
||||
Network::Testnet,
|
||||
);
|
||||
let template_public_84 = Descriptor::new_bip84_public(
|
||||
handmade_public_84,
|
||||
&handmade_public_84,
|
||||
"d1d04177".to_string(),
|
||||
KeychainKind::External,
|
||||
Network::Testnet,
|
||||
);
|
||||
let template_public_86 = Descriptor::new_bip86_public(
|
||||
handmade_public_86,
|
||||
&handmade_public_86,
|
||||
"d1d04177".to_string(),
|
||||
KeychainKind::External,
|
||||
Network::Testnet,
|
||||
@@ -409,11 +390,8 @@ mod test {
|
||||
fn test_descriptor_from_string() {
|
||||
let descriptor1 = Descriptor::new("wpkh(tprv8hwWMmPE4BVNxGdVt3HhEERZhondQvodUY7Ajyseyhudr4WabJqWKWLr4Wi2r26CDaNCQhhxEftEaNzz7dPGhWuKFU4VULesmhEfZYyBXdE/0/*)".to_string(), Network::Testnet);
|
||||
let descriptor2 = Descriptor::new("wpkh(tprv8hwWMmPE4BVNxGdVt3HhEERZhondQvodUY7Ajyseyhudr4WabJqWKWLr4Wi2r26CDaNCQhhxEftEaNzz7dPGhWuKFU4VULesmhEfZYyBXdE/0/*)".to_string(), Network::Bitcoin);
|
||||
// Creating a Descriptor using an extended key that doesn't match the network provided will throw and InvalidNetwork Error
|
||||
// Creating a Descriptor using an extended key that doesn't match the network provided will throw a DescriptorError::Key with inner InvalidNetwork error
|
||||
assert!(descriptor1.is_ok());
|
||||
assert_matches!(
|
||||
descriptor2.unwrap_err(),
|
||||
bdk::Error::Descriptor(Key(InvalidNetwork))
|
||||
)
|
||||
assert_matches!(descriptor2.unwrap_err(), DescriptorError::Key { .. });
|
||||
}
|
||||
}
|
||||
|
||||
1614
bdk-ffi/src/error.rs
Normal file
1614
bdk-ffi/src/error.rs
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,8 +1,15 @@
|
||||
use crate::wallet::{Update, Wallet};
|
||||
use std::ops::Deref;
|
||||
use crate::bitcoin::Transaction;
|
||||
use crate::error::EsploraError;
|
||||
use crate::types::{FullScanRequest, SyncRequest};
|
||||
use crate::wallet::Update;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use bdk::wallet::Update as BdkUpdate;
|
||||
use bdk::Error as BdkError;
|
||||
use bdk::bitcoin::Transaction as BdkTransaction;
|
||||
use bdk::chain::spk_client::FullScanRequest as BdkFullScanRequest;
|
||||
use bdk::chain::spk_client::FullScanResult as BdkFullScanResult;
|
||||
use bdk::chain::spk_client::SyncRequest as BdkSyncRequest;
|
||||
use bdk::chain::spk_client::SyncResult as BdkSyncResult;
|
||||
use bdk::KeychainKind;
|
||||
use bdk_esplora::esplora_client::{BlockingClient, Builder};
|
||||
use bdk_esplora::EsploraExt;
|
||||
|
||||
@@ -12,57 +19,65 @@ pub struct EsploraClient(BlockingClient);
|
||||
|
||||
impl EsploraClient {
|
||||
pub fn new(url: String) -> Self {
|
||||
let client = Builder::new(url.as_str()).build_blocking().unwrap();
|
||||
let client = Builder::new(url.as_str()).build_blocking();
|
||||
Self(client)
|
||||
}
|
||||
|
||||
// This is a temporary solution for scanning. The long-term solution involves not passing
|
||||
// the wallet to the client at all.
|
||||
pub fn scan(
|
||||
pub fn full_scan(
|
||||
&self,
|
||||
wallet: Arc<Wallet>,
|
||||
request: Arc<FullScanRequest>,
|
||||
stop_gap: u64,
|
||||
parallel_requests: u64,
|
||||
) -> Result<Arc<Update>, BdkError> {
|
||||
let wallet = wallet.get_wallet();
|
||||
|
||||
let previous_tip = wallet.latest_checkpoint();
|
||||
let keychain_spks = wallet.spks_of_all_keychains().into_iter().collect();
|
||||
|
||||
let (update_graph, last_active_indices) = self
|
||||
) -> Result<Arc<Update>, EsploraError> {
|
||||
// using option and take is not ideal but the only way to take full ownership of the request
|
||||
let request: BdkFullScanRequest<KeychainKind> = request
|
||||
.0
|
||||
.scan_txs_with_keychains(
|
||||
keychain_spks,
|
||||
None,
|
||||
None,
|
||||
stop_gap as usize,
|
||||
parallel_requests as usize,
|
||||
)
|
||||
.unwrap();
|
||||
.lock()
|
||||
.unwrap()
|
||||
.take()
|
||||
.ok_or(EsploraError::RequestAlreadyConsumed)?;
|
||||
|
||||
let missing_heights = update_graph.missing_heights(wallet.local_chain());
|
||||
let chain_update = self
|
||||
.0
|
||||
.update_local_chain(previous_tip, missing_heights)
|
||||
.unwrap();
|
||||
let result: BdkFullScanResult<KeychainKind> =
|
||||
self.0
|
||||
.full_scan(request, stop_gap as usize, parallel_requests as usize)?;
|
||||
|
||||
let update = BdkUpdate {
|
||||
last_active_indices,
|
||||
graph: update_graph,
|
||||
chain: Some(chain_update),
|
||||
let update = bdk::wallet::Update {
|
||||
last_active_indices: result.last_active_indices,
|
||||
graph: result.graph_update,
|
||||
chain: Some(result.chain_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)?;
|
||||
|
||||
pub fn broadcast(&self, transaction: Arc<crate::bitcoin::Transaction>) -> Result<(), BdkError> {
|
||||
let bdk_transaction: bdk::bitcoin::Transaction = transaction.deref().clone().into();
|
||||
self.0
|
||||
.broadcast(&bdk_transaction)
|
||||
.map_err(|e| BdkError::Generic(e.to_string()))
|
||||
let result: BdkSyncResult = self.0.sync(request, parallel_requests as usize)?;
|
||||
|
||||
let update = bdk::wallet::Update {
|
||||
last_active_indices: BTreeMap::default(),
|
||||
graph: result.graph_update,
|
||||
chain: Some(result.chain_update),
|
||||
};
|
||||
|
||||
Ok(Arc::new(Update(update)))
|
||||
}
|
||||
|
||||
// pub fn estimate_fee();
|
||||
pub fn broadcast(&self, transaction: &Transaction) -> Result<(), EsploraError> {
|
||||
let bdk_transaction: BdkTransaction = transaction.into();
|
||||
self.0
|
||||
.broadcast(&bdk_transaction)
|
||||
.map_err(EsploraError::from)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
use crate::Network;
|
||||
use crate::error::{Bip32Error, Bip39Error, DescriptorKeyError};
|
||||
|
||||
use bdk::bitcoin::bip32::DerivationPath as BdkDerivationPath;
|
||||
use bdk::bitcoin::key::Secp256k1;
|
||||
use bdk::bitcoin::secp256k1::rand;
|
||||
use bdk::bitcoin::secp256k1::rand::Rng;
|
||||
use bdk::bitcoin::Network;
|
||||
use bdk::keys::bip39::WordCount;
|
||||
use bdk::keys::bip39::{Language, Mnemonic as BdkMnemonic};
|
||||
use bdk::keys::{
|
||||
@@ -12,20 +13,14 @@ use bdk::keys::{
|
||||
};
|
||||
use bdk::miniscript::descriptor::{DescriptorXKey, Wildcard};
|
||||
use bdk::miniscript::BareCtx;
|
||||
use bdk::Error as BdkError;
|
||||
|
||||
use std::ops::Deref;
|
||||
use std::str::FromStr;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
/// 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,
|
||||
}
|
||||
pub(crate) struct Mnemonic(pub(crate) BdkMnemonic);
|
||||
|
||||
impl Mnemonic {
|
||||
/// Generates Mnemonic with a random entropy
|
||||
pub(crate) fn new(word_count: WordCount) -> Self {
|
||||
// TODO 4: I DON'T KNOW IF THIS IS A DECENT WAY TO GENERATE ENTROPY PLEASE CONFIRM
|
||||
let mut rng = rand::thread_rng();
|
||||
@@ -35,27 +30,23 @@ impl Mnemonic {
|
||||
let generated_key: GeneratedKey<_, BareCtx> =
|
||||
BdkMnemonic::generate_with_entropy((word_count, Language::English), entropy).unwrap();
|
||||
let mnemonic = BdkMnemonic::parse_in(Language::English, generated_key.to_string()).unwrap();
|
||||
Mnemonic { inner: mnemonic }
|
||||
Mnemonic(mnemonic)
|
||||
}
|
||||
|
||||
/// Parse a Mnemonic with given string
|
||||
pub(crate) fn from_string(mnemonic: String) -> Result<Self, BdkError> {
|
||||
pub(crate) fn from_string(mnemonic: String) -> Result<Self, Bip39Error> {
|
||||
BdkMnemonic::from_str(&mnemonic)
|
||||
.map(|m| Mnemonic { inner: m })
|
||||
.map_err(|e| BdkError::Generic(e.to_string()))
|
||||
.map(Mnemonic)
|
||||
.map_err(Bip39Error::from)
|
||||
}
|
||||
|
||||
/// 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> {
|
||||
pub(crate) fn from_entropy(entropy: Vec<u8>) -> Result<Self, Bip39Error> {
|
||||
BdkMnemonic::from_entropy(entropy.as_slice())
|
||||
.map(|m| Mnemonic { inner: m })
|
||||
.map_err(|e| BdkError::Generic(e.to_string()))
|
||||
.map(Mnemonic)
|
||||
.map_err(Bip39Error::from)
|
||||
}
|
||||
|
||||
/// Returns Mnemonic as string
|
||||
pub(crate) fn as_string(&self) -> String {
|
||||
self.inner.to_string()
|
||||
self.0.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,53 +55,48 @@ pub(crate) struct DerivationPath {
|
||||
}
|
||||
|
||||
impl DerivationPath {
|
||||
pub(crate) fn new(path: String) -> Result<Self, BdkError> {
|
||||
pub(crate) fn new(path: String) -> Result<Self, Bip32Error> {
|
||||
BdkDerivationPath::from_str(&path)
|
||||
.map(|x| DerivationPath {
|
||||
inner_mutex: Mutex::new(x),
|
||||
})
|
||||
.map_err(|e| BdkError::Generic(e.to_string()))
|
||||
.map_err(Bip32Error::from)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct DescriptorSecretKey {
|
||||
pub(crate) inner: BdkDescriptorSecretKey,
|
||||
}
|
||||
pub struct DescriptorSecretKey(pub(crate) BdkDescriptorSecretKey);
|
||||
|
||||
impl DescriptorSecretKey {
|
||||
pub(crate) fn new(network: Network, mnemonic: Arc<Mnemonic>, password: Option<String>) -> Self {
|
||||
let mnemonic = mnemonic.inner.clone();
|
||||
pub(crate) fn new(network: Network, mnemonic: &Mnemonic, password: Option<String>) -> Self {
|
||||
let mnemonic = mnemonic.0.clone();
|
||||
let xkey: ExtendedKey = (mnemonic, password).into_extended_key().unwrap();
|
||||
let descriptor_secret_key = BdkDescriptorSecretKey::XPrv(DescriptorXKey {
|
||||
origin: None,
|
||||
xkey: xkey.into_xprv(network.into()).unwrap(),
|
||||
xkey: xkey.into_xprv(network).unwrap(),
|
||||
derivation_path: BdkDerivationPath::master(),
|
||||
wildcard: Wildcard::Unhardened,
|
||||
});
|
||||
Self {
|
||||
inner: descriptor_secret_key,
|
||||
}
|
||||
Self(descriptor_secret_key)
|
||||
}
|
||||
|
||||
pub(crate) fn from_string(private_key: String) -> Result<Self, BdkError> {
|
||||
pub(crate) fn from_string(private_key: String) -> Result<Self, DescriptorKeyError> {
|
||||
let descriptor_secret_key = BdkDescriptorSecretKey::from_str(private_key.as_str())
|
||||
.map_err(|e| BdkError::Generic(e.to_string()))?;
|
||||
Ok(Self {
|
||||
inner: descriptor_secret_key,
|
||||
})
|
||||
.map_err(DescriptorKeyError::from)?;
|
||||
Ok(Self(descriptor_secret_key))
|
||||
}
|
||||
|
||||
pub(crate) fn derive(&self, path: Arc<DerivationPath>) -> Result<Arc<Self>, BdkError> {
|
||||
pub(crate) fn derive(&self, path: &DerivationPath) -> Result<Arc<Self>, DescriptorKeyError> {
|
||||
let secp = Secp256k1::new();
|
||||
let descriptor_secret_key = &self.inner;
|
||||
let descriptor_secret_key = &self.0;
|
||||
let path = path.inner_mutex.lock().unwrap().deref().clone();
|
||||
match descriptor_secret_key {
|
||||
BdkDescriptorSecretKey::Single(_) => Err(BdkError::Generic(
|
||||
"Cannot derive from a single key".to_string(),
|
||||
)),
|
||||
BdkDescriptorSecretKey::Single(_) => Err(DescriptorKeyError::InvalidKeyType),
|
||||
BdkDescriptorSecretKey::XPrv(descriptor_x_key) => {
|
||||
let derived_xprv = descriptor_x_key.xkey.derive_priv(&secp, &path)?;
|
||||
let derived_xprv = descriptor_x_key
|
||||
.xkey
|
||||
.derive_priv(&secp, &path)
|
||||
.map_err(DescriptorKeyError::from)?;
|
||||
let key_source = match descriptor_x_key.origin.clone() {
|
||||
Some((fingerprint, origin_path)) => (fingerprint, origin_path.extend(path)),
|
||||
None => (descriptor_x_key.xkey.fingerprint(&secp), path),
|
||||
@@ -121,23 +107,17 @@ impl DescriptorSecretKey {
|
||||
derivation_path: BdkDerivationPath::default(),
|
||||
wildcard: descriptor_x_key.wildcard,
|
||||
});
|
||||
Ok(Arc::new(Self {
|
||||
inner: derived_descriptor_secret_key,
|
||||
}))
|
||||
Ok(Arc::new(Self(derived_descriptor_secret_key)))
|
||||
}
|
||||
BdkDescriptorSecretKey::MultiXPrv(_) => Err(BdkError::Generic(
|
||||
"Cannot derive from a multi key".to_string(),
|
||||
)),
|
||||
BdkDescriptorSecretKey::MultiXPrv(_) => Err(DescriptorKeyError::InvalidKeyType),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn extend(&self, path: Arc<DerivationPath>) -> Result<Arc<Self>, BdkError> {
|
||||
let descriptor_secret_key = &self.inner;
|
||||
pub(crate) fn extend(&self, path: &DerivationPath) -> Result<Arc<Self>, DescriptorKeyError> {
|
||||
let descriptor_secret_key = &self.0;
|
||||
let path = path.inner_mutex.lock().unwrap().deref().clone();
|
||||
match descriptor_secret_key {
|
||||
BdkDescriptorSecretKey::Single(_) => Err(BdkError::Generic(
|
||||
"Cannot extend from a single key".to_string(),
|
||||
)),
|
||||
BdkDescriptorSecretKey::Single(_) => Err(DescriptorKeyError::InvalidKeyType),
|
||||
BdkDescriptorSecretKey::XPrv(descriptor_x_key) => {
|
||||
let extended_path = descriptor_x_key.derivation_path.extend(path);
|
||||
let extended_descriptor_secret_key = BdkDescriptorSecretKey::XPrv(DescriptorXKey {
|
||||
@@ -146,27 +126,20 @@ impl DescriptorSecretKey {
|
||||
derivation_path: extended_path,
|
||||
wildcard: descriptor_x_key.wildcard,
|
||||
});
|
||||
Ok(Arc::new(Self {
|
||||
inner: extended_descriptor_secret_key,
|
||||
}))
|
||||
Ok(Arc::new(Self(extended_descriptor_secret_key)))
|
||||
}
|
||||
BdkDescriptorSecretKey::MultiXPrv(_) => Err(BdkError::Generic(
|
||||
"Cannot derive from a multi key".to_string(),
|
||||
)),
|
||||
BdkDescriptorSecretKey::MultiXPrv(_) => Err(DescriptorKeyError::InvalidKeyType),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn as_public(&self) -> Arc<DescriptorPublicKey> {
|
||||
let secp = Secp256k1::new();
|
||||
let descriptor_public_key = self.inner.to_public(&secp).unwrap();
|
||||
Arc::new(DescriptorPublicKey {
|
||||
inner: descriptor_public_key,
|
||||
})
|
||||
let descriptor_public_key = self.0.to_public(&secp).unwrap();
|
||||
Arc::new(DescriptorPublicKey(descriptor_public_key))
|
||||
}
|
||||
|
||||
/// Get the private key as bytes.
|
||||
pub(crate) fn secret_bytes(&self) -> Vec<u8> {
|
||||
let inner = &self.inner;
|
||||
let inner = &self.0;
|
||||
let secret_bytes: Vec<u8> = match inner {
|
||||
BdkDescriptorSecretKey::Single(_) => {
|
||||
unreachable!()
|
||||
@@ -183,35 +156,32 @@ impl DescriptorSecretKey {
|
||||
}
|
||||
|
||||
pub(crate) fn as_string(&self) -> String {
|
||||
self.inner.to_string()
|
||||
self.0.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct DescriptorPublicKey {
|
||||
pub(crate) inner: BdkDescriptorPublicKey,
|
||||
}
|
||||
pub struct DescriptorPublicKey(pub(crate) BdkDescriptorPublicKey);
|
||||
|
||||
impl DescriptorPublicKey {
|
||||
pub(crate) fn from_string(public_key: String) -> Result<Self, BdkError> {
|
||||
pub(crate) fn from_string(public_key: String) -> Result<Self, DescriptorKeyError> {
|
||||
let descriptor_public_key = BdkDescriptorPublicKey::from_str(public_key.as_str())
|
||||
.map_err(|e| BdkError::Generic(e.to_string()))?;
|
||||
Ok(Self {
|
||||
inner: descriptor_public_key,
|
||||
})
|
||||
.map_err(DescriptorKeyError::from)?;
|
||||
Ok(Self(descriptor_public_key))
|
||||
}
|
||||
|
||||
pub(crate) fn derive(&self, path: Arc<DerivationPath>) -> Result<Arc<Self>, BdkError> {
|
||||
pub(crate) fn derive(&self, path: &DerivationPath) -> Result<Arc<Self>, DescriptorKeyError> {
|
||||
let secp = Secp256k1::new();
|
||||
let descriptor_public_key = &self.inner;
|
||||
let descriptor_public_key = &self.0;
|
||||
let path = path.inner_mutex.lock().unwrap().deref().clone();
|
||||
|
||||
match descriptor_public_key {
|
||||
BdkDescriptorPublicKey::Single(_) => Err(BdkError::Generic(
|
||||
"Cannot derive from a single key".to_string(),
|
||||
)),
|
||||
BdkDescriptorPublicKey::Single(_) => Err(DescriptorKeyError::InvalidKeyType),
|
||||
BdkDescriptorPublicKey::XPub(descriptor_x_key) => {
|
||||
let derived_xpub = descriptor_x_key.xkey.derive_pub(&secp, &path)?;
|
||||
let derived_xpub = descriptor_x_key
|
||||
.xkey
|
||||
.derive_pub(&secp, &path)
|
||||
.map_err(DescriptorKeyError::from)?;
|
||||
let key_source = match descriptor_x_key.origin.clone() {
|
||||
Some((fingerprint, origin_path)) => (fingerprint, origin_path.extend(path)),
|
||||
None => (descriptor_x_key.xkey.fingerprint(), path),
|
||||
@@ -222,23 +192,17 @@ impl DescriptorPublicKey {
|
||||
derivation_path: BdkDerivationPath::default(),
|
||||
wildcard: descriptor_x_key.wildcard,
|
||||
});
|
||||
Ok(Arc::new(Self {
|
||||
inner: derived_descriptor_public_key,
|
||||
}))
|
||||
Ok(Arc::new(Self(derived_descriptor_public_key)))
|
||||
}
|
||||
BdkDescriptorPublicKey::MultiXPub(_) => Err(BdkError::Generic(
|
||||
"Cannot derive from a multi xpub".to_string(),
|
||||
)),
|
||||
BdkDescriptorPublicKey::MultiXPub(_) => Err(DescriptorKeyError::InvalidKeyType),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn extend(&self, path: Arc<DerivationPath>) -> Result<Arc<Self>, BdkError> {
|
||||
let descriptor_public_key = &self.inner;
|
||||
pub(crate) fn extend(&self, path: &DerivationPath) -> Result<Arc<Self>, DescriptorKeyError> {
|
||||
let descriptor_public_key = &self.0;
|
||||
let path = path.inner_mutex.lock().unwrap().deref().clone();
|
||||
match descriptor_public_key {
|
||||
BdkDescriptorPublicKey::Single(_) => Err(BdkError::Generic(
|
||||
"Cannot extend from a single key".to_string(),
|
||||
)),
|
||||
BdkDescriptorPublicKey::Single(_) => Err(DescriptorKeyError::InvalidKeyType),
|
||||
BdkDescriptorPublicKey::XPub(descriptor_x_key) => {
|
||||
let extended_path = descriptor_x_key.derivation_path.extend(path);
|
||||
let extended_descriptor_public_key = BdkDescriptorPublicKey::XPub(DescriptorXKey {
|
||||
@@ -247,67 +211,60 @@ impl DescriptorPublicKey {
|
||||
derivation_path: extended_path,
|
||||
wildcard: descriptor_x_key.wildcard,
|
||||
});
|
||||
Ok(Arc::new(Self {
|
||||
inner: extended_descriptor_public_key,
|
||||
}))
|
||||
Ok(Arc::new(Self(extended_descriptor_public_key)))
|
||||
}
|
||||
BdkDescriptorPublicKey::MultiXPub(_) => Err(BdkError::Generic(
|
||||
"Cannot derive from a multi xpub".to_string(),
|
||||
)),
|
||||
BdkDescriptorPublicKey::MultiXPub(_) => Err(DescriptorKeyError::InvalidKeyType),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn as_string(&self) -> String {
|
||||
self.inner.to_string()
|
||||
self.0.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)]
|
||||
mod test {
|
||||
use crate::keys::{DerivationPath, DescriptorPublicKey, DescriptorSecretKey, Mnemonic};
|
||||
use crate::BdkError;
|
||||
// use bdk::bitcoin::hashes::hex::ToHex;
|
||||
use crate::error::DescriptorKeyError;
|
||||
use bdk::bitcoin::Network;
|
||||
use std::sync::Arc;
|
||||
|
||||
fn get_inner() -> DescriptorSecretKey {
|
||||
let mnemonic = Mnemonic::from_string("chaos fabric time speed sponsor all flat solution wisdom trophy crack object robot pave observe combine where aware bench orient secret primary cable detect".to_string()).unwrap();
|
||||
DescriptorSecretKey::new(Network::Testnet.into(), Arc::new(mnemonic), None)
|
||||
DescriptorSecretKey::new(Network::Testnet, &mnemonic, None)
|
||||
}
|
||||
|
||||
fn derive_dsk(
|
||||
key: &DescriptorSecretKey,
|
||||
path: &str,
|
||||
) -> Result<Arc<DescriptorSecretKey>, BdkError> {
|
||||
let path = Arc::new(DerivationPath::new(path.to_string()).unwrap());
|
||||
key.derive(path)
|
||||
) -> Result<Arc<DescriptorSecretKey>, DescriptorKeyError> {
|
||||
let path = DerivationPath::new(path.to_string()).unwrap();
|
||||
key.derive(&path)
|
||||
}
|
||||
|
||||
fn extend_dsk(
|
||||
key: &DescriptorSecretKey,
|
||||
path: &str,
|
||||
) -> Result<Arc<DescriptorSecretKey>, BdkError> {
|
||||
let path = Arc::new(DerivationPath::new(path.to_string()).unwrap());
|
||||
key.extend(path)
|
||||
) -> Result<Arc<DescriptorSecretKey>, DescriptorKeyError> {
|
||||
let path = DerivationPath::new(path.to_string()).unwrap();
|
||||
key.extend(&path)
|
||||
}
|
||||
|
||||
fn derive_dpk(
|
||||
key: &DescriptorPublicKey,
|
||||
path: &str,
|
||||
) -> Result<Arc<DescriptorPublicKey>, BdkError> {
|
||||
let path = Arc::new(DerivationPath::new(path.to_string()).unwrap());
|
||||
key.derive(path)
|
||||
) -> Result<Arc<DescriptorPublicKey>, DescriptorKeyError> {
|
||||
let path = DerivationPath::new(path.to_string()).unwrap();
|
||||
key.derive(&path)
|
||||
}
|
||||
|
||||
fn extend_dpk(
|
||||
key: &DescriptorPublicKey,
|
||||
path: &str,
|
||||
) -> Result<Arc<DescriptorPublicKey>, BdkError> {
|
||||
let path = Arc::new(DerivationPath::new(path.to_string()).unwrap());
|
||||
key.extend(path)
|
||||
) -> Result<Arc<DescriptorPublicKey>, DescriptorKeyError> {
|
||||
let path = DerivationPath::new(path.to_string()).unwrap();
|
||||
key.extend(&path)
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -347,7 +304,7 @@ mod test {
|
||||
assert_eq!(extended_dpk.as_string(), "tpubD6NzVbkrYhZ4WywdEfYbbd62yuvqLjAZuPsNyvzCNV85JekAEMbKHWSHLF9h3j45SxewXDcLv328B1SEZrxg4iwGfmdt1pDFjZiTkGiFqGa/0/*");
|
||||
let wif = "L2wTu6hQrnDMiFNWA5na6jB12ErGQqtXwqpSL7aWquJaZG8Ai3ch";
|
||||
let extended_key = DescriptorSecretKey::from_string(wif.to_string()).unwrap();
|
||||
let result = extended_key.derive(Arc::new(DerivationPath::new("m/0".to_string()).unwrap()));
|
||||
let result = extended_key.derive(&DerivationPath::new("m/0".to_string()).unwrap());
|
||||
dbg!(&result);
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
@@ -1,385 +1,58 @@
|
||||
mod bitcoin;
|
||||
mod descriptor;
|
||||
mod error;
|
||||
mod esplora;
|
||||
mod keys;
|
||||
mod types;
|
||||
mod wallet;
|
||||
|
||||
// TODO 6: Why are these imports required?
|
||||
use crate::bitcoin::Address;
|
||||
use crate::bitcoin::Network;
|
||||
use crate::bitcoin::FeeRate;
|
||||
use crate::bitcoin::OutPoint;
|
||||
use crate::bitcoin::PartiallySignedTransaction;
|
||||
use crate::bitcoin::Psbt;
|
||||
use crate::bitcoin::Script;
|
||||
use crate::bitcoin::Transaction;
|
||||
use crate::bitcoin::TxOut;
|
||||
use crate::descriptor::Descriptor;
|
||||
use crate::error::AddressError;
|
||||
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::EsploraError;
|
||||
use crate::error::ExtractTxError;
|
||||
use crate::error::FeeRateError;
|
||||
use crate::error::PersistenceError;
|
||||
use crate::error::PsbtParseError;
|
||||
use crate::error::SignerError;
|
||||
use crate::error::TransactionError;
|
||||
use crate::error::TxidParseError;
|
||||
use crate::error::WalletCreationError;
|
||||
use crate::esplora::EsploraClient;
|
||||
use crate::keys::DerivationPath;
|
||||
use crate::keys::DescriptorPublicKey;
|
||||
use crate::keys::DescriptorSecretKey;
|
||||
use crate::keys::Mnemonic;
|
||||
use crate::types::AddressInfo;
|
||||
use crate::types::Balance;
|
||||
use crate::types::CanonicalTx;
|
||||
use crate::types::ChainPosition;
|
||||
use crate::types::FullScanRequest;
|
||||
use crate::types::LocalOutput;
|
||||
use crate::types::ScriptAmount;
|
||||
use crate::types::SyncRequest;
|
||||
use crate::wallet::BumpFeeTxBuilder;
|
||||
use crate::wallet::SentAndReceivedValues;
|
||||
use crate::wallet::TxBuilder;
|
||||
use crate::wallet::Update;
|
||||
use crate::wallet::Wallet;
|
||||
|
||||
use bdk::bitcoin::Network;
|
||||
use bdk::keys::bip39::WordCount;
|
||||
use bdk::wallet::tx_builder::ChangeSpendPolicy;
|
||||
use bdk::wallet::AddressIndex as BdkAddressIndex;
|
||||
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");
|
||||
|
||||
/// 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);
|
||||
// }
|
||||
// }
|
||||
|
||||
115
bdk-ffi/src/types.rs
Normal file
115
bdk-ffi/src/types.rs
Normal file
@@ -0,0 +1,115 @@
|
||||
use crate::bitcoin::{Address, OutPoint, Script, Transaction, TxOut};
|
||||
|
||||
use bdk::chain::spk_client::FullScanRequest as BdkFullScanRequest;
|
||||
use bdk::chain::spk_client::SyncRequest as BdkSyncRequest;
|
||||
use bdk::chain::tx_graph::CanonicalTx as BdkCanonicalTx;
|
||||
use bdk::chain::{ChainPosition as BdkChainPosition, ConfirmationTimeHeightAnchor};
|
||||
use bdk::wallet::AddressInfo as BdkAddressInfo;
|
||||
use bdk::wallet::Balance as BdkBalance;
|
||||
use bdk::KeychainKind;
|
||||
use bdk::LocalOutput as BdkLocalOutput;
|
||||
|
||||
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<bdk::bitcoin::Transaction>, ConfirmationTimeHeightAnchor>>
|
||||
for CanonicalTx
|
||||
{
|
||||
fn from(
|
||||
tx: BdkCanonicalTx<'_, Arc<bdk::bitcoin::Transaction>, 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: u64,
|
||||
}
|
||||
|
||||
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: u64,
|
||||
pub trusted_pending: u64,
|
||||
pub untrusted_pending: u64,
|
||||
pub confirmed: u64,
|
||||
pub trusted_spendable: u64,
|
||||
pub total: u64,
|
||||
}
|
||||
|
||||
impl From<BdkBalance> for Balance {
|
||||
fn from(bdk_balance: BdkBalance) -> Self {
|
||||
Balance {
|
||||
immature: bdk_balance.immature,
|
||||
trusted_pending: bdk_balance.trusted_pending,
|
||||
untrusted_pending: bdk_balance.untrusted_pending,
|
||||
confirmed: bdk_balance.confirmed,
|
||||
trusted_spendable: bdk_balance.trusted_spendable(),
|
||||
total: bdk_balance.total(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct FullScanRequest(pub(crate) Mutex<Option<BdkFullScanRequest<KeychainKind>>>);
|
||||
|
||||
pub struct SyncRequest(pub(crate) Mutex<Option<BdkSyncRequest>>);
|
||||
@@ -1,285 +1,163 @@
|
||||
use crate::bitcoin::{OutPoint, PartiallySignedTransaction};
|
||||
use crate::bitcoin::{FeeRate, OutPoint, Psbt, Script, Transaction};
|
||||
use crate::descriptor::Descriptor;
|
||||
use crate::{AddressIndex, AddressInfo, Network, ScriptAmount};
|
||||
use crate::{Balance, Script};
|
||||
use std::collections::HashSet;
|
||||
use crate::error::{
|
||||
CalculateFeeError, CannotConnectError, CreateTxError, PersistenceError, SignerError,
|
||||
TxidParseError, WalletCreationError,
|
||||
};
|
||||
use crate::types::{
|
||||
AddressInfo, Balance, CanonicalTx, FullScanRequest, LocalOutput, ScriptAmount, SyncRequest,
|
||||
};
|
||||
|
||||
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::bitcoin::Network;
|
||||
use bdk::bitcoin::Psbt as BdkPsbt;
|
||||
use bdk::bitcoin::{OutPoint as BdkOutPoint, Sequence, Txid};
|
||||
use bdk::wallet::tx_builder::ChangeSpendPolicy;
|
||||
use bdk::wallet::{ChangeSet, Update as BdkUpdate};
|
||||
use bdk::Wallet as BdkWallet;
|
||||
use bdk::{KeychainKind, SignOptions};
|
||||
use bdk_file_store::Store;
|
||||
|
||||
use std::collections::HashSet;
|
||||
use std::str::FromStr;
|
||||
use std::sync::{Arc, Mutex, MutexGuard};
|
||||
|
||||
#[derive(Debug)]
|
||||
const MAGIC_BYTES: &[u8] = "bdkffi".as_bytes();
|
||||
|
||||
pub struct Wallet {
|
||||
// TODO 8: Do we really need the mutex on the wallet? Could this be an Arc?
|
||||
inner_mutex: Mutex<BdkWallet>,
|
||||
}
|
||||
|
||||
impl Wallet {
|
||||
pub fn new_no_persist(
|
||||
pub fn new(
|
||||
descriptor: Arc<Descriptor>,
|
||||
change_descriptor: Option<Arc<Descriptor>>,
|
||||
persistence_backend_path: String,
|
||||
network: Network,
|
||||
) -> Result<Self, BdkError> {
|
||||
) -> Result<Self, WalletCreationError> {
|
||||
let descriptor = descriptor.as_string_private();
|
||||
let change_descriptor = change_descriptor.map(|d| d.as_string_private());
|
||||
let db = Store::<ChangeSet>::open_or_create_new(MAGIC_BYTES, persistence_backend_path)?;
|
||||
|
||||
let wallet =
|
||||
BdkWallet::new_no_persist(&descriptor, change_descriptor.as_ref(), network.into())?;
|
||||
let wallet: BdkWallet =
|
||||
BdkWallet::new_or_load(&descriptor, change_descriptor.as_ref(), db, network)?;
|
||||
|
||||
Ok(Wallet {
|
||||
inner_mutex: Mutex::new(wallet),
|
||||
})
|
||||
}
|
||||
|
||||
// TODO 10: Do we need this mutex
|
||||
pub(crate) fn get_wallet(&self) -> MutexGuard<BdkWallet> {
|
||||
self.inner_mutex.lock().expect("wallet")
|
||||
}
|
||||
|
||||
pub fn get_address(&self, address_index: AddressIndex) -> AddressInfo {
|
||||
self.get_wallet().get_address(address_index.into()).into()
|
||||
pub fn reveal_next_address(
|
||||
&self,
|
||||
keychain_kind: KeychainKind,
|
||||
) -> Result<AddressInfo, PersistenceError> {
|
||||
self.get_wallet()
|
||||
.reveal_next_address(keychain_kind)
|
||||
.map(|address_info| address_info.into())
|
||||
.map_err(|e| PersistenceError::Write {
|
||||
error_message: e.to_string(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn apply_update(&self, update: Arc<Update>) -> Result<(), CannotConnectError> {
|
||||
self.get_wallet()
|
||||
.apply_update(update.0.clone())
|
||||
.map_err(CannotConnectError::from)
|
||||
}
|
||||
|
||||
pub fn commit(&self) -> Result<bool, PersistenceError> {
|
||||
self.get_wallet()
|
||||
.commit()
|
||||
.map_err(|e| PersistenceError::Write {
|
||||
error_message: e.to_string(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn network(&self) -> Network {
|
||||
self.get_wallet().network().into()
|
||||
self.get_wallet().network()
|
||||
}
|
||||
|
||||
pub fn get_internal_address(&self, address_index: AddressIndex) -> AddressInfo {
|
||||
self.get_wallet()
|
||||
.get_internal_address(address_index.into())
|
||||
.into()
|
||||
pub fn get_balance(&self) -> Balance {
|
||||
let bdk_balance: bdk::wallet::Balance = self.get_wallet().get_balance();
|
||||
Balance::from(bdk_balance)
|
||||
}
|
||||
|
||||
// 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?
|
||||
pub fn is_mine(&self, script: &Script) -> bool {
|
||||
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(
|
||||
&self,
|
||||
psbt: Arc<PartiallySignedTransaction>,
|
||||
psbt: Arc<Psbt>,
|
||||
// sign_options: Option<SignOptions>,
|
||||
) -> Result<bool, BdkError> {
|
||||
let mut psbt = psbt.inner.lock().unwrap();
|
||||
) -> Result<bool, SignerError> {
|
||||
let mut psbt = psbt.0.lock().unwrap();
|
||||
self.get_wallet()
|
||||
.sign(&mut psbt, SignOptions::default())
|
||||
.map_err(|e| BdkError::Generic(e.to_string()))
|
||||
.map_err(SignerError::from)
|
||||
}
|
||||
|
||||
pub fn sent_and_received(&self, tx: &Transaction) -> SentAndReceivedValues {
|
||||
let (sent, received): (u64, u64) = self.get_wallet().sent_and_received(&tx.into());
|
||||
SentAndReceivedValues { sent, received }
|
||||
}
|
||||
|
||||
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<u64, CalculateFeeError> {
|
||||
self.get_wallet()
|
||||
.calculate_fee(&tx.into())
|
||||
.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 struct SentAndReceivedValues {
|
||||
pub sent: u64,
|
||||
pub received: u64,
|
||||
}
|
||||
|
||||
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)]
|
||||
pub struct TxBuilder {
|
||||
pub(crate) recipients: Vec<(BdkScriptBuf, u64)>,
|
||||
@@ -287,11 +165,11 @@ pub struct TxBuilder {
|
||||
pub(crate) unspendable: HashSet<OutPoint>,
|
||||
pub(crate) change_policy: ChangeSpendPolicy,
|
||||
pub(crate) manually_selected_only: bool,
|
||||
pub(crate) fee_rate: Option<f32>,
|
||||
// pub(crate) fee_absolute: Option<u64>,
|
||||
pub(crate) fee_rate: Option<FeeRate>,
|
||||
pub(crate) fee_absolute: Option<u64>,
|
||||
pub(crate) drain_wallet: bool,
|
||||
// pub(crate) drain_to: Option<BdkScript>,
|
||||
// pub(crate) rbf: Option<RbfValue>,
|
||||
pub(crate) drain_to: Option<BdkScriptBuf>,
|
||||
pub(crate) rbf: Option<RbfValue>,
|
||||
// pub(crate) data: Vec<u8>,
|
||||
}
|
||||
|
||||
@@ -304,16 +182,15 @@ impl TxBuilder {
|
||||
change_policy: ChangeSpendPolicy::ChangeAllowed,
|
||||
manually_selected_only: false,
|
||||
fee_rate: None,
|
||||
// fee_absolute: None,
|
||||
fee_absolute: None,
|
||||
drain_wallet: false,
|
||||
// drain_to: None,
|
||||
// rbf: None,
|
||||
drain_to: None,
|
||||
rbf: None,
|
||||
// data: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Add a recipient to the internal list.
|
||||
pub(crate) fn add_recipient(&self, script: Arc<Script>, amount: u64) -> Arc<Self> {
|
||||
pub(crate) fn add_recipient(&self, script: &Script, amount: u64) -> Arc<Self> {
|
||||
let mut recipients: Vec<(BdkScriptBuf, u64)> = self.recipients.clone();
|
||||
recipients.append(&mut vec![(script.0.clone(), amount)]);
|
||||
|
||||
@@ -334,8 +211,6 @@ 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> {
|
||||
let mut unspendable_hash_set = self.unspendable.clone();
|
||||
unspendable_hash_set.insert(unspendable);
|
||||
@@ -345,15 +220,17 @@ impl TxBuilder {
|
||||
})
|
||||
}
|
||||
|
||||
/// Add an outpoint to the internal list of UTXOs that must be spent. These have priority over the "unspendable"
|
||||
/// utxos, meaning that if a utxo is present both in the "utxos" and the "unspendable" list, it will be spent.
|
||||
pub(crate) fn unspendable(&self, unspendable: Vec<OutPoint>) -> Arc<Self> {
|
||||
Arc::new(TxBuilder {
|
||||
unspendable: unspendable.into_iter().collect(),
|
||||
..self.clone()
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn add_utxo(&self, outpoint: OutPoint) -> Arc<Self> {
|
||||
self.add_utxos(vec![outpoint])
|
||||
}
|
||||
|
||||
/// 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> {
|
||||
let mut utxos = self.utxos.to_vec();
|
||||
utxos.append(&mut outpoints);
|
||||
@@ -370,7 +247,6 @@ 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> {
|
||||
Arc::new(TxBuilder {
|
||||
change_policy: ChangeSpendPolicy::ChangeForbidden,
|
||||
@@ -378,7 +254,6 @@ 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> {
|
||||
Arc::new(TxBuilder {
|
||||
change_policy: ChangeSpendPolicy::OnlyChange,
|
||||
@@ -386,8 +261,6 @@ 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> {
|
||||
Arc::new(TxBuilder {
|
||||
manually_selected_only: true,
|
||||
@@ -395,32 +268,20 @@ impl TxBuilder {
|
||||
})
|
||||
}
|
||||
|
||||
// /// 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> {
|
||||
pub(crate) fn fee_rate(&self, fee_rate: &FeeRate) -> Arc<Self> {
|
||||
Arc::new(TxBuilder {
|
||||
fee_rate: Some(sat_per_vb),
|
||||
fee_rate: Some(fee_rate.clone()),
|
||||
..self.clone()
|
||||
})
|
||||
}
|
||||
|
||||
// /// Set an absolute fee.
|
||||
// pub(crate) fn fee_absolute(&self, fee_amount: u64) -> Arc<Self> {
|
||||
// Arc::new(TxBuilder {
|
||||
// fee_absolute: Some(fee_amount),
|
||||
// ..self.clone()
|
||||
// })
|
||||
// }
|
||||
pub(crate) fn fee_absolute(&self, fee_amount: u64) -> Arc<Self> {
|
||||
Arc::new(TxBuilder {
|
||||
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> {
|
||||
Arc::new(TxBuilder {
|
||||
drain_wallet: true,
|
||||
@@ -428,52 +289,28 @@ impl TxBuilder {
|
||||
})
|
||||
}
|
||||
|
||||
// /// Sets the address to drain excess coins to. Usually, when there are excess coins they are sent to a change address
|
||||
// /// generated by the wallet. This option replaces the usual change address with an arbitrary ScriptPubKey of your choosing.
|
||||
// /// Just as with a change output, if the drain output is not needed (the excess coins are too small) it will not be included
|
||||
// /// in the resulting transaction. The only difference is that it is valid to use 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 drain_to(&self, script: &Script) -> Arc<Self> {
|
||||
Arc::new(TxBuilder {
|
||||
drain_to: Some(script.0.clone()),
|
||||
..self.clone()
|
||||
})
|
||||
}
|
||||
|
||||
/// Add data as an output using OP_RETURN.
|
||||
// pub(crate) fn add_data(&self, data: Vec<u8>) -> Arc<Self> {
|
||||
// Arc::new(TxBuilder {
|
||||
// data,
|
||||
// ..self.clone()
|
||||
// })
|
||||
// }
|
||||
pub(crate) fn enable_rbf(&self) -> Arc<Self> {
|
||||
Arc::new(TxBuilder {
|
||||
rbf: Some(RbfValue::Default),
|
||||
..self.clone()
|
||||
})
|
||||
}
|
||||
|
||||
/// Finish building the transaction. Returns the BIP174 PSBT.
|
||||
pub(crate) fn finish(
|
||||
&self,
|
||||
wallet: &Wallet,
|
||||
) -> Result<Arc<PartiallySignedTransaction>, BdkError> {
|
||||
pub(crate) fn enable_rbf_with_sequence(&self, nsequence: u32) -> Arc<Self> {
|
||||
Arc::new(TxBuilder {
|
||||
rbf: Some(RbfValue::Value(nsequence)),
|
||||
..self.clone()
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn finish(&self, wallet: &Arc<Wallet>) -> Result<Arc<Psbt>, CreateTxError> {
|
||||
// TODO: I had to change the wallet here to be mutable. Why is that now required with the 1.0 API?
|
||||
let mut wallet = wallet.get_wallet();
|
||||
let mut tx_builder = wallet.build_tx();
|
||||
@@ -483,137 +320,106 @@ impl TxBuilder {
|
||||
tx_builder.change_policy(self.change_policy);
|
||||
if !self.utxos.is_empty() {
|
||||
let bdk_utxos: Vec<BdkOutPoint> = self.utxos.iter().map(BdkOutPoint::from).collect();
|
||||
let utxos: &[BdkOutPoint] = &bdk_utxos;
|
||||
tx_builder.add_utxos(utxos)?;
|
||||
tx_builder
|
||||
.add_utxos(&bdk_utxos)
|
||||
.map_err(CreateTxError::from)?;
|
||||
}
|
||||
if !self.unspendable.is_empty() {
|
||||
let bdk_unspendable: Vec<BdkOutPoint> =
|
||||
self.unspendable.iter().map(BdkOutPoint::from).collect();
|
||||
tx_builder.unspendable(bdk_unspendable);
|
||||
}
|
||||
// if !self.unspendable.is_empty() {
|
||||
// let bdk_unspendable: Vec<BdkOutPoint> =
|
||||
// self.unspendable.iter().map(BdkOutPoint::from).collect();
|
||||
// tx_builder.unspendable(bdk_unspendable);
|
||||
// }
|
||||
if self.manually_selected_only {
|
||||
tx_builder.manually_selected_only();
|
||||
}
|
||||
if let Some(sat_per_vb) = self.fee_rate {
|
||||
tx_builder.fee_rate(FeeRate::from_sat_per_vb(sat_per_vb));
|
||||
if let Some(fee_rate) = &self.fee_rate {
|
||||
tx_builder.fee_rate(fee_rate.0);
|
||||
}
|
||||
if let Some(fee_amount) = self.fee_absolute {
|
||||
tx_builder.fee_absolute(fee_amount);
|
||||
}
|
||||
// if let Some(fee_amount) = self.fee_absolute {
|
||||
// tx_builder.fee_absolute(fee_amount);
|
||||
// }
|
||||
if self.drain_wallet {
|
||||
tx_builder.drain_wallet();
|
||||
}
|
||||
// if let Some(script) = &self.drain_to {
|
||||
// tx_builder.drain_to(script.clone());
|
||||
// }
|
||||
// if let Some(rbf) = &self.rbf {
|
||||
// match *rbf {
|
||||
// RbfValue::Default => {
|
||||
// tx_builder.enable_rbf();
|
||||
// }
|
||||
// RbfValue::Value(nsequence) => {
|
||||
// tx_builder.enable_rbf_with_sequence(Sequence(nsequence));
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// if !&self.data.is_empty() {
|
||||
// tx_builder.add_data(self.data.as_slice());
|
||||
// }
|
||||
if let Some(script) = &self.drain_to {
|
||||
tx_builder.drain_to(script.clone());
|
||||
}
|
||||
if let Some(rbf) = &self.rbf {
|
||||
match *rbf {
|
||||
RbfValue::Default => {
|
||||
tx_builder.enable_rbf();
|
||||
}
|
||||
RbfValue::Value(nsequence) => {
|
||||
tx_builder.enable_rbf_with_sequence(Sequence(nsequence));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// tx_builder.finish().map(|psbt| psbt.serialize_hex())
|
||||
// tx_builder.finish().into()
|
||||
let psbt = tx_builder.finish()?;
|
||||
let psbt = tx_builder.finish().map_err(CreateTxError::from)?;
|
||||
|
||||
Ok(Arc::new(psbt.into()))
|
||||
}
|
||||
}
|
||||
|
||||
// /// The BumpFeeTxBuilder is used to bump the fee on a transaction that has been broadcast and has its RBF flag set to true.
|
||||
// #[derive(Clone)]
|
||||
// pub(crate) struct BumpFeeTxBuilder {
|
||||
// pub(crate) txid: String,
|
||||
// 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: f32) -> Self {
|
||||
// Self {
|
||||
// txid,
|
||||
// fee_rate,
|
||||
// allow_shrinking: None,
|
||||
// rbf: None,
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// /// 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
|
||||
// /// can’t be found among the recipients of the transaction we are bumping.
|
||||
// pub(crate) fn allow_shrinking(&self, address: String) -> Arc<Self> {
|
||||
// Arc::new(Self {
|
||||
// allow_shrinking: Some(address),
|
||||
// ..self.clone()
|
||||
// })
|
||||
// }
|
||||
//
|
||||
// /// Enable signaling RBF. This will use the default `nsequence` value of `0xFFFFFFFD`.
|
||||
// pub(crate) fn enable_rbf(&self) -> Arc<Self> {
|
||||
// Arc::new(Self {
|
||||
// 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(Self {
|
||||
// rbf: Some(RbfValue::Value(nsequence)),
|
||||
// ..self.clone()
|
||||
// })
|
||||
// }
|
||||
//
|
||||
// /// Finish building the transaction. Returns the BIP174 PSBT.
|
||||
// pub(crate) fn finish(
|
||||
// &self,
|
||||
// wallet: &Wallet,
|
||||
// ) -> Result<Arc<PartiallySignedTransaction>, BdkError> {
|
||||
// let wallet = wallet.get_wallet();
|
||||
// let txid = Txid::from_str(self.txid.as_str())?;
|
||||
// let mut tx_builder = wallet.build_fee_bump(txid)?;
|
||||
// tx_builder.fee_rate(FeeRate::from_sat_per_vb(self.fee_rate));
|
||||
// if let Some(allow_shrinking) = &self.allow_shrinking {
|
||||
// let address = 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)
|
||||
// }
|
||||
// }
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct BumpFeeTxBuilder {
|
||||
pub(crate) txid: String,
|
||||
pub(crate) fee_rate: Arc<FeeRate>,
|
||||
pub(crate) rbf: Option<RbfValue>,
|
||||
}
|
||||
|
||||
impl BumpFeeTxBuilder {
|
||||
pub(crate) fn new(txid: String, fee_rate: Arc<FeeRate>) -> Self {
|
||||
Self {
|
||||
txid,
|
||||
fee_rate,
|
||||
rbf: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn enable_rbf(&self) -> Arc<Self> {
|
||||
Arc::new(Self {
|
||||
rbf: Some(RbfValue::Default),
|
||||
..self.clone()
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn enable_rbf_with_sequence(&self, nsequence: u32) -> Arc<Self> {
|
||||
Arc::new(Self {
|
||||
rbf: Some(RbfValue::Value(nsequence)),
|
||||
..self.clone()
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn finish(&self, wallet: &Wallet) -> Result<Arc<Psbt>, CreateTxError> {
|
||||
let txid = Txid::from_str(self.txid.as_str()).map_err(|_| CreateTxError::UnknownUtxo {
|
||||
outpoint: self.txid.clone(),
|
||||
})?;
|
||||
let mut wallet = wallet.get_wallet();
|
||||
let mut tx_builder = wallet.build_fee_bump(txid).map_err(CreateTxError::from)?;
|
||||
tx_builder.fee_rate(self.fee_rate.0);
|
||||
if let Some(rbf) = &self.rbf {
|
||||
match *rbf {
|
||||
RbfValue::Default => {
|
||||
tx_builder.enable_rbf();
|
||||
}
|
||||
RbfValue::Value(nsequence) => {
|
||||
tx_builder.enable_rbf_with_sequence(Sequence(nsequence));
|
||||
}
|
||||
}
|
||||
}
|
||||
let psbt: BdkPsbt = tx_builder.finish()?;
|
||||
|
||||
Ok(Arc::new(psbt.into()))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum RbfValue {
|
||||
Default,
|
||||
Value(u32),
|
||||
}
|
||||
|
||||
// // 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 crate::database::DatabaseConfig;
|
||||
|
||||
@@ -5,7 +5,7 @@ supported bindings languages.
|
||||
|
||||
To skip integration tests and only run unit tests use `cargo test --lib`.
|
||||
|
||||
To run all tests including integration tests use `CLASSPATH=./tests/jna/jna-5.8.0.jar cargo test`.
|
||||
To run all tests including integration tests use `CLASSPATH=./tests/jna/jna-5.14.0.jar cargo test`.
|
||||
|
||||
Before running integration tests you must install the following development tools:
|
||||
|
||||
|
||||
BIN
bdk-ffi/tests/jna/jna-5.14.0.jar
Normal file
BIN
bdk-ffi/tests/jna/jna-5.14.0.jar
Normal file
Binary file not shown.
Binary file not shown.
3
bdk-ffi/uniffi-android.toml
Normal file
3
bdk-ffi/uniffi-android.toml
Normal file
@@ -0,0 +1,3 @@
|
||||
[bindings.kotlin]
|
||||
android = true
|
||||
android_cleaner = true
|
||||
@@ -1,5 +1,5 @@
|
||||
# bdk-jvm
|
||||
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.
|
||||
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.
|
||||
|
||||
## How to Use
|
||||
To use the Kotlin language bindings for [`bdk`] in your JVM project add the following to your gradle dependencies:
|
||||
@@ -13,24 +13,6 @@ 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
|
||||
To use a snapshot release, specify the snapshot repository url in the `repositories` block and use the snapshot version in the `dependencies` block:
|
||||
```kotlin
|
||||
@@ -47,17 +29,17 @@ dependencies {
|
||||
* [Tatooine Faucet](https://github.com/thunderbiscuit/tatooine)
|
||||
|
||||
## How to build
|
||||
_Note that Kotlin version `1.6.10` or later is required to build the library._
|
||||
1. Install JDK 11. It must be version 11 (not 17), otherwise it won't build. For example, with SDKMAN!:
|
||||
_Note that Kotlin version `1.9.23` or later is required to build the library._
|
||||
1. Install JDK 17. For example, with SDKMAN!:
|
||||
```shell
|
||||
curl -s "https://get.sdkman.io" | bash
|
||||
source "$HOME/.sdkman/bin/sdkman-init.sh"
|
||||
sdk install java 11.0.19-tem
|
||||
sdk install java 17.0.2-tem
|
||||
```
|
||||
2. Install Rust (note that we are currently building using Rust 1.73.0):
|
||||
2. Install Rust (note that we are currently building using Rust 1.77.1):
|
||||
```shell
|
||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
|
||||
rustup default 1.73.0
|
||||
rustup default 1.77.1
|
||||
```
|
||||
3. Clone this repository.
|
||||
```shell
|
||||
@@ -75,7 +57,7 @@ rustup target add x86_64-apple-darwin aarch64-apple-darwin
|
||||
## How to publish to your local Maven repo
|
||||
```shell
|
||||
cd bdk-jvm
|
||||
./gradlew publishToMavenLocal --exclude-task signMavenPublication
|
||||
./gradlew publishToMavenLocal -P localBuild
|
||||
```
|
||||
|
||||
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:
|
||||
@@ -84,7 +66,7 @@ signing.gnupg.keyName=<YOUR_GNUPG_ID>
|
||||
signing.gnupg.passphrase=<YOUR_GNUPG_PASSPHRASE>
|
||||
```
|
||||
|
||||
and use the `publishToMavenLocal` task without excluding the signing task:
|
||||
and use the `publishToMavenLocal` task without the `localBuild` flag:
|
||||
```shell
|
||||
./gradlew publishToMavenLocal
|
||||
```
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
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"
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
org.gradle.jvmargs=-Xmx1536m
|
||||
android.enableJetifier=true
|
||||
kotlin.code.style=official
|
||||
libraryVersion=0.31.0-SNAPSHOT
|
||||
libraryVersion=1.0.0-alpha.10-SNAPSHOT
|
||||
|
||||
BIN
bdk-jvm/gradle/wrapper/gradle-wrapper.jar
vendored
BIN
bdk-jvm/gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
@@ -1,5 +1,7 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.2-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-all.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
||||
31
bdk-jvm/gradlew
vendored
31
bdk-jvm/gradlew
vendored
@@ -55,7 +55,7 @@
|
||||
# Darwin, MinGW, and NonStop.
|
||||
#
|
||||
# (3) This script is generated from the Groovy template
|
||||
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# within the Gradle project.
|
||||
#
|
||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||
@@ -80,13 +80,11 @@ do
|
||||
esac
|
||||
done
|
||||
|
||||
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
||||
|
||||
APP_NAME="Gradle"
|
||||
# This is normally unused
|
||||
# shellcheck disable=SC2034
|
||||
APP_BASE_NAME=${0##*/}
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
# 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
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD=maximum
|
||||
@@ -133,22 +131,29 @@ location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD=java
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
if ! command -v java >/dev/null 2>&1
|
||||
then
|
||||
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||
case $MAX_FD in #(
|
||||
max*)
|
||||
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC3045
|
||||
MAX_FD=$( ulimit -H -n ) ||
|
||||
warn "Could not query maximum file descriptor limit"
|
||||
esac
|
||||
case $MAX_FD in #(
|
||||
'' | soft) :;; #(
|
||||
*)
|
||||
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC3045
|
||||
ulimit -n "$MAX_FD" ||
|
||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||
esac
|
||||
@@ -193,6 +198,10 @@ if "$cygwin" || "$msys" ; then
|
||||
done
|
||||
fi
|
||||
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Collect all arguments for the java command;
|
||||
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
|
||||
# shell script including quotes and variable substitutions, so put them in
|
||||
@@ -205,6 +214,12 @@ set -- \
|
||||
org.gradle.wrapper.GradleWrapperMain \
|
||||
"$@"
|
||||
|
||||
# Stop when "xargs" is not available.
|
||||
if ! command -v xargs >/dev/null 2>&1
|
||||
then
|
||||
die "xargs is not available"
|
||||
fi
|
||||
|
||||
# Use "xargs" to parse quoted args.
|
||||
#
|
||||
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||
|
||||
15
bdk-jvm/gradlew.bat
vendored
15
bdk-jvm/gradlew.bat
vendored
@@ -14,7 +14,7 @@
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%" == "" @echo off
|
||||
@if "%DEBUG%"=="" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@@ -25,7 +25,8 @@
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
if "%DIRNAME%"=="" set DIRNAME=.
|
||||
@rem This is normally unused
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@@ -40,7 +41,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto execute
|
||||
if %ERRORLEVEL% equ 0 goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
@@ -75,13 +76,15 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||
if %ERRORLEVEL% equ 0 goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||
exit /b 1
|
||||
set EXIT_CODE=%ERRORLEVEL%
|
||||
if %EXIT_CODE% equ 0 set EXIT_CODE=1
|
||||
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
|
||||
exit /b %EXIT_CODE%
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
23
bdk-jvm/justfile
Normal file
23
bdk-jvm/justfile
Normal file
@@ -0,0 +1,23 @@
|
||||
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,19 +6,15 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
||||
val libraryVersion: String by project
|
||||
|
||||
plugins {
|
||||
id("org.jetbrains.kotlin.jvm") version "1.6.10"
|
||||
id("java-library")
|
||||
id("maven-publish")
|
||||
id("signing")
|
||||
id("org.jetbrains.kotlin.jvm")
|
||||
id("org.gradle.java-library")
|
||||
id("org.gradle.maven-publish")
|
||||
id("org.gradle.signing")
|
||||
|
||||
// Custom plugin to generate the native libs and bindings file
|
||||
id("org.bitcoindevkit.plugins.generate-jvm-bindings")
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
java {
|
||||
sourceCompatibility = JavaVersion.VERSION_11
|
||||
targetCompatibility = JavaVersion.VERSION_11
|
||||
@@ -41,7 +37,7 @@ tasks.test {
|
||||
testing {
|
||||
suites {
|
||||
val test by getting(JvmTestSuite::class) {
|
||||
useKotlinTest("1.6.10")
|
||||
useKotlinTest("1.9.23")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -59,10 +55,12 @@ tasks.withType<Test> {
|
||||
dependencies {
|
||||
implementation(platform("org.jetbrains.kotlin:kotlin-bom"))
|
||||
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk7")
|
||||
implementation("net.java.dev.jna:jna:5.8.0")
|
||||
implementation("net.java.dev.jna:jna:5.14.0")
|
||||
api("org.slf4j:slf4j-api:1.7.30")
|
||||
testImplementation("junit:junit:4.13.2")
|
||||
testRuntimeOnly("org.junit.vintage:junit-vintage-engine:5.8.2")
|
||||
|
||||
// testImplementation("org.junit.jupiter:junit-jupiter-api:5.10.1")
|
||||
// testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.10.1")
|
||||
// testRuntimeOnly("org.junit.vintage:junit-vintage-engine:5.8.2")
|
||||
testImplementation("ch.qos.logback:logback-classic:1.2.3")
|
||||
testImplementation("ch.qos.logback:logback-core:1.2.3")
|
||||
}
|
||||
@@ -90,6 +88,13 @@ afterEvaluate {
|
||||
url.set("https://github.com/bitcoindevkit/bdk/blob/master/LICENSE-MIT")
|
||||
}
|
||||
}
|
||||
developers {
|
||||
developer {
|
||||
id.set("bdkdevelopers")
|
||||
name.set("Bitcoin Dev Kit Developers")
|
||||
email.set("dev@bitcoindevkit.org")
|
||||
}
|
||||
}
|
||||
scm {
|
||||
connection.set("scm:git:github.com/bitcoindevkit/bdk-ffi.git")
|
||||
developerConnection.set("scm:git:ssh://github.com/bitcoindevkit/bdk-ffi.git")
|
||||
@@ -102,6 +107,10 @@ afterEvaluate {
|
||||
}
|
||||
|
||||
signing {
|
||||
if (project.hasProperty("localBuild")) {
|
||||
isRequired = false
|
||||
}
|
||||
|
||||
val signingKeyId: String? by project
|
||||
val signingKey: String? by project
|
||||
val signingPassword: String? by project
|
||||
|
||||
@@ -1,29 +1,80 @@
|
||||
package org.bitcoindevkit
|
||||
|
||||
import java.io.File
|
||||
import kotlin.test.AfterTest
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
private const val SIGNET_ESPLORA_URL = "http://signet.bitcoindevkit.net"
|
||||
private const val TESTNET_ESPLORA_URL = "https://esplora.testnet.kuutamo.cloud"
|
||||
|
||||
class LiveTxBuilderTest {
|
||||
private val persistenceFilePath = run {
|
||||
val currentDirectory = System.getProperty("user.dir")
|
||||
"$currentDirectory/bdk_persistence.db"
|
||||
}
|
||||
|
||||
@AfterTest
|
||||
fun cleanup() {
|
||||
val file = File(persistenceFilePath)
|
||||
if (file.exists()) {
|
||||
file.delete()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testTxBuilder() {
|
||||
val descriptor = Descriptor("wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)", Network.TESTNET)
|
||||
val wallet = Wallet.newNoPersist(descriptor, null, Network.TESTNET)
|
||||
val esploraClient = EsploraClient("https://mempool.space/testnet/api")
|
||||
val update = esploraClient.scan(wallet, 10uL, 1uL)
|
||||
val wallet = Wallet(descriptor, null, persistenceFilePath, 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.getBalance().total()}")
|
||||
wallet.commit()
|
||||
println("Balance: ${wallet.getBalance().total}")
|
||||
|
||||
assert(wallet.getBalance().total() > 0uL)
|
||||
assert(wallet.getBalance().total > 0uL)
|
||||
|
||||
val recipient: Address = Address("tb1qrnfslnrve9uncz9pzpvf83k3ukz22ljgees989", Network.TESTNET)
|
||||
val psbt: PartiallySignedTransaction = TxBuilder()
|
||||
val recipient: Address = Address("tb1qrnfslnrve9uncz9pzpvf83k3ukz22ljgees989", Network.SIGNET)
|
||||
val psbt: Psbt = TxBuilder()
|
||||
.addRecipient(recipient.scriptPubkey(), 4200uL)
|
||||
.feeRate(2.0f)
|
||||
.feeRate(FeeRate.fromSatPerVb(2uL))
|
||||
.finish(wallet)
|
||||
|
||||
println(psbt.serialize())
|
||||
|
||||
assertTrue(psbt.serialize().startsWith("cHNi"), "PSBT should start with 'cHNi'")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun complexTxBuilder() {
|
||||
val externalDescriptor = Descriptor("wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)", Network.TESTNET)
|
||||
val changeDescriptor = Descriptor("wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/1/*)", Network.TESTNET)
|
||||
val wallet = Wallet(externalDescriptor, changeDescriptor, persistenceFilePath, Network.SIGNET)
|
||||
val esploraClient = EsploraClient(SIGNET_ESPLORA_URL)
|
||||
val fullScanRequest: FullScanRequest = wallet.startFullScan()
|
||||
val update = esploraClient.fullScan(fullScanRequest, 10uL, 1uL)
|
||||
wallet.applyUpdate(update)
|
||||
wallet.commit()
|
||||
println("Balance: ${wallet.getBalance().total}")
|
||||
|
||||
assert(wallet.getBalance().total > 0uL)
|
||||
|
||||
val recipient1: Address = Address("tb1qrnfslnrve9uncz9pzpvf83k3ukz22ljgees989", Network.SIGNET)
|
||||
val recipient2: Address = Address("tb1qw2c3lxufxqe2x9s4rdzh65tpf4d7fssjgh8nv6", Network.SIGNET)
|
||||
val allRecipients: List<ScriptAmount> = listOf(
|
||||
ScriptAmount(recipient1.scriptPubkey(), 4200uL),
|
||||
ScriptAmount(recipient2.scriptPubkey(), 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,42 +1,71 @@
|
||||
package org.bitcoindevkit
|
||||
|
||||
import java.io.File
|
||||
import kotlin.test.AfterTest
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
private const val SIGNET_ESPLORA_URL = "http://signet.bitcoindevkit.net"
|
||||
private const val TESTNET_ESPLORA_URL = "https://esplora.testnet.kuutamo.cloud"
|
||||
|
||||
class LiveWalletTest {
|
||||
private val persistenceFilePath = run {
|
||||
val currentDirectory = System.getProperty("user.dir")
|
||||
"$currentDirectory/bdk_persistence.db"
|
||||
}
|
||||
|
||||
@AfterTest
|
||||
fun cleanup() {
|
||||
val file = File(persistenceFilePath)
|
||||
if (file.exists()) {
|
||||
file.delete()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSyncedBalance() {
|
||||
val descriptor: Descriptor = Descriptor("wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)", Network.TESTNET)
|
||||
val wallet: Wallet = Wallet.newNoPersist(descriptor, null, Network.TESTNET)
|
||||
val esploraClient: EsploraClient = EsploraClient("https://mempool.space/testnet/api")
|
||||
// val esploraClient = EsploraClient("https://blockstream.info/testnet/api")
|
||||
val update = esploraClient.scan(wallet, 10uL, 1uL)
|
||||
val descriptor: Descriptor = Descriptor("wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)", Network.SIGNET)
|
||||
val wallet: Wallet = Wallet(descriptor, null, persistenceFilePath, 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.getBalance().total()}")
|
||||
wallet.commit()
|
||||
println("Balance: ${wallet.getBalance().total}")
|
||||
|
||||
assert(wallet.getBalance().total() > 0uL)
|
||||
assert(wallet.getBalance().total > 0uL)
|
||||
|
||||
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.txid()}")
|
||||
println("Sent ${sentAndReceived.sent}")
|
||||
println("Received ${sentAndReceived.received}")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testBroadcastTransaction() {
|
||||
val descriptor = Descriptor("wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)", Network.TESTNET)
|
||||
val wallet = Wallet.newNoPersist(descriptor, null, Network.TESTNET)
|
||||
val esploraClient = EsploraClient("https://mempool.space/testnet/api")
|
||||
val update = esploraClient.scan(wallet, 10uL, 1uL)
|
||||
|
||||
val descriptor = Descriptor("wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)", Network.SIGNET)
|
||||
val wallet: Wallet = Wallet(descriptor, null, persistenceFilePath, 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.getBalance().total()}")
|
||||
println("New address: ${wallet.getAddress(AddressIndex.New).address.asString()}")
|
||||
wallet.commit()
|
||||
println("Balance: ${wallet.getBalance().total}")
|
||||
println("New address: ${wallet.revealNextAddress(KeychainKind.EXTERNAL).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."
|
||||
assert(wallet.getBalance().total > 0uL) {
|
||||
"Wallet balance must be greater than 0! Please send funds to ${wallet.revealNextAddress(KeychainKind.EXTERNAL).address.asString()} and try again."
|
||||
}
|
||||
|
||||
val recipient: Address = Address("tb1qrnfslnrve9uncz9pzpvf83k3ukz22ljgees989", Network.TESTNET)
|
||||
val recipient: Address = Address("tb1qrnfslnrve9uncz9pzpvf83k3ukz22ljgees989", Network.SIGNET)
|
||||
|
||||
val psbt: PartiallySignedTransaction = TxBuilder()
|
||||
val psbt: Psbt = TxBuilder()
|
||||
.addRecipient(recipient.scriptPubkey(), 4200uL)
|
||||
.feeRate(2.0f)
|
||||
.feeRate(FeeRate.fromSatPerVb(2uL))
|
||||
.finish(wallet)
|
||||
|
||||
println(psbt.serialize())
|
||||
@@ -46,8 +75,14 @@ class LiveWalletTest {
|
||||
assertTrue(walletDidSign)
|
||||
|
||||
val tx: Transaction = psbt.extractTx()
|
||||
|
||||
println("Txid is: ${tx.txid()}")
|
||||
|
||||
val txFee: ULong = wallet.calculateFee(tx)
|
||||
println("Tx fee is: ${txFee}")
|
||||
|
||||
val feeRate: FeeRate = wallet.calculateFeeRate(tx)
|
||||
println("Tx fee rate is: ${feeRate.toSatPerVbCeil()} sat/vB")
|
||||
|
||||
esploraClient.broadcast(tx)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,26 @@
|
||||
package org.bitcoindevkit
|
||||
|
||||
import java.io.File
|
||||
import kotlin.test.AfterTest
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertTrue
|
||||
import kotlin.test.assertFalse
|
||||
|
||||
class OfflineWalletTest {
|
||||
private val persistenceFilePath = run {
|
||||
val currentDirectory = System.getProperty("user.dir")
|
||||
"$currentDirectory/bdk_persistence.db"
|
||||
}
|
||||
|
||||
@AfterTest
|
||||
fun cleanup() {
|
||||
val file = File(persistenceFilePath)
|
||||
if (file.exists()) {
|
||||
file.delete()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testDescriptorBip86() {
|
||||
val mnemonic: Mnemonic = Mnemonic(WordCount.WORDS12)
|
||||
@@ -20,12 +36,18 @@ class OfflineWalletTest {
|
||||
"wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)",
|
||||
Network.TESTNET
|
||||
)
|
||||
val wallet: Wallet = Wallet.newNoPersist(
|
||||
val wallet: Wallet = Wallet(
|
||||
descriptor,
|
||||
null,
|
||||
persistenceFilePath,
|
||||
Network.TESTNET
|
||||
)
|
||||
val addressInfo: AddressInfo = wallet.getAddress(AddressIndex.New)
|
||||
val addressInfo: AddressInfo = wallet.revealNextAddress(KeychainKind.EXTERNAL)
|
||||
|
||||
assertTrue(addressInfo.address.isValidForNetwork(Network.TESTNET), "Address is not valid for testnet network")
|
||||
assertTrue(addressInfo.address.isValidForNetwork(Network.SIGNET), "Address is not valid for signet network")
|
||||
assertFalse(addressInfo.address.isValidForNetwork(Network.REGTEST), "Address is valid for regtest network, but it shouldn't be")
|
||||
assertFalse(addressInfo.address.isValidForNetwork(Network.BITCOIN), "Address is valid for bitcoin network, but it shouldn't be")
|
||||
|
||||
assertEquals(
|
||||
expected = "tb1qzg4mckdh50nwdm9hkzq06528rsu73hjxxzem3e",
|
||||
@@ -39,15 +61,16 @@ class OfflineWalletTest {
|
||||
"wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)",
|
||||
Network.TESTNET
|
||||
)
|
||||
val wallet: Wallet = Wallet.newNoPersist(
|
||||
val wallet: Wallet = Wallet(
|
||||
descriptor,
|
||||
null,
|
||||
persistenceFilePath,
|
||||
Network.TESTNET
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
expected = 0uL,
|
||||
actual = wallet.getBalance().total()
|
||||
actual = wallet.getBalance().total
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,20 +112,20 @@ internal class UniFfiJvmPlugin : Plugin<Project> {
|
||||
|
||||
// TODO 2: Is the Windows name the correct one?
|
||||
// TODO 3: This will not work on mac Intel (x86_64 architecture)
|
||||
// val libraryPath = when (operatingSystem) {
|
||||
// OS.LINUX -> "./target/x86_64-unknown-linux-gnu/release-smaller/libbdkffi.so"
|
||||
// OS.MAC -> "./target/aarch64-apple-darwin/release-smaller/libbdkffi.dylib"
|
||||
// OS.WINDOWS -> "./target/x86_64-pc-windows-msvc/release-smaller/bdkffi.dll"
|
||||
// else -> throw Exception("Unsupported OS")
|
||||
// }
|
||||
val libraryPath = when (operatingSystem) {
|
||||
OS.LINUX -> "./target/x86_64-unknown-linux-gnu/release-smaller/libbdkffi.so"
|
||||
OS.MAC -> "./target/aarch64-apple-darwin/release-smaller/libbdkffi.dylib"
|
||||
OS.WINDOWS -> "./target/x86_64-pc-windows-msvc/release-smaller/bdkffi.dll"
|
||||
else -> throw Exception("Unsupported OS")
|
||||
}
|
||||
|
||||
// workingDir("${project.projectDir}/../../bdk-ffi/")
|
||||
// val cargoArgs: List<String> = listOf("run", "--bin", "uniffi-bindgen", "generate", "--library", libraryPath, "--language", "kotlin", "--out-dir", "../bdk-jvm/lib/src/main/kotlin/", "--no-format")
|
||||
workingDir("${project.projectDir}/../../bdk-ffi/")
|
||||
val cargoArgs: List<String> = listOf("run", "--bin", "uniffi-bindgen", "generate", "--library", libraryPath, "--language", "kotlin", "--out-dir", "../bdk-jvm/lib/src/main/kotlin/", "--no-format")
|
||||
|
||||
// The code above was for the migration to uniffi 0.24.3 using the --library flag
|
||||
// The code below works with uniffi 0.23.0
|
||||
workingDir("${project.projectDir}/../../bdk-ffi/")
|
||||
val cargoArgs: List<String> = listOf("run", "--bin", "uniffi-bindgen", "generate", "src/bdk.udl", "--language", "kotlin", "--out-dir", "../bdk-jvm/lib/src/main/kotlin", "--no-format")
|
||||
// workingDir("${project.projectDir}/../../bdk-ffi/")
|
||||
// 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")
|
||||
args(cargoArgs)
|
||||
|
||||
|
||||
@@ -2,3 +2,15 @@ rootProject.name = "bdk-jvm"
|
||||
|
||||
include(":lib")
|
||||
includeBuild("plugins")
|
||||
|
||||
pluginManagement {
|
||||
repositories {
|
||||
gradlePluginPortal()
|
||||
}
|
||||
}
|
||||
|
||||
dependencyResolutionManagement {
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
|
||||
14
bdk-python/justfile
Normal file
14
bdk-python/justfile
Normal file
@@ -0,0 +1,14 @@
|
||||
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,14 +4,15 @@ set -euo pipefail
|
||||
${PYBIN}/python --version
|
||||
${PYBIN}/pip install -r requirements.txt
|
||||
|
||||
echo "Generating bdk.py..."
|
||||
cd ../bdk-ffi/
|
||||
cargo run --bin uniffi-bindgen generate src/bdk.udl --language python --out-dir ../bdk-python/src/bdkpython/ --no-format
|
||||
rustup default 1.77.1
|
||||
|
||||
echo "Generating native binaries..."
|
||||
rustup default 1.73.0
|
||||
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..."
|
||||
cp ./target/release-smaller/libbdkffi.so ../bdk-python/src/bdkpython/libbdkffi.so
|
||||
|
||||
|
||||
@@ -2,17 +2,18 @@
|
||||
|
||||
set -euo pipefail
|
||||
python3 --version
|
||||
pip install --user -r requirements.txt
|
||||
pip install -r requirements.txt
|
||||
|
||||
echo "Generating bdk.py..."
|
||||
cd ../bdk-ffi/
|
||||
cargo run --bin uniffi-bindgen generate src/bdk.udl --language python --out-dir ../bdk-python/src/bdkpython/ --no-format
|
||||
rustup default 1.77.1
|
||||
rustup target add aarch64-apple-darwin
|
||||
|
||||
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 "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
|
||||
|
||||
echo "Copying libraries libbdkffi.dylib..."
|
||||
cp ./target/aarch64-apple-darwin/release-smaller/libbdkffi.dylib ../bdk-python/src/bdkpython/libbdkffi.dylib
|
||||
|
||||
|
||||
@@ -2,17 +2,18 @@
|
||||
|
||||
set -euo pipefail
|
||||
python3 --version
|
||||
pip install --user -r requirements.txt
|
||||
pip install -r requirements.txt
|
||||
|
||||
echo "Generating bdk.py..."
|
||||
cd ../bdk-ffi/
|
||||
cargo run --bin uniffi-bindgen generate src/bdk.udl --language python --out-dir ../bdk-python/src/bdkpython/ --no-format
|
||||
rustup default 1.77.1
|
||||
rustup target add x86_64-apple-darwin
|
||||
|
||||
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 "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
|
||||
|
||||
echo "Copying libraries libbdkffi.dylib..."
|
||||
cp ./target/x86_64-apple-darwin/release-smaller/libbdkffi.dylib ../bdk-python/src/bdkpython/libbdkffi.dylib
|
||||
|
||||
|
||||
@@ -2,17 +2,18 @@
|
||||
|
||||
set -euo pipefail
|
||||
python3 --version
|
||||
pip install --user -r requirements.txt
|
||||
pip install -r requirements.txt
|
||||
|
||||
echo "Generating bdk.py..."
|
||||
cd ../bdk-ffi/
|
||||
cargo run --bin uniffi-bindgen generate src/bdk.udl --language python --out-dir ../bdk-python/src/bdkpython/ --no-format
|
||||
rustup default 1.77.1
|
||||
rustup target add x86_64-pc-windows-msvc
|
||||
|
||||
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 "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
|
||||
|
||||
echo "Copying libraries bdkffi.dll..."
|
||||
cp ./target/x86_64-pc-windows-msvc/release-smaller/bdkffi.dll ../bdk-python/src/bdkpython/bdkffi.dll
|
||||
|
||||
|
||||
@@ -13,11 +13,12 @@ pip install bdkpython
|
||||
## Simple example
|
||||
```python
|
||||
import bdkpython as bdk
|
||||
```
|
||||
"""
|
||||
|
||||
setup(
|
||||
name="bdkpython",
|
||||
version="0.31.0.dev0",
|
||||
version="1.0.0a10.dev",
|
||||
description="The Python language bindings for the Bitcoin Development Kit",
|
||||
long_description=LONG_DESCRIPTION,
|
||||
long_description_content_type="text/markdown",
|
||||
@@ -26,6 +27,7 @@ setup(
|
||||
packages=["bdkpython"],
|
||||
package_dir={"bdkpython": "./src/bdkpython"},
|
||||
url="https://github.com/bitcoindevkit/bdk-ffi",
|
||||
author="Bitcoin Dev Kit Developers <dev@bitcoindevkit.org>",
|
||||
license="MIT or Apache 2.0",
|
||||
# This is required to ensure the library name includes the python version, abi, and platform tags
|
||||
# See issue #350 for more information
|
||||
|
||||
@@ -1,38 +1,93 @@
|
||||
import bdkpython as bdk
|
||||
import unittest
|
||||
import os
|
||||
|
||||
class TestLiveTxBuilder(unittest.TestCase):
|
||||
SIGNET_ESPLORA_URL = "http://signet.bitcoindevkit.net"
|
||||
TESTNET_ESPLORA_URL = "https://esplora.testnet.kuutamo.cloud"
|
||||
|
||||
class LiveTxBuilderTest(unittest.TestCase):
|
||||
|
||||
def tearDown(self) -> None:
|
||||
if os.path.exists("./bdk_persistence.db"):
|
||||
os.remove("./bdk_persistence.db")
|
||||
|
||||
def test_tx_builder(self):
|
||||
descriptor: bdk.Descriptor = bdk.Descriptor(
|
||||
"wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)",
|
||||
bdk.Network.TESTNET
|
||||
bdk.Network.SIGNET
|
||||
)
|
||||
wallet: bdk.Wallet = bdk.Wallet.new_no_persist(
|
||||
wallet: bdk.Wallet = bdk.Wallet(
|
||||
descriptor,
|
||||
None,
|
||||
bdk.Network.TESTNET
|
||||
"./bdk_persistence.db",
|
||||
bdk.Network.SIGNET
|
||||
)
|
||||
esploraClient: bdk.EsploraClient = bdk.EsploraClient(url = "https://mempool.space/testnet/api")
|
||||
update = esploraClient.scan(
|
||||
wallet = wallet,
|
||||
stop_gap = 10,
|
||||
parallel_requests = 1
|
||||
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.get_balance().total(), 0)
|
||||
|
||||
wallet.commit()
|
||||
|
||||
self.assertGreater(wallet.get_balance().total, 0)
|
||||
|
||||
recipient = bdk.Address(
|
||||
address = "tb1qrnfslnrve9uncz9pzpvf83k3ukz22ljgees989",
|
||||
network = bdk.Network.TESTNET
|
||||
address="tb1qrnfslnrve9uncz9pzpvf83k3ukz22ljgees989",
|
||||
network=bdk.Network.SIGNET
|
||||
)
|
||||
|
||||
psbt = bdk.TxBuilder().add_recipient(script=recipient.script_pubkey(), amount=4200).fee_rate(2.0).finish(wallet)
|
||||
# print(psbt.serialize())
|
||||
|
||||
psbt = bdk.TxBuilder().add_recipient(script=recipient.script_pubkey(), amount=4200).fee_rate(fee_rate=bdk.FeeRate.from_sat_per_vb(2)).finish(wallet)
|
||||
|
||||
self.assertTrue(psbt.serialize().startswith("cHNi"), "The PSBT should start with cHNi")
|
||||
|
||||
|
||||
def complex_tx_builder(self):
|
||||
descriptor: bdk.Descriptor = bdk.Descriptor(
|
||||
"wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)",
|
||||
bdk.Network.SIGNET
|
||||
)
|
||||
change_descriptor: bdk.Descriptor = bdk.Descriptor(
|
||||
"wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)",
|
||||
bdk.Network.SIGNET
|
||||
)
|
||||
wallet: bdk.Wallet = bdk.Wallet(
|
||||
descriptor,
|
||||
change_descriptor,
|
||||
"./bdk_persistence.db",
|
||||
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)
|
||||
wallet.commit()
|
||||
|
||||
self.assertGreater(wallet.get_balance().total, 0)
|
||||
|
||||
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__':
|
||||
unittest.main()
|
||||
|
||||
@@ -1,62 +1,90 @@
|
||||
import bdkpython as bdk
|
||||
import unittest
|
||||
import os
|
||||
|
||||
class TestLiveWallet(unittest.TestCase):
|
||||
SIGNET_ESPLORA_URL = "http://signet.bitcoindevkit.net"
|
||||
TESTNET_ESPLORA_URL = "https://esplora.testnet.kuutamo.cloud"
|
||||
|
||||
class LiveWalletTest(unittest.TestCase):
|
||||
|
||||
def tearDown(self) -> None:
|
||||
if os.path.exists("./bdk_persistence.db"):
|
||||
os.remove("./bdk_persistence.db")
|
||||
|
||||
def test_synced_balance(self):
|
||||
descriptor: bdk.Descriptor = bdk.Descriptor(
|
||||
"wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)",
|
||||
bdk.Network.TESTNET
|
||||
"wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)",
|
||||
bdk.Network.SIGNET
|
||||
)
|
||||
wallet: bdk.Wallet = bdk.Wallet.new_no_persist(
|
||||
wallet: bdk.Wallet = bdk.Wallet(
|
||||
descriptor,
|
||||
None,
|
||||
bdk.Network.TESTNET
|
||||
"./bdk_persistence.db",
|
||||
bdk.Network.SIGNET
|
||||
)
|
||||
esploraClient: bdk.EsploraClient = bdk.EsploraClient(url = "https://mempool.space/testnet/api")
|
||||
update = esploraClient.scan(
|
||||
wallet = wallet,
|
||||
stop_gap = 10,
|
||||
parallel_requests = 1
|
||||
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)
|
||||
wallet.commit()
|
||||
|
||||
self.assertGreater(wallet.get_balance().total, 0)
|
||||
|
||||
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.txid()}")
|
||||
print(f"Sent {sent_and_received.sent}")
|
||||
print(f"Received {sent_and_received.received}")
|
||||
|
||||
self.assertGreater(wallet.get_balance().total(), 0)
|
||||
|
||||
def test_broadcast_transaction(self):
|
||||
descriptor: bdk.Descriptor = bdk.Descriptor(
|
||||
"wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)",
|
||||
bdk.Network.TESTNET
|
||||
bdk.Network.SIGNET
|
||||
)
|
||||
wallet: bdk.Wallet = bdk.Wallet.new_no_persist(
|
||||
wallet: bdk.Wallet = bdk.Wallet(
|
||||
descriptor,
|
||||
None,
|
||||
bdk.Network.TESTNET
|
||||
"./bdk_persistence.db",
|
||||
bdk.Network.SIGNET
|
||||
)
|
||||
esploraClient: bdk.EsploraClient = bdk.EsploraClient(url = "https://mempool.space/testnet/api")
|
||||
update = esploraClient.scan(
|
||||
wallet = wallet,
|
||||
stop_gap = 10,
|
||||
parallel_requests = 1
|
||||
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.get_balance().total(), 0)
|
||||
|
||||
wallet.commit()
|
||||
|
||||
self.assertGreater(wallet.get_balance().total, 0)
|
||||
|
||||
recipient = bdk.Address(
|
||||
address = "tb1qrnfslnrve9uncz9pzpvf83k3ukz22ljgees989",
|
||||
network = bdk.Network.TESTNET
|
||||
address="tb1qrnfslnrve9uncz9pzpvf83k3ukz22ljgees989",
|
||||
network=bdk.Network.SIGNET
|
||||
)
|
||||
|
||||
psbt = bdk.TxBuilder().add_recipient(script=recipient.script_pubkey(), amount=4200).fee_rate(2.0).finish(wallet)
|
||||
|
||||
psbt: bdk.Psbt = bdk.TxBuilder().add_recipient(script=recipient.script_pubkey(), amount=4200).fee_rate(fee_rate=bdk.FeeRate.from_sat_per_vb(2)).finish(wallet)
|
||||
# print(psbt.serialize())
|
||||
self.assertTrue(psbt.serialize().startswith("cHNi"), "The PSBT should start with cHNi")
|
||||
|
||||
|
||||
walletDidSign = wallet.sign(psbt)
|
||||
self.assertTrue(walletDidSign)
|
||||
tx = psbt.extract_tx()
|
||||
print(f"Transaction Id: {tx.txid()}")
|
||||
fee = wallet.calculate_fee(tx)
|
||||
print(f"Transaction Fee: {fee}")
|
||||
fee_rate = wallet.calculate_fee_rate(tx)
|
||||
print(f"Transaction Fee Rate: {fee_rate.to_sat_per_vb_ceil()} sat/vB")
|
||||
|
||||
esploraClient.broadcast(tx)
|
||||
esplora_client.broadcast(tx)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import bdkpython as bdk
|
||||
import unittest
|
||||
|
||||
class TestSimpleWallet(unittest.TestCase):
|
||||
class OfflineDescriptorTest(unittest.TestCase):
|
||||
|
||||
def test_descriptor_bip86(self):
|
||||
mnemonic: bdk.Mnemonic = bdk.Mnemonic.from_string("space echo position wrist orient erupt relief museum myself grain wisdom tumble")
|
||||
|
||||
@@ -1,34 +1,46 @@
|
||||
import bdkpython as bdk
|
||||
import unittest
|
||||
import os
|
||||
|
||||
class TestSimpleWallet(unittest.TestCase):
|
||||
class OfflineWalletTest(unittest.TestCase):
|
||||
|
||||
def tearDown(self) -> None:
|
||||
if os.path.exists("./bdk_persistence.db"):
|
||||
os.remove("./bdk_persistence.db")
|
||||
|
||||
def test_new_address(self):
|
||||
descriptor: bdk.Descriptor = bdk.Descriptor(
|
||||
"wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)",
|
||||
bdk.Network.TESTNET
|
||||
)
|
||||
wallet: Wallet = bdk.Wallet.new_no_persist(
|
||||
wallet: Wallet = bdk.Wallet(
|
||||
descriptor,
|
||||
None,
|
||||
"./bdk_persistence.db",
|
||||
bdk.Network.TESTNET
|
||||
)
|
||||
address_info: bdk.AddressInfo = wallet.get_address(bdk.AddressIndex.NEW())
|
||||
|
||||
address_info: bdk.AddressInfo = wallet.reveal_next_address(bdk.KeychainKind.EXTERNAL)
|
||||
|
||||
self.assertTrue(address_info.address.is_valid_for_network(bdk.Network.TESTNET), "Address is not valid for testnet network")
|
||||
self.assertTrue(address_info.address.is_valid_for_network(bdk.Network.SIGNET), "Address is not valid for signet network")
|
||||
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")
|
||||
|
||||
self.assertEqual("tb1qzg4mckdh50nwdm9hkzq06528rsu73hjxxzem3e", address_info.address.as_string())
|
||||
|
||||
|
||||
def test_balance(self):
|
||||
descriptor: bdk.Descriptor = bdk.Descriptor(
|
||||
"wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)",
|
||||
bdk.Network.TESTNET
|
||||
)
|
||||
wallet: bdk.Wallet = bdk.Wallet.new_no_persist(
|
||||
wallet: bdk.Wallet = bdk.Wallet(
|
||||
descriptor,
|
||||
None,
|
||||
"./bdk_persistence.db",
|
||||
bdk.Network.TESTNET
|
||||
)
|
||||
|
||||
self.assertEqual(wallet.get_balance().total(), 0)
|
||||
|
||||
self.assertEqual(wallet.get_balance().total, 0)
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
||||
@@ -29,8 +29,7 @@ let package = Package(
|
||||
.binaryTarget(name: "bdkFFI", path: "./bdkFFI.xcframework"),
|
||||
.target(
|
||||
name: "BitcoinDevKit",
|
||||
dependencies: ["bdkFFI"],
|
||||
swiftSettings: [.unsafeFlags(["-suppress-warnings"])]
|
||||
dependencies: ["bdkFFI"]
|
||||
),
|
||||
.testTarget(
|
||||
name: "BitcoinDevKitTests",
|
||||
|
||||
@@ -1,34 +1,107 @@
|
||||
import XCTest
|
||||
@testable import BitcoinDevKit
|
||||
|
||||
let SIGNET_ESPLORA_URL = "http://signet.bitcoindevkit.net"
|
||||
let TESTNET_ESPLORA_URL = "https://esplora.testnet.kuutamo.cloud"
|
||||
|
||||
final class LiveTxBuilderTests: XCTestCase {
|
||||
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).db"
|
||||
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 {
|
||||
let descriptor = try Descriptor(
|
||||
descriptor: "wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)",
|
||||
network: Network.testnet
|
||||
network: Network.signet
|
||||
)
|
||||
let wallet = try Wallet.newNoPersist(
|
||||
let wallet = try Wallet(
|
||||
descriptor: descriptor,
|
||||
changeDescriptor: nil,
|
||||
network: .testnet
|
||||
persistenceBackendPath: dbFilePath.path,
|
||||
network: .signet
|
||||
)
|
||||
let esploraClient = EsploraClient(url: "https://mempool.space/testnet/api")
|
||||
let update = try esploraClient.scan(
|
||||
wallet: wallet,
|
||||
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)
|
||||
try wallet.commit()
|
||||
|
||||
XCTAssertGreaterThan(wallet.getBalance().total(), UInt64(0), "Wallet must have positive balance, please add funds")
|
||||
XCTAssertGreaterThan(wallet.getBalance().total, UInt64(0), "Wallet must have positive balance, please add funds")
|
||||
|
||||
let recipient: Address = try Address(address: "tb1qrnfslnrve9uncz9pzpvf83k3ukz22ljgees989", network: .testnet)
|
||||
let psbt: PartiallySignedTransaction = try TxBuilder()
|
||||
let recipient: Address = try Address(address: "tb1qrnfslnrve9uncz9pzpvf83k3ukz22ljgees989", network: .signet)
|
||||
let psbt: Psbt = try TxBuilder()
|
||||
.addRecipient(script: recipient.scriptPubkey(), amount: 4200)
|
||||
.feeRate(satPerVbyte: 2.0)
|
||||
.feeRate(feeRate: FeeRate.fromSatPerVb(satPerVb: 2))
|
||||
.finish(wallet: wallet)
|
||||
|
||||
print(psbt.serialize())
|
||||
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,
|
||||
persistenceBackendPath: dbFilePath.path,
|
||||
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)
|
||||
try wallet.commit()
|
||||
|
||||
XCTAssertGreaterThan(wallet.getBalance().total, UInt64(0), "Wallet must have positive balance, please add funds")
|
||||
|
||||
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: 4200),
|
||||
ScriptAmount(script: recipient2.scriptPubkey(), amount: 4200)
|
||||
]
|
||||
|
||||
let psbt: Psbt = try TxBuilder()
|
||||
.setRecipients(recipients: allRecipients)
|
||||
.feeRate(feeRate: FeeRate.fromSatPerVb(satPerVb: 4))
|
||||
.changePolicy(changePolicy: ChangeSpendPolicy.changeForbidden)
|
||||
.enableRbf()
|
||||
.finish(wallet: wallet)
|
||||
|
||||
try! wallet.sign(psbt: psbt)
|
||||
|
||||
XCTAssertTrue(psbt.serialize().hasPrefix("cHNi"), "PSBT should start with cHNI")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,65 +1,109 @@
|
||||
import XCTest
|
||||
@testable import BitcoinDevKit
|
||||
|
||||
let SIGNET_ESPLORA_URL = "http://signet.bitcoindevkit.net"
|
||||
let TESTNET_ESPLORA_URL = "https://esplora.testnet.kuutamo.cloud"
|
||||
|
||||
final class LiveWalletTests: XCTestCase {
|
||||
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).db"
|
||||
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 {
|
||||
let descriptor = try Descriptor(
|
||||
descriptor: "wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)",
|
||||
network: Network.testnet
|
||||
network: Network.signet
|
||||
)
|
||||
let wallet = try Wallet.newNoPersist(
|
||||
let wallet = try Wallet(
|
||||
descriptor: descriptor,
|
||||
changeDescriptor: nil,
|
||||
network: .testnet
|
||||
persistenceBackendPath: dbFilePath.path,
|
||||
network: .signet
|
||||
)
|
||||
let esploraClient = EsploraClient(url: "https://mempool.space/testnet/api")
|
||||
let update = try esploraClient.scan(
|
||||
wallet: wallet,
|
||||
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)
|
||||
try wallet.commit()
|
||||
|
||||
XCTAssertGreaterThan(wallet.getBalance().total(), UInt64(0))
|
||||
XCTAssertGreaterThan(wallet.getBalance().total, UInt64(0))
|
||||
|
||||
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.txid())")
|
||||
print("Sent \(sentAndReceived.sent)")
|
||||
print("Received \(sentAndReceived.received)")
|
||||
}
|
||||
}
|
||||
|
||||
func testBroadcastTransaction() throws {
|
||||
let descriptor = try Descriptor(
|
||||
descriptor: "wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)",
|
||||
network: Network.testnet
|
||||
network: Network.signet
|
||||
)
|
||||
let wallet = try Wallet.newNoPersist(
|
||||
let wallet = try Wallet(
|
||||
descriptor: descriptor,
|
||||
changeDescriptor: nil,
|
||||
network: .testnet
|
||||
persistenceBackendPath: dbFilePath.path,
|
||||
network: .signet
|
||||
)
|
||||
let esploraClient = EsploraClient(url: "https://mempool.space/testnet/api")
|
||||
let update = try esploraClient.scan(
|
||||
wallet: wallet,
|
||||
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)
|
||||
try wallet.commit()
|
||||
|
||||
XCTAssertGreaterThan(wallet.getBalance().total(), UInt64(0), "Wallet must have positive balance, please add funds")
|
||||
|
||||
print("Balance: \(wallet.getBalance().total())")
|
||||
XCTAssertGreaterThan(wallet.getBalance().total, UInt64(0), "Wallet must have positive balance, please add funds")
|
||||
|
||||
let recipient: Address = try Address(address: "tb1qrnfslnrve9uncz9pzpvf83k3ukz22ljgees989", network: .testnet)
|
||||
let psbt: PartiallySignedTransaction = try
|
||||
print("Balance: \(wallet.getBalance().total)")
|
||||
|
||||
let recipient: Address = try Address(address: "tb1qrnfslnrve9uncz9pzpvf83k3ukz22ljgees989", network: .signet)
|
||||
let psbt: Psbt = try
|
||||
TxBuilder()
|
||||
.addRecipient(script: recipient.scriptPubkey(), amount: 4200)
|
||||
.feeRate(satPerVbyte: 2.0)
|
||||
.feeRate(feeRate: FeeRate.fromSatPerVb(satPerVb: 2))
|
||||
.finish(wallet: wallet)
|
||||
|
||||
print(psbt.serialize())
|
||||
XCTAssertTrue(psbt.serialize().hasPrefix("cHNi"), "PSBT should start with cHNI")
|
||||
|
||||
|
||||
let walletDidSign: Bool = try wallet.sign(psbt: psbt)
|
||||
XCTAssertTrue(walletDidSign, "Wallet did not sign transaction")
|
||||
|
||||
let tx: Transaction = psbt.extractTx()
|
||||
|
||||
let tx: Transaction = try! psbt.extractTx()
|
||||
print(tx.txid())
|
||||
let fee: UInt64 = 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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,18 +2,49 @@ import XCTest
|
||||
@testable import BitcoinDevKit
|
||||
|
||||
final class OfflineWalletTests: XCTestCase {
|
||||
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).db"
|
||||
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 {
|
||||
let descriptor: Descriptor = try Descriptor(
|
||||
descriptor: "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)",
|
||||
network: Network.testnet
|
||||
)
|
||||
let wallet: Wallet = try Wallet.newNoPersist(
|
||||
let wallet = try Wallet(
|
||||
descriptor: descriptor,
|
||||
changeDescriptor: nil,
|
||||
persistenceBackendPath: dbFilePath.path,
|
||||
network: .testnet
|
||||
)
|
||||
let addressInfo: AddressInfo = wallet.getAddress(addressIndex: AddressIndex.new)
|
||||
|
||||
let addressInfo: AddressInfo = try wallet.revealNextAddress(keychain: KeychainKind.external)
|
||||
|
||||
XCTAssertTrue(addressInfo.address.isValidForNetwork(network: Network.testnet),
|
||||
"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.asString(), "tb1qzg4mckdh50nwdm9hkzq06528rsu73hjxxzem3e")
|
||||
}
|
||||
|
||||
@@ -22,12 +53,13 @@ final class OfflineWalletTests: XCTestCase {
|
||||
descriptor: "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)",
|
||||
network: Network.testnet
|
||||
)
|
||||
let wallet: Wallet = try Wallet.newNoPersist(
|
||||
let wallet = try Wallet(
|
||||
descriptor: descriptor,
|
||||
changeDescriptor: nil,
|
||||
persistenceBackendPath: dbFilePath.path,
|
||||
network: .testnet
|
||||
)
|
||||
|
||||
XCTAssertEqual(wallet.getBalance().total(), 0)
|
||||
XCTAssertEqual(wallet.getBalance().total, 0)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
</array>
|
||||
<key>SupportedPlatform</key>
|
||||
<string>macos</string>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>12.0</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>LibraryIdentifier</key>
|
||||
@@ -31,6 +33,8 @@
|
||||
<string>ios</string>
|
||||
<key>SupportedPlatformVariant</key>
|
||||
<string>simulator</string>
|
||||
<key>MinimumOSVersion</key>
|
||||
<string>15.0</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>LibraryIdentifier</key>
|
||||
@@ -43,6 +47,8 @@
|
||||
</array>
|
||||
<key>SupportedPlatform</key>
|
||||
<string>ios</string>
|
||||
<key>MinimumOSVersion</key>
|
||||
<string>15.0</string>
|
||||
</dict>
|
||||
</array>
|
||||
<key>CFBundlePackageType</key>
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
<?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>CFBundleIdentifier</key>
|
||||
<string>com.bitcoindevkit.bdkFFI</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>bdkFFI</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1.0.0</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0.0</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>bdkFFI</string>
|
||||
<key>MinimumOSVersion</key>
|
||||
<string>100</string>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,18 @@
|
||||
<?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>CFBundleIdentifier</key>
|
||||
<string>com.bitcoindevkit.bdkFFI</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>bdkFFI</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1.0.0</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0.0</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>bdkFFI</string>
|
||||
<key>MinimumOSVersion</key>
|
||||
<string>15.0</string>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,18 @@
|
||||
<?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>CFBundleIdentifier</key>
|
||||
<string>com.bitcoindevkit.bdkFFI</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>bdkFFI</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1.0.0</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0.0</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>bdkFFI</string>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>12.0</string>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -3,17 +3,15 @@
|
||||
# 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 default 1.77.1
|
||||
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
|
||||
rustup target add aarch64-apple-ios # iOS arm64
|
||||
rustup target add x86_64-apple-ios # iOS x86_64
|
||||
rustup target add aarch64-apple-ios-sim # simulator mac M1
|
||||
rustup target add aarch64-apple-darwin # mac M1
|
||||
rustup target add x86_64-apple-darwin # mac x86_64
|
||||
|
||||
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
|
||||
cd ../bdk-ffi/ || exit
|
||||
|
||||
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
|
||||
@@ -21,13 +19,14 @@ cargo build --package bdk-ffi --profile release-smaller --target x86_64-apple-io
|
||||
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
|
||||
|
||||
cargo run --bin uniffi-bindgen generate --library ./target/aarch64-apple-ios/release-smaller/libbdkffi.dylib --language swift --out-dir ../bdk-swift/Sources/BitcoinDevKit --no-format
|
||||
|
||||
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
|
||||
cd ../bdk-swift/ || exit
|
||||
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
|
||||
@@ -37,5 +36,3 @@ cp ../bdk-ffi/target/lipo-ios-sim/release-smaller/libbdkffi.a bdkFFI.xcframework
|
||||
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
|
||||
14
bdk-swift/justfile
Normal file
14
bdk-swift/justfile
Normal file
@@ -0,0 +1,14 @@
|
||||
default:
|
||||
just --list
|
||||
|
||||
build:
|
||||
bash ./build-local-swift.sh
|
||||
|
||||
clean:
|
||||
rm -rf ../bdk-ffi/target/
|
||||
|
||||
test:
|
||||
swift test
|
||||
|
||||
test-offline:
|
||||
swift test --skip LiveWalletTests --skip LiveTxBuilderTests
|
||||
23
docs/adr/01-naming.md
Normal file
23
docs/adr/01-naming.md
Normal file
@@ -0,0 +1,23 @@
|
||||
# Naming convention
|
||||
|
||||
Producing language bindings potentially requires renaming a number of types and methods, and this document outlines the approach we have decided to take when thinking through this problem for bdk-ffi libraries.
|
||||
|
||||
## Context and Problem Statement
|
||||
|
||||
The tool we use to produce language bindings for bdk-ffi libraries is [uniffi]. While the library is powerful, it also comes with some caveats. Some of those include the inability to expose to foreign bindings Rust-specific types like tuples, and the inability to expose generics. This means that at least _some_ wrapping and transforming of certain things are required between the pure Rust code coming from the bdk library and the final language bindings in Swift, Kotlin, and Python.
|
||||
|
||||
With wrapping comes (a) the requirement for naming potentially new types, and (b) the ability to "wrap" behaviour that could be useful for end users. This document addresses point (a).
|
||||
|
||||
## Decision Drivers
|
||||
|
||||
Our main goals are:
|
||||
1. Keep the multiple language bindings libraries maintainable.
|
||||
2. Help users of bdk help each other and working with a similarly shaped API across languages.
|
||||
|
||||
## Decision Outcome
|
||||
|
||||
We decided to try and keep the names of all types the same between the Rust libraries and the bindings, and in cases where new types had to be created, to keep them in the style and spirit of the bdk and rust-bitcoin libraries.
|
||||
|
||||
There is so far one exception to this rule, where we renamed the `ScriptBuf` type from rust-bitcoin to `Script`. This was done because the concept of owned vs. borrowed types is strictly a Rust concept, and is not passed onto the languages bindings in any way, and therefore keeping the script type as `Script` was our preferred option in this case.
|
||||
|
||||
[uniffi]: https://github.com/mozilla/uniffi-rs/
|
||||
26
docs/adr/02-wrapping.md
Normal file
26
docs/adr/02-wrapping.md
Normal file
@@ -0,0 +1,26 @@
|
||||
# Wrapping BDK APIs
|
||||
|
||||
Producing language bindings potentially requires wrapping a number of APIs, and this document outlines the approach we have decided to take when thinking through this problem for bdk-ffi libraries.
|
||||
|
||||
## Context and Problem Statement
|
||||
|
||||
The tool we use to produce language bindings for bdk-ffi libraries is [uniffi]. While the library is powerful, it also comes with some caveats. Some of those include the inability to expose to foreign bindings Rust-specific types like tuples, and the inability to expose generics. This means that at least _some_ wrapping and transforming of certain things are required between the pure Rust code coming from the bdk library and the final language bindings in Swift, Kotlin, and Python.
|
||||
|
||||
With wrapping comes (a) the requirement for naming potentially new types, and (b) the ability to "wrap" behaviour that could be useful for end users. This document addresses point (b).
|
||||
|
||||
## Decision Drivers
|
||||
|
||||
Our main goals are:
|
||||
1. Keep the multiple language bindings libraries maintainable.
|
||||
2. Help users of bdk help each other and working with a similarly shaped API across languages.
|
||||
|
||||
## Decision Outcome
|
||||
|
||||
There are three potential reasons for wrapping Rust BDK APIs:
|
||||
1. The Rust types are not available in the target language (e.g., a function returns a tuple, which can't be returned in Swift/Kotlin)
|
||||
2. Some complex functionality is available in the Rust bitcoin/miniscript/bdk ecosystem, but exposing all underlying types required for this functionality is out of scope at the time a particular feature is required
|
||||
3. Some extra feature/utility might be interesting for our end-users
|
||||
|
||||
Our approach with the bdk-ffi libraries is to only provide wrapping for cases (1) and (2) mentioned above. If extra functionality to the BDK API would be useful, we open issues upstream and merge those in Rust first, and then expose it in our bindings. This approach favors (a) keeping the bindings libraries as thin as possible, minimizing the potential for integrating bugs at the bindings layer, and (b) keeping the API as close as we can to Rust BDK, promoting collaboration between users of BDK across languages, including with teams that use BDK in bindings (mobile) and server-side (Rust).
|
||||
|
||||
[uniffi]: https://github.com/mozilla/uniffi-rs/
|
||||
12
docs/adr/README.md
Normal file
12
docs/adr/README.md
Normal file
@@ -0,0 +1,12 @@
|
||||
# Architectural Decision Records
|
||||
|
||||
This directory contains a series of Architectural Decision Records or "ADRs" for the bdk-ffi project. We're going to use it as a kind of collective memory of the decisions we've made and the path we've taken to get the project to its current point.
|
||||
|
||||
A good example of simple and well executed ADRs can be found on the [uniffi](https://github.com/mozilla/uniffi-rs/) project repository. See their [readme](https://github.com/mozilla/uniffi-rs/tree/main/docs/adr) and [template](https://github.com/mozilla/uniffi-rs/blob/main/docs/adr/template.md) for more information.
|
||||
|
||||
Some more readings on ADRs:
|
||||
- https://www.ozimmer.ch/practices/2023/04/03/ADRCreation.html
|
||||
- https://github.com/joelparkerhenderson/architecture-decision-record
|
||||
- https://adr.github.io/
|
||||
- https://betterprogramming.pub/the-ultimate-guide-to-architectural-decision-records-6d74fd3850ee
|
||||
- https://www.redhat.com/architect/architecture-decision-records
|
||||
Reference in New Issue
Block a user