Compare commits

..

150 Commits

Author SHA1 Message Date
thunderbiscuit
8b66ac96ab Update libraries to official release versions 2023-06-15 12:17:24 -04:00
thunderbiscuit
039b64de5c Update JVM readme with specific Rust version 2023-06-15 11:10:28 -04:00
thunderbiscuit
fe7e4e21c0 Update Android readme with specific Rust and NDK versions 2023-06-15 11:09:48 -04:00
thunderbiscuit
77f89afc68 Update library version to 0.29.0 2023-06-15 10:15:23 -04:00
thunderbiscuit
25033f6bd6 Add Kotlin API docs for Script.toBytes 2023-06-12 13:36:50 -04:00
thunderbiscuit
3cb2c2c394 Expose Script.to_bytes() method 2023-06-12 13:36:44 -04:00
Steve Myers
5092987b26 [swift] suppress keyword escape warnings 2023-06-06 14:17:30 -05:00
Steve Myers
aea25dbf21 Update bdk-swift local build script 2023-06-05 16:45:29 -05:00
thunderbiscuit
ed67eba910 Align JVM target version for Kotlin and Java compile tasks
This commit resolves a build error related to mismatched JVM target
versions for the Kotlin and Java compile tasks. Previously, the
'compileJava' task was targeting JVM 11, while the 'compileKotlin'
task was targeting JVM 8.

Both tasks have now been set to target JVM 11, ensuring consistency
and eliminating the build error.
2023-06-05 10:35:23 -04:00
thunderbiscuit
90606b2455 Small style cleanups in JVM Gradle plugin 2023-06-05 10:09:27 -04:00
yellowhatpro
49e8fe461e added support for x86_64-pc-windows-msvc 64-bit MSVC 2023-06-05 10:09:14 -04:00
Matthew
de88184b8c github: add feature request issue template 2023-05-30 15:27:34 -05:00
thunderbiscuit
3be2c0495f Clean up is_mine method body 2023-05-15 09:01:39 -04:00
thunderbiscuit
2c4c64515f Fix Clippy warning 2023-05-03 15:13:22 -04:00
thunderbiscuit
17323d3184 Add Kotlin API docs for isMine method 2023-05-03 14:21:37 -04:00
thunderbiscuit
b382511a9e Add is_mine method on Wallet type
Closes #354
2023-05-03 14:19:50 -04:00
Steve Myers
d3895441d3 Update bdk-swift/build-local-swift.sh for aarch64-apple-ios-sim to use release-smaller profile 2023-05-01 11:37:05 -05:00
Steve Myers
269512a673 Fix bdk-swift/build-local-swift.sh for aarch64-apple-ios-sim 2023-05-01 10:40:07 -05:00
thunderbiscuit
d27206787a Add supported Python library changes to changelog 2023-04-28 09:00:37 -04:00
thunderbiscuit
c1b1fd6f5d Update changelog for 0.28.0 release 2023-04-28 08:57:11 -04:00
thunderbiscuit
7062fbd047 Update minor_release and patch_release templates 2023-04-28 08:56:58 -04:00
thunderbiscuit
0e34a6bacf Bump snapshot and dev versions of libraries 2023-04-28 08:56:47 -04:00
thunderbiscuit
89e85a20cf Fix Python libraries' names to prepare for releasing 2023-04-25 13:53:27 -04:00
David Sterling
d8718c3f05 Update bdk-jvm README build instructions
The current build instructions for bdk-jvm are insufficient for someone to build bdk-jvm from scratch. It's not outlined that JDK 11 and Rust is required on the system.
2023-04-25 13:48:32 -04:00
thunderbiscuit
871a06d1ce Add library build task to Android test CI workflow 2023-04-24 14:17:58 -04:00
thunderbiscuit
b820d6a2ba Remove support for Python 3.6 and 3.7 2023-04-24 13:57:12 -04:00
thunderbiscuit
79d9fa2909 Use ubuntu-20.04 image for all Linux CI runs 2023-04-24 13:53:56 -04:00
thunderbiscuit
a0e0467d39 Add workflow dispatch to all test workflows 2023-04-24 13:52:42 -04:00
thunderbiscuit
f2296704e6 Pin Rust version in CI workflows to 1.67 2023-04-24 13:51:37 -04:00
thunderbiscuit
b8b60dda87 Prepare language bindings libraries for 0.28 release 2023-04-17 14:34:03 -04:00
Steve Myers
a50e19e7e0 Update bdk to 0.28 2023-04-13 10:22:53 -04:00
Steve Myers
fab9ae8ae5 Add PartiallySignedTransaction.json_serialize() function 2023-04-13 09:21:22 -04:00
thunderbiscuit
478b12c489 Add API docs for fromScript method on Address 2023-04-13 08:50:08 -04:00
andreasgriffin
63b85b9100 Add from_script method to Address type 2023-04-13 08:50:03 -04:00
Steve Myers
0e6b472793 Merge bitcoindevkit/bdk-ffi#335: Use version 21 of the Android NDK in the CI test, build, and publish workflows
beb75dd552 Use version 21 of the Android NDK in the CI test, build, and publish workflows (thunderbiscuit)

Pull request description:

  This PR reverts an update to our Android CI workflows.

  For info on why this is needed, see issues #242 and #243.

ACKs for top commit:
  notmandatory:
    ACK beb75dd552

Tree-SHA512: dfee0b1f335318b86fa490fa85e7ef5d7a032e01a78db64a186c5d75e9e77e8f0e2af58ee5407d4fe38a0aa3aabbc7b06b23189aef0dfaad1d246a354452870d
2023-04-12 21:38:07 -05:00
thunderbiscuit
beb75dd552 Use version 21 of the Android NDK in the CI test, build, and publish workflows 2023-04-05 11:10:21 -04:00
thunderbiscuit
5ee8698e0a Add Kotlin API docs for new AddressInfo type 2023-03-28 19:51:15 -04:00
thunderbiscuit
ac600a1312 Clean up type aliases and use more explicit variable names 2023-03-28 19:47:38 -04:00
thunderbiscuit
f26031db80 Update tests for new AddressInfo type 2023-03-28 15:05:42 -04:00
thunderbiscuit
e7e1a6057e Use Address type in address field on AddressInfo 2023-03-28 13:29:06 -04:00
thunderbiscuit
2b7c104f11 Clean up of docs regarding removed cli tool 2023-03-27 13:39:28 -04:00
thunderbiscuit
6bab5a159d Remove bdk-ffi-bindgen tool section in root readme 2023-03-24 12:56:06 -04:00
thunderbiscuit
b6c8b145bb Fix bdk-android publishing CI workflow 2023-03-24 12:38:32 -04:00
Steve Myers
67610abeb6 Merge bitcoindevkit/bdk-ffi#326: Add SignOptions to Wallet.sign() params
4e5537acd2 Add SignOptions to Wallet.sign() params (Steve Myers)

Pull request description:

  ### Description

  Fixes #323

  ### Notes to the reviewers

  I didn't include the `tap_leaves_options: TapLeavesOptions` because it would require more deeply nested structures and is not currently required.

  ### Changelog notice

  - Add SignOptions to Wallet.sign() params.

  ### Checklists

  #### All Submissions:

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

  #### New Features:

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

Top commit has no ACKs.

Tree-SHA512: e846388b93b5270dd6d1f86ebff462d1c69aa0ea8359d7e4955c88eed4dac1ce00f8da3ecf2432e6eba6e1e5b2f52e3e789918aa31a4b739a7d0a5185b5c3c0c
2023-03-24 11:12:23 -05:00
Steve Myers
4e5537acd2 Add SignOptions to Wallet.sign() params 2023-03-24 10:51:44 -05:00
Steve Myers
6be4ddaf7b Merge bitcoindevkit/bdk-ffi#325: Expose Address payload and network properties
cd10c75e96 Add to_qr_uri() method on Address type (thunderbiscuit)
cbd44249f3 Add kotlin api-docs for Address.payload() and Address.network() (Steve Myers)
20c31d5383 Expose Address payload and network properties (Steve Myers)
616cb21738 Update Cargo.lock (Steve Myers)

Pull request description:

  ### Description

  Fixes #319

  ### Notes to the reviewers

  The test data was generated with:
  ```
  bitcoin-cli -rpcwallet="regtest2" -datadir=/tmp/regtest1/bitcoind -regtest validateaddress "bcrt1qqjn9gky9mkrm3c28e5e87t5akd3twg6xezp0tv"
  {
    "isvalid": true,
    "address": "bcrt1qqjn9gky9mkrm3c28e5e87t5akd3twg6xezp0tv",
    "scriptPubKey": "001404a6545885dd87b8e147cd327f2e9db362b72346",
    "isscript": false,
    "iswitness": true,
    "witness_version": 0,
    "witness_program": "04a6545885dd87b8e147cd327f2e9db362b72346"
  }
  ```

  ### Changelog notice

  Added
  - Expose Address payload and network properties.

  ### Checklists

  #### All Submissions:

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

  #### New Features:

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

ACKs for top commit:
  thunderbiscuit:
    ACK cd10c75e96.

Tree-SHA512: 65a1d550577fd4a35cfeb8fb765002e47e0703ff40fc7a68c07b78bc1ba642d33a0efbdfa8f9322024841ba84d25c0e289294c10ae4c1b4d52eb9beffa3d0e10
2023-03-23 16:39:00 -05:00
thunderbiscuit
cd10c75e96 Add to_qr_uri() method on Address type 2023-03-23 17:03:40 -04:00
Steve Myers
cbd44249f3 Add kotlin api-docs for Address.payload() and Address.network() 2023-03-23 15:41:17 -05:00
Steve Myers
20c31d5383 Expose Address payload and network properties 2023-03-23 15:41:17 -05:00
Steve Myers
616cb21738 Update Cargo.lock 2023-03-23 15:41:17 -05:00
Steve Myers
d3a6453eda Merge bitcoindevkit/bdk-ffi#307: Expose more of the Transaction type
cba69e681a Clean up of From traits implementations (thunderbiscuit)
35d8fb3139 Clean up extract_tx method on PSBT (thunderbiscuit)
f003a6275e Clean up conversion between BDK TransactionDetails to ffi TransactionDetails (thunderbiscuit)
2f62377eec Add Eq and PartialEq traits on the Transaction type (thunderbiscuit)
81e208222a Add uniffi/cli as default feature to cargo (thunderbiscuit)
3dc6596aa2 Add include_raw boolean parameter on wallet list_transactions method (thunderbiscuit)
2342265c26 Remove unused NetworkLocalUtxo type (thunderbiscuit)
6c561228c2 Use the latest version of the Android NDK (thunderbiscuit)
e86909ab3d Clean up input and output methods on Transaction type (thunderbiscuit)
8e51756a3a Fix clippy errors (thunderbiscuit)
40263b425e Remove deprecated bdk-ffi-bindgen tool (thunderbiscuit)
9437051668 Fix fmt and clippy errors (thunderbiscuit)
7557e214c8 Add optional transaction field on the TransactionDetails type (thunderbiscuit)
40ca62086c Expose all fields on the Transaction type (thunderbiscuit)
e0506deffa Add new utility methods on Transaction type (thunderbiscuit)
d3e183a498 Add Kotlin API docs for new Transaction methods (thunderbiscuit)
1e9ecfbe52 Add weight, size, and vsize methods on the Transaction type (thunderbiscuit)

Pull request description:

  ## Description
  We've recently exposed the `Transaction` type, and I think a few methods on it would be useful. ~This is a draft PR with the first 3: `weight()`, `size()`, and `vsize()`~ _Edit: it's now much more_. I think there might be other methods we might want to expose as well. [Take a look at the docs to see them all](https://docs.rs/bitcoin/0.29.2/bitcoin/blockdata/transaction/struct.Transaction.html).

  Other candidates have now been added:
  1. [is_explicitly_rbf](https://docs.rs/bitcoin/0.29.2/bitcoin/blockdata/transaction/struct.Transaction.html#method.is_explicitly_rbf)
  2. [is_lock_time_enabled](https://docs.rs/bitcoin/0.29.2/bitcoin/blockdata/transaction/struct.Transaction.html#method.is_lock_time_enabled)
  3. [is_absolute_timelock_satisfied](https://docs.rs/bitcoin/0.29.2/bitcoin/blockdata/transaction/struct.Transaction.html#method.is_absolute_timelock_satisfied)
  4. [is_coin_base](https://docs.rs/bitcoin/0.29.2/bitcoin/blockdata/transaction/struct.Transaction.html#method.is_coin_base)
  5. [txid](https://docs.rs/bitcoin/0.29.2/bitcoin/blockdata/transaction/struct.Transaction.html#method.txid)

  This PR is growing in size but I decided to add all 4 fields on the `Transaction` type. This is useful because it means we can now add the `transaction` field on the `TransactionDetails` type (also added in this PR).

  I still have a few questions regarding all the translation between the ffi and bdk/rust types, some of the traits I had to remove (Eq and PartialEq on the TransactionDetails type) as well as the usage of `Option<Arc<T>>` vs `Arc<Option<T>>`. Will outline those tomorrow.

  Closes #303
  Closes #187

  ## Changelog notice
  ```md
  APIs Added
  - `Transaction` type now exposes the `.weight()`, `.size()`, `.vsize()`, `is_explicitly_rbf()`, `is_lock_time_enabled()`, `is_coin_base(), and `txid()` methods [#307]

  [#307]: https://github.com/bitcoindevkit/bdk-ffi/pull/307
  ```

  ## Checklists

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

  #### New Features:
  * [ ] I've added tests for the new feature
  * [x] I've added docs for the new feature

ACKs for top commit:
  notmandatory:
    ACK cba69e681a

Tree-SHA512: 83e860407b230b6cdca59c0d74a486e52e7ea34d655d418ddc115418551a61665cad8f1d6182858d8ed6d7d72a8558e595e61644efed6c704de8a0b6960a0df2
2023-03-23 15:40:19 -05:00
thunderbiscuit
cba69e681a Clean up of From traits implementations 2023-03-23 15:16:53 -04:00
thunderbiscuit
35d8fb3139 Clean up extract_tx method on PSBT 2023-03-23 13:14:48 -04:00
thunderbiscuit
f003a6275e Clean up conversion between BDK TransactionDetails to ffi TransactionDetails 2023-03-23 13:08:55 -04:00
thunderbiscuit
2f62377eec Add Eq and PartialEq traits on the Transaction type 2023-03-23 12:48:04 -04:00
thunderbiscuit
81e208222a Add uniffi/cli as default feature to cargo 2023-03-23 12:39:22 -04:00
thunderbiscuit
3dc6596aa2 Add include_raw boolean parameter on wallet list_transactions method 2023-03-23 12:32:54 -04:00
thunderbiscuit
2342265c26 Remove unused NetworkLocalUtxo type 2023-03-23 12:21:18 -04:00
thunderbiscuit
6c561228c2 Use the latest version of the Android NDK 2023-03-21 17:01:54 -04:00
thunderbiscuit
e86909ab3d Clean up input and output methods on Transaction type 2023-03-20 15:16:39 -04:00
thunderbiscuit
8e51756a3a Fix clippy errors 2023-03-14 08:22:05 -04:00
thunderbiscuit
40263b425e Remove deprecated bdk-ffi-bindgen tool 2023-03-13 15:04:48 -04:00
thunderbiscuit
9437051668 Fix fmt and clippy errors 2023-03-13 14:52:17 -04:00
thunderbiscuit
7557e214c8 Add optional transaction field on the TransactionDetails type 2023-03-13 14:49:07 -04:00
thunderbiscuit
40ca62086c Expose all fields on the Transaction type 2023-03-13 12:13:09 -04:00
thunderbiscuit
e0506deffa Add new utility methods on Transaction type
This PR adds the txid(), is_coin_base(), is_explicitly_rbf(),
and is_lock_time_enabled() methods.
Fixes #303
2023-03-13 08:54:37 -04:00
thunderbiscuit
d3e183a498 Add Kotlin API docs for new Transaction methods 2023-03-13 08:54:31 -04:00
thunderbiscuit
1e9ecfbe52 Add weight, size, and vsize methods on the Transaction type 2023-03-13 08:54:25 -04:00
thunderbiscuit
d48bacd29b Streamline CI workflows 2023-03-10 21:03:24 -05:00
thunderbiscuit
c1243f9e1c Update publish Python CI workflow 2023-03-01 13:43:24 -05:00
thunderbiscuit
9c6069e389 Run tests in Python CI workflow 2023-03-01 11:37:08 -05:00
thunderbiscuit
488edf8bd2 Fix Python build workflow to account for Linux/Windows 2023-03-01 10:02:45 -05:00
thunderbiscuit
90763d42a2 Fix Swift script and CI workflow 2023-02-28 12:42:54 -05:00
thunderbiscuit
44b2ef1382 Fix cargo test workflow 2023-02-28 12:04:43 -05:00
thunderbiscuit
25617d1f23 Fix cargo clippy CI workflow 2023-02-28 09:27:56 -05:00
thunderbiscuit
2fcafe2b80 Fix Rust CI workflow 2023-02-28 08:31:51 -05:00
thunderbiscuit
5728b50100 Fix formatting warning in Gradle plugins 2023-02-27 20:07:02 -05:00
thunderbiscuit
d08317775b Update Python build scripts for uniffi-rs 0.23.0 2023-02-27 20:04:32 -05:00
thunderbiscuit
c93f292b0e Upgrade uniffi-rs to 0.23.0 and migrate Gradle plugins 2023-02-27 14:43:52 -05:00
thunderbiscuit
a75c868eb2 Update minor_release and patch_release templates 2023-02-22 14:27:03 -05:00
thunderbiscuit
974ff66caf Bump snapshot and dev versions of libraries 2023-02-21 15:45:46 -05:00
thunderbiscuit
2309b19209 Update changelog for all versions since 0.10.0 2023-02-21 15:42:50 -05:00
thunderbiscuit
3128fad690 Update AddressIndex enum use in Python test and example 2023-02-18 09:53:13 -05:00
thunderbiscuit
a1b112cbbb Remove license files from bdk-python 2023-02-18 09:42:33 -05:00
thunderbiscuit
553c337241 Update JVM readme 2023-02-17 17:31:00 -05:00
thunderbiscuit
90d12a96c5 Update Android readme to add section on x86 emulators 2023-02-17 16:00:41 -05:00
thunderbiscuit
5ca1d17adb Fix Android tests to account for new AddressIndex sealed class 2023-02-17 15:49:20 -05:00
thunderbiscuit
f121372c73 Fix PartiallySignedTransaction type name in Kotlin API docs 2023-02-17 15:23:47 -05:00
Steve Myers
44a78cc459 Update bdk from 0.27 to 0.27.1 2023-02-17 09:14:28 -06:00
Steve Myers
2dbad2ddd5 Merge bitcoindevkit/bdk-ffi#308: Bump bdk version to 0.27 and bdk-ffi to 0.27.0
ec71ef58be Bump bdk version to 0.27 and bdk-ffi to 0.27.0 (thunderbiscuit)

Pull request description:

  ## Description
  This PR updates BDK to the latest version, `0.27.0`.

  ### Changelog notice
  ```txt
  - Update BDK to latest version 0.27 [#308]

  [#308](https://github.com/bitcoindevkit/bdk-ffi/pull/308)
  ```

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

ACKs for top commit:
  notmandatory:
    ACK ec71ef58be

Tree-SHA512: 2d97b2af5247a5021fb7e5db70a903ded243b8f298133da1b83f7610d4e8ffc5e8e1b65e60ae07a781334de9c1630dfc6f9d58f62ff240783c9c22afc1f8f907
2023-02-14 17:00:29 -06:00
thunderbiscuit
ec71ef58be Bump bdk version to 0.27 and bdk-ffi to 0.27.0 2023-02-14 12:45:29 -06:00
thunderbiscuit
4ca7919ca9 Clean up samples in Kotlin API docs 2023-02-02 12:26:29 -05:00
thunderbiscuit
07aa1f8950 Add new AddressIndex variants to Kotlin API docs 2023-02-02 12:26:28 -05:00
Ed Ball
d42789db9b fix: incorrect peek_address index 2023-02-02 12:26:27 -05:00
Ed Ball
10f893a4b3 test: reset_address 2023-02-02 12:26:26 -05:00
Ed Ball
13043065b6 expose: get_address reset method 2023-02-02 12:26:25 -05:00
Ed Ball
5fa0b916a2 test: more descriptive and complete peek_address tests 2023-02-02 12:26:24 -05:00
Ed Ball
7611a65620 fix jvm test AddressIndex class capitalisation 2023-02-02 12:26:23 -05:00
Ed Ball
458a162c2a remove redundant to_string from tests 2023-02-02 12:26:22 -05:00
Ed Ball
d15783dba0 add missing bdk-ffi AddressIndex Peek docstrings 2023-02-02 12:26:20 -05:00
Ed Ball
2723577a84 test: get_address peek ffi tests 2023-02-02 12:26:14 -05:00
Ed Ball
7fefb8a7b9 expose: get_address peek 2023-02-02 12:25:42 -05:00
Ed Ball
aa842c3844 fix incorrect address in get_internal_address test 2023-02-01 20:30:24 +01:00
thunderbiscuit
df019c11ec Add new getInternalAddress method to Kotlin API docs 2023-02-01 14:03:49 -05:00
Ed Ball
11d7d6b80f test: get_internal_address LastUnused 2023-02-01 14:43:22 +01:00
Ed Ball
85f8a8a526 fix: redundant problematic to_string in get_internal_address tests 2023-02-01 14:37:36 +01:00
Ed Ball
5e75c856c5 test: get internal/external address 2023-01-31 22:25:30 +01:00
Ed Ball
b854c78dde feat: expose get_internal_address 2023-01-28 19:10:54 +01:00
thunderbiscuit
f5d4750ae4 Fix Python CI release workflow 2023-01-24 13:22:58 -05:00
Steve Myers
f75ead02ff Merge bitcoindevkit/bdk-ffi#296: Add Transaction struct, update PSBT and Blockchain to use it
8e54ada436 Add Transaction struct, update PSBT and Blockchain to use it (Steve Myers)

Pull request description:

  ### Description

  Add new `Transaction` structure that can be created from transaction consensus encoded bytes. Update `PartiallySignedTransaction.extract_tx()` to return a `Transaction` instead of a the transaction bytes. Update `Blockchain.broadcast()` to take a `Transaction` parameter.

  ### Notes to the reviewers

  Fixes #157.

  ### Changelog notice

  #### Added

  - New `Transaction` structure that can be created from or serialized to consensus encoded bytes.

  #### Changed

  - `PartiallySignedTransaction.extract_tx()` returns a `Transaction` instead of a the transaction bytes.
  - `Blockchain.broadcast()` takes a `Transaction` instead of a `PartiallySignedTransaction`.

  ### Checklists

  #### All Submissions:

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

  #### New Features:

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

  #### Bugfixes:

  * [ ] This pull request breaks the existing API
  * [ ] I've added tests to reproduce the issue which are now passing
  * [ ] I'm linking the issue being fixed by this PR

ACKs for top commit:
  thunderbiscuit:
    Tested ACK 8e54ada436. I ran a script using bdk-jvm and your test vector. Looks good! Another win for the LDK/BDK integration.

Tree-SHA512: f5bfdafad93f0b4ada0756a8e68bdb12f2de680d8ae8a5083dbbeded6fc314bd79c8913f0b7f01e2eb807676092777ad8bf9466996cd51fa9587b8bb53170ca8
2023-01-23 15:47:39 -06:00
Steve Myers
8e54ada436 Add Transaction struct, update PSBT and Blockchain to use it 2023-01-23 14:30:36 -06:00
thunderbiscuit
302ad8dea8 Move Rust tests to their respective files 2023-01-19 22:59:53 -05:00
thunderbiscuit
561e93f6dd Add pycache directory to gitignore 2023-01-19 16:29:21 -05:00
thunderbiscuit
b8230799cf Fix GitHub repository link in setup.py file 2023-01-19 16:11:52 -05:00
thunderbiscuit
d2bec60046 Bump development versions for JVM, Android, and Python 2023-01-17 15:49:46 -05:00
thunderbiscuit
aadc622006 Refactor: Fix up imports 2023-01-17 15:47:48 -05:00
thunderbiscuit
202dcfa2b5 Refactor: separate database from lib.rs 2023-01-17 15:37:10 -05:00
thunderbiscuit
46bd9a1f15 Refactor: separate keys from lib.rs 2023-01-17 15:35:20 -05:00
thunderbiscuit
6fcb8985f1 Refactor: separate descriptor from lib.rs 2023-01-17 15:29:23 -05:00
thunderbiscuit
dd58a9d548 Refactor: separate blockchain from lib.rs 2023-01-17 15:29:05 -05:00
thunderbiscuit
d2a4e2adba Refactor: separate transaction builders from lib.rs 2023-01-17 15:18:55 -05:00
thunderbiscuit
cbc740c407 Refactor: separate psbt struct from lib.rs 2023-01-17 15:18:48 -05:00
thunderbiscuit
f50ecdb7e7 Refactor: separate wallet struct from lib.rs 2023-01-17 15:18:41 -05:00
Shobit Beltangdy
f01e0e30f3 expose estimate_fee api
bdk::blockchain::Blockchain contains an `estimate_fee` api. This commit adds the bindings for estimate_fee.

This will fix https://github.com/bitcoindevkit/bdk-ffi/issues/287

Tested this by adding a unit test in lib.rs. Also generated the python code and verified that it is able to invoke estimate_fee and get the fee rate
2023-01-13 16:47:12 -08:00
thunderbiscuit
5f7d8a9077 Fix typos in Kotlin API docs 2023-01-10 16:15:12 -05:00
thunderbiscuit
d77f5b84c3 Update Swift readme for 0.26.0 release 2023-01-09 21:36:19 -05:00
thunderbiscuit
6555170d28 Fix Python tests and examples for 0.26.0 release 2023-01-09 21:24:02 -05:00
thunderbiscuit
c763609b8b Update bdk-jvm readme for 0.26.0 release 2023-01-09 21:08:20 -05:00
thunderbiscuit
340fa19bb8 Update bdk-android tests and readme 2023-01-09 21:03:41 -05:00
thunderbiscuit
ff2734663e Update Kotlin API docs to reflect new descriptor templates 2023-01-09 20:48:44 -05:00
Steve Myers
f399b799e4 Add validate_domain option to ElectrumConfig 2023-01-09 14:41:49 -05:00
Steve Myers
cdde26a8ea Bump bdk-ff version to 0.26.0 and bdk version to 0.26 2023-01-03 09:07:38 -06:00
Shobit Beltangdy
34af88df6f Update Blockchain.broadcast doc to reflect no return type
This fixes #284
2022-12-18 14:32:29 -08:00
Shobit Beltangdy
12e9a18357 Update Wallet.getBalance doc to reflect Balance return type
This is a fix for issue #284
2022-12-17 09:11:56 -08:00
thunderbiscuit
bada9b82e0 Add assert_matches dev-dependency 2022-12-16 12:28:06 -05:00
thunderbiscuit
41f15fe80f Change descriptor parameter in wallet constructor to use Descriptor instead of String 2022-12-16 11:28:55 -05:00
thunderbiscuit
54953fa208 Add default string constructor for Descriptor 2022-12-16 11:28:50 -05:00
thunderbiscuit
b7ccf81c7a Add tests for templates 2022-12-16 11:28:45 -05:00
thunderbiscuit
3792a98426 Add BIP44/49/84 templates 2022-12-16 11:28:38 -05:00
thunderbiscuit
5dd79e9632 Add readme to api-docs directory 2022-12-16 11:26:25 -05:00
thunderbiscuit
9ae938ca8c Add JNA issue fix in documentation 2022-12-16 11:26:19 -05:00
thunderbiscuit
05aa7157df Clean up CI tests for bdk-android and bdk-jvm 2022-12-16 11:23:04 -05:00
thunderbiscuit
275bd94148 Add dependency on the Android plugin library building task for tests 2022-12-16 11:23:02 -05:00
thunderbiscuit
146cb039c3 Add dependency on the JVM plugin library building task for tests 2022-12-16 11:22:43 -05:00
Steve Myers
41fdadb09c Merge bitcoindevkit/bdk-ffi#125: Add RpcConfig, BlockchainConfig::Rpc, and Auth
4ed6e364e6 Add API docs for RPC blockchain config and auth (thunderbiscuit)
d0cd3b0f38 Add Auth, RpcSyncParams, and RpcConfig (Steve Myers)

Pull request description:

  Fixes #117. This adds the RPC blockchain config but I don't currently have a way to test it as part of the `bdk-kotlin` or `bdk-swift` automated testing, which would need to be able to spin up a local regtest bitcoind for the tests.

  For now this will only be manually tested.

ACKs for top commit:
  thunderbiscuit:
    ACK [4ed6e36](4ed6e364e6).

Tree-SHA512: 2f114753a683c32ec957b26e23918eb3c1de07073fd0293e06fb960f8226f09d4d9ebf89f8d04f9da5cd459619f224d732b81e21c2173afeccb8ce56cc558582
2022-12-16 10:10:06 -06:00
thunderbiscuit
4ed6e364e6 Add API docs for RPC blockchain config and auth 2022-12-16 10:36:32 -05:00
Steve Myers
d0cd3b0f38 Add Auth, RpcSyncParams, and RpcConfig 2022-12-13 15:10:28 -06:00
Steve Myers
f216417fd2 Update api-docs for TxBuilder.drain_to change 2022-12-07 15:34:32 -06:00
Steve Myers
7d1a4500ef Change TxBuilder.drain_to argument to Script instead of address String 2022-12-07 11:54:22 -06:00
thunderbiscuit
a408387bff Bump snapshot and dev libraries' versions 2022-12-07 11:59:40 -05:00
63 changed files with 3967 additions and 2495 deletions

View File

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

View File

@@ -4,98 +4,82 @@ 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.
### Summary
### 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.
```sh
# 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
```sh
cd ./bdk-jvm/
./gradlew buildJvmLib
./gradlew test
```
10. - [ ] Update the readme if necessary
#### _Swift_
11. - [ ] Run the tests and adjust if necessary
```sh
./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 ./generate.sh
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 summary to be used in announcements-->
### Commit
<--latest commit ID to include in this release-->
### Changelog
<--add notices from PRs merged since the prior release, see ["keep a changelog"]-->
### Checklist
Release numbering must follow [Semantic Versioning]. These steps assume the current `master`
branch **development** version is *MAJOR.MINOR.0*.
#### On the day of the feature freeze
Change the `master` branch to the next MINOR+1 version:
- [ ] Switch to the `master` branch.
- [ ] Create a new PR branch called `bump_dev_MAJOR_MINOR+1`, eg. `bump_dev_0_22`.
- [ ] Bump the `bump_dev_MAJOR_MINOR+1` branch to the next development MINOR+1 version.
- Change the `Cargo.toml` version value to `MAJOR.MINOR+1.0`.
- The commit message should be "Bump version to MAJOR.MINOR+1.0".
- [ ] Create PR and merge the `bump_dev_MAJOR_MINOR+1` branch to `master`.
- Title PR "Bump version to MAJOR.MINOR+1.0".
Create a new release branch:
- [ ] Double check that your local `master` is up-to-date with the upstream repo.
- [ ] Create a new branch called `release/MAJOR.MINOR+1` from `master`.
Add a release candidate tag, this is optional and only needed for major `bdk-ffi` changes that
require a longer testing cycle:
- [ ] Bump the `release/MAJOR.MINOR+1` branch to `MAJOR.MINOR+1.0-rc.1` version.
- Change the `Cargo.toml` version value to `MAJOR.MINOR+1.0-rc.1`.
- The commit message should be "Bump version to MAJOR.MINOR+1.0-rc.1".
- [ ] Add a tag to the `HEAD` commit in the `release/MAJOR.MINOR+1` branch.
- The tag name should be `vMAJOR.MINOR+1.0-rc.1`
- Use message "Release MAJOR.MINOR+1.0 rc.1".
- Make sure the tag is signed, for extra safety use the explicit `--sign` flag.
- [ ] Push the `release/MAJOR.MINOR` branch and new tag to the `bitcoindevkit/bdk` repo.
- Use `git push --tags` option to push the new `vMAJOR.MINOR+1.0-rc.1` tag.
If any issues need to be fixed before the *MAJOR.MINOR+1.0* version is released:
- [ ] Merge fix PRs to the `master` branch.
- [ ] Git cherry-pick fix commits to the `release/MAJOR.MINOR+1` branch.
- [ ] Verify fixes in `release/MAJOR.MINOR+1` branch.
- [ ] Bump the `release/MAJOR.MINOR+1` branch to `MAJOR.MINOR+1.0-rc.x+1` version.
- Change the `Cargo.toml` version value to `MAJOR.MINOR+1.0-rc.x+1`.
- The commit message should be "Bump version to MAJOR.MINOR+1.0-rc.x+1".
- [ ] Add a tag to the `HEAD` commit in the `release/MAJOR.MINOR+1` branch.
- The tag name should be `vMAJOR.MINOR+1.0-rc.x+1`, where x is the current release candidate number.
- Use tag message "Release MAJOR.MINOR+1.0 rc.x+1".
- Make sure the tag is signed, for extra safety use the explicit `--sign` flag.
- [ ] Push the new tag to the `bitcoindevkit/bdk` repo.
- Use `git push --tags` option to push the new `vMAJOR.MINOR+1.0-rc.x+1` tag.
#### On the day of the release
Tag and publish new release:
- [ ] Bump the `release/MAJOR.MINOR+1` branch to `MAJOR.MINOR+1.0` version.
- Change the `Cargo.toml` version value to `MAJOR.MINOR+1.0`.
- The commit message should be "Bump version to MAJOR.MINOR+1.0".
- [ ] Add a tag to the `HEAD` commit in the `release/MAJOR.MINOR+1` branch.
- The tag name should be `vMAJOR.MINOR+1.0`
- The first line of the tag message should be "Release MAJOR.MINOR+1.0".
- In the body of the tag message put a copy of the **Summary** and **Changelog** for the release.
- Make sure the tag is signed, for extra safety use the explicit `--sign` flag.
- [ ] Wait for the CI to finish one last time.
- [ ] Push the new tag to the `bitcoindevkit/bdk` repo.
- [ ] Create the release on GitHub.
- Go to "tags", click on the dots on the right and select "Create Release".
- Set the title to `Release MAJOR.MINOR+1.0`.
- In the release notes body put the **Summary** and **Changelog**.
- Use the "+ Auto-generate release notes" button to add details from included PRs.
- Until we reach a `1.0.0` release check the "Pre-release" box.
- [ ] After downstream language repos are also updated announce the release, using the **Summary**,
on Discord, Twitter and Mastodon.
- [ ] Celebrate 🎉
[Semantic Versioning]: https://semver.org/
[crates.io]: https://crates.io/crates/bdk
[docs.rs]: https://docs.rs/bdk/latest/bdk
["keep a changelog"]: https://keepachangelog.com/en/1.0.0/
### 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).
- [ ] Create a new branch off of `master` called `release/version`
18. - [ ] Open a PR to that branch to update the Android, JVM, and Python libraries' versions. See [example PR here](https://github.com/bitcoindevkit/bdk-ffi/pull/316).
- [ ] Update bdk-android version from `SNAPSHOT` version to release version
- [ ] Update bdk-jvm version from `SNAPSHOT` version to release version
- [ ] Update bdk-python version from `.dev` version to release version
19. - [ ] Merge the PR updating all the languages to their release versions
20. - [ ] Create the tag 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) and push it to GitHub.
```sh
git tag v0.6.0 --sign --edit
git push upstream v0.6.0
```
21. - [ ] Aggregate all the changelog notices from the PRs and add them to the changelog file
22. - [ ] Open a PR on master with the changes to the changelog file and the development versions bump. See [example PR here](https://github.com/bitcoindevkit/bdk-ffi/pull/317).
23. - [ ] Make release on GitHub (set as pre-release and generate auto release notes between the previous tag and the new one)
24. - [ ] Trigger manual releases for all 4 libraries (for Swift, 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`)
25. - [ ] Bump the versions on master from `0.9.0-SNAPSHOT` to `0.10.0-SNAPSHOT`, `0.6.0.dev0` to `0.7.0.dev0`
26. - [ ] Build and publish API docs for JVM, Android, and Java on the website
```bash!
./gradlew dokkaHtml # bdk-jvm (Dokka)
./gradlew dokkaJavadoc # bdk-jvm (java-style documentation)
./gradlew dokkaHtml # bdk-android (Dokka)
```
27. - [ ] Tweet about the library
28. - [ ] Post in the announcement channel

View File

@@ -4,66 +4,82 @@ about: Create a new patch release [for release managers only]
title: 'Release MAJOR.MINOR.PATCH+1'
labels: 'release'
assignees: ''
---
## Create a new patch release
# Creating a new patch 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.
### Summary
### 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.
```sh
# 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
```sh
cd ./bdk-jvm/
./gradlew buildJvmLib
./gradlew test
```
10. - [ ] Update the readme if necessary
#### _Swift_
11. - [ ] Run the tests and adjust if necessary
```sh
./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 ./generate.sh
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 summary to be used in announcements-->
### Commit
<--latest commit ID to include in this release-->
### Changelog
<--add notices from PRs merged since the prior release, see ["keep a changelog"]-->
### Checklist
Release numbering must follow [Semantic Versioning]. These steps assume the current `master`
branch **development** version is *MAJOR.MINOR.PATCH*.
### On the day of the patch release
Change the `master` branch to the new PATCH+1 version:
- [ ] Switch to the `master` branch.
- [ ] Create a new PR branch called `bump_dev_MAJOR_MINOR_PATCH+1`, eg. `bump_dev_0_22_1`.
- [ ] Bump the `bump_dev_MAJOR_MINOR` branch to the next development PATCH+1 version.
- Change the `Cargo.toml` version value to `MAJOR.MINOR.PATCH+1`.
- The commit message should be "Bump version to MAJOR.MINOR.PATCH+1".
- [ ] Create PR and merge the `bump_dev_MAJOR_MINOR_PATCH+1` branch to `master`.
- Title PR "Bump version to MAJOR.MINOR.PATCH+1".
Cherry-pick, tag and publish new PATCH+1 release:
- [ ] Merge fix PRs to the `master` branch.
- [ ] Git cherry-pick fix commits to the `release/MAJOR.MINOR` branch to be patched.
- [ ] Verify fixes in `release/MAJOR.MINOR` branch.
- [ ] Bump the `release/MAJOR.MINOR.PATCH+1` branch to `MAJOR.MINOR.PATCH+1` version.
- Change the `Cargo.toml` version value to `MAJOR.MINOR.MINOR.PATCH+1`.
- The commit message should be "Bump version to MAJOR.MINOR.PATCH+1".
- [ ] Add a tag to the `HEAD` commit in the `release/MAJOR.MINOR` branch.
- The tag name should be `vMAJOR.MINOR.PATCH+1`
- The first line of the tag message should be "Release MAJOR.MINOR.PATCH+1".
- In the body of the tag message put a copy of the **Summary** and **Changelog** for the release.
- Make sure the tag is signed, for extra safety use the explicit `--sign` flag.
- [ ] Wait for the CI to finish one last time.
- [ ] Push the new tag to the `bitcoindevkit/bdk` repo.
- [ ] Create the release on GitHub.
- Go to "tags", click on the dots on the right and select "Create Release".
- Set the title to `Release MAJOR.MINOR.PATCH+1`.
- In the release notes body put the **Summary** and **Changelog**.
- Use the "+ Auto-generate release notes" button to add details from included PRs.
- Until we reach a `1.0.0` release check the "Pre-release" box.
- [ ] After downstream language repos are also updated announce the release, using the **Summary**,
on Discord, Twitter and Mastodon.
- [ ] Celebrate 🎉
[Semantic Versioning]: https://semver.org/
[crates.io]: https://crates.io/crates/bdk
[docs.rs]: https://docs.rs/bdk/latest/bdk
["keep a changelog"]: https://keepachangelog.com/en/1.0.0/
### 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).
- [ ] Create a new branch off of `master` called `release/version`
18. - [ ] Open a PR to that branch to update the Android, JVM, and Python libraries' versions. See [example PR here](https://github.com/bitcoindevkit/bdk-ffi/pull/316).
- [ ] Update bdk-android version from `SNAPSHOT` version to release version
- [ ] Update bdk-jvm version from `SNAPSHOT` version to release version
- [ ] Update bdk-python version from `.dev` version to release version
19. - [ ] Merge the PR updating all the languages to their release versions
20. - [ ] Create the tag 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) and push it to GitHub.
```sh
git tag v0.6.0 --sign --edit
git push upstream v0.6.0
```
21. - [ ] Aggregate all the changelog notices from the PRs and add them to the changelog file
22. - [ ] Open a PR on master with the changes to the changelog file and the development versions bump. See [example PR here](https://github.com/bitcoindevkit/bdk-ffi/pull/317).
23. - [ ] Make release on GitHub (set as pre-release and generate auto release notes between the previous tag and the new one)
24. - [ ] Trigger manual releases for all 4 libraries (for Swift, 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`)
25. - [ ] Bump the versions on master from `0.9.0-SNAPSHOT` to `0.10.0-SNAPSHOT`, `0.6.0.dev0` to `0.7.0.dev0`
26. - [ ] Build and publish API docs for JVM, Android, and Java on the website
```bash!
./gradlew dokkaHtml # bdk-jvm (Dokka)
./gradlew dokkaJavadoc # bdk-jvm (java-style documentation)
./gradlew dokkaHtml # bdk-android (Dokka)
```
27. - [ ] Tweet about the library
28. - [ ] Post in the announcement channel

View File

@@ -10,7 +10,7 @@ on:
jobs:
security_audit:
name: Security Audit
name: Security audit
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v2

View File

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

View File

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

View File

@@ -1,26 +1,28 @@
name: Publish bdk-android to Maven Central
on: [workflow_dispatch]
# The default Android NDK on the ubuntu-22.04 image is 25.2.9519653
# We replace the default environment variable ANDROID_NDK_ROOT: /usr/local/lib/android/sdk/ndk/25.2.9519653
# with an older version of the NDK (21.4.7075529) using the fix proposed here: https://github.com/actions/runner-images/issues/5930
# For information on why this is needed at the moment see issues #242 and #243, and PR #282
env:
ANDROID_NDK_ROOT: /usr/local/lib/android/sdk/ndk/21.4.7075529
# By default, the new ubuntu-20.04 images use the following ANDROID_NDK_ROOT
# ANDROID_NDK_ROOT: /usr/local/lib/android/sdk/ndk/25.0.8775105
jobs:
build:
runs-on: ubuntu-20.04
steps:
- name: Install Android NDK 21.4.7075529
- name: "Install Android NDK 21.4.7075529"
run: |
ANDROID_ROOT=/usr/local/lib/android
ANDROID_SDK_ROOT=${ANDROID_ROOT}/sdk
SDKMANAGER=${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin/sdkmanager
echo "y" | $SDKMANAGER "ndk;21.4.7075529"
- name: Check out PR branch
- name: "Check out PR branch"
uses: actions/checkout@v2
- name: cache
- name: "Cache"
uses: actions/cache@v2
with:
path: |
@@ -29,21 +31,24 @@ jobs:
./target
key: ${{ runner.os }}-${{ hashFiles('**/Cargo.toml','**/Cargo.lock') }}
- name: Set up JDK
- name: "Set up JDK"
uses: actions/setup-java@v2
with:
distribution: temurin
java-version: 11
- name: Install rust android targets
- name: "Set default Rust version to 1.67.0"
run: rustup default 1.67.0
- name: "Install Rust Android targets"
run: rustup target add x86_64-linux-android aarch64-linux-android armv7-linux-androideabi
- name: Build bdk-android library
- name: "Build bdk-android library"
run: |
cd bdk-android
./gradlew buildAndroidLib
- name: Publish to Maven Local and Maven Central
- name: "Publish to Maven Local and Maven Central"
env:
ORG_GRADLE_PROJECT_signingKeyId: ${{ secrets.PGP_KEY_ID }}
ORG_GRADLE_PROJECT_signingKey: ${{ secrets.PGP_SECRET_KEY }}

View File

@@ -3,10 +3,10 @@ on: [workflow_dispatch]
jobs:
build-jvm-macOS-M1-native-lib:
name: Create M1 and x86_64 JVM native binaries
name: "Create M1 and x86_64 JVM native binaries"
runs-on: macos-12
steps:
- name: Checkout publishing branch
- name: "Checkout publishing branch"
uses: actions/checkout@v2
- name: Cache
@@ -24,6 +24,9 @@ jobs:
distribution: temurin
java-version: 11
- name: "Set default Rust version to 1.67.0"
run: rustup default 1.67.0
- name: Install aarch64 Rust target
run: rustup target add aarch64-apple-darwin
@@ -43,7 +46,7 @@ jobs:
build-jvm-full-library:
name: Create full bdk-jvm library
needs: [build-jvm-macOS-M1-native-lib]
runs-on: ubuntu-22.04
runs-on: ubuntu-20.04
steps:
- name: Checkout publishing branch
uses: actions/checkout@v2
@@ -68,6 +71,9 @@ jobs:
distribution: temurin
java-version: 11
- name: "Set default Rust version to 1.67.0"
run: rustup default 1.67.0
- name: Build bdk-jvm library
run: |
cd bdk-jvm

View File

@@ -8,8 +8,8 @@ on: [workflow_dispatch]
jobs:
build-manylinux2014-x86_64-wheel:
name: 'Build Manylinux 2014 x86_64 wheel'
runs-on: ubuntu-latest
name: "Build Manylinux 2014 x86_64 wheel"
runs-on: ubuntu-20.04
defaults:
run:
working-directory: bdk-python
@@ -17,127 +17,133 @@ jobs:
image: quay.io/pypa/manylinux2014_x86_64
env:
PLAT: manylinux2014_x86_64
PYBIN: '/opt/python/${{ matrix.python }}/bin'
PYBIN: "/opt/python/${{ matrix.python }}/bin"
strategy:
matrix:
python: # Update this list whenever the docker image is updated (check /opt/python/)
- cp36-cp36m
- cp37-cp37m
- cp38-cp38
- cp39-cp39
- cp310-cp310
- pp37-pypy37_pp73
- pp38-pypy38_pp73
steps:
- name: checkout
- name: "Checkout"
uses: actions/checkout@v2
with:
submodules: true
- uses: actions-rs/toolchain@v1
with:
toolchain: stable
- name: install requirements
- name: "Set default Rust version to 1.67.0"
run: rustup default 1.67.0
- name: "Install requirements"
run: ${PYBIN}/pip install -r requirements.txt
- name: generate bindings
- name: "Generate bdk.py and binaries"
run: bash generate.sh
- name: build wheel
run: ${PYBIN}/pip wheel . --no-deps -w /tmp/wheelhouse
- name: repair wheel
run: auditwheel repair /tmp/wheelhouse/* --plat "$PLAT" -w /tmp/wheelhouse-repaired
- name: "Build wheel"
# Specifying the plat-name argument is necessary to build a wheel with the correct name,
# see issue #350 for more information
run: ${PYBIN}/python setup.py bdist_wheel --plat-name manylinux_2_17_x86_64 --verbose
- uses: actions/upload-artifact@v2
with:
name: bdkpython-manylinux2014-x86_64-${{ matrix.python }}
path: /tmp/wheelhouse-repaired/*.whl
path: /home/runner/work/bdk-ffi/bdk-ffi/bdk-python/dist/*.whl
build-macos-universal-wheel:
name: 'Build macOS universal wheel'
runs-on: macos-latest
name: "Build macOS universal wheel"
runs-on: macos-12
defaults:
run:
working-directory: bdk-python
strategy:
matrix:
python:
- '3.7'
- '3.8'
- '3.9'
- '3.10'
- "3.8"
- "3.9"
- "3.10"
steps:
- name: checkout
- name: "Checkout"
uses: actions/checkout@v2
with:
submodules: true
- uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python }}
- run: python3 --version
- run: rustup target add aarch64-apple-darwin
- run: pip3 install --user -r requirements.txt
- run: pip3 install --user wheel
- run: bash generate.sh
- name: build wheel
- name: "Generate bdk.py and binaries"
run: |
python3 --version
rustup target add aarch64-apple-darwin
pip3 install --user -r requirements.txt
bash generate.sh
- name: "Build wheel"
env:
ARCHFLAGS: "-arch x86_64 -arch arm64"
run: python3 setup.py -v bdist_wheel
# Specifying the plat-name argument is necessary to build a wheel with the correct name,
# see issue #350 for more information
run: python3 setup.py bdist_wheel --plat-name macosx_12_0_universal2 --verbose
- uses: actions/upload-artifact@v2
with:
name: bdkpython-macos-${{ matrix.python }}
path: dist/*.whl
path: /Users/runner/work/bdk-ffi/bdk-ffi/bdk-python/dist/*.whl
build-windows-wheel:
name: 'Build windows wheel'
runs-on: windows-latest
name: "Build Windows wheel"
runs-on: windows-2022
defaults:
run:
working-directory: bdk-python
strategy:
matrix:
python:
- '3.7'
- '3.8'
- '3.9'
- '3.10'
- "3.8"
- "3.9"
- "3.10"
steps:
- name: checkout
- name: "Checkout"
uses: actions/checkout@v2
with:
submodules: true
- uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python }}
- run: python --version
- run: pip install --user -r requirements.txt
- run: bash generate.sh
shell: bash
- run: pip install --user wheel
- name: build wheel
run: python setup.py -v bdist_wheel
- name: "Generate bdk.py and binaries"
run: |
python --version
pip install --user -r requirements.txt
bash generate.sh
- name: "Build wheel"
run: python setup.py bdist_wheel --verbose
- uses: actions/upload-artifact@v2
with:
name: bdkpython-win-${{ matrix.python }}
path: dist/*.whl
path: D:\a\bdk-ffi\bdk-ffi\bdk-python\dist\*.whl
publish-pypi:
name: 'Publish on PyPI'
runs-on: ubuntu-latest
name: "Publish on PyPI"
runs-on: ubuntu-20.04
defaults:
run:
working-directory: bdk-python
needs: [build-manylinux2014-x86_64-wheel, build-macos-universal-wheel, build-windows-wheel]
# needs: [build-macos-universal-wheel]
steps:
- name: Checkout
- name: "Checkout"
uses: actions/checkout@v2
- name: 'Download artifacts in dist/ directory'
- name: "Download artifacts in dist/ directory"
uses: actions/download-artifact@v2
with:
path: dist/
# - name: Display structure of downloaded files
# run: ls -R
# - name: 'Publish on test PyPI'
# - name: "Publish on test PyPI"
# uses: pypa/gh-action-pypi-publish@release/v1
# with:
# user: __token__
@@ -145,7 +151,7 @@ jobs:
# repository_url: https://test.pypi.org/legacy/
# packages_dir: dist/*/
- name: 'Publish on PyPI'
- name: "Publish on PyPI"
uses: pypa/gh-action-pypi-publish@release/v1
with:
user: __token__

View File

@@ -1,5 +1,6 @@
name: Test Android
on:
workflow_dispatch:
push:
paths:
- "bdk-ffi/**"
@@ -9,26 +10,28 @@ on:
- "bdk-ffi/**"
- "bdk-android/**"
# The default Android NDK on the ubuntu-22.04 image is 25.2.9519653
# We replace the default environment variable ANDROID_NDK_ROOT: /usr/local/lib/android/sdk/ndk/25.2.9519653
# with an older version of the NDK (21.4.7075529) using the fix proposed here: https://github.com/actions/runner-images/issues/5930
# For information on why this is needed at the moment see issues #242 and #243, and PR #282
env:
ANDROID_NDK_ROOT: /usr/local/lib/android/sdk/ndk/21.4.7075529
# By default, the new ubuntu-20.04 images use the following ANDROID_NDK_ROOT
# ANDROID_NDK_ROOT: /usr/local/lib/android/sdk/ndk/25.0.8775105
jobs:
build:
runs-on: ubuntu-20.04
steps:
- name: Install Android NDK 21.4.7075529
- name: "Install Android NDK 21.4.7075529"
run: |
ANDROID_ROOT=/usr/local/lib/android
ANDROID_SDK_ROOT=${ANDROID_ROOT}/sdk
SDKMANAGER=${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin/sdkmanager
echo "y" | $SDKMANAGER "ndk;21.4.7075529"
- name: Check out PR branch
- name: "Check out PR branch"
uses: actions/checkout@v2
- name: cache
- name: "Cache"
uses: actions/cache@v2
with:
path: |
@@ -37,21 +40,26 @@ jobs:
./target
key: ${{ runner.os }}-${{ hashFiles('**/Cargo.toml','**/Cargo.lock') }}
- name: Set up JDK
- name: "Set up JDK"
uses: actions/setup-java@v2
with:
distribution: temurin
java-version: 11
- name: Install rust android targets
- name: "Set default Rust version to 1.67.0"
run: rustup default 1.67.0
- name: "Install Rust Android targets"
run: rustup target add x86_64-linux-android aarch64-linux-android armv7-linux-androideabi
- name: Build bdk-android library
- name: "Build Android library"
run: |
cd bdk-android
./gradlew buildAndroidLib
- name: Run Android tests
run: |
cd bdk-android
./gradlew test --console=rich
# There are currently no unit tests for bdk-android and the integration tests require the macOS image
# which is not working with the older NDK version we are using, so for now we just make sure that the library builds.
# - name: "Run Android unit tests"
# run: |
# cd bdk-android
# ./gradlew test --console=rich

View File

@@ -1,5 +1,6 @@
name: Test JVM
name: Test Kotlin/JVM
on:
workflow_dispatch:
push:
paths:
- "bdk-ffi/**"
@@ -16,7 +17,7 @@ jobs:
- name: Check out PR branch
uses: actions/checkout@v2
- name: cache
- name: Cache
uses: actions/cache@v2
with:
path: |
@@ -31,10 +32,8 @@ jobs:
distribution: temurin
java-version: 11
- name: Build bdk-jvm library
run: |
cd bdk-jvm
./gradlew buildJvmLib
- name: "Set default Rust version to 1.67.0"
run: rustup default 1.67.0
- name: Run JVM tests
run: |

150
.github/workflows/test-python.yaml vendored Normal file
View File

@@ -0,0 +1,150 @@
name: Test Python
on:
workflow_dispatch:
push:
paths:
- "bdk-ffi/**"
- "bdk-python/**"
pull_request:
paths:
- "bdk-ffi/**"
- "bdk-python/**"
# We use manylinux2014 because older CentOS versions used by 2010 and 1 have a very old glibc version, which
# makes it very hard to use GitHub's javascript actions (checkout, upload-artifact, etc).
# They mount their own nodejs interpreter inside your container, but since that's not statically linked it
# tries to load glibc and fails because it requires a more recent version.
jobs:
build-manylinux2014-x86_64-wheel:
name: "Build and test Manylinux 2014 x86_64 wheels"
runs-on: ubuntu-20.04
defaults:
run:
working-directory: bdk-python
container:
image: quay.io/pypa/manylinux2014_x86_64
env:
PLAT: manylinux2014_x86_64
PYBIN: "/opt/python/${{ matrix.python }}/bin"
strategy:
matrix:
python:
- cp38-cp38
- cp39-cp39
- cp310-cp310
steps:
- name: "Checkout"
uses: actions/checkout@v2
with:
submodules: true
- uses: actions-rs/toolchain@v1
with:
toolchain: stable
- name: "Set default Rust version to 1.67.0"
run: rustup default 1.67.0
- name: "Install requirements"
run: ${PYBIN}/pip install -r requirements.txt
- name: "Generate bdk.py and binaries"
run: bash generate.sh
- name: "Build wheel"
# Specifying the plat-name argument is necessary to build a wheel with the correct name,
# see issue #350 for more information
run: ${PYBIN}/python setup.py bdist_wheel --plat-name manylinux_2_17_x86_64 --verbose
- name: "Install wheel"
run: ${PYBIN}/pip install ./dist/*.whl
- name: "Run tests"
run: ${PYBIN}/python -m unittest tests/test_bdk.py --verbose
- name: "Upload artifact test"
uses: actions/upload-artifact@v2
with:
name: bdkpython-manylinux2014-x86_64-${{ matrix.python }}
path: /home/runner/work/bdk-ffi/bdk-ffi/bdk-python/dist/*.whl
build-macos-universal-wheel:
name: "Build and test macOS wheels"
runs-on: macos-12
defaults:
run:
working-directory: bdk-python
strategy:
matrix:
python:
# 3.8 returns an error for the macos-12 image when we try to install the wheel:
# bdkpython-0.28.0.dev0-cp38-cp38-macosx_12_0_universal2.whl is not a supported wheel on this platform.
# - "3.8"
- "3.9"
- "3.10"
steps:
- name: Checkout
uses: actions/checkout@v2
with:
submodules: true
- uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python }}
- name: "Generate bdk.py and binaries"
run: |
python3 --version
rustup target add aarch64-apple-darwin
pip3 install --user -r requirements.txt
bash generate.sh
- name: "Build wheel"
env:
ARCHFLAGS: "-arch x86_64 -arch arm64"
run: python3 setup.py bdist_wheel --plat-name macosx_12_0_universal2 --verbose
- name: "Install wheel"
run: pip3 install ./dist/*.whl
- name: "Run tests"
run: python3 -m unittest tests/test_bdk.py --verbose
build-windows-wheel:
name: "Build and test Windows wheels"
runs-on: windows-2022
defaults:
run:
working-directory: bdk-python
strategy:
matrix:
python:
- "3.8"
- "3.9"
- "3.10"
steps:
- name: "Checkout"
uses: actions/checkout@v2
with:
submodules: true
- uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python }}
- name: "Generate bdk.py"
run: |
python --version
pip install --user -r requirements.txt
bash generate.sh
- name: "Build wheel"
run: python setup.py bdist_wheel --verbose
# TODO: On Windows the pip install ./dist/*.whl step fails with the following error:
# Run pip install ./dist/*.whl
# WARNING: Requirement './dist/*.whl' looks like a filename, but the file does not exist
# ERROR: *.whl is not a valid wheel filename.*.whl is not a valid wheel name
# So we skip the installing and the tests and simply test that the wheel builds
# - name: Install wheel
# run: pip install ./dist/*.whl
# - name: Run tests
# run: python -m unittest tests/test_bdk.py --verbose

View File

@@ -1,5 +1,6 @@
name: Test Swift
on:
workflow_dispatch:
push:
paths:
- "bdk-ffi/**"
@@ -13,9 +14,12 @@ jobs:
build:
runs-on: macos-12
steps:
- name: Checkout branch
- name: Checkout
uses: actions/checkout@v2
- name: "Set default Rust version to 1.67.0"
run: rustup default 1.67.0
- name: Install Rust targets
run: |
rustup install nightly-x86_64-apple-darwin
@@ -24,15 +28,15 @@ jobs:
- name: Run bdk-ffi-bindgen
working-directory: bdk-ffi
run: cargo run --package bdk-ffi-bindgen -- --language swift --out-dir ../bdk-swift/Sources/BitcoinDevKit
run: cargo run --bin uniffi-bindgen generate src/bdk.udl --language swift --out-dir ../bdk-swift/Sources/BitcoinDevKit --no-format
- name: Build bdk-ffi for x86_64-apple-darwin
run: cargo build --profile release-smaller --target x86_64-apple-darwin
run: cargo build --package bdk-ffi --profile release-smaller --target x86_64-apple-darwin
- name: Build bdk-ffi for aarch64-apple-darwin
run: cargo build --profile release-smaller --target aarch64-apple-darwin
run: cargo build --package bdk-ffi --profile release-smaller --target aarch64-apple-darwin
- name: Create lipo-ios-sim and lipo-macos
- name: Create lipo-macos
run: |
mkdir -p target/lipo-macos/release-smaller
lipo target/aarch64-apple-darwin/release-smaller/libbdkffi.a target/x86_64-apple-darwin/release-smaller/libbdkffi.a -create -output target/lipo-macos/release-smaller/libbdkffi.a

3
.gitignore vendored
View File

@@ -31,3 +31,6 @@ bdkFFI.h
BitcoinDevKit.swift
bdk.swift
.build
# Python related
__pycache__

View File

@@ -1,13 +1,111 @@
# Changelog
All notable changes to this project prior to release **0.9.0** are documented in this file. Future
changelog information can be found in each release's git tag and can be viewed with `git tag -ln100 "v*"`.
Changelog info is also documented on the [GitHub releases](https://github.com/bitcoindevkit/bdk-ffi/releases)
page. See [DEVELOPMENT_CYCLE.md](DEVELOPMENT_CYCLE.md) for more details.
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).
## [Unreleased]
## [v0.28.0]
- Update BDK to version 0.28.0 [#341]
- Drop support of pypy releases of Python libraries [#351]
- Drop support for Python 3.6 and 3.7 [#351]
- Drop support for very old Linux versions that do not support the manylinux_2_17_x86_64 platform tag [#351]
- APIs changed:
- Expose Address payload and network properties. [#325]
- Add SignOptions to Wallet.sign() params. [#326]
- address field on `AddressInfo` type is now of type `Address` [#333]
- new PartiallySignedTransaction.json_serialize() function to get JSON serialized value of all PSBT fields. [#334]
- Add from_script constructor to `Address` type [#337]
[#325]: https://github.com/bitcoindevkit/bdk-ffi/pull/325
[#326]: https://github.com/bitcoindevkit/bdk-ffi/pull/326
[#333]: https://github.com/bitcoindevkit/bdk-ffi/pull/333
[#334]: https://github.com/bitcoindevkit/bdk-ffi/pull/334
[#337]: https://github.com/bitcoindevkit/bdk-ffi/pull/337
[#341]: https://github.com/bitcoindevkit/bdk-ffi/pull/341
[#351]: https://github.com/bitcoindevkit/bdk-ffi/pull/351
## [v0.27.1]
- Update BDK to version 0.27.1 [#312]
- APIs changed
- `PartiallySignedTransaction.extract_tx()` returns a `Transaction` instead of the transaction bytes. [#296]
- `Blockchain.broadcast()` takes a `Transaction` instead of a `PartiallySignedTransaction`. [#296]
- APIs added
- New `Transaction` structure that can be created from or serialized to consensus encoded bytes. [#296]
- Add Wallet.get_internal_address() API [#304]
- Add `AddressIndex::Peek(index)` and `AddressIndex::Reset(index)` APIs [#305]
[#296]: https://github.com/bitcoindevkit/bdk-ffi/pull/296
[#304]: https://github.com/bitcoindevkit/bdk-ffi/pull/304
[#305]: https://github.com/bitcoindevkit/bdk-ffi/pull/305
[#312]: https://github.com/bitcoindevkit/bdk-ffi/pull/312
## [v0.26.0]
- Update BDK to version 0.26.0 [#288]
- APIs changed
- The descriptor and change_descriptor arguments on the wallet constructor now take a `Descriptor` instead of a `String`. [#260]
- TxBuilder.drain_to() argument is now `Script` instead of address `String`. [#279]
- APIs added
- Added RpcConfig, BlockchainConfig::Rpc, and Auth [#125]
- Added Descriptor type in [#260] with the following methods:
- Default constructor requires a descriptor in String format and a Network
- new_bip44 constructor returns a Descriptor with structure pkh(key/44'/{0,1}'/0'/{0,1}/*)
- new_bip44_public constructor returns a Descriptor with structure pkh(key/{0,1}/*)
- new_bip49 constructor returns a Descriptor with structure sh(wpkh(key/49'/{0,1}'/0'/{0,1}/*))
- new_bip49_public constructor returns a Descriptor with structure sh(wpkh(key/{0,1}/*))
- new_bip84 constructor returns a Descriptor with structure wpkh(key/84'/{0,1}'/0'/{0,1}/*)
- new_bip84_public constructor returns a Descriptor with structure wpkh(key/{0,1}/*)
- as_string returns the public version of the output descriptor
- as_string_private returns the private version of the output descriptor if available, otherwise return the public version
[#125]: https://github.com/bitcoindevkit/bdk-ffi/pull/125
[#260]: https://github.com/bitcoindevkit/bdk-ffi/pull/260
[#279]: https://github.com/bitcoindevkit/bdk-ffi/pull/279
[#288]: https://github.com/bitcoindevkit/bdk-ffi/pull/288
## [v0.25.0]
- Update BDK to version 0.25.0 [#272]
- APIs Added:
- from_string() constructors now available on DescriptorSecretKey and DescriptorPublicKey [#247]
[#247]: https://github.com/bitcoindevkit/bdk-ffi/pull/247
[#272]: https://github.com/bitcoindevkit/bdk-ffi/pull/272
## [v0.11.0]
- Update BDK to version 0.24.0 [#221]
- APIs changed
- The constructor on the DescriptorSecretKey type now takes a Mnemonic instead of a String.
- APIs added
- Added Mnemonic struct [#219] with following methods:
- new(word_count: WordCount) generates and returns Mnemonic with random entropy
- from_string(mnemonic: String) converts string Mnemonic to Mnemonic type with error
- from_entropy(entropy: Vec<u8>) generates and returns Mnemonic with given entropy
- as_string() view Mnemonic as string
- APIs removed
- generate_mnemonic(word_count: WordCount)
[#219]: https://github.com/bitcoindevkit/bdk-ffi/pull/219
[#221]: https://github.com/bitcoindevkit/bdk-ffi/pull/221
## [v0.10.0]
- Update BDK to version 0.23.0 [#204]
- Update uniffi-rs to latest version 0.21.0 [#216]
- Breaking Changes
- Changed `TxBuilder.finish()` to return new `TxBuilderResult` [#209]
- `TxBuilder.add_recipient()` now takes a `Script` instead of an `Address` [#192]
- `AddressAmount` is now `ScriptAmount` [#192]
- APIs Added
- Added `TxBuilderResult` with PSBT and TransactionDetails [#209]
- `Address` and `Script` structs have been added [#192]
- Add `PartiallySignedBitcoinTransaction.extract_tx()` function [#192]
- Add `secret_bytes()` method on the `DescriptorSecretKey` [#199]
- Add `PartiallySignedBitcoinTransaction.combine()` method [#200]
[#192]: https://github.com/bitcoindevkit/bdk-ffi/pull/192
[#199]: https://github.com/bitcoindevkit/bdk-ffi/pull/199
[#200]: https://github.com/bitcoindevkit/bdk-ffi/pull/200
[#204]: https://github.com/bitcoindevkit/bdk-ffi/pull/204
[#209]: https://github.com/bitcoindevkit/bdk-ffi/pull/209
[#216]: https://github.com/bitcoindevkit/bdk-ffi/pull/216
## [v0.9.0]
- Breaking Changes
@@ -22,7 +120,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- APIs Added [#154]
- `generate_mnemonic()`, returns string mnemonic
- `interface DescriptorSecretKey`
- `new(Network, string_mnenoinc, password)`, contructs DescriptorSecretKey
- `new(Network, string_mnenoinc, password)`, constructs DescriptorSecretKey
- `derive(DerivationPath)`, derives and returns child DescriptorSecretKey
- `extend(DerivationPath)`, extends and returns DescriptorSecretKey
- `as_public()`, returns DescriptorSecretKey as DescriptorPublicKey
@@ -102,9 +200,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
[BIP 0174]:https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki#encoding
## [v0.2.0]
[unreleased]: https://github.com/bitcoindevkit/bdk-ffi/compare/v0.9.0...HEAD
[v0.28.0]: https://github.com/bitcoindevkit/bdk-ffi/compare/v0.27.1...v0.28.0
[v0.27.1]: https://github.com/bitcoindevkit/bdk-ffi/compare/v0.26.0...v0.27.1
[v0.26.0]: https://github.com/bitcoindevkit/bdk-ffi/compare/v0.25.0...v0.26.0
[v0.25.0]: https://github.com/bitcoindevkit/bdk-ffi/compare/v0.11.0...v0.25.0
[v0.11.0]: https://github.com/bitcoindevkit/bdk-ffi/compare/v0.10.0...v0.11.0
[v0.10.0]: https://github.com/bitcoindevkit/bdk-ffi/compare/v0.9.0...v0.10.0
[v0.9.0]: https://github.com/bitcoindevkit/bdk-ffi/compare/v0.8.0...v0.9.0
[v0.8.0]: https://github.com/bitcoindevkit/bdk-ffi/compare/v0.7.0...v0.8.0
[v0.7.0]: https://github.com/bitcoindevkit/bdk-ffi/compare/v0.6.0...v0.7.0

799
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
[workspace]
members = ["bdk-ffi", "bdk-ffi-bindgen"]
default-members = ["bdk-ffi", "bdk-ffi-bindgen"]
members = ["bdk-ffi"]
default-members = ["bdk-ffi"]
exclude = ["api-docs", "bdk-android", "bdk-jvm", "bdk-python", "bdk-swift"]
[profile.release-smaller]
@@ -9,4 +9,4 @@ opt-level = 'z' # Optimize for size.
lto = true # Enable Link Time Optimization
codegen-units = 1 # Reduce number of codegen units to increase optimizations.
panic = 'abort' # Abort on panic
strip = true # Strip symbols from binary*
strip = true # Strip symbols from binary*

View File

@@ -8,8 +8,7 @@
</p>
The workspace in this repository creates the `libbdkffi` multi-language library for the Rust-based
[bdk] library from the [Bitcoin Dev Kit] project. The `bdk-ffi-bindgen` package builds a tool for
generating the actual language binding code used to access the `libbdkffi` library.
[bdk] library from the [Bitcoin Dev Kit] project.
Each supported language and the platform(s) it's packaged for has its own directory. The Rust code in this project is in the bdk-ffi directory and is a wrapper around the [bdk] library to expose its APIs in a uniform way using the [mozilla/uniffi-rs] bindings generator for each supported target language.
@@ -23,13 +22,6 @@ 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] | |
## Language bindings generator tool
Use the `bdk-ffi-bindgen` tool to generate language binding code for the above supported languages.
To run `bdk-ffi-bindgen` and see the available options use the command:
```shell
cargo run -p bdk-ffi-bindgen -- --help
```
## Contributing
### Adding new structs and functions

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

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

View File

@@ -24,32 +24,54 @@ enum class Network {
*
* @property index Child index of this address.
* @property address Address.
* @property keychain Type of keychain.
*
* @sample org.bitcoindevkit.addressInfoSample
*/
data class AddressInfo (
var index: UInt,
var address: String
var address: Address,
var keychain: KeychainKind
)
/**
* The address index selection strategy to use to derive an address from the wallets external descriptor.
*
* If youre unsure which one to use, use `AddressIndex.NEW`.
* If youre unsure which one to use, use `AddressIndex.New`.
*
* @sample org.bitcoindevkit.addressIndexSample
*/
enum class AddressIndex {
sealed class AddressIndex {
/** Return a new address after incrementing the current descriptor index. */
NEW,
object New: AddressIndex(),
/** Return the address for the current descriptor index if it has not been used in a received transaction.
/**
* Return the address for the current descriptor index if it has not been used in a received transaction.
* Otherwise return a new address as with `AddressIndex.NEW`. Use with caution, if the wallet
* has not yet detected an address has been used it could return an already used address.
* This function is primarily meant for situations where the caller is untrusted;
* for example when deriving donation addresses on-demand for a public web page.
*/
LAST_UNUSED,
object LastUnused: AddressIndex(),
/**
* 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.
*/
data class Peek(val index: UInt): AddressIndex(),
/**
* Return the address for a specific descriptor index and reset the current descriptor index
* used by [AddressIndex.New] and [AddressIndex.LastUsed] to this value.
* Use with caution, if an index is given that is less than the current descriptor index
* then the returned address and subsequent addresses returned by calls to [AddressIndex.New]
* and [AddressIndex.LastUsed] may have already been used. Also if the index is reset to a
* value earlier than the [Blockchain] stopGap (default is 20) then a
* larger stopGap should be used to monitor for all possibly used addresses.
*/
data class Reset(val index: UInt): AddressIndex()
}
/**
@@ -120,15 +142,17 @@ data class SledDbConfiguration(
* @property retry Request retry count.
* @property timeout Request timeout (seconds).
* @property stopGap Stop searching addresses for transactions after finding an unused gap of this length.
* @property validateDomain Validate the domain when using SSL.
*
* @sample org.bitcoindevkit.electrumBlockchainConfigSample
*/
data class ElectrumConfig (
data class ElectrumConfig(
var url: String,
var socks5: String?,
var retry: UByte,
var timeout: UByte?,
var stopGap: ULong
var stopGap: ULong,
var validateDomain: Boolean
)
/**
@@ -142,7 +166,7 @@ data class ElectrumConfig (
*
* @sample org.bitcoindevkit.esploraBlockchainConfigSample
*/
data class EsploraConfig (
data class EsploraConfig(
var baseUrl: String,
var proxy: String?,
var concurrency: UByte?,
@@ -150,6 +174,56 @@ data class EsploraConfig (
var timeout: ULong?
)
/**
* Authentication mechanism for RPC connection to full node.
*/
sealed class Auth {
/** No authentication */
object None: Auth()
/** Authentication with username and password, usually [Auth.Cookie] should be preferred */
data class UserPass(val username: String, val password: String): Auth()
/** Authentication with a cookie file */
data class Cookie(val file: String): Auth()
}
/**
* Sync parameters for Bitcoin Core RPC.
*
* In general, BDK tries to sync `scriptPubKey`s cached in `Database` with
* `scriptPubKey`s imported in the Bitcoin Core Wallet. These parameters are used for determining
* how the `importdescriptors` RPC calls are to be made.
*
* @property startScriptCount The minimum number of scripts to scan for on initial sync.
* @property startTime Time in unix seconds in which initial sync will start scanning from (0 to start from genesis).
* @property forceStartTime Forces every sync to use `start_time` as import timestamp.
* @property pollRateSec RPC poll rate (in seconds) to get state updates.
*/
data class RcpSyncParams(
val startScriptCount: ULong,
val startTime: Ulong,
val forceStartTime: Boolean,
val pollRateSec: ULong,
)
/**
* RpcBlockchain configuration options
*
* @property url The bitcoin node url.
* @property auth The bicoin node authentication mechanism.
* @property network The network we are using (it will be checked the bitcoin node network matches this).
* @property walletName The wallet name in the bitcoin node.
* @property syncParams Sync parameters.
*/
data class RpcConfig(
val url: String,
val auth: Auth,
val network: Network,
val walletName: String,
val syncParams: RcpSyncParams?,
)
/**
* Type that can contain any of the blockchain configurations defined by the library.
*
@@ -161,6 +235,9 @@ sealed class BlockchainConfig {
/** Esplora client. */
data class Esplora(val config: EsploraConfig) : BlockchainConfig()
/** Bitcoin Core RPC client. */
data class Rpc(val config: RpcConfig) : BlockchainConfig()
}
/**
@@ -173,6 +250,7 @@ sealed class BlockchainConfig {
* @property confirmationTime If the transaction is confirmed, [BlockTime] contains height and timestamp of the block containing the transaction. This property is null for unconfirmed transactions.
*/
data class TransactionDetails (
var transaction?: Transaction,
var fee: ULong?,
var received: ULong,
var sent: ULong,
@@ -193,7 +271,10 @@ class Blockchain(
config: BlockchainConfig
) {
/** Broadcast a transaction. */
fun broadcast(psbt: PartiallySignedBitcoinTransaction): String {}
fun broadcast(transaction: Transaction) {}
/** Estimate the fee rate required to confirm a transaction in a given target of blocks. */
fun estimateFee(target: ULong): FeeRate {}
/** Get the current height of the blockchain. */
fun getHeight(): UInt {}
@@ -202,6 +283,70 @@ class Blockchain(
fun getBlockHash(height: UInt): String {}
}
/**
* A bitcoin transaction.
*
* @constructor Build a new Bitcoin Transaction.
*
* @param transactionBytes The transaction bytes, bitcoin consensus encoded.
*/
class Transaction(transactionBytes: List<UByte>) {
/** Computes the txid. */
fun txid(): String {}
/**
* Returns the "weight" of this transaction, as defined by BIP141.
*
* For transactions with an empty witness, this is simply the consensus-serialized size times four.
* For transactions with a witness, this is the non-witness consensus-serialized size multiplied by three
* plus the with-witness consensus-serialized size.
*/
fun weight(): ULong {}
/** Returns the regular byte-wise consensus-serialized size of this transaction. */
fun size(): ULong {}
/**
* Returns the "virtual size" (vsize) of this transaction.
*
* Will be ceil(weight / 4.0). Note this implements the virtual size as per BIP141, which is different to
* what is implemented in Bitcoin Core. The computation should be the same for any remotely sane transaction.
*/
fun vsize(): ULong {}
/** Return the transaction bytes, bitcoin consensus encoded. */
fun serialize(): List<UByte> {}
/** Is this a coin base transaction? */
fun isCoinBase(): Boolean {}
/**
* Returns true if the transaction itself opted in to be BIP-125-replaceable (RBF).
* This does not cover the case where a transaction becomes replaceable due to ancestors being RBF.
*/
fun isExplicitlyRbf(): Boolean {}
/** Returns true if this transactions nLockTime is enabled (BIP-65). */
fun isLockTimeEnabled(): Boolean {}
/** The protocol version, is currently expected to be 1 or 2 (BIP 68). */
fun version(): Int {}
/**
* Block height or timestamp. Transaction cannot be included in a block until this height/time.
* Relevant BIPs
* BIP-65 OP_CHECKLOCKTIMEVERIFY
* BIP-113 Median time-past as endpoint for lock-time calculations
*/
fun lockTime(): UInt {}
/** List of transaction inputs. */
fun input(): List<TxIn> {}
/** List of transaction outputs. */
fun output(): List<TxOut> {}
}
/**
* A partially signed bitcoin transaction.
*
@@ -209,21 +354,24 @@ class Blockchain(
*
* @param psbtBase64 The PSBT in base64 format.
*/
class PartiallySignedBitcoinTransaction(psbtBase64: String) {
class PartiallySignedTransaction(psbtBase64: String) {
/** Return the PSBT in string format, using a base64 encoding. */
fun serialize(): String {}
/** Get the txid of the PSBT. */
fun txid(): String {}
/** Return the transaction as bytes. */
fun extractTx(): List<UByte>
/** Extract the transaction. */
fun extractTx(): Transaction {}
/**
* Combines this PartiallySignedTransaction with another PSBT as described by BIP 174.
* In accordance with BIP 174 this function is commutative i.e., `A.combine(B) == B.combine(A)`
*/
fun combine(other: PartiallySignedBitcoinTransaction): PartiallySignedBitcoinTransaction
fun combine(other: PartiallySignedTransaction): PartiallySignedTransaction
/** Serialize the PSBT data structure as a String of JSON. */
fun jsonSerialize(): String
}
/**
@@ -241,11 +389,29 @@ data class OutPoint (
* A transaction output, which defines new coins to be created from old ones.
*
* @property value The value of the output, in satoshis.
* @property address The address of the output.
* @property scriptPubkey The script which must be satisfied for the output to be spent.
*/
data class TxOut (
var value: ULong,
var address: String
var scriptPubkey: Script
)
/**
* Bitcoin transaction input.
*
* It contains the location of the previous transactions output, that it spends and set of scripts that satisfy its spending conditions.
*
* @property previousOutput The reference to the previous output that is being used an an input.
* @property scriptSig The script which pushes values on the stack which will cause the referenced outputs script to be accepted.
* @property sequence The sequence number, which suggests to miners which of two conflicting transactions should be preferred, or 0xFFFFFFFF to ignore this feature. This is generally never used since the miner behaviour cannot be enforced.
* @property witness Witness data: an array of byte-arrays. Note that this field is not (de)serialized with the rest of the TxIn in Encodable/Decodable, as it is (de)serialized at the end of the full Transaction. It is (de)serialized with the rest of the TxIn in other (de)serialization routines.
*
*/
data class TxIn (
var previousOutput: OutPoint,
var scriptSig: Script,
var sequence: UInt,
var witness: List<List<UByte>>
)
/**
@@ -295,15 +461,15 @@ data class BlockTime (
* @constructor Create a BDK wallet.
*
* @param descriptor The main (or "external") descriptor.
* @param changeDescriptor The change (or "internal") descriptor.
* @param changeDescriptor? The change (or "internal") descriptor.
* @param network The network to act on.
* @param databaseConfig The database configuration.
*
* @sample org.bitcoindevkit.walletSample
*/
class Wallet(
descriptor: String,
changeDescriptor: String,
descriptor: Descriptor,
changeDescriptor: Descriptor?,
network: Network,
databaseConfig: DatabaseConfig,
) {
@@ -314,14 +480,38 @@ class Wallet(
*/
fun getAddress(addressIndex: AddressIndex): AddressInfo {}
/** Return the balance, meaning the sum of this wallets unspent outputs values. Note that this method only operates on the internal database, which first needs to be [Wallet.sync] manually. */
fun getBalance(): ULong {}
/**
* 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].
*/
fun getInternalAddress(addressIndex: AddressIndex): AddressInfo {}
/** Sign a transaction with all the wallets signers. */
fun sign(psbt: PartiallySignedBitcoinTransaction): Boolean {}
/** Return whether or not a script is part of this wallet (either internal or external). */
fun isMine(script: Script): Boolean {}
/** Return the wallet's balance, across different categories. See [Balance] for the categories. Note that this method only operates on the internal database, which first needs to be [Wallet.sync] manually. */
fun getBalance(): Balance {}
/**
* Sign a transaction with all the wallet's signers, in the order specified by every signer's
* `SignerOrdering`.
*
* 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.
*
* @param psbt PSBT to be signed
* @param signOptions signing options
* @return true if the PSBT was finalized, or false otherwise
*/
fun sign(psbt: PartiallySignedTransaction, signOptions: SignOptions?): Boolean {}
/** Return the list of transactions made and received by the wallet. Note that this method only operate on the internal database, which first needs to be [Wallet.sync] manually. */
fun listTransactions(): List<TransactionDetails> {}
fun listTransactions(includeRaw: Boolean): List<TransactionDetails> {}
/** Get the Bitcoin network the wallet is using. */
fun network(): Network {}
@@ -410,7 +600,7 @@ class TxBuilder() {
* [drainWallet] to spend all of them. When bumping the fees of a transaction made with this option,
* you probably want to use [BumpFeeTxBuilder.allowShrinking] to allow this output to be reduced to pay for the extra fees.
*/
fun drainTo(address: String): TxBuilder {}
fun drainTo(script: Script): TxBuilder {}
/** Enable signaling RBF. This will use the default `nsequence` value of `0xFFFFFFFD`. */
fun enableRbf(): TxBuilder {}
@@ -426,6 +616,29 @@ class TxBuilder() {
fun finish(wallet: Wallet): TxBuilderResult {}
}
/**
* Options for a software signer.
*
* Adjust the behavior of our software signers and the way a transaction is finalized.
*
* @property trustWitnessUtxo Whether the signer should trust the `witness_utxo`, if the `non_witness_utxo` hasn't been provided. Defaults to `false`.
* @property assumeHeight Whether the wallet should assume a specific height has been reached when trying to finalize a transaction.
* @property allowAllSighashes Whether the signer should use the sighash_type set in the PSBT when signing, no matter what its value is. Defaults to `false`.
* @property removePartialSigs Whether to remove partial signatures from the PSBT inputs while finalizing PSBT. Defaults to `true`.
* @property tryFinalize Whether to try finalizing the PSBT after the inputs are signed. Defaults to `true`.
* @property signWithTapInternalKey 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`.
* @property allowGrinding Whether we should grind ECDSA signature to ensure signing with low r or not. Defaults to `true`.
*/
data class SignOptions (
var trustWitnessUtxo: Boolean,
var assumeHeight: UInt?,
var allowAllSighashes: Boolean,
var removePartialSigs: Boolean,
var tryFinalize: Boolean,
var signWithTapInternalKey: Boolean,
var allowGrinding: Boolean
)
/**
* A object holding a ScriptPubKey and an amount.
*
@@ -495,7 +708,7 @@ class DescriptorSecretKey(network: Network, mnemonic: Mnemonic, password: String
/** Return the public version of the descriptor. */
fun asPublic(): DescriptorPublicKey {}
/* Return the raw private key as bytes. */
/** Return the raw private key as bytes. */
fun secretBytes(): List<UByte>
/** Return the private descriptor as a string. */
@@ -523,6 +736,62 @@ class DescriptorPublicKey(network: Network, mnemonic: String, password: String?)
fun asString(): String
}
/**
* A output descriptor.
*
* @param descriptor The descriptor in string format.
* @param network The network this descriptor is to be used on.
*
* @sample org.bitcoindevkit.descriptorTemplates1
* @sample org.bitcoindevkit.descriptorTemplates2
*/
class Descriptor(descriptor: String, network: Network) {
/**
* BIP44 template. Expands to pkh(key/44'/{0,1}'/0'/{0,1}/\*)
* Since there are hardened derivation steps, this template requires a private derivable key (generally a xprv/tprv).
*/
fun newBip44(secretKey: DescriptorSecretKey, keychain: KeychainKind, network: Network) {}
/**
* BIP44 public template. Expands to pkh(key/{0,1}/\*)
* This assumes that the key used has already been derived with m/44'/0'/0' for Mainnet or m/44'/1'/0' for Testnet.
* This template requires the parent fingerprint to populate correctly the metadata of PSBTs.
*/
fun newBip44Public(publicKey: DescriptorPublicKey, fingerprint: String, keychain: KeychainKind, network: Network) {}
/**
* BIP49 template. Expands to sh(wpkh(key/49'/{0,1}'/0'/{0,1}/\*))
* Since there are hardened derivation steps, this template requires a private derivable key (generally a xprv/tprv).
*/
fun newBip49(secretKey: DescriptorSecretKey, keychain: KeychainKind, network: Network) {}
/**
* BIP49 public template. Expands to sh(wpkh(key/{0,1}/\*))
* This assumes that the key used has already been derived with m/49'/0'/0' for Mainnet or m/49'/1'/0' for Testnet.
* This template requires the parent fingerprint to populate correctly the metadata of PSBTs.
*/
fun newBip49Public(publicKey: DescriptorPublicKey, fingerprint: String, keychain: KeychainKind, network: Network) {}
/**
* BIP84 template. Expands to wpkh(key/84'/{0,1}'/0'/{0,1}/\*)
* Since there are hardened derivation steps, this template requires a private derivable key (generally a xprv/tprv).
*/
fun newBip84(secretKey: DescriptorSecretKey, keychain: KeychainKind, network: Network) {}
/**
* BIP84 public template. Expands to wpkh(key/{0,1}/\*)
* This assumes that the key used has already been derived with m/84'/0'/0' for Mainnet or m/84'/1'/0' for Testnet.
* This template requires the parent fingerprint to populate correctly the metadata of PSBTs.
*/
fun newBip84Public(publicKey: DescriptorPublicKey, fingerprint: String, keychain: KeychainKind, network: Network) {}
/** Return the public version of the output descriptor. */
fun asString(): String {}
/** Return the private version of the output descriptor if available, otherwise return the public version. */
fun asStringPrivate(): String {}
}
/**
* An enum describing entropy length (aka word count) in the mnemonic.
*/
@@ -553,14 +822,17 @@ enum class WordCount {
* @sample org.bitcoindevkit.txBuilderResultSample2
*/
data class TxBuilderResult (
var psbt: PartiallySignedBitcoinTransaction,
var psbt: PartiallySignedTransaction,
var transactionDetails: TransactionDetails
)
/**
* A bitcoin script.
*/
class Script(rawOutputScript: List<UByte>)
class Script(rawOutputScript: List<UByte>) {
/** Return the script as bytes. */
fun toBytes(): List<UByte> {}
}
/**
* A bitcoin address.
@@ -568,8 +840,62 @@ class Script(rawOutputScript: List<UByte>)
* @param address The address in string format.
*/
class Address(address: String) {
/* Return the ScriptPubKey. */
/** Construct an [`Address`] from an output script. */
fun fromScript(script: Script, network: Network): Address {}
/** Return the Payload */
fun payload(): Payload
/** Return the Network. */
fun network(): Network
/** Return the ScriptPubKey. */
fun scriptPubkey(): Script
/**
* Creates a URI string bitcoin:address optimized to be encoded in QR codes.
*
* If the address is bech32, both the schema and the address become uppercase. If the address is base58, the schema is lowercase and the address is left mixed case.
*
* Quoting BIP 173 "inside QR codes uppercase SHOULD be used, as those permit the use of alphanumeric mode, which is 45% more compact than the normal byte mode."
*/
fun toQrUri(): String
/** Return the address as a string. */
fun asString(): String
}}
/**
* The method used to produce an address.
*/
sealed class Payload {
/** P2PKH address. */
data class PubkeyHash(
val pubkeyHash: List<UByte>
) : Payload()
/** P2SH address. */
data class ScriptHash(
val scriptHash: List<UByte>
) : Payload()
/** Segwit address. */
data class WitnessProgram(
val version: WitnessVersion,
val program: List<UByte>
) : Payload()
}
/**
* Version of the witness program.
*
* Helps limit possible versions of the witness according to the specification. If a plain u8 type
* was used instead it would mean that the version may be > 16, which would be incorrect.
* First byte of scriptPubkey in transaction output for transactions starting with opcodes ranging
* from 0 to 16 (inclusive).
*/
enum class WitnessVersion {
V0, V1, V2, V3, V4, V5, V6, V7, V8, V9, V10, V11, V12, V13, V14, V15, V16
}
/**

View File

@@ -72,8 +72,8 @@ fun addressIndexSample() {
databaseConfig = DatabaseConfig.Memory
)
fun getLastUnusedAddress(): AddressInfo {
return wallet.getAddress(AddressIndex.LAST_UNUSED)
fun peekAddress100(): AddressInfo {
return wallet.getAddress(AddressIndex.Peek(100u))
}
}
@@ -85,13 +85,9 @@ fun addressInfoSample() {
databaseConfig = DatabaseConfig.Memory
)
fun getLastUnusedAddress(): AddressInfo {
return wallet.getAddress(AddressIndex.NEW)
}
val newAddress: AddressInfo = wallet.getAddress(AddressIndex.New)
val newAddress: AddressInfo = getLastUnusedAddress()
println("New address at index ${newAddress.index} is ${newAddress.address}")
println("New address at index ${newAddress.index} is ${newAddress.address.asString()}")
}
fun blockchainSample() {
@@ -107,7 +103,16 @@ fun blockchainSample() {
val blockchain: Blockchain = Blockchain(blockchainConfig)
blockchain.broadcast(signedPsbt)
val feeRate: FeeRate = blockchain.estimateFee(3u)
val (psbt, txDetails) = TxBuilder()
.addRecipient(faucetAddress.scriptPubkey(), 1000uL)
.feeRate(feeRate.asSatPerVb())
.finish(wallet)
wallet.sign(psbt)
blockchain.broadcast(psbt)
}
@@ -243,3 +248,34 @@ fun mnemonicSample() {
println(mnemonic0.asString(), mnemonic1.asString(), mnemonic2.asString())
}
fun descriptorTemplates1() {
// Bip84 private descriptor
val recoveryPhrase: String = "scene change clap smart together mind wheel knee clip normal trial unusual"
val mnemonic = Mnemonic.fromString(recoveryPhrase)
val bip32ExtendedRootKey: DescriptorSecretKey = DescriptorSecretKey(Network.TESTNET, mnemonic, null)
val bip84ExternalDescriptor: Descriptor = Descriptor.newBip84(bip32ExtendedRootKey, KeychainKind.EXTERNAL, Network.TESTNET)
}
fun descriptorTemplates2() {
// Bip49 public descriptor
// assume we already have the xpub for m/49'/0'/1' created on an external device that only shared the xpub with the wallet
// using the template requires the parent fingerprint to populate correctly the metadata of PSBTs, which the external device would provide
// the xpub (tpub for testnet): tpubDC65ZRvk1NDddHrVAUAZrUPJ772QXzooNYmPywYF9tMyNLYKf5wpKE7ZJvK9kvfG3FV7rCsHBNXy1LVKW95jrmC7c7z4hq7a27aD2sRrAhR
// the fingerprint: d1d04177
val descriptorPublicKey: DescriptorPublicKey = DescriptorPublicKey.fromString("tpubDC65ZRvk1NDddHrVAUAZrUPJ772QXzooNYmPywYF9tMyNLYKf5wpKE7ZJvK9kvfG3FV7rCsHBNXy1LVKW95jrmC7c7z4hq7a27aD2sRrAhR")
val bip49PublicDescriptor: Descriptor = Descriptor.newBip49Public(
publicKey = descriptorPublicKey,
fingerprint = "d1d04177",
keychain = KeychainKind.EXTERNAL,
network = Network.TESTNET,
)
println(bip49PublicDescriptor.asString()) // sh(wpkh([d1d04177/49'/1'/0']tpubDC65ZRvk1NDddHrVAUAZrUPJ772QXzooNYmPywYF9tMyNLYKf5wpKE7ZJvK9kvfG3FV7rCsHBNXy1LVKW95jrmC7c7z4hq7a27aD2sRrAhR/0/*))#a7lxzefl
println(bip49PublicDescriptor.asStringPrivate()) // sh(wpkh([d1d04177/49'/1'/0']tpubDC65ZRvk1NDddHrVAUAZrUPJ772QXzooNYmPywYF9tMyNLYKf5wpKE7ZJvK9kvfG3FV7rCsHBNXy1LVKW95jrmC7c7z4hq7a27aD2sRrAhR/0/*))#a7lxzefl
// Creating it starting from the full xprv derived from a mnemonic will give you the same public descriptor
val mnemonic = Mnemonic.fromString("chaos fabric time speed sponsor all flat solution wisdom trophy crack object robot pave observe combine where aware bench orient secret primary cable detect")
val bip32ExtendedRootKey: DescriptorSecretKey = DescriptorSecretKey(Network.TESTNET, mnemonic, null)
val bip49PrivateDescriptor: Descriptor = Descriptor.newBip49(bip32ExtendedRootKey, KeychainKind.EXTERNAL, Network.TESTNET)
println(bip49PrivateDescriptor.asString()) // sh(wpkh([d1d04177/49'/1'/0']tpubDC65ZRvk1NDddHrVAUAZrUPJ772QXzooNYmPywYF9tMyNLYKf5wpKE7ZJvK9kvfG3FV7rCsHBNXy1LVKW95jrmC7c7z4hq7a27aD2sRrAhR/0/*))#a7lxzefl
}

View File

@@ -19,16 +19,16 @@ import org.bitcoindevkit.*
// ...
val externalDescriptor = "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)"
val internalDescriptor = "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)"
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)
ElectrumConfig("ssl://electrum.blockstream.info:60002", null, 5u, null, 10u, true)
)
val wallet = Wallet(externalDescriptor, internalDescriptor, Network.TESTNET, databaseConfig, blockchainConfig)
val newAddress = wallet.getAddress(AddressIndex.LAST_UNUSED)
val newAddress = wallet.getAddress(AddressIndex.LastUnused)
```
### Snapshot releases
@@ -54,19 +54,23 @@ _Note that Kotlin version `1.6.10` or later is required to build the library._
```shell
git clone https://github.com/bitcoindevkit/bdk-ffi
```
2. Follow the "General" bdk-ffi ["Getting Started (Developer)"] instructions.
3. If building on macOS install required intel and m1 jvm targets
2. Follow the "General" bdk-ffi ["Getting Started (Developer)"] instructions.
3. Install Rust (note that we are currently building using Rust 1.67.0):
```shell
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
rustup default 1.67.0
```
4. Install required targets
```sh
rustup target add x86_64-linux-android aarch64-linux-android armv7-linux-androideabi
```
```sh
rustup target add x86_64-linux-android aarch64-linux-android armv7-linux-androideabi
```
5. Install Android SDK and Build-Tools for API level 30+
6. Setup `$ANDROID_SDK_ROOT` and `$ANDROID_NDK_ROOT` path variables (which are required by the
build tool), for example (NDK major version 21 is required):
```shell
export ANDROID_SDK_ROOT=~/Android/Sdk
export ANDROID_NDK_ROOT=$ANDROID_SDK_ROOT/ndk/21.<NDK_VERSION>
```
build tool), for example (note that currently, NDK version 21.4.7075529 is required):
```shell
export ANDROID_SDK_ROOT=~/Android/Sdk
export ANDROID_NDK_ROOT=$ANDROID_SDK_ROOT/ndk/21.4.7075529
```
7. Build kotlin bindings
```sh
# build Android library
@@ -95,5 +99,22 @@ and use the `publishToMavenLocal` task without excluding the signing task:
./gradlew publishToMavenLocal
```
## Known issues
### JNA dependency
Depending on the JVM version you use, you might not have the JNA dependency on your classpath. The exception thrown will be
```shell
class file for com.sun.jna.Pointer not found
```
The solution is to add JNA as a dependency like so:
```kotlin
dependencies {
// ...
implementation("net.java.dev.jna:jna:5.12.1")
}
```
### x86 emulators
For some older versions of macOS, Android Studio will recommend users install the x86 version of the emulator by default. This will not work with the bdk-android library, as we do not support 32-bit architectures. Make sure you install an x86_64 emulator to work with bdk-android.
[`bdk`]: https://github.com/bitcoindevkit/bdk
[`bdk-ffi`]: https://github.com/bitcoindevkit/bdk-ffi

View File

@@ -2,4 +2,4 @@ org.gradle.jvmargs=-Xmx1536m
android.useAndroidX=true
android.enableJetifier=true
kotlin.code.style=official
libraryVersion=0.25.0
libraryVersion=0.29.0

View File

@@ -1,3 +1,5 @@
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
// library version is defined in gradle.properties
val libraryVersion: String by project
@@ -107,3 +109,9 @@ signing {
useInMemoryPgpKeys(signingKeyId, signingKey, signingPassword)
sign(publishing.publications)
}
// This task dependency ensures that we build the bindings
// binaries before running the tests
tasks.withType<KotlinCompile> {
dependsOn("buildAndroidLib")
}

View File

@@ -36,8 +36,7 @@ class AndroidLibTest {
}
}
private val descriptor =
"wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)"
private val descriptor = Descriptor("wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)", Network.TESTNET)
private val databaseConfig = DatabaseConfig.Memory
@@ -47,14 +46,15 @@ class AndroidLibTest {
null,
5u,
null,
100u
100u,
true,
)
)
@Test
fun memoryWalletNewAddress() {
val wallet = Wallet(descriptor, null, Network.TESTNET, databaseConfig)
val address = wallet.getAddress(AddressIndex.NEW).address
val address = wallet.getAddress(AddressIndex.New).address.asString()
assertEquals("tb1qzg4mckdh50nwdm9hkzq06528rsu73hjxxzem3e", address)
}

View File

@@ -21,8 +21,7 @@ internal class UniFfiAndroidPlugin : Plugin<Project> {
val buildAndroidAarch64Binary by tasks.register<Exec>("buildAndroidAarch64Binary") {
workingDir("${projectDir}/../../bdk-ffi")
val cargoArgs: MutableList<String> =
mutableListOf("build", "--profile", "release-smaller", "--target", "aarch64-linux-android")
val cargoArgs: List<String> = listOf("build", "--profile", "release-smaller", "--target", "aarch64-linux-android")
executable("cargo")
args(cargoArgs)
@@ -36,10 +35,9 @@ internal class UniFfiAndroidPlugin : Plugin<Project> {
environment(
// add build toolchain to PATH
Pair("PATH",
"${System.getenv("PATH")}:${System.getenv("ANDROID_NDK_ROOT")}/toolchains/llvm/prebuilt/$llvmArchPath/bin"),
Pair("CFLAGS", "-D__ANDROID_API__=21"),
Pair("PATH", "${System.getenv("PATH")}:${System.getenv("ANDROID_NDK_ROOT")}/toolchains/llvm/prebuilt/$llvmArchPath/bin"),
Pair("CFLAGS", "-D__ANDROID_MIN_SDK_VERSION__=21"),
Pair("AR", "llvm-ar"),
Pair("CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER", "aarch64-linux-android21-clang"),
Pair("CC", "aarch64-linux-android21-clang")
)
@@ -53,8 +51,7 @@ internal class UniFfiAndroidPlugin : Plugin<Project> {
val buildAndroidX86_64Binary by tasks.register<Exec>("buildAndroidX86_64Binary") {
workingDir("${project.projectDir}/../../bdk-ffi")
val cargoArgs: MutableList<String> =
mutableListOf("build", "--profile", "release-smaller", "--target", "x86_64-linux-android")
val cargoArgs: List<String> = listOf("build", "--profile", "release-smaller", "--target", "x86_64-linux-android")
executable("cargo")
args(cargoArgs)
@@ -68,10 +65,9 @@ internal class UniFfiAndroidPlugin : Plugin<Project> {
environment(
// add build toolchain to PATH
Pair("PATH",
"${System.getenv("PATH")}:${System.getenv("ANDROID_NDK_ROOT")}/toolchains/llvm/prebuilt/$llvmArchPath/bin"),
Pair("CFLAGS", "-D__ANDROID_API__=21"),
Pair("PATH", "${System.getenv("PATH")}:${System.getenv("ANDROID_NDK_ROOT")}/toolchains/llvm/prebuilt/$llvmArchPath/bin"),
Pair("CFLAGS", "-D__ANDROID_MIN_SDK_VERSION__=21"),
Pair("AR", "llvm-ar"),
Pair("CARGO_TARGET_X86_64_LINUX_ANDROID_LINKER", "x86_64-linux-android21-clang"),
Pair("CC", "x86_64-linux-android21-clang")
)
@@ -85,8 +81,7 @@ internal class UniFfiAndroidPlugin : Plugin<Project> {
val buildAndroidArmv7Binary by tasks.register<Exec>("buildAndroidArmv7Binary") {
workingDir("${project.projectDir}/../../bdk-ffi")
val cargoArgs: MutableList<String> =
mutableListOf("build", "--profile", "release-smaller", "--target", "armv7-linux-androideabi")
val cargoArgs: List<String> = listOf("build", "--profile", "release-smaller", "--target", "armv7-linux-androideabi")
executable("cargo")
args(cargoArgs)
@@ -100,12 +95,10 @@ internal class UniFfiAndroidPlugin : Plugin<Project> {
environment(
// add build toolchain to PATH
Pair("PATH",
"${System.getenv("PATH")}:${System.getenv("ANDROID_NDK_ROOT")}/toolchains/llvm/prebuilt/$llvmArchPath/bin"),
Pair("CFLAGS", "-D__ANDROID_API__=21"),
Pair("CARGO_TARGET_ARMV7_LINUX_ANDROIDEABI_LINKER",
"armv7a-linux-androideabi21-clang"),
Pair("PATH", "${System.getenv("PATH")}:${System.getenv("ANDROID_NDK_ROOT")}/toolchains/llvm/prebuilt/$llvmArchPath/bin"),
Pair("CFLAGS", "-D__ANDROID_MIN_SDK_VERSION__=21"),
Pair("AR", "llvm-ar"),
Pair("CARGO_TARGET_ARMV7_LINUX_ANDROIDEABI_LINKER", "armv7a-linux-androideabi21-clang"),
Pair("CC", "armv7a-linux-androideabi21-clang")
)
@@ -145,17 +138,10 @@ internal class UniFfiAndroidPlugin : Plugin<Project> {
dependsOn(moveNativeAndroidLibs)
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")
executable("cargo")
args(
"run",
"--package",
"bdk-ffi-bindgen",
"--",
"--language",
"kotlin",
"--out-dir",
"../bdk-android/lib/src/main/kotlin"
)
args(cargoArgs)
doLast {
println("Android bindings file successfully created")

View File

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

View File

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

View File

@@ -1,19 +1,28 @@
[package]
name = "bdk-ffi"
version = "0.25.0"
version = "0.29.0"
authors = ["Steve Myers <steve@notmandatory.org>", "Sudarsan Balaji <sudarsan.balaji@artfuldev.com>"]
edition = "2018"
license = "MIT OR Apache-2.0"
[lib]
crate-type = ["staticlib", "cdylib"]
crate-type = ["lib", "staticlib", "cdylib"]
name = "bdkffi"
[dependencies]
bdk = { version = "0.25", features = ["all-keys", "use-esplora-ureq", "sqlite-bundled"] }
[[bin]]
name = "uniffi-bindgen"
path = "uniffi-bindgen.rs"
uniffi_macros = { version = "0.21.0", features = ["builtin-bindgen"] }
uniffi = { version = "0.21.0", features = ["builtin-bindgen"] }
[features]
default = ["uniffi/cli"]
[dependencies]
bdk = { version = "0.28", features = ["all-keys", "use-esplora-ureq", "sqlite-bundled", "rpc"] }
uniffi = { version = "0.23.0" }
[build-dependencies]
uniffi_build = { version = "0.21.0", features = ["builtin-bindgen"] }
uniffi = { version = "0.23.0", features = ["build"] }
[dev-dependencies]
uniffi = { version = "0.23.0", features = ["bindgen-tests"] }
assert_matches = "1.5.0"

View File

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

View File

@@ -45,16 +45,21 @@ enum BdkError {
"Esplora",
"Sled",
"Rusqlite",
"Rpc",
};
dictionary AddressInfo {
u32 index;
string address;
Address address;
KeychainKind keychain;
};
enum AddressIndex {
"New",
"LastUnused",
[Enum]
interface AddressIndex {
New();
LastUnused();
Peek(u32 index);
Reset(u32 index);
};
enum Network {
@@ -90,6 +95,7 @@ interface DatabaseConfig {
};
dictionary TransactionDetails {
Transaction? transaction;
u64? fee;
u64 received;
u64 sent;
@@ -116,6 +122,7 @@ dictionary ElectrumConfig {
u8 retry;
u8? timeout;
u64 stop_gap;
boolean validate_domain;
};
dictionary EsploraConfig {
@@ -126,10 +133,33 @@ dictionary EsploraConfig {
u64? timeout;
};
[Enum]
interface Auth {
None();
UserPass(string username, string password);
Cookie(string file);
};
dictionary RpcSyncParams {
u64 start_script_count;
u64 start_time;
boolean force_start_time;
u64 poll_rate_sec;
};
dictionary RpcConfig {
string url;
Auth auth;
Network network;
string wallet_name;
RpcSyncParams? sync_params;
};
[Enum]
interface BlockchainConfig {
Electrum(ElectrumConfig config);
Esplora(EsploraConfig config);
Rpc(RpcConfig config);
};
interface Blockchain {
@@ -137,7 +167,10 @@ interface Blockchain {
constructor(BlockchainConfig config);
[Throws=BdkError]
void broadcast([ByRef] PartiallySignedTransaction psbt);
void broadcast([ByRef] Transaction transaction);
[Throws=BdkError]
FeeRate estimate_fee(u64 target);
[Throws=BdkError]
u32 get_height();
@@ -155,9 +188,16 @@ dictionary OutPoint {
u32 vout;
};
dictionary TxIn {
OutPoint previous_output;
Script script_sig;
u32 sequence;
sequence<sequence<u8>> witness;
};
dictionary TxOut {
u64 value;
string address;
Script script_pubkey;
};
enum KeychainKind {
@@ -179,27 +219,33 @@ dictionary ScriptAmount {
interface Wallet {
[Throws=BdkError]
constructor(string descriptor, string? change_descriptor, Network network, DatabaseConfig database_config);
constructor(Descriptor descriptor, Descriptor? change_descriptor, Network network, DatabaseConfig database_config);
Network network();
[Throws=BdkError]
AddressInfo get_address(AddressIndex address_index);
[Throws=BdkError]
Balance get_balance();
AddressInfo get_internal_address(AddressIndex address_index);
[Throws=BdkError]
boolean sign([ByRef] PartiallySignedTransaction psbt);
[Throws=BdkError]
sequence<TransactionDetails> list_transactions();
Network network();
[Throws=BdkError]
void sync([ByRef] Blockchain blockchain, Progress? progress);
boolean is_mine(Script script);
[Throws=BdkError]
sequence<LocalUtxo> list_unspent();
[Throws=BdkError]
sequence<TransactionDetails> list_transactions(boolean include_raw);
[Throws=BdkError]
Balance get_balance();
[Throws=BdkError]
boolean sign([ByRef] PartiallySignedTransaction psbt, SignOptions? sign_options);
[Throws=BdkError]
void sync([ByRef] Blockchain blockchain, Progress? progress);
};
interface FeeRate {
@@ -209,6 +255,45 @@ interface FeeRate {
float as_sat_per_vb();
};
dictionary SignOptions {
boolean trust_witness_utxo;
u32? assume_height;
boolean allow_all_sighashes;
boolean remove_partial_sigs;
boolean try_finalize;
boolean sign_with_tap_internal_key;
boolean allow_grinding;
};
interface Transaction {
[Throws=BdkError]
constructor(sequence<u8> transaction_bytes);
string txid();
u64 weight();
u64 size();
u64 vsize();
sequence<u8> serialize();
boolean is_coin_base();
boolean is_explicitly_rbf();
boolean is_lock_time_enabled();
i32 version();
u32 lock_time();
sequence<TxIn> input();
sequence<TxOut> output();
};
interface PartiallySignedTransaction {
[Throws=BdkError]
constructor(string psbt_base64);
@@ -217,7 +302,7 @@ interface PartiallySignedTransaction {
string txid();
sequence<u8> extract_tx();
Transaction extract_tx();
[Throws=BdkError]
PartiallySignedTransaction combine(PartiallySignedTransaction other);
@@ -225,6 +310,8 @@ interface PartiallySignedTransaction {
u64? fee_amount();
FeeRate? fee_rate();
string json_serialize();
};
dictionary TxBuilderResult {
@@ -257,7 +344,7 @@ interface TxBuilder {
TxBuilder drain_wallet();
TxBuilder drain_to(string address);
TxBuilder drain_to(Script script);
TxBuilder enable_rbf();
@@ -333,13 +420,82 @@ interface DescriptorPublicKey {
string as_string();
};
interface Descriptor {
[Throws=BdkError]
constructor(string descriptor, Network network);
[Name=new_bip44]
constructor(DescriptorSecretKey secret_key, KeychainKind keychain, Network network);
[Name=new_bip44_public]
constructor(DescriptorPublicKey public_key, string fingerprint, KeychainKind keychain, Network network);
[Name=new_bip49]
constructor(DescriptorSecretKey secret_key, KeychainKind keychain, Network network);
[Name=new_bip49_public]
constructor(DescriptorPublicKey public_key, string fingerprint, KeychainKind keychain, Network network);
[Name=new_bip84]
constructor(DescriptorSecretKey secret_key, KeychainKind keychain, Network network);
[Name=new_bip84_public]
constructor(DescriptorPublicKey public_key, string fingerprint, KeychainKind keychain, Network network);
string as_string();
string as_string_private();
};
interface Address {
[Throws=BdkError]
constructor(string address);
[Name=from_script, Throws=BdkError]
constructor(Script script, Network network);
Payload payload();
Network network();
Script script_pubkey();
string to_qr_uri();
string as_string();
};
[Enum]
interface Payload {
PubkeyHash(sequence<u8> pubkey_hash);
ScriptHash(sequence<u8> script_hash);
WitnessProgram(WitnessVersion version, sequence<u8> program);
};
enum WitnessVersion {
"V0",
"V1",
"V2",
"V3",
"V4",
"V5",
"V6",
"V7",
"V8",
"V9",
"V10",
"V11",
"V12",
"V13",
"V14",
"V15",
"V16"
};
interface Script {
constructor(sequence<u8> raw_output_script);
sequence<u8> to_bytes();
};

201
bdk-ffi/src/blockchain.rs Normal file
View File

@@ -0,0 +1,201 @@
// use crate::BlockchainConfig;
use crate::{BdkError, Transaction};
use bdk::bitcoin::Network;
use bdk::blockchain::any::{AnyBlockchain, AnyBlockchainConfig};
use bdk::blockchain::rpc::Auth as BdkAuth;
use bdk::blockchain::rpc::RpcSyncParams as BdkRpcSyncParams;
use bdk::blockchain::Blockchain as BdkBlockchain;
use bdk::blockchain::GetBlockHash;
use bdk::blockchain::GetHeight;
use bdk::blockchain::{
electrum::ElectrumBlockchainConfig, esplora::EsploraBlockchainConfig,
rpc::RpcConfig as BdkRpcConfig, ConfigurableBlockchain,
};
use bdk::FeeRate;
use std::convert::{From, TryFrom};
use std::path::PathBuf;
use std::sync::{Arc, Mutex, MutexGuard};
pub(crate) struct Blockchain {
blockchain_mutex: Mutex<AnyBlockchain>,
}
impl Blockchain {
pub(crate) fn new(blockchain_config: BlockchainConfig) -> Result<Self, BdkError> {
let any_blockchain_config = match blockchain_config {
BlockchainConfig::Electrum { config } => {
AnyBlockchainConfig::Electrum(ElectrumBlockchainConfig {
retry: config.retry,
socks5: config.socks5,
timeout: config.timeout,
url: config.url,
stop_gap: usize::try_from(config.stop_gap).unwrap(),
validate_domain: config.validate_domain,
})
}
BlockchainConfig::Esplora { config } => {
AnyBlockchainConfig::Esplora(EsploraBlockchainConfig {
base_url: config.base_url,
proxy: config.proxy,
concurrency: config.concurrency,
stop_gap: usize::try_from(config.stop_gap).unwrap(),
timeout: config.timeout,
})
}
BlockchainConfig::Rpc { config } => AnyBlockchainConfig::Rpc(BdkRpcConfig {
url: config.url,
auth: config.auth.into(),
network: config.network,
wallet_name: config.wallet_name,
sync_params: config.sync_params.map(|p| p.into()),
}),
};
let blockchain = AnyBlockchain::from_config(&any_blockchain_config)?;
Ok(Self {
blockchain_mutex: Mutex::new(blockchain),
})
}
pub(crate) fn get_blockchain(&self) -> MutexGuard<AnyBlockchain> {
self.blockchain_mutex.lock().expect("blockchain")
}
pub(crate) fn broadcast(&self, transaction: &Transaction) -> Result<(), BdkError> {
let tx = &transaction.internal;
self.get_blockchain().broadcast(tx)
}
pub(crate) fn estimate_fee(&self, target: u64) -> Result<Arc<FeeRate>, BdkError> {
let result: Result<FeeRate, bdk::Error> =
self.get_blockchain().estimate_fee(target as usize);
result.map(Arc::new)
}
pub(crate) fn get_height(&self) -> Result<u32, BdkError> {
self.get_blockchain().get_height()
}
pub(crate) fn get_block_hash(&self, height: u32) -> Result<String, BdkError> {
self.get_blockchain()
.get_block_hash(u64::from(height))
.map(|hash| hash.to_string())
}
}
/// Configuration for an ElectrumBlockchain
pub struct ElectrumConfig {
/// URL of the Electrum server (such as ElectrumX, Esplora, BWT) may start with ssl:// or tcp:// and include a port
/// e.g. ssl://electrum.blockstream.info:60002
pub url: String,
/// URL of the socks5 proxy server or a Tor service
pub socks5: Option<String>,
/// Request retry count
pub retry: u8,
/// Request timeout (seconds)
pub timeout: Option<u8>,
/// Stop searching addresses for transactions after finding an unused gap of this length
pub stop_gap: u64,
/// Validate the domain when using SSL
pub validate_domain: bool,
}
/// Configuration for an EsploraBlockchain
pub struct EsploraConfig {
/// Base URL of the esplora service
/// e.g. https://blockstream.info/api/
pub base_url: String,
/// Optional URL of the proxy to use to make requests to the Esplora server
/// The string should be formatted as: <protocol>://<user>:<password>@host:<port>.
/// Note that the format of this value and the supported protocols change slightly between the
/// sync version of esplora (using ureq) and the async version (using reqwest). For more
/// details check with the documentation of the two crates. Both of them are compiled with
/// the socks feature enabled.
/// The proxy is ignored when targeting wasm32.
pub proxy: Option<String>,
/// Number of parallel requests sent to the esplora service (default: 4)
pub concurrency: Option<u8>,
/// Stop searching addresses for transactions after finding an unused gap of this length.
pub stop_gap: u64,
/// Socket timeout.
pub timeout: Option<u64>,
}
pub enum Auth {
/// No authentication
None,
/// Authentication with username and password, usually [Auth::Cookie] should be preferred
UserPass {
/// Username
username: String,
/// Password
password: String,
},
/// Authentication with a cookie file
Cookie {
/// Cookie file
file: String,
},
}
impl From<Auth> for BdkAuth {
fn from(auth: Auth) -> Self {
match auth {
Auth::None => BdkAuth::None,
Auth::UserPass { username, password } => BdkAuth::UserPass { username, password },
Auth::Cookie { file } => BdkAuth::Cookie {
file: PathBuf::from(file),
},
}
}
}
/// Sync parameters for Bitcoin Core RPC.
///
/// In general, BDK tries to sync `scriptPubKey`s cached in `Database` with
/// `scriptPubKey`s imported in the Bitcoin Core Wallet. These parameters are used for determining
/// how the `importdescriptors` RPC calls are to be made.
pub struct RpcSyncParams {
/// The minimum number of scripts to scan for on initial sync.
pub start_script_count: u64,
/// Time in unix seconds in which initial sync will start scanning from (0 to start from genesis).
pub start_time: u64,
/// Forces every sync to use `start_time` as import timestamp.
pub force_start_time: bool,
/// RPC poll rate (in seconds) to get state updates.
pub poll_rate_sec: u64,
}
impl From<RpcSyncParams> for BdkRpcSyncParams {
fn from(params: RpcSyncParams) -> Self {
BdkRpcSyncParams {
start_script_count: params.start_script_count as usize,
start_time: params.start_time,
force_start_time: params.force_start_time,
poll_rate_sec: params.poll_rate_sec,
}
}
}
/// RpcBlockchain configuration options
pub struct RpcConfig {
/// The bitcoin node url
pub url: String,
/// The bitcoin node authentication mechanism
pub auth: Auth,
/// The network we are using (it will be checked the bitcoin node network matches this)
pub network: Network,
/// The wallet name in the bitcoin node, consider using [crate::wallet::wallet_name_from_descriptor] for this
pub wallet_name: String,
/// Sync parameters
pub sync_params: Option<RpcSyncParams>,
}
/// Type that can contain any of the blockchain configurations defined by the library.
pub enum BlockchainConfig {
/// Electrum client
Electrum { config: ElectrumConfig },
/// Esplora client
Esplora { config: EsploraConfig },
/// Bitcoin Core RPC client
Rpc { config: RpcConfig },
}

14
bdk-ffi/src/database.rs Normal file
View File

@@ -0,0 +1,14 @@
use bdk::database::any::{SledDbConfiguration, SqliteDbConfiguration};
/// Type that can contain any of the database configurations defined by the library
/// This allows storing a single configuration that can be loaded into an AnyDatabaseConfig
/// instance. Wallets that plan to offer users the ability to switch blockchain backend at runtime
/// will find this particularly useful.
pub enum DatabaseConfig {
/// Memory database has no config
Memory,
/// Simple key-value embedded database based on sled
Sled { config: SledDbConfiguration },
/// Sqlite embedded database using rusqlite
Sqlite { config: SqliteDbConfiguration },
}

334
bdk-ffi/src/descriptor.rs Normal file
View File

@@ -0,0 +1,334 @@
use crate::{BdkError, DescriptorPublicKey, DescriptorSecretKey};
use bdk::bitcoin::secp256k1::Secp256k1;
use bdk::bitcoin::util::bip32::Fingerprint;
use bdk::bitcoin::Network;
use bdk::descriptor::{ExtendedDescriptor, IntoWalletDescriptor, KeyMap};
use bdk::keys::{
DescriptorPublicKey as BdkDescriptorPublicKey, DescriptorSecretKey as BdkDescriptorSecretKey,
};
use bdk::template::{
Bip44, Bip44Public, Bip49, Bip49Public, Bip84, Bip84Public, DescriptorTemplate,
};
use bdk::KeychainKind;
use std::ops::Deref;
use std::str::FromStr;
use std::sync::Arc;
#[derive(Debug)]
pub(crate) struct Descriptor {
pub(crate) extended_descriptor: ExtendedDescriptor,
pub(crate) key_map: KeyMap,
}
impl Descriptor {
pub(crate) fn new(descriptor: String, network: Network) -> Result<Self, BdkError> {
let secp = Secp256k1::new();
let (extended_descriptor, key_map) = descriptor.into_wallet_descriptor(&secp, network)?;
Ok(Self {
extended_descriptor,
key_map,
})
}
pub(crate) fn new_bip44(
secret_key: Arc<DescriptorSecretKey>,
keychain_kind: KeychainKind,
network: Network,
) -> Self {
let derivable_key = secret_key.descriptor_secret_key_mutex.lock().unwrap();
match derivable_key.deref() {
BdkDescriptorSecretKey::XPrv(descriptor_x_key) => {
let derivable_key = descriptor_x_key.xkey;
let (extended_descriptor, key_map, _) =
Bip44(derivable_key, keychain_kind).build(network).unwrap();
Self {
extended_descriptor,
key_map,
}
}
BdkDescriptorSecretKey::Single(_) => {
unreachable!()
}
}
}
pub(crate) fn new_bip44_public(
public_key: Arc<DescriptorPublicKey>,
fingerprint: String,
keychain_kind: KeychainKind,
network: Network,
) -> Self {
let fingerprint = Fingerprint::from_str(fingerprint.as_str()).unwrap();
let derivable_key = public_key.descriptor_public_key_mutex.lock().unwrap();
match derivable_key.deref() {
BdkDescriptorPublicKey::XPub(descriptor_x_key) => {
let derivable_key = descriptor_x_key.xkey;
let (extended_descriptor, key_map, _) =
Bip44Public(derivable_key, fingerprint, keychain_kind)
.build(network)
.unwrap();
Self {
extended_descriptor,
key_map,
}
}
BdkDescriptorPublicKey::Single(_) => {
unreachable!()
}
}
}
pub(crate) fn new_bip49(
secret_key: Arc<DescriptorSecretKey>,
keychain_kind: KeychainKind,
network: Network,
) -> Self {
let derivable_key = secret_key.descriptor_secret_key_mutex.lock().unwrap();
match derivable_key.deref() {
BdkDescriptorSecretKey::XPrv(descriptor_x_key) => {
let derivable_key = descriptor_x_key.xkey;
let (extended_descriptor, key_map, _) =
Bip49(derivable_key, keychain_kind).build(network).unwrap();
Self {
extended_descriptor,
key_map,
}
}
BdkDescriptorSecretKey::Single(_) => {
unreachable!()
}
}
}
pub(crate) fn new_bip49_public(
public_key: Arc<DescriptorPublicKey>,
fingerprint: String,
keychain_kind: KeychainKind,
network: Network,
) -> Self {
let fingerprint = Fingerprint::from_str(fingerprint.as_str()).unwrap();
let derivable_key = public_key.descriptor_public_key_mutex.lock().unwrap();
match derivable_key.deref() {
BdkDescriptorPublicKey::XPub(descriptor_x_key) => {
let derivable_key = descriptor_x_key.xkey;
let (extended_descriptor, key_map, _) =
Bip49Public(derivable_key, fingerprint, keychain_kind)
.build(network)
.unwrap();
Self {
extended_descriptor,
key_map,
}
}
BdkDescriptorPublicKey::Single(_) => {
unreachable!()
}
}
}
pub(crate) fn new_bip84(
secret_key: Arc<DescriptorSecretKey>,
keychain_kind: KeychainKind,
network: Network,
) -> Self {
let derivable_key = secret_key.descriptor_secret_key_mutex.lock().unwrap();
match derivable_key.deref() {
BdkDescriptorSecretKey::XPrv(descriptor_x_key) => {
let derivable_key = descriptor_x_key.xkey;
let (extended_descriptor, key_map, _) =
Bip84(derivable_key, keychain_kind).build(network).unwrap();
Self {
extended_descriptor,
key_map,
}
}
BdkDescriptorSecretKey::Single(_) => {
unreachable!()
}
}
}
pub(crate) fn new_bip84_public(
public_key: Arc<DescriptorPublicKey>,
fingerprint: String,
keychain_kind: KeychainKind,
network: Network,
) -> Self {
let fingerprint = Fingerprint::from_str(fingerprint.as_str()).unwrap();
let derivable_key = public_key.descriptor_public_key_mutex.lock().unwrap();
match derivable_key.deref() {
BdkDescriptorPublicKey::XPub(descriptor_x_key) => {
let derivable_key = descriptor_x_key.xkey;
let (extended_descriptor, key_map, _) =
Bip84Public(derivable_key, fingerprint, keychain_kind)
.build(network)
.unwrap();
Self {
extended_descriptor,
key_map,
}
}
BdkDescriptorPublicKey::Single(_) => {
unreachable!()
}
}
}
pub(crate) fn as_string_private(&self) -> String {
let descriptor = &self.extended_descriptor;
let key_map = &self.key_map;
descriptor.to_string_with_secret(key_map)
}
pub(crate) fn as_string(&self) -> String {
self.extended_descriptor.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::database::DatabaseConfig;
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)
}
#[test]
fn test_descriptor_templates() {
let master: Arc<DescriptorSecretKey> = Arc::new(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(),
))
.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(),
))
.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(),
))
.unwrap()
.as_public();
println!("Public 84: {}", handmade_public_84.as_string());
// Public 84: [d1d04177/84'/1'/0']tpubDDNxbq17egjFk2edjv8oLnzxk52zny9aAYNv9CMqTzA4mQDiQq818sEkNe9Gzmd4QU8558zftqbfoVBDQorG3E4Wq26tB2JeE4KUoahLkx6/*
let template_private_44 =
Descriptor::new_bip44(master.clone(), KeychainKind::External, Network::Testnet);
let template_private_49 =
Descriptor::new_bip49(master.clone(), KeychainKind::External, Network::Testnet);
let template_private_84 =
Descriptor::new_bip84(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());
// for the public versions of the templates these are incorrect, bug report and fix in bitcoindevkit/bdk#817 and bitcoindevkit/bdk#818
let template_public_44 = Descriptor::new_bip44_public(
handmade_public_44,
"d1d04177".to_string(),
KeychainKind::External,
Network::Testnet,
);
let template_public_49 = Descriptor::new_bip49_public(
handmade_public_49,
"d1d04177".to_string(),
KeychainKind::External,
Network::Testnet,
);
let template_public_84 = Descriptor::new_bip84_public(
handmade_public_84,
"d1d04177".to_string(),
KeychainKind::External,
Network::Testnet,
);
println!("Template public 49: {}", template_public_49.as_string());
println!("Template public 44: {}", template_public_44.as_string());
println!("Template public 84: {}", template_public_84.as_string());
// when using a public key, both as_string and as_string_private return the same string
assert_eq!(
template_public_44.as_string_private(),
template_public_44.as_string()
);
assert_eq!(
template_public_49.as_string_private(),
template_public_49.as_string()
);
assert_eq!(
template_public_84.as_string_private(),
template_public_84.as_string()
);
// when using as_string on a private key, we get the same result as when using it on a public key
assert_eq!(
template_private_44.as_string(),
template_public_44.as_string()
);
assert_eq!(
template_private_49.as_string(),
template_public_49.as_string()
);
assert_eq!(
template_private_84.as_string(),
template_public_84.as_string()
);
}
#[test]
fn test_descriptor_from_string() {
let descriptor1 = Descriptor::new("wpkh(tprv8hwWMmPE4BVNxGdVt3HhEERZhondQvodUY7Ajyseyhudr4WabJqWKWLr4Wi2r26CDaNCQhhxEftEaNzz7dPGhWuKFU4VULesmhEfZYyBXdE/0/*)".to_string(), Network::Testnet);
let descriptor2 = Descriptor::new("wpkh(tprv8hwWMmPE4BVNxGdVt3HhEERZhondQvodUY7Ajyseyhudr4WabJqWKWLr4Wi2r26CDaNCQhhxEftEaNzz7dPGhWuKFU4VULesmhEfZYyBXdE/0/*)".to_string(), Network::Bitcoin);
// Creating a Descriptor using an extended key that doesn't match the network provided will throw and InvalidNetwork Error
assert!(descriptor1.is_ok());
assert_matches!(
descriptor2.unwrap_err(),
bdk::Error::Descriptor(Key(InvalidNetwork))
)
}
#[test]
fn test_wallet_from_descriptor() {
let descriptor1 = Descriptor::new("wpkh(tprv8hwWMmPE4BVNxGdVt3HhEERZhondQvodUY7Ajyseyhudr4WabJqWKWLr4Wi2r26CDaNCQhhxEftEaNzz7dPGhWuKFU4VULesmhEfZYyBXdE/0/*)".to_string(), Network::Testnet).unwrap();
let wallet1 = Wallet::new(
Arc::new(Descriptor::new("wpkh(tprv8hwWMmPE4BVNxGdVt3HhEERZhondQvodUY7Ajyseyhudr4WabJqWKWLr4Wi2r26CDaNCQhhxEftEaNzz7dPGhWuKFU4VULesmhEfZYyBXdE/0/*)".to_string(), Network::Testnet).unwrap()),
None,
Network::Testnet,
DatabaseConfig::Memory
);
let wallet2 = Wallet::new(
Arc::new(descriptor1),
None,
Network::Bitcoin,
DatabaseConfig::Memory,
);
// Creating a wallet using a Descriptor with an extended key that doesn't match the network provided in the wallet constructor will throw and InvalidNetwork Error
assert!(wallet1.is_ok());
assert_matches!(
wallet2.unwrap_err(),
bdk::Error::Descriptor(Key(InvalidNetwork))
)
}
}

376
bdk-ffi/src/keys.rs Normal file
View File

@@ -0,0 +1,376 @@
use crate::BdkError;
use bdk::bitcoin::secp256k1::Secp256k1;
use bdk::bitcoin::util::bip32::DerivationPath as BdkDerivationPath;
use bdk::bitcoin::Network;
use bdk::descriptor::DescriptorXKey;
use bdk::keys::bip39::{Language, Mnemonic as BdkMnemonic, WordCount};
use bdk::keys::{
DerivableKey, DescriptorPublicKey as BdkDescriptorPublicKey,
DescriptorSecretKey as BdkDescriptorSecretKey, ExtendedKey, GeneratableKey, GeneratedKey,
};
use bdk::miniscript::BareCtx;
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 {
internal: BdkMnemonic,
}
impl Mnemonic {
/// Generates Mnemonic with a random entropy
pub(crate) fn new(word_count: WordCount) -> Self {
let generated_key: GeneratedKey<_, BareCtx> =
BdkMnemonic::generate((word_count, Language::English)).unwrap();
let mnemonic = BdkMnemonic::parse_in(Language::English, generated_key.to_string()).unwrap();
Mnemonic { internal: mnemonic }
}
/// Parse a Mnemonic with given string
pub(crate) fn from_string(mnemonic: String) -> Result<Self, BdkError> {
BdkMnemonic::from_str(&mnemonic)
.map(|m| Mnemonic { internal: m })
.map_err(|e| BdkError::Generic(e.to_string()))
}
/// Create a new Mnemonic in the specified language from the given entropy.
/// Entropy must be a multiple of 32 bits (4 bytes) and 128-256 bits in length.
pub(crate) fn from_entropy(entropy: Vec<u8>) -> Result<Self, BdkError> {
BdkMnemonic::from_entropy(entropy.as_slice())
.map(|m| Mnemonic { internal: m })
.map_err(|e| BdkError::Generic(e.to_string()))
}
/// Returns Mnemonic as string
pub(crate) fn as_string(&self) -> String {
self.internal.to_string()
}
}
pub(crate) struct DerivationPath {
derivation_path_mutex: Mutex<BdkDerivationPath>,
}
impl DerivationPath {
pub(crate) fn new(path: String) -> Result<Self, BdkError> {
BdkDerivationPath::from_str(&path)
.map(|x| DerivationPath {
derivation_path_mutex: Mutex::new(x),
})
.map_err(|e| BdkError::Generic(e.to_string()))
}
}
#[derive(Debug)]
pub(crate) struct DescriptorSecretKey {
pub(crate) descriptor_secret_key_mutex: Mutex<BdkDescriptorSecretKey>,
}
impl DescriptorSecretKey {
pub(crate) fn new(network: Network, mnemonic: Arc<Mnemonic>, password: Option<String>) -> Self {
let mnemonic = mnemonic.internal.clone();
let xkey: ExtendedKey = (mnemonic, password).into_extended_key().unwrap();
let descriptor_secret_key = BdkDescriptorSecretKey::XPrv(DescriptorXKey {
origin: None,
xkey: xkey.into_xprv(network).unwrap(),
derivation_path: BdkDerivationPath::master(),
wildcard: bdk::descriptor::Wildcard::Unhardened,
});
Self {
descriptor_secret_key_mutex: Mutex::new(descriptor_secret_key),
}
}
pub(crate) fn from_string(private_key: String) -> Result<Self, BdkError> {
let descriptor_secret_key = BdkDescriptorSecretKey::from_str(private_key.as_str())
.map_err(|e| BdkError::Generic(e.to_string()))?;
Ok(Self {
descriptor_secret_key_mutex: Mutex::new(descriptor_secret_key),
})
}
pub(crate) fn derive(&self, path: Arc<DerivationPath>) -> Result<Arc<Self>, BdkError> {
let secp = Secp256k1::new();
let descriptor_secret_key = self.descriptor_secret_key_mutex.lock().unwrap();
let path = path.derivation_path_mutex.lock().unwrap().deref().clone();
match descriptor_secret_key.deref() {
BdkDescriptorSecretKey::XPrv(descriptor_x_key) => {
let derived_xprv = descriptor_x_key.xkey.derive_priv(&secp, &path)?;
let key_source = match descriptor_x_key.origin.clone() {
Some((fingerprint, origin_path)) => (fingerprint, origin_path.extend(path)),
None => (descriptor_x_key.xkey.fingerprint(&secp), path),
};
let derived_descriptor_secret_key = BdkDescriptorSecretKey::XPrv(DescriptorXKey {
origin: Some(key_source),
xkey: derived_xprv,
derivation_path: BdkDerivationPath::default(),
wildcard: descriptor_x_key.wildcard,
});
Ok(Arc::new(Self {
descriptor_secret_key_mutex: Mutex::new(derived_descriptor_secret_key),
}))
}
BdkDescriptorSecretKey::Single(_) => Err(BdkError::Generic(
"Cannot derive from a single key".to_string(),
)),
}
}
pub(crate) fn extend(&self, path: Arc<DerivationPath>) -> Result<Arc<Self>, BdkError> {
let descriptor_secret_key = self.descriptor_secret_key_mutex.lock().unwrap();
let path = path.derivation_path_mutex.lock().unwrap().deref().clone();
match descriptor_secret_key.deref() {
BdkDescriptorSecretKey::XPrv(descriptor_x_key) => {
let extended_path = descriptor_x_key.derivation_path.extend(path);
let extended_descriptor_secret_key = BdkDescriptorSecretKey::XPrv(DescriptorXKey {
origin: descriptor_x_key.origin.clone(),
xkey: descriptor_x_key.xkey,
derivation_path: extended_path,
wildcard: descriptor_x_key.wildcard,
});
Ok(Arc::new(Self {
descriptor_secret_key_mutex: Mutex::new(extended_descriptor_secret_key),
}))
}
BdkDescriptorSecretKey::Single(_) => Err(BdkError::Generic(
"Cannot extend from a single key".to_string(),
)),
}
}
pub(crate) fn as_public(&self) -> Arc<DescriptorPublicKey> {
let secp = Secp256k1::new();
let descriptor_public_key = self
.descriptor_secret_key_mutex
.lock()
.unwrap()
.to_public(&secp)
.unwrap();
Arc::new(DescriptorPublicKey {
descriptor_public_key_mutex: Mutex::new(descriptor_public_key),
})
}
/// Get the private key as bytes.
pub(crate) fn secret_bytes(&self) -> Vec<u8> {
let descriptor_secret_key = self.descriptor_secret_key_mutex.lock().unwrap();
let secret_bytes: Vec<u8> = match descriptor_secret_key.deref() {
BdkDescriptorSecretKey::XPrv(descriptor_x_key) => {
descriptor_x_key.xkey.private_key.secret_bytes().to_vec()
}
BdkDescriptorSecretKey::Single(_) => {
unreachable!()
}
};
secret_bytes
}
pub(crate) fn as_string(&self) -> String {
self.descriptor_secret_key_mutex.lock().unwrap().to_string()
}
}
#[derive(Debug)]
pub(crate) struct DescriptorPublicKey {
pub(crate) descriptor_public_key_mutex: Mutex<BdkDescriptorPublicKey>,
}
impl DescriptorPublicKey {
pub(crate) fn from_string(public_key: String) -> Result<Self, BdkError> {
let descriptor_public_key = BdkDescriptorPublicKey::from_str(public_key.as_str())
.map_err(|e| BdkError::Generic(e.to_string()))?;
Ok(Self {
descriptor_public_key_mutex: Mutex::new(descriptor_public_key),
})
}
pub(crate) fn derive(&self, path: Arc<DerivationPath>) -> Result<Arc<Self>, BdkError> {
let secp = Secp256k1::new();
let descriptor_public_key = self.descriptor_public_key_mutex.lock().unwrap();
let path = path.derivation_path_mutex.lock().unwrap().deref().clone();
match descriptor_public_key.deref() {
BdkDescriptorPublicKey::XPub(descriptor_x_key) => {
let derived_xpub = descriptor_x_key.xkey.derive_pub(&secp, &path)?;
let key_source = match descriptor_x_key.origin.clone() {
Some((fingerprint, origin_path)) => (fingerprint, origin_path.extend(path)),
None => (descriptor_x_key.xkey.fingerprint(), path),
};
let derived_descriptor_public_key = BdkDescriptorPublicKey::XPub(DescriptorXKey {
origin: Some(key_source),
xkey: derived_xpub,
derivation_path: BdkDerivationPath::default(),
wildcard: descriptor_x_key.wildcard,
});
Ok(Arc::new(Self {
descriptor_public_key_mutex: Mutex::new(derived_descriptor_public_key),
}))
}
BdkDescriptorPublicKey::Single(_) => Err(BdkError::Generic(
"Cannot derive from a single key".to_string(),
)),
}
}
pub(crate) fn extend(&self, path: Arc<DerivationPath>) -> Result<Arc<Self>, BdkError> {
let descriptor_public_key = self.descriptor_public_key_mutex.lock().unwrap();
let path = path.derivation_path_mutex.lock().unwrap().deref().clone();
match descriptor_public_key.deref() {
BdkDescriptorPublicKey::XPub(descriptor_x_key) => {
let extended_path = descriptor_x_key.derivation_path.extend(path);
let extended_descriptor_public_key = BdkDescriptorPublicKey::XPub(DescriptorXKey {
origin: descriptor_x_key.origin.clone(),
xkey: descriptor_x_key.xkey,
derivation_path: extended_path,
wildcard: descriptor_x_key.wildcard,
});
Ok(Arc::new(Self {
descriptor_public_key_mutex: Mutex::new(extended_descriptor_public_key),
}))
}
BdkDescriptorPublicKey::Single(_) => Err(BdkError::Generic(
"Cannot extend from a single key".to_string(),
)),
}
}
pub(crate) fn as_string(&self) -> String {
self.descriptor_public_key_mutex.lock().unwrap().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 bdk::bitcoin::Network;
use std::sync::Arc;
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)
}
fn derive_dsk(
key: &DescriptorSecretKey,
path: &str,
) -> Result<Arc<DescriptorSecretKey>, BdkError> {
let path = Arc::new(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)
}
fn derive_dpk(
key: &DescriptorPublicKey,
path: &str,
) -> Result<Arc<DescriptorPublicKey>, BdkError> {
let path = Arc::new(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)
}
#[test]
fn test_generate_descriptor_secret_key() {
let master_dsk = get_descriptor_secret_key();
assert_eq!(master_dsk.as_string(), "tprv8ZgxMBicQKsPdWuqM1t1CDRvQtQuBPyfL6GbhQwtxDKgUAVPbxmj71pRA8raTqLrec5LyTs5TqCxdABcZr77bt2KyWA5bizJHnC4g4ysm4h/*");
assert_eq!(master_dsk.as_public().as_string(), "tpubD6NzVbkrYhZ4WywdEfYbbd62yuvqLjAZuPsNyvzCNV85JekAEMbKHWSHLF9h3j45SxewXDcLv328B1SEZrxg4iwGfmdt1pDFjZiTkGiFqGa/*");
}
#[test]
fn test_derive_self() {
let master_dsk = get_descriptor_secret_key();
let derived_dsk: &DescriptorSecretKey = &derive_dsk(&master_dsk, "m").unwrap();
assert_eq!(derived_dsk.as_string(), "[d1d04177]tprv8ZgxMBicQKsPdWuqM1t1CDRvQtQuBPyfL6GbhQwtxDKgUAVPbxmj71pRA8raTqLrec5LyTs5TqCxdABcZr77bt2KyWA5bizJHnC4g4ysm4h/*");
let master_dpk: &DescriptorPublicKey = &master_dsk.as_public();
let derived_dpk: &DescriptorPublicKey = &derive_dpk(master_dpk, "m").unwrap();
assert_eq!(derived_dpk.as_string(), "[d1d04177]tpubD6NzVbkrYhZ4WywdEfYbbd62yuvqLjAZuPsNyvzCNV85JekAEMbKHWSHLF9h3j45SxewXDcLv328B1SEZrxg4iwGfmdt1pDFjZiTkGiFqGa/*");
}
#[test]
fn test_derive_descriptors_keys() {
let master_dsk = get_descriptor_secret_key();
let derived_dsk: &DescriptorSecretKey = &derive_dsk(&master_dsk, "m/0").unwrap();
assert_eq!(derived_dsk.as_string(), "[d1d04177/0]tprv8d7Y4JLmD25jkKbyDZXcdoPHu1YtMHuH21qeN7mFpjfumtSU7eZimFYUCSa3MYzkEYfSNRBV34GEr2QXwZCMYRZ7M1g6PUtiLhbJhBZEGYJ/*");
let master_dpk: &DescriptorPublicKey = &master_dsk.as_public();
let derived_dpk: &DescriptorPublicKey = &derive_dpk(master_dpk, "m/0").unwrap();
assert_eq!(derived_dpk.as_string(), "[d1d04177/0]tpubD9oaCiP1MPmQdndm7DCD3D3QU34pWd6BbKSRedoZF1UJcNhEk3PJwkALNYkhxeTKL29oGNR7psqvT1KZydCGqUDEKXN6dVQJY2R8ooLPy8m/*");
}
#[test]
fn test_extend_descriptor_keys() {
let master_dsk = get_descriptor_secret_key();
let extended_dsk: &DescriptorSecretKey = &extend_dsk(&master_dsk, "m/0").unwrap();
assert_eq!(extended_dsk.as_string(), "tprv8ZgxMBicQKsPdWuqM1t1CDRvQtQuBPyfL6GbhQwtxDKgUAVPbxmj71pRA8raTqLrec5LyTs5TqCxdABcZr77bt2KyWA5bizJHnC4g4ysm4h/0/*");
let master_dpk: &DescriptorPublicKey = &master_dsk.as_public();
let extended_dpk: &DescriptorPublicKey = &extend_dpk(master_dpk, "m/0").unwrap();
assert_eq!(extended_dpk.as_string(), "tpubD6NzVbkrYhZ4WywdEfYbbd62yuvqLjAZuPsNyvzCNV85JekAEMbKHWSHLF9h3j45SxewXDcLv328B1SEZrxg4iwGfmdt1pDFjZiTkGiFqGa/0/*");
let wif = "L2wTu6hQrnDMiFNWA5na6jB12ErGQqtXwqpSL7aWquJaZG8Ai3ch";
let extended_key = DescriptorSecretKey::from_string(wif.to_string()).unwrap();
let result = extended_key.derive(Arc::new(DerivationPath::new("m/0".to_string()).unwrap()));
dbg!(&result);
assert!(result.is_err());
}
#[test]
fn test_from_str_descriptor_secret_key() {
let key1 = "L2wTu6hQrnDMiFNWA5na6jB12ErGQqtXwqpSL7aWquJaZG8Ai3ch";
let key2 = "tprv8ZgxMBicQKsPcwcD4gSnMti126ZiETsuX7qwrtMypr6FBwAP65puFn4v6c3jrN9VwtMRMph6nyT63NrfUL4C3nBzPcduzVSuHD7zbX2JKVc/1/1/1/*";
let private_descriptor_key1 = DescriptorSecretKey::from_string(key1.to_string()).unwrap();
let private_descriptor_key2 = DescriptorSecretKey::from_string(key2.to_string()).unwrap();
dbg!(private_descriptor_key1);
dbg!(private_descriptor_key2);
// Should error out because you can't produce a DescriptorSecretKey from an xpub
let key0 = "tpubDBrgjcxBxnXyL575sHdkpKohWu5qHKoQ7TJXKNrYznh5fVEGBv89hA8ENW7A8MFVpFUSvgLqc4Nj1WZcpePX6rrxviVtPowvMuGF5rdT2Vi";
assert!(DescriptorSecretKey::from_string(key0.to_string()).is_err());
}
#[test]
fn test_derive_and_extend_descriptor_secret_key() {
let master_dsk = get_descriptor_secret_key();
// derive DescriptorSecretKey with path "m/0" from master
let derived_dsk: &DescriptorSecretKey = &derive_dsk(&master_dsk, "m/0").unwrap();
assert_eq!(derived_dsk.as_string(), "[d1d04177/0]tprv8d7Y4JLmD25jkKbyDZXcdoPHu1YtMHuH21qeN7mFpjfumtSU7eZimFYUCSa3MYzkEYfSNRBV34GEr2QXwZCMYRZ7M1g6PUtiLhbJhBZEGYJ/*");
// extend derived_dsk with path "m/0"
let extended_dsk: &DescriptorSecretKey = &extend_dsk(derived_dsk, "m/0").unwrap();
assert_eq!(extended_dsk.as_string(), "[d1d04177/0]tprv8d7Y4JLmD25jkKbyDZXcdoPHu1YtMHuH21qeN7mFpjfumtSU7eZimFYUCSa3MYzkEYfSNRBV34GEr2QXwZCMYRZ7M1g6PUtiLhbJhBZEGYJ/0/*");
}
#[test]
fn test_derive_hardened_path_using_public() {
let master_dpk = get_descriptor_secret_key().as_public();
let derived_dpk = &derive_dpk(&master_dpk, "m/84h/1h/0h");
assert!(derived_dpk.is_err());
}
#[test]
fn test_retrieve_master_secret_key() {
let master_dpk = get_descriptor_secret_key();
let master_private_key = master_dpk.secret_bytes().to_hex();
assert_eq!(
master_private_key,
"e93315d6ce401eb4db803a56232f0ed3e69b053774e6047df54f1bd00e5ea936"
)
}
}

File diff suppressed because it is too large Load Diff

119
bdk-ffi/src/psbt.rs Normal file
View File

@@ -0,0 +1,119 @@
use bdk::bitcoin::hashes::hex::ToHex;
use bdk::bitcoin::util::psbt::PartiallySignedTransaction as BdkPartiallySignedTransaction;
use bdk::bitcoincore_rpc::jsonrpc::serde_json;
use bdk::psbt::PsbtUtils;
use std::ops::Deref;
use std::str::FromStr;
use std::sync::{Arc, Mutex};
use crate::{BdkError, FeeRate, Transaction};
#[derive(Debug)]
pub(crate) struct PartiallySignedTransaction {
pub(crate) internal: Mutex<BdkPartiallySignedTransaction>,
}
impl PartiallySignedTransaction {
pub(crate) fn new(psbt_base64: String) -> Result<Self, BdkError> {
let psbt: BdkPartiallySignedTransaction =
BdkPartiallySignedTransaction::from_str(&psbt_base64)?;
Ok(PartiallySignedTransaction {
internal: Mutex::new(psbt),
})
}
pub(crate) fn serialize(&self) -> String {
let psbt = self.internal.lock().unwrap().clone();
psbt.to_string()
}
pub(crate) fn txid(&self) -> String {
let tx = self.internal.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.internal.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.internal.lock().unwrap().clone();
let mut original_psbt = self.internal.lock().unwrap().clone();
original_psbt.combine(other_psbt)?;
Ok(Arc::new(PartiallySignedTransaction {
internal: Mutex::new(original_psbt),
}))
}
/// The total transaction fee amount, sum of input amounts minus sum of output amounts, in Sats.
/// If the PSBT is missing a TxOut for an input returns None.
pub(crate) fn fee_amount(&self) -> Option<u64> {
self.internal.lock().unwrap().fee_amount()
}
/// The transaction's fee rate. This value will only be accurate if calculated AFTER the
/// `PartiallySignedTransaction` is finalized and all witness/signature data is added to the
/// transaction.
/// If the PSBT is missing a TxOut for an input returns None.
pub(crate) fn fee_rate(&self) -> Option<Arc<FeeRate>> {
self.internal.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.internal.lock().unwrap();
serde_json::to_string(psbt.deref()).unwrap()
}
}
// 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::wallet::{TxBuilder, Wallet};
use bdk::wallet::get_funded_wallet;
use std::sync::Mutex;
#[test]
fn test_psbt_fee() {
let test_wpkh = "wpkh(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW)";
let (funded_wallet, _, _) = get_funded_wallet(test_wpkh);
let test_wallet = Wallet {
wallet_mutex: Mutex::new(funded_wallet),
};
let drain_to_address = "tb1ql7w62elx9ucw4pj5lgw4l028hmuw80sndtntxt".to_string();
let drain_to_script = crate::Address::new(drain_to_address)
.unwrap()
.script_pubkey();
let tx_builder = TxBuilder::new()
.fee_rate(2.0)
.drain_wallet()
.drain_to(drain_to_script.clone());
//dbg!(&tx_builder);
assert!(tx_builder.drain_wallet);
assert_eq!(tx_builder.drain_to, Some(drain_to_script.script.clone()));
let tx_builder_result = tx_builder.finish(&test_wallet).unwrap();
assert!(tx_builder_result.psbt.fee_rate().is_some());
assert_eq!(
tx_builder_result.psbt.fee_rate().unwrap().as_sat_per_vb(),
2.682927
);
assert!(tx_builder_result.psbt.fee_amount().is_some());
assert_eq!(tx_builder_result.psbt.fee_amount().unwrap(), 220);
}
}

857
bdk-ffi/src/wallet.rs Normal file
View File

@@ -0,0 +1,857 @@
use bdk::bitcoin::blockdata::script::Script as BdkScript;
use bdk::bitcoin::{Address as BdkAddress, Network, OutPoint as BdkOutPoint, Sequence, Txid};
use bdk::database::any::AnyDatabase;
use bdk::database::{AnyDatabaseConfig, ConfigurableDatabase};
use bdk::wallet::tx_builder::ChangeSpendPolicy;
use bdk::{
FeeRate, LocalUtxo as BdkLocalUtxo, SignOptions as BdkSignOptions,
SyncOptions as BdkSyncOptions, Wallet as BdkWallet,
};
use std::collections::HashSet;
use std::ops::Deref;
use std::str::FromStr;
use std::sync::{Arc, Mutex, MutexGuard};
use crate::blockchain::Blockchain;
use crate::database::DatabaseConfig;
use crate::descriptor::Descriptor;
use crate::psbt::PartiallySignedTransaction;
use crate::{
AddressIndex, AddressInfo, Balance, BdkError, LocalUtxo, OutPoint, Progress, ProgressHolder,
RbfValue, Script, ScriptAmount, TransactionDetails, TxBuilderResult,
};
#[derive(Debug)]
pub(crate) struct Wallet {
pub(crate) wallet_mutex: Mutex<BdkWallet<AnyDatabase>>,
}
/// 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(crate) fn new(
descriptor: Arc<Descriptor>,
change_descriptor: Option<Arc<Descriptor>>,
network: Network,
database_config: DatabaseConfig,
) -> Result<Self, BdkError> {
let any_database_config = match database_config {
DatabaseConfig::Memory => AnyDatabaseConfig::Memory(()),
DatabaseConfig::Sled { config } => AnyDatabaseConfig::Sled(config),
DatabaseConfig::Sqlite { config } => AnyDatabaseConfig::Sqlite(config),
};
let database = AnyDatabase::from_config(&any_database_config)?;
let descriptor: String = descriptor.as_string_private();
let change_descriptor: Option<String> = change_descriptor.map(|d| d.as_string_private());
let wallet_mutex = Mutex::new(BdkWallet::new(
&descriptor,
change_descriptor.as_ref(),
network,
database,
)?);
Ok(Wallet { wallet_mutex })
}
pub(crate) fn get_wallet(&self) -> MutexGuard<BdkWallet<AnyDatabase>> {
self.wallet_mutex.lock().expect("wallet")
}
/// Get the Bitcoin network the wallet is using.
pub(crate) fn network(&self) -> Network {
self.get_wallet().network()
}
/// Return whether or not a script is part of this wallet (either internal or external).
pub(crate) fn is_mine(&self, script: Arc<Script>) -> Result<bool, BdkError> {
self.get_wallet().is_mine(&script.script)
}
/// 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.
pub(crate) fn get_address(&self, address_index: AddressIndex) -> Result<AddressInfo, BdkError> {
self.get_wallet()
.get_address(address_index.into())
.map(AddressInfo::from)
}
/// 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(
&self,
address_index: AddressIndex,
) -> Result<AddressInfo, BdkError> {
self.get_wallet()
.get_internal_address(address_index.into())
.map(AddressInfo::from)
}
/// Return the balance, meaning the sum of this wallets unspent outputs values. Note that this method only operates
/// on the internal database, which first needs to be Wallet.sync manually.
pub(crate) fn get_balance(&self) -> Result<Balance, BdkError> {
self.get_wallet().get_balance().map(|b| b.into())
}
/// 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.internal.lock().unwrap();
self.get_wallet().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.get_wallet().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.get_wallet().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(crate) struct TxBuilder {
pub(crate) recipients: Vec<(BdkScript, u64)>,
pub(crate) utxos: Vec<OutPoint>,
pub(crate) unspendable: HashSet<OutPoint>,
pub(crate) change_policy: ChangeSpendPolicy,
pub(crate) manually_selected_only: bool,
pub(crate) fee_rate: Option<f32>,
pub(crate) fee_absolute: Option<u64>,
pub(crate) drain_wallet: bool,
pub(crate) drain_to: Option<BdkScript>,
pub(crate) rbf: Option<RbfValue>,
pub(crate) data: Vec<u8>,
}
impl TxBuilder {
pub(crate) fn new() -> Self {
TxBuilder {
recipients: Vec::new(),
utxos: Vec::new(),
unspendable: HashSet::new(),
change_policy: ChangeSpendPolicy::ChangeAllowed,
manually_selected_only: false,
fee_rate: None,
fee_absolute: None,
drain_wallet: false,
drain_to: None,
rbf: None,
data: Vec::new(),
}
}
/// Add a recipient to the internal list.
pub(crate) fn add_recipient(&self, script: Arc<Script>, amount: u64) -> Arc<Self> {
let mut recipients: Vec<(BdkScript, u64)> = self.recipients.clone();
recipients.append(&mut vec![(script.script.clone(), amount)]);
Arc::new(TxBuilder {
recipients,
..self.clone()
})
}
pub(crate) fn set_recipients(&self, recipients: Vec<ScriptAmount>) -> Arc<Self> {
let recipients = recipients
.iter()
.map(|script_amount| (script_amount.script.script.clone(), script_amount.amount))
.collect();
Arc::new(TxBuilder {
recipients,
..self.clone()
})
}
/// Add a utxo to the internal list of unspendable utxos. Its important to note that the "must-be-spent"
/// utxos added with [TxBuilder.addUtxo] have priority over this. See the Rust docs of the two linked methods for more details.
pub(crate) fn add_unspendable(&self, unspendable: OutPoint) -> Arc<Self> {
let mut unspendable_hash_set = self.unspendable.clone();
unspendable_hash_set.insert(unspendable);
Arc::new(TxBuilder {
unspendable: unspendable_hash_set,
..self.clone()
})
}
/// 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 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);
Arc::new(TxBuilder {
utxos,
..self.clone()
})
}
/// 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,
..self.clone()
})
}
/// 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,
..self.clone()
})
}
/// 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,
..self.clone()
})
}
/// Replace the internal list of unspendable utxos with a new list. Its important to note that the "must-be-spent" utxos added with
/// TxBuilder.addUtxo have priority over these. See the Rust docs of the two linked methods for more details.
pub(crate) fn unspendable(&self, unspendable: Vec<OutPoint>) -> Arc<Self> {
Arc::new(TxBuilder {
unspendable: unspendable.into_iter().collect(),
..self.clone()
})
}
/// Set a custom fee rate.
pub(crate) fn fee_rate(&self, sat_per_vb: f32) -> Arc<Self> {
Arc::new(TxBuilder {
fee_rate: Some(sat_per_vb),
..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()
})
}
/// 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,
..self.clone()
})
}
/// 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.script.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()
})
}
/// 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()
})
}
/// Finish building the transaction. Returns the BIP174 PSBT.
pub(crate) fn finish(&self, wallet: &Wallet) -> Result<TxBuilderResult, BdkError> {
let wallet = wallet.get_wallet();
let mut tx_builder = wallet.build_tx();
for (script, amount) in &self.recipients {
tx_builder.add_recipient(script.clone(), *amount);
}
tx_builder.change_policy(self.change_policy);
if !self.utxos.is_empty() {
let bdk_utxos: Vec<BdkOutPoint> = self.utxos.iter().map(BdkOutPoint::from).collect();
let utxos: &[BdkOutPoint] = &bdk_utxos;
tx_builder.add_utxos(utxos)?;
}
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_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());
}
tx_builder
.finish()
.map(|(psbt, tx_details)| TxBuilderResult {
psbt: Arc::new(PartiallySignedTransaction {
internal: Mutex::new(psbt),
}),
transaction_details: TransactionDetails::from(tx_details),
})
}
}
/// 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
/// cant 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 {
internal: Mutex::new(psbt),
})
.map(Arc::new)
}
}
// The goal of these tests to to ensure `bdk-ffi` intermediate code correctly calls `bdk` APIs.
// These tests should not be used to verify `bdk` behavior that is already tested in the `bdk`
// crate.
#[cfg(test)]
mod test {
use crate::database::DatabaseConfig;
use crate::descriptor::Descriptor;
use crate::keys::{DescriptorSecretKey, Mnemonic};
use crate::wallet::{AddressIndex, TxBuilder, Wallet};
use crate::Script;
use assert_matches::assert_matches;
use bdk::bitcoin::{Address, Network};
use bdk::wallet::get_funded_wallet;
use bdk::KeychainKind;
use std::str::FromStr;
use std::sync::{Arc, Mutex};
#[test]
fn test_drain_wallet() {
let test_wpkh = "wpkh(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW)";
let (funded_wallet, _, _) = get_funded_wallet(test_wpkh);
let test_wallet = Wallet {
wallet_mutex: Mutex::new(funded_wallet),
};
let drain_to_address = "tb1ql7w62elx9ucw4pj5lgw4l028hmuw80sndtntxt".to_string();
let drain_to_script = crate::Address::new(drain_to_address)
.unwrap()
.script_pubkey();
let tx_builder = TxBuilder::new()
.drain_wallet()
.drain_to(drain_to_script.clone());
assert!(tx_builder.drain_wallet);
assert_eq!(tx_builder.drain_to, Some(drain_to_script.script.clone()));
let tx_builder_result = tx_builder.finish(&test_wallet).unwrap();
let psbt = tx_builder_result.psbt.internal.lock().unwrap().clone();
let tx_details = tx_builder_result.transaction_details;
// confirm one input with 50,000 sats
assert_eq!(psbt.inputs.len(), 1);
let input_value = psbt
.inputs
.get(0)
.cloned()
.unwrap()
.non_witness_utxo
.unwrap()
.output
.get(0)
.unwrap()
.value;
assert_eq!(input_value, 50_000_u64);
// confirm one output to correct address with all sats - fee
assert_eq!(psbt.outputs.len(), 1);
let output_address = Address::from_script(
&psbt
.unsigned_tx
.output
.get(0)
.cloned()
.unwrap()
.script_pubkey,
Network::Testnet,
)
.unwrap();
assert_eq!(
output_address,
Address::from_str("tb1ql7w62elx9ucw4pj5lgw4l028hmuw80sndtntxt").unwrap()
);
let output_value = psbt.unsigned_tx.output.get(0).cloned().unwrap().value;
assert_eq!(output_value, 49_890_u64); // input - fee
assert_eq!(
tx_details.txid,
"312f1733badab22dc26b8dcbc83ba5629fb7b493af802e8abe07d865e49629c5"
);
assert_eq!(tx_details.received, 0);
assert_eq!(tx_details.sent, 50000);
assert!(tx_details.fee.is_some());
assert_eq!(tx_details.fee.unwrap(), 110);
assert!(tx_details.confirmation_time.is_none());
}
#[test]
fn test_peek_reset_address() {
let test_wpkh = "wpkh(tprv8hwWMmPE4BVNxGdVt3HhEERZhondQvodUY7Ajyseyhudr4WabJqWKWLr4Wi2r26CDaNCQhhxEftEaNzz7dPGhWuKFU4VULesmhEfZYyBXdE/0/*)";
let descriptor = Descriptor::new(test_wpkh.to_string(), Network::Regtest).unwrap();
let change_descriptor = Descriptor::new(
test_wpkh.to_string().replace("/0/*", "/1/*"),
Network::Regtest,
)
.unwrap();
let wallet = Wallet::new(
Arc::new(descriptor),
Some(Arc::new(change_descriptor)),
Network::Regtest,
DatabaseConfig::Memory,
)
.unwrap();
assert_eq!(
wallet
.get_address(AddressIndex::Peek { index: 2 })
.unwrap()
.address
.as_string(),
"bcrt1q5g0mq6dkmwzvxscqwgc932jhgcxuqqkjv09tkj"
);
assert_eq!(
wallet
.get_address(AddressIndex::Peek { index: 1 })
.unwrap()
.address
.as_string(),
"bcrt1q0xs7dau8af22rspp4klya4f7lhggcnqfun2y3a"
);
// new index still 0
assert_eq!(
wallet
.get_address(AddressIndex::New)
.unwrap()
.address
.as_string(),
"bcrt1qqjn9gky9mkrm3c28e5e87t5akd3twg6xezp0tv"
);
// new index now 1
assert_eq!(
wallet
.get_address(AddressIndex::New)
.unwrap()
.address
.as_string(),
"bcrt1q0xs7dau8af22rspp4klya4f7lhggcnqfun2y3a"
);
// new index now 2
assert_eq!(
wallet
.get_address(AddressIndex::New)
.unwrap()
.address
.as_string(),
"bcrt1q5g0mq6dkmwzvxscqwgc932jhgcxuqqkjv09tkj"
);
// peek index 1
assert_eq!(
wallet
.get_address(AddressIndex::Peek { index: 1 })
.unwrap()
.address
.as_string(),
"bcrt1q0xs7dau8af22rspp4klya4f7lhggcnqfun2y3a"
);
// reset to index 0
assert_eq!(
wallet
.get_address(AddressIndex::Reset { index: 0 })
.unwrap()
.address
.as_string(),
"bcrt1qqjn9gky9mkrm3c28e5e87t5akd3twg6xezp0tv"
);
// new index 1 again
assert_eq!(
wallet
.get_address(AddressIndex::New)
.unwrap()
.address
.as_string(),
"bcrt1q0xs7dau8af22rspp4klya4f7lhggcnqfun2y3a"
);
}
#[test]
fn test_get_address() {
let test_wpkh = "wpkh(tprv8hwWMmPE4BVNxGdVt3HhEERZhondQvodUY7Ajyseyhudr4WabJqWKWLr4Wi2r26CDaNCQhhxEftEaNzz7dPGhWuKFU4VULesmhEfZYyBXdE/0/*)";
let descriptor = Descriptor::new(test_wpkh.to_string(), Network::Regtest).unwrap();
let change_descriptor = Descriptor::new(
test_wpkh.to_string().replace("/0/*", "/1/*"),
Network::Regtest,
)
.unwrap();
let wallet = Wallet::new(
Arc::new(descriptor),
Some(Arc::new(change_descriptor)),
Network::Regtest,
DatabaseConfig::Memory,
)
.unwrap();
assert_eq!(
wallet
.get_address(AddressIndex::New)
.unwrap()
.address
.as_string(),
"bcrt1qqjn9gky9mkrm3c28e5e87t5akd3twg6xezp0tv"
);
assert_eq!(
wallet
.get_address(AddressIndex::New)
.unwrap()
.address
.as_string(),
"bcrt1q0xs7dau8af22rspp4klya4f7lhggcnqfun2y3a"
);
assert_eq!(
wallet
.get_address(AddressIndex::LastUnused)
.unwrap()
.address
.as_string(),
"bcrt1q0xs7dau8af22rspp4klya4f7lhggcnqfun2y3a"
);
assert_eq!(
wallet
.get_internal_address(AddressIndex::New)
.unwrap()
.address
.as_string(),
"bcrt1qpmz73cyx00r4a5dea469j40ax6d6kqyd67nnpj"
);
assert_eq!(
wallet
.get_internal_address(AddressIndex::New)
.unwrap()
.address
.as_string(),
"bcrt1qaux734vuhykww9632v8cmdnk7z2mw5lsf74v6k"
);
assert_eq!(
wallet
.get_internal_address(AddressIndex::LastUnused)
.unwrap()
.address
.as_string(),
"bcrt1qaux734vuhykww9632v8cmdnk7z2mw5lsf74v6k"
);
}
#[test]
fn test_is_mine() {
// is_mine should return true for addresses generated by the wallet
let mnemonic: Mnemonic = Mnemonic::from_string("chaos fabric time speed sponsor all flat solution wisdom trophy crack object robot pave observe combine where aware bench orient secret primary cable detect".to_string()).unwrap();
let secret_key: DescriptorSecretKey =
DescriptorSecretKey::new(Network::Testnet, Arc::new(mnemonic), None);
let descriptor: Descriptor = Descriptor::new_bip84(
Arc::new(secret_key),
KeychainKind::External,
Network::Testnet,
);
let wallet: Wallet = Wallet::new(
Arc::new(descriptor),
None,
Network::Testnet,
DatabaseConfig::Memory,
)
.unwrap();
let address = wallet.get_address(AddressIndex::New).unwrap();
let script: Arc<Script> = address.address.script_pubkey();
let is_mine_1: bool = wallet.is_mine(script).unwrap();
assert!(is_mine_1);
// is_mine returns false when provided a script that is not in the wallet
let other_wpkh = "wpkh(tprv8hwWMmPE4BVNxGdVt3HhEERZhondQvodUY7Ajyseyhudr4WabJqWKWLr4Wi2r26CDaNCQhhxEftEaNzz7dPGhWuKFU4VULesmhEfZYyBXdE/0/*)";
let other_descriptor = Descriptor::new(other_wpkh.to_string(), Network::Testnet).unwrap();
let other_wallet = Wallet::new(
Arc::new(other_descriptor),
None,
Network::Testnet,
DatabaseConfig::Memory,
)
.unwrap();
let other_address = other_wallet.get_address(AddressIndex::New).unwrap();
let other_script: Arc<Script> = other_address.address.script_pubkey();
let is_mine_2: bool = wallet.is_mine(other_script).unwrap();
assert_matches!(is_mine_2, false);
}
}

View File

@@ -6,10 +6,5 @@ class TestBdk(unittest.TestCase):
def test_some_enum(self):
network = Network.TESTNET
def test_some_dict(self):
a = AddressInfo(index=42, address="testaddress")
self.assertEqual(42, a.index)
self.assertEqual("testaddress", a.address)
if __name__=='__main__':
unittest.main()

View File

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

View File

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

View File

@@ -1,4 +1,4 @@
# bdk-android
# 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.
## How to Use
@@ -19,16 +19,16 @@ import org.bitcoindevkit.*
// ...
val externalDescriptor = "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)"
val internalDescriptor = "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)"
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)
ElectrumConfig("ssl://electrum.blockstream.info:60002", null, 5u, null, 10u, true)
)
val wallet = Wallet(externalDescriptor, internalDescriptor, Network.TESTNET, databaseConfig, blockchainConfig)
val newAddress = wallet.getAddress(AddressIndex.LAST_UNUSED)
val newAddress = wallet.getAddress(AddressIndex.LastUnused)
```
### Snapshot releases
@@ -43,27 +43,34 @@ dependencies {
}
```
## Example Projects
* [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. Clone this repository.
1. Install JDK 11. It must be version 11 (not 17), otherwise it won't build. 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
```
2. Install Rust (note that we are currently building using Rust 1.67.0):
```shell
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
rustup default 1.67.0
```
3. Clone this repository.
```shell
git clone https://github.com/bitcoindevkit/bdk-ffi
```
2. Follow the "General" bdk-ffi ["Getting Started (Developer)"] instructions.
3. If building on macOS install required intel and m1 jvm targets
4. If building on macOS install required intel and m1 jvm targets
```sh
rustup target add x86_64-apple-darwin aarch64-apple-darwin
```
4. Build kotlin bindings
```sh
# build JVM library
./gradlew buildJvmLib
```
5. Build kotlin bindings
```sh
./gradlew buildJvmLib
```
## How to publish to your local Maven repo
```shell
@@ -82,5 +89,19 @@ and use the `publishToMavenLocal` task without excluding the signing task:
./gradlew publishToMavenLocal
```
## Known issues
## JNA dependency
Depending on the JVM version you use, you might not have the JNA dependency on your classpath. The exception thrown will be
```shell
class file for com.sun.jna.Pointer not found
```
The solution is to add JNA as a dependency like so:
```kotlin
dependencies {
// ...
implementation("net.java.dev.jna:jna:5.12.1")
}
```
[`bdk`]: https://github.com/bitcoindevkit/bdk
[`bdk-ffi`]: https://github.com/bitcoindevkit/bdk-ffi

View File

@@ -1,4 +1,4 @@
org.gradle.jvmargs=-Xmx1536m
android.enableJetifier=true
kotlin.code.style=official
libraryVersion=0.25.0
libraryVersion=0.29.0

View File

@@ -1,5 +1,6 @@
import org.gradle.api.tasks.testing.logging.TestExceptionFormat.*
import org.gradle.api.tasks.testing.logging.TestLogEvent.*
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
// library version is defined in gradle.properties
val libraryVersion: String by project
@@ -19,8 +20,8 @@ repositories {
}
java {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
withSourcesJar()
withJavadocJar()
}
@@ -101,3 +102,13 @@ signing {
useInMemoryPgpKeys(signingKeyId, signingKey, signingPassword)
sign(publishing.publications)
}
// This task dependency ensures that we build the bindings
// binaries before running the tests
tasks.withType<KotlinCompile> {
dependsOn("buildJvmLib")
kotlinOptions {
jvmTarget = "11"
}
}

View File

@@ -28,8 +28,7 @@ class JvmLibTest {
}
}
private val descriptor =
"wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)"
private val descriptor = Descriptor("wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)", Network.TESTNET)
private val databaseConfig = DatabaseConfig.Memory
@@ -39,14 +38,15 @@ class JvmLibTest {
null,
5u,
null,
100u
100u,
true,
)
)
@Test
fun memoryWalletNewAddress() {
val wallet = Wallet(descriptor, null, Network.TESTNET, databaseConfig)
val address = wallet.getAddress(AddressIndex.NEW).address
val address = wallet.getAddress(AddressIndex.New).address.asString()
assertEquals("tb1qzg4mckdh50nwdm9hkzq06528rsu73hjxxzem3e", address)
}

View File

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

View File

@@ -27,13 +27,20 @@ internal class UniFfiJvmPlugin : Plugin<Project> {
val cargoArgs: List<String> = listOf("build", "--profile", "release-smaller", "--target", "aarch64-apple-darwin")
args(cargoArgs)
}
} else if(operatingSystem == OS.LINUX) {
} else if (operatingSystem == OS.LINUX) {
exec {
workingDir("${project.projectDir}/../../bdk-ffi")
executable("cargo")
val cargoArgs: List<String> = listOf("build", "--profile", "release-smaller", "--target", "x86_64-unknown-linux-gnu")
args(cargoArgs)
}
} else if (operatingSystem == OS.WINDOWS) {
exec {
workingDir("${project.projectDir}/../../bdk-ffi")
executable("cargo")
val cargoArgs: List<String> = listOf("build", "--profile", "release-smaller", "--target", "x86_64-pc-windows-msvc")
args(cargoArgs)
}
}
}
@@ -70,13 +77,25 @@ internal class UniFfiJvmPlugin : Plugin<Project> {
ext = "so"
)
)
} else if (operatingSystem == OS.WINDOWS) {
libsToCopy.add(
CopyMetadata(
targetDir = "x86_64-pc-windows-msvc",
resDir = "win32-x86-64",
ext = "dll"
)
)
}
val libName = when (operatingSystem) {
OS.WINDOWS -> "bdkffi"
else -> "libbdkffi"
}
libsToCopy.forEach {
doFirst {
copy {
with(it) {
from("${project.projectDir}/../../target/${this.targetDir}/release-smaller/libbdkffi.${this.ext}")
from("${project.projectDir}/../../target/${this.targetDir}/release-smaller/${libName}.${this.ext}")
into("${project.projectDir}/../../bdk-jvm/lib/src/main/resources/${this.resDir}/")
}
}
@@ -90,17 +109,10 @@ internal class UniFfiJvmPlugin : Plugin<Project> {
dependsOn(moveNativeJvmLibs)
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(
"run",
"--package",
"bdk-ffi-bindgen",
"--",
"--language",
"kotlin",
"--out-dir",
"../bdk-jvm/lib/src/main/kotlin"
)
args(cargoArgs)
doLast {
println("JVM bindings file successfully created")

View File

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

View File

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

View File

@@ -1,17 +0,0 @@
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

2
bdk-python/MANIFEST.in Normal file
View File

@@ -0,0 +1,2 @@
include ./src/bdkpython/libbdkffi.dylib
include ./src/bdkpython/libbdkffi.so

View File

@@ -11,10 +11,10 @@ pip install bdkpython
## Run the tests
```shell
pip3 install --requirement requirements.txt
pip install --requirement requirements.txt
bash ./generate.sh
python3 setup.py --verbose bdist_wheel
pip3 install ./dist/bdkpython-<yourversion>-py3-none-any.whl
python setup.py bdist_wheel --verbose
pip install ./dist/bdkpython-<yourversion>-py3-none-any.whl --force-reinstall
python -m unittest --verbose tests/test_bdk.py
```
@@ -23,11 +23,11 @@ python -m unittest --verbose tests/test_bdk.py
# Install dependencies
pip install --requirement requirements.txt
# Generate the bindings first
# Generate the bindings
bash generate.sh
# Build the wheel
python3 setup.py --verbose bdist_wheel
python setup.py --verbose bdist_wheel
```
## Run tox to build and test locally
@@ -35,7 +35,7 @@ python3 setup.py --verbose bdist_wheel
# install dev requirements
pip install --requirement requirements-dev.txt
# build bindings glue code (located at .bdk-python/src/bdkpython/bdk.py)
# build bindings glue code (located at ./src/bdkpython/bdk.py)
source ./generate.sh
# build and test

View File

@@ -1,11 +1,24 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR=$(dirname "$(realpath $0)")
PY_SRC="${SCRIPT_DIR}/src/bdkpython/"
OS=$(uname -s)
echo "Generating bdk.py..."
# GENERATE_PYTHON_BINDINGS_OUT="$PY_SRC" GENERATE_PYTHON_BINDINGS_FIXUP_LIB_PATH=bdkffi cargo run --manifest-path ./bdk-ffi/Cargo.toml --release --bin generate --features generate-python
# BDKFFI_BINDGEN_PYTHON_FIXUP_PATH=bdkffi cargo run --manifest-path ./bdk-ffi/Cargo.toml --package bdk-ffi-bindgen -- --language python --udl-file ./bdk-ffi/src/bdk.udl --out-dir ./src/bdkpython/
BDKFFI_BINDGEN_OUTPUT_DIR="$PY_SRC" BDKFFI_BINDGEN_PYTHON_FIXUP_PATH=bdkffi cargo run --manifest-path ../bdk-ffi/Cargo.toml --package bdk-ffi-bindgen -- --language python --udl-file ../bdk-ffi/src/bdk.udl
cd ../bdk-ffi/
cargo run --bin uniffi-bindgen generate src/bdk.udl --language python --out-dir ../bdk-python/src/bdkpython/ --no-format
echo "Generating native binaries..."
cargo build --profile release-smaller
case $OS in
"Darwin")
echo "Copying macOS libbdkffi.dylib..."
cp ../target/release-smaller/libbdkffi.dylib ../bdk-python/src/bdkpython/libbdkffi.dylib
;;
"Linux")
echo "Copying linux libbdkffi.so..."
cp ../target/release-smaller/libbdkffi.so ../bdk-python/src/bdkpython/libbdkffi.so
;;
esac
cd ../bdk-python/
echo "All done!"

View File

@@ -1,3 +1,4 @@
semantic-version==2.9.0
setuptools-rust==1.1.2
typing_extensions==4.0.1
setuptools==67.4.0
wheel==0.38.4

View File

@@ -1,9 +1,6 @@
#!/usr/bin/env python
import os
from setuptools import setup
from setuptools_rust import Binding, RustExtension
LONG_DESCRIPTION = """# bdkpython
The Python language bindings for the [Bitcoin Dev Kit](https://github.com/bitcoindevkit).
@@ -18,7 +15,7 @@ pip install bdkpython
import bdkpython as bdk
descriptor = "wpkh(tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy/84h/0h/0h/0/*)"
descriptor = bdk.Descriptor("wpkh(tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy/84h/0h/0h/0/*)", bdk.Network.TESTNET)
db_config = bdk.DatabaseConfig.MEMORY()
blockchain_config = bdk.BlockchainConfig.ELECTRUM(
bdk.ElectrumConfig(
@@ -26,7 +23,8 @@ blockchain_config = bdk.BlockchainConfig.ELECTRUM(
None,
5,
None,
100
100,
True,
)
)
blockchain = bdk.Blockchain(blockchain_config)
@@ -39,7 +37,7 @@ wallet = bdk.Wallet(
)
# print new receive address
address_info = wallet.get_address(bdk.AddressIndex.LAST_UNUSED)
address_info = wallet.get_address(bdk.AddressIndex.LAST_UNUSED())
address = address_info.address
index = address_info.index
print(f"New BIP84 testnet address: {address} at index {index}")
@@ -51,23 +49,20 @@ balance = wallet.get_balance()
print(f"Wallet balance is: {balance.total}")
"""
rust_ext = RustExtension(
target="bdkpython.bdkffi",
path="../bdk-ffi/Cargo.toml",
binding=Binding.NoBinding,
)
setup(
name='bdkpython',
version='0.25.0',
name="bdkpython",
version="0.29.0",
description="The Python language bindings for the Bitcoin Development Kit",
long_description=LONG_DESCRIPTION,
long_description_content_type='text/markdown',
rust_extensions=[rust_ext],
long_description_content_type="text/markdown",
include_package_data = True,
zip_safe=False,
packages=['bdkpython'],
package_dir={'bdkpython': './src/bdkpython'},
url="https://github.com/bitcoindevkit/bdk-python",
packages=["bdkpython"],
package_dir={"bdkpython": "./src/bdkpython"},
url="https://github.com/bitcoindevkit/bdk-ffi",
author="Alekos Filini <alekos.filini@gmail.com>, Steve Myers <steve@notmandatory.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
has_ext_modules=lambda: True,
)

View File

@@ -1 +1 @@
from bdkpython.bdk import *
from bdkpython.bdk import *

View File

View File

@@ -1,7 +1,8 @@
import bdkpython as bdk
import unittest
descriptor = "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)"
descriptor = bdk.Descriptor("wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)", bdk.Network.TESTNET)
db_config = bdk.DatabaseConfig.MEMORY()
blockchain_config = bdk.BlockchainConfig.ELECTRUM(
bdk.ElectrumConfig(
@@ -9,7 +10,8 @@ blockchain_config = bdk.BlockchainConfig.ELECTRUM(
None,
5,
None,
100
100,
True,
)
)
blockchain = bdk.Blockchain(blockchain_config)
@@ -24,8 +26,8 @@ class TestSimpleBip84Wallet(unittest.TestCase):
network=bdk.Network.TESTNET,
database_config=db_config
)
address_info = wallet.get_address(bdk.AddressIndex.LAST_UNUSED)
address = address_info.address
address_info = wallet.get_address(bdk.AddressIndex.LAST_UNUSED())
address = address_info.address.as_string()
# print(f"New address is {address}")
assert address == "tb1qzg4mckdh50nwdm9hkzq06528rsu73hjxxzem3e", f"Wrong address {address}, should be tb1qzg4mckdh50nwdm9hkzq06528rsu73hjxxzem3e"
@@ -41,6 +43,21 @@ class TestSimpleBip84Wallet(unittest.TestCase):
# print(f"Balance is {balance.total} sat")
assert balance.total > 0, "Balance is 0, send testnet coins to tb1qzg4mckdh50nwdm9hkzq06528rsu73hjxxzem3e"
def test_output_address_from_script_pubkey(self):
wallet = bdk.Wallet(
descriptor=descriptor,
change_descriptor=None,
network=bdk.Network.TESTNET,
database_config=db_config,
)
wallet.sync(blockchain, None)
first_tx = list(wallet.list_transactions(True))[0]
assert first_tx.txid == '35d3de8dd429ec4c9684168c1fbb9a4fb6db6f2ce89be214a024657a73ef4908'
output1, output2 = list(first_tx.transaction.output())
assert bdk.Address.from_script(output1.script_pubkey, bdk.Network.TESTNET).as_string() == 'tb1qw6ly2te8k9vy2mwj3g6gx82hj7hc8f5q3vry8t'
assert bdk.Address.from_script(output2.script_pubkey, bdk.Network.TESTNET).as_string() == 'tb1qzsvpnmme78yl60j7ldh9aqvhvxr4mz7mjpmh22'
if __name__ == '__main__':
unittest.main()

View File

@@ -10,7 +10,7 @@ deps =
-rrequirements.txt
-rrequirements-dev.txt
commands =
python3 setup.py -v build
python3 setup.py -v install
python3 setup.py build --verbose
python3 setup.py install --verbose
pytest --verbose --override-ini console_output_style=count
python3 setup.py --verbose bdist_wheel
python3 setup.py bdist_wheel --verbose

View File

@@ -29,7 +29,9 @@ let package = Package(
.binaryTarget(name: "bdkFFI", path: "./bdkFFI.xcframework"),
.target(
name: "BitcoinDevKit",
dependencies: ["bdkFFI"]),
dependencies: ["bdkFFI"],
swiftSettings: [.unsafeFlags(["-suppress-warnings"])]
),
.testTarget(
name: "BitcoinDevKitTests",
dependencies: ["BitcoinDevKit"]),

View File

@@ -5,14 +5,14 @@ This project builds a Swift package that provides [Swift] language bindings for
Supported target platforms are:
- MacOS, X86_64 and M1 (aarch64)
- macOS, X86_64 and M1 (aarch64)
- iOS, iPhones (aarch64)
- iOS simulator, X86_64 and M1 (aarch64)
## How to Use
To use the Swift language bindings for [`bdk`] in your [Xcode] iOS or MacOS project add
the github repository https://github.com/bitcoindevkit/bdk-swift and select one of the
To use the Swift language bindings for [`bdk`] in your [Xcode] iOS or macOS project add
the GitHub repository https://github.com/bitcoindevkit/bdk-swift and select one of the
release versions. You may then import and use the `BitcoinDevKit` library in your Swift
code. For example:
@@ -40,11 +40,11 @@ swift test
## How to Build and Publish
If you are a maintainer of this project or want to build and publish this project to your
own Github repository use the following steps:
own GitHub repository use the following steps:
1. If it doesn't already exist, create a new `release/0.MINOR` branch from the `master` branch.
2. Add a tag `v0.MINOR.PATCH`.
3. Run the `publish-spm` workflow on Github from the `bdk-swift` repo for version `0.MINOR.PATCH`.
3. Run the `publish-spm` workflow on GitHub from the `bdk-swift` repo for version `0.MINOR.PATCH`.
[Swift]: https://developer.apple.com/swift/
[Xcode]: https://developer.apple.com/documentation/Xcode

View File

@@ -3,10 +3,13 @@ import XCTest
final class BitcoinDevKitTests: XCTestCase {
func testMemoryWalletNewAddress() throws {
let desc = "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)"
let desc = try Descriptor(
descriptor: "wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)",
network: Network.regtest
)
let databaseConfig = DatabaseConfig.memory
let wallet = try Wallet.init(descriptor: desc, changeDescriptor: nil, network: Network.regtest, databaseConfig: databaseConfig)
let addressInfo = try wallet.getAddress(addressIndex: AddressIndex.new)
XCTAssertEqual(addressInfo.address, "bcrt1qzg4mckdh50nwdm9hkzq06528rsu73hjxytqkxs")
XCTAssertEqual(addressInfo.address.asString(), "bcrt1qzg4mckdh50nwdm9hkzq06528rsu73hjxytqkxs")
}
}

View File

@@ -5,25 +5,25 @@
#
# Run the script from the repo root directory, ie: ./bdk-swift/build-local-swift.sh
rustup install nightly-x86_64-apple-darwin
rustup component add rust-src --toolchain nightly-x86_64-apple-darwin
rustup install nightly-2023-04-10
rustup component add rust-src --toolchain nightly-2023-04-10
rustup target add aarch64-apple-ios x86_64-apple-ios
rustup target add aarch64-apple-ios-sim --toolchain nightly
rustup target add aarch64-apple-ios-sim --toolchain nightly-2023-04-10
rustup target add aarch64-apple-darwin x86_64-apple-darwin
pushd bdk-ffi
mkdir -p Sources/BitcoinDevKit
cargo run --package bdk-ffi-bindgen -- --language swift --out-dir ../bdk-swift/Sources/BitcoinDevKit
cargo run --bin uniffi-bindgen generate src/bdk.udl --language swift --out-dir ../bdk-swift/Sources/BitcoinDevKit --no-format
popd
cargo build --package bdk-ffi --profile release-smaller --target x86_64-apple-darwin
cargo build --package bdk-ffi --profile release-smaller --target aarch64-apple-darwin
cargo build --package bdk-ffi --profile release-smaller --target x86_64-apple-ios
cargo build --package bdk-ffi --profile release-smaller --target aarch64-apple-ios
cargo +nightly build --package bdk-ffi --release -Z build-std --target aarch64-apple-ios-sim
cargo +nightly-2023-04-10 build --package bdk-ffi --profile release-smaller --target aarch64-apple-ios-sim
mkdir -p target/lipo-ios-sim/release-smaller
lipo target/aarch64-apple-ios-sim/release/libbdkffi.a target/x86_64-apple-ios/release-smaller/libbdkffi.a -create -output target/lipo-ios-sim/release-smaller/libbdkffi.a
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