Compare commits

...

57 Commits

Author SHA1 Message Date
thunderbiscuit
d5cf483223 chore: update libraries to official alpha 11 release versions 2024-05-21 13:11:41 -04:00
thunderbiscuit
c6174199dd test: fix swift tests to print amount in sats 2024-05-21 12:06:55 -04:00
thunderbiscuit
9c45254c3e test: fix live android tests 2024-05-21 11:36:29 -04:00
thunderbiscuit
260a0a65b3 chore: bump library version to alpha 11 2024-05-21 11:35:46 -04:00
thunderbiscuit
72985f14ad tests: update python tests to use amount type 2024-05-21 10:30:48 -04:00
thunderbiscuit
5e3e24906f feat: add lock_time method on transaction type 2024-05-16 14:30:47 -04:00
thunderbiscuit
c702894143 feat: add output method on transaction type 2024-05-16 14:28:43 -04:00
thunderbiscuit
ecdd7c239b feat: add input method on transaction type 2024-05-16 14:03:28 -04:00
thunderbiscuit
ca8a3d0471 refactor: streamline blockchain clients error names 2024-05-16 10:41:04 -04:00
thunderbiscuit
8f4c80cb98 test: add electrum client test 2024-05-16 10:17:51 -04:00
thunderbiscuit
4aec4b0434 feat: add broadcast method on electrum client 2024-05-16 10:17:51 -04:00
thunderbiscuit
1913c45ef9 feat: add sync method on electrum client 2024-05-16 10:17:51 -04:00
thunderbiscuit
815fe5f62d feat: add full_scan method on electrum client 2024-05-16 10:17:50 -04:00
thunderbiscuit
8d30c86076 feat: add simple electrum client 2024-05-16 10:17:50 -04:00
thunderbiscuit
c88b33473b test: add memory wallet test 2024-05-16 10:16:58 -04:00
thunderbiscuit
79e7ab73ea feat: add memory wallet 2024-05-15 14:11:02 -04:00
Matthew
f169b1a52f chore: standard capitalization in error messages 2024-05-14 16:33:03 -05:00
Matthew
97d9bb6fbf chore: bump rust bdk to alpha 11 2024-05-14 14:44:26 -05:00
thunderbiscuit
f27bada9c9 test: better messages when tests fail for low balance 2024-05-10 12:45:56 -04:00
thunderbiscuit
1b0b50a954 test: use signet for live tests 2024-05-10 10:51:07 -04:00
thunderbiscuit
6e207802b2 ci: fix live tests workflow for swift library 2024-05-10 10:35:21 -04:00
thunderbiscuit
ac15ed7380 ci: explicitly use rust compiler 1.77.1 on python ci runs 2024-05-10 10:10:43 -04:00
thunderbiscuit
e9a76287c8 feat: expose commit method on wallet type 2024-05-08 12:22:49 -04:00
thunderbiscuit
72b5bfd4c9 feat: add sync and full_scan methods on esplora client 2024-05-07 15:49:50 -04:00
thunderbiscuit
19723240b7 chore: bump rust bdk to alpha 10 2024-05-07 15:46:49 -04:00
Matthew
7d951578d0 refactor: standardize justfile task names and parameters across projects 2024-05-06 14:57:26 -05:00
Matthew
b7fe91b003 refactor(error): alias types from external crates 2024-05-06 13:29:28 -05:00
thunderbiscuit
330dc96b8a build: migrate uniffi bindings files generation to library mode 2024-05-03 09:43:26 -04:00
thunderbiscuit
5557bb94ea fix: python test command in justfile 2024-05-03 09:43:20 -04:00
Matthew
9b5b96710e chore: add from to cannotconnecterror 2024-05-02 13:12:37 -05:00
Matthew
75d155c67a chore: add error tests 2024-05-01 15:48:26 -05:00
Matthew
6522dfdd26 chore: remove allow_shrinking 2024-05-01 11:31:28 -05:00
Matthew
ebaa6fda2f feat: add bumpfee finish related error 2024-05-01 11:31:27 -05:00
thunderbiscuit
5e8271e158 refactor: remove pre-1.0 code 2024-04-30 16:13:18 -04:00
thunderbiscuit
431ab90f04 fix: set rust compiler version before running cargo commands in python 2024-04-30 11:46:01 -04:00
thunderbiscuit
d4736a64d1 build: stop build if android ndk root is not defined for android lib 2024-04-30 10:53:15 -04:00
thunderbiscuit
f31678bf37 chore: bump development versions 2024-04-29 11:49:55 -04:00
thunderbiscuit
8130a419f2 fix: adjust errors on keys module 2024-04-29 09:47:37 -04:00
thunderbiscuit
262704751c feat: add check command to justfile 2024-04-29 09:39:37 -04:00
thunderbiscuit
00a8e1ba8b refactor: use tuple struct for psbt type 2024-04-29 09:39:37 -04:00
thunderbiscuit
d0514f678e refactor: use tuple struct for transaction type 2024-04-29 09:39:36 -04:00
thunderbiscuit
f6cc63539d refactor: use tuple struct for address type 2024-04-29 09:39:36 -04:00
thunderbiscuit
df64a96dd2 refactor: use tuple struct for descriptorpublickey type 2024-04-29 09:39:35 -04:00
thunderbiscuit
4cd6a80ce0 refactor: use tuple struct for descriptorsecretkey type 2024-04-29 09:37:24 -04:00
thunderbiscuit
e48af63fe6 refactor: use tuple struct for Mnemonic type 2024-04-29 09:34:13 -04:00
thunderbiscuit
eff4abcbfb feat: add weight method on transaction type 2024-04-29 09:20:31 -04:00
Matthew
e1a93379ce feat: add descriptor key related error 2024-04-26 14:46:40 -05:00
Matthew
e609b57bff feat: add finish related error 2024-04-25 17:07:20 -05:00
thunderbiscuit
282fcfce0a refactor: move feerate type to bitcoin module 2024-04-24 21:39:08 -04:00
thunderbiscuit
4dd4e91ccd fix: clippy warnings 2024-04-24 21:35:18 -04:00
thunderbiscuit
4f8b7f4ba5 build: use correct rustup command for ios library build script 2024-04-24 21:35:18 -04:00
thunderbiscuit
4d737d3393 build: use rust compiler 1.77.1 for all builds 2024-04-24 21:35:18 -04:00
thunderbiscuit
e14124b454 chore: bump dependencies in cargo lockfile 2024-04-24 21:35:12 -04:00
Matthew
0a75fc1279 feat: add derivation path related error 2024-04-24 13:59:23 -05:00
Matthew
6ac386c8df feat: add mnemonic related error 2024-04-23 14:05:57 -05:00
Matthew
126bc61df6 feat: add apply_update related error 2024-04-23 10:52:46 -05:00
Matthew
c63e7ad392 feat: add sign related error 2024-04-22 12:10:50 -05:00
58 changed files with 3221 additions and 1502 deletions

View File

@@ -17,7 +17,7 @@ jobs:
strategy:
matrix:
rust:
- version: 1.73.0
- version: 1.77.1
clippy: true
steps:
- name: "Checkout"

View File

@@ -27,8 +27,8 @@ jobs:
distribution: temurin
java-version: 11
- name: "Set default Rust version to 1.73.0"
run: rustup default 1.73.0
- name: "Set default Rust version to 1.77.1"
run: rustup default 1.77.1
- name: "Build bdk-jvm library"
run: |
@@ -48,7 +48,8 @@ jobs:
uses: actions/checkout@v3
- name: "Build Swift package"
run: bash ./bdk-swift/build-local-swift.sh
working-directory: bdk-swift
run: bash ./build-local-swift.sh
- name: "Run live Swift tests"
working-directory: bdk-swift
@@ -74,9 +75,11 @@ jobs:
uses: actions/checkout@v3
with:
submodules: true
- uses: actions-rs/toolchain@v1
- name: "Install Rust 1.77.1"
uses: actions-rs/toolchain@v1
with:
toolchain: stable
toolchain: 1.77.1
- name: "Generate bdk.py and binaries"
run: bash ./scripts/generate-linux.sh

View File

@@ -25,8 +25,8 @@ jobs:
distribution: temurin
java-version: 17
- name: "Set default Rust version to 1.73.0"
run: rustup default 1.73.0
- name: "Set default Rust version to 1.77.1"
run: rustup default 1.77.1
- name: "Install Rust Android targets"
run: rustup target add x86_64-linux-android aarch64-linux-android armv7-linux-androideabi

View File

@@ -24,8 +24,8 @@ jobs:
distribution: temurin
java-version: 17
- name: "Set default Rust version to 1.73.0"
run: rustup default 1.73.0
- name: "Set default Rust version to 1.77.1"
run: rustup default 1.77.1
- name: "Install aarch64 Rust target"
run: rustup target add aarch64-apple-darwin
@@ -54,8 +54,8 @@ jobs:
distribution: temurin
java-version: 17
- name: "Set default Rust version to 1.73.0"
run: rustup default 1.73.0
- name: "Set default Rust version to 1.77.1"
run: rustup default 1.77.1
- name: "Install x86_64-pc-windows-msvc Rust target"
run: rustup target add x86_64-pc-windows-msvc
@@ -94,8 +94,8 @@ jobs:
distribution: temurin
java-version: 17
- name: "Set default Rust version to 1.73.0"
run: rustup default 1.73.0
- name: "Set default Rust version to 1.77.1"
run: rustup default 1.77.1
- name: "Build bdk-jvm library"
run: |

View File

@@ -31,11 +31,12 @@ jobs:
uses: actions/checkout@v3
with:
submodules: true
# TODO 2: Other CI workflows use explicit Rust compiler versions, I think we should do the same here
- uses: actions-rs/toolchain@v1
with:
toolchain: stable
- name: "Install Rust 1.77.1"
uses: actions-rs/toolchain@v1
with:
toolchain: 1.77.1
- name: "Generate bdk.py and binaries"
run: bash ./scripts/generate-linux.sh

View File

@@ -37,8 +37,8 @@ jobs:
distribution: temurin
java-version: 17
- name: "Set default Rust version to 1.73.0"
run: rustup default 1.73.0
- name: "Set default Rust version to 1.77.1"
run: rustup default 1.77.1
- name: "Install Rust Android targets"
run: rustup target add x86_64-linux-android aarch64-linux-android armv7-linux-androideabi

View File

@@ -32,8 +32,8 @@ jobs:
distribution: temurin
java-version: 17
- name: "Set default Rust version to 1.73.0"
run: rustup default 1.73.0
- name: "Set default Rust version to 1.77.1"
run: rustup default 1.77.1
- name: "Run JVM tests"
run: |

View File

@@ -40,10 +40,12 @@ jobs:
uses: actions/checkout@v3
with:
submodules: true
- uses: actions-rs/toolchain@v1
with:
toolchain: stable
- name: "Install Rust 1.77.1"
uses: actions-rs/toolchain@v1
with:
toolchain: 1.77.1
- name: "Generate bdk.py and binaries"
run: bash ./scripts/generate-linux.sh

View File

@@ -3,6 +3,9 @@ Changelog information can also be found in each release's git tag (which can be
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [1.0.0-alpha.11]
This release adds the new `Amount` type, as well as more fine-grain errors.
## [1.0.0-alpha.7]
This release brings back into the 1.0 API a number of APIs from the 0.31 release, and adds the new flat file persistence feature, as well as more fine-grain errors.

View File

@@ -37,7 +37,7 @@ just publishlocal
```
## Minimum Supported Rust Version (MSRV)
This library should compile with any combination of features with Rust 1.73.0.
This library should compile with any combination of features with Rust 1.77.1.
## Contributing
To add new structs and functions, see the [UniFFI User Guide](https://mozilla.github.io/uniffi-rs/) and the [uniffi-examples](https://thunderbiscuit.github.io/uniffi-examples/) repository.

View File

@@ -38,22 +38,27 @@ _Note that Kotlin version `1.9.23` or later is required to build the library._
git clone https://github.com/bitcoindevkit/bdk-ffi
```
2. Follow the "General" bdk-ffi ["Getting Started (Developer)"] instructions.
3. Install Rust (note that we are currently building using Rust 1.73.0):
3. Install Rust (note that we are currently building using Rust 1.77.1):
```shell
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
rustup default 1.73.0
rustup default 1.77.1
```
4. Install required targets
```sh
rustup target add x86_64-linux-android aarch64-linux-android armv7-linux-androideabi
```
5. Install Android SDK and Build-Tools for API level 30+
6. Setup `$ANDROID_SDK_ROOT` and `$ANDROID_NDK_ROOT` path variables (which are required by the
build tool), for example (note that currently, NDK version 25.2.9519653 or above is required):
6. Setup `ANDROID_SDK_ROOT` and `ANDROID_NDK_ROOT` path variables which are required by the build tool. Note that currently, NDK version 25.2.9519653 or above is required. For example:
```shell
export ANDROID_SDK_ROOT=~/Android/Sdk
# macOS
export ANDROID_SDK_ROOT=~/Library/Android/sdk
export ANDROID_NDK_ROOT=$ANDROID_SDK_ROOT/ndk/25.2.9519653
# linux
export ANDROID_SDK_ROOT=/usr/local/lib/android/sdk
export ANDROID_NDK_ROOT=$ANDROID_SDK_ROOT/ndk/25.2.9519653
```
7. Build kotlin bindings
```sh
# build Android library

View File

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

View File

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

View File

@@ -8,10 +8,13 @@ import java.io.File
import kotlin.test.AfterTest
import kotlin.test.assertTrue
private const val SIGNET_ESPLORA_URL = "http://signet.bitcoindevkit.net"
private const val TESTNET_ESPLORA_URL = "https://esplora.testnet.kuutamo.cloud"
@RunWith(AndroidJUnit4::class)
class LiveTxBuilderTest {
private val persistenceFilePath = InstrumentationRegistry
.getInstrumentation().targetContext.filesDir.path + "/bdk_persistence.db"
.getInstrumentation().targetContext.filesDir.path + "/bdk_persistence3.db"
@AfterTest
fun cleanup() {
@@ -23,18 +26,22 @@ class LiveTxBuilderTest {
@Test
fun testTxBuilder() {
val descriptor = Descriptor("wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)", Network.TESTNET)
val wallet = Wallet(descriptor, null, persistenceFilePath, Network.TESTNET)
val esploraClient = EsploraClient("https://esplora.testnet.kuutamo.cloud/")
val update = esploraClient.fullScan(wallet, 10uL, 1uL)
val descriptor = Descriptor("wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)", Network.SIGNET)
val wallet = Wallet(descriptor, null, persistenceFilePath, Network.SIGNET)
val esploraClient: EsploraClient = EsploraClient(SIGNET_ESPLORA_URL)
val fullScanRequest: FullScanRequest = wallet.startFullScan()
val update = esploraClient.fullScan(fullScanRequest, 10uL, 1uL)
wallet.applyUpdate(update)
println("Balance: ${wallet.getBalance().total}")
wallet.commit()
println("Balance: ${wallet.getBalance().total.toSat()}")
assert(wallet.getBalance().total > 0uL)
assert(wallet.getBalance().total.toSat() > 0uL) {
"Wallet balance must be greater than 0! Please send funds to ${wallet.revealNextAddress(KeychainKind.EXTERNAL).address.asString()} and try again."
}
val recipient: Address = Address("tb1qrnfslnrve9uncz9pzpvf83k3ukz22ljgees989", Network.TESTNET)
val recipient: Address = Address("tb1qrnfslnrve9uncz9pzpvf83k3ukz22ljgees989", Network.SIGNET)
val psbt: Psbt = TxBuilder()
.addRecipient(recipient.scriptPubkey(), 4200uL)
.addRecipient(recipient.scriptPubkey(), Amount.fromSat(4200uL))
.feeRate(FeeRate.fromSatPerVb(2uL))
.finish(wallet)
@@ -44,21 +51,25 @@ class LiveTxBuilderTest {
@Test
fun complexTxBuilder() {
val externalDescriptor = Descriptor("wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)", Network.TESTNET)
val changeDescriptor = Descriptor("wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/1/*)", Network.TESTNET)
val wallet = Wallet(externalDescriptor, changeDescriptor, persistenceFilePath, Network.TESTNET)
val esploraClient = EsploraClient("https://esplora.testnet.kuutamo.cloud/")
val update = esploraClient.fullScan(wallet, 10uL, 1uL)
val externalDescriptor = Descriptor("wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)", Network.SIGNET)
val changeDescriptor = Descriptor("wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/1/*)", Network.SIGNET)
val wallet = Wallet(externalDescriptor, changeDescriptor, persistenceFilePath, Network.SIGNET)
val esploraClient: EsploraClient = EsploraClient(SIGNET_ESPLORA_URL)
val fullScanRequest: FullScanRequest = wallet.startFullScan()
val update = esploraClient.fullScan(fullScanRequest, 10uL, 1uL)
wallet.applyUpdate(update)
println("Balance: ${wallet.getBalance().total}")
wallet.commit()
println("Balance: ${wallet.getBalance().total.toSat()}")
assert(wallet.getBalance().total > 0uL)
assert(wallet.getBalance().total.toSat() > 0uL) {
"Wallet balance must be greater than 0! Please send funds to ${wallet.revealNextAddress(KeychainKind.EXTERNAL).address.asString()} and try again."
}
val recipient1: Address = Address("tb1qrnfslnrve9uncz9pzpvf83k3ukz22ljgees989", Network.TESTNET)
val recipient2: Address = Address("tb1qw2c3lxufxqe2x9s4rdzh65tpf4d7fssjgh8nv6", Network.TESTNET)
val recipient1: Address = Address("tb1qrnfslnrve9uncz9pzpvf83k3ukz22ljgees989", Network.SIGNET)
val recipient2: Address = Address("tb1qw2c3lxufxqe2x9s4rdzh65tpf4d7fssjgh8nv6", Network.SIGNET)
val allRecipients: List<ScriptAmount> = listOf(
ScriptAmount(recipient1.scriptPubkey(), 4200uL),
ScriptAmount(recipient2.scriptPubkey(), 4200uL),
ScriptAmount(recipient1.scriptPubkey(), Amount.fromSat(4200uL)),
ScriptAmount(recipient2.scriptPubkey(), Amount.fromSat(4200uL)),
)
val psbt: Psbt = TxBuilder()

View File

@@ -8,10 +8,13 @@ import java.io.File
import kotlin.test.AfterTest
import kotlin.test.assertTrue
private const val SIGNET_ESPLORA_URL = "http://signet.bitcoindevkit.net"
private const val TESTNET_ESPLORA_URL = "https://esplora.testnet.kuutamo.cloud"
@RunWith(AndroidJUnit4::class)
class LiveWalletTest {
private val persistenceFilePath = InstrumentationRegistry
.getInstrumentation().targetContext.filesDir.path + "/bdk_persistence.db"
.getInstrumentation().targetContext.filesDir.path + "/bdk_persistence2.db"
@AfterTest
fun cleanup() {
@@ -23,16 +26,20 @@ class LiveWalletTest {
@Test
fun testSyncedBalance() {
val descriptor: Descriptor = Descriptor("wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)", Network.TESTNET)
val wallet: Wallet = Wallet(descriptor, null, persistenceFilePath, Network.TESTNET)
val esploraClient: EsploraClient = EsploraClient("https://esplora.testnet.kuutamo.cloud/")
val update = esploraClient.fullScan(wallet, 10uL, 1uL)
val descriptor: Descriptor = Descriptor("wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)", Network.SIGNET)
val wallet: Wallet = Wallet(descriptor, null, persistenceFilePath, Network.SIGNET)
val esploraClient: EsploraClient = EsploraClient(SIGNET_ESPLORA_URL)
val fullScanRequest: FullScanRequest = wallet.startFullScan()
val update = esploraClient.fullScan(fullScanRequest, 10uL, 1uL)
wallet.applyUpdate(update)
println("Balance: ${wallet.getBalance().total}")
wallet.commit()
println("Balance: ${wallet.getBalance().total.toSat()}")
val balance: Balance = wallet.getBalance()
println("Balance: $balance")
assert(wallet.getBalance().total > 0uL)
assert(wallet.getBalance().total.toSat() > 0uL) {
"Wallet balance must be greater than 0! Please send funds to ${wallet.revealNextAddress(KeychainKind.EXTERNAL).address.asString()} and try again."
}
println("Transactions count: ${wallet.transactions().count()}")
val transactions = wallet.transactions().take(3)
@@ -46,23 +53,23 @@ class LiveWalletTest {
@Test
fun testBroadcastTransaction() {
val descriptor = Descriptor("wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)", Network.TESTNET)
val wallet = Wallet(descriptor, null, persistenceFilePath, Network.TESTNET)
val esploraClient = EsploraClient("https://esplora.testnet.kuutamo.cloud/")
val update = esploraClient.fullScan(wallet, 10uL, 1uL)
val descriptor = Descriptor("wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)", Network.SIGNET)
val wallet = Wallet(descriptor, null, persistenceFilePath, Network.SIGNET)
val esploraClient = EsploraClient(SIGNET_ESPLORA_URL)
val fullScanRequest: FullScanRequest = wallet.startFullScan()
val update = esploraClient.fullScan(fullScanRequest, 10uL, 1uL)
wallet.applyUpdate(update)
println("Balance: ${wallet.getBalance().total}")
println("New address: ${wallet.getAddress(AddressIndex.New).address}")
wallet.commit()
println("Balance: ${wallet.getBalance().total.toSat()}")
assert(wallet.getBalance().total > 0uL) {
"Wallet balance must be greater than 0! Please send funds to ${wallet.getAddress(AddressIndex.New).address} and try again."
assert(wallet.getBalance().total.toSat() > 0uL) {
"Wallet balance must be greater than 0! Please send funds to ${wallet.revealNextAddress(KeychainKind.EXTERNAL).address} and try again."
}
val recipient: Address = Address("tb1qrnfslnrve9uncz9pzpvf83k3ukz22ljgees989", Network.TESTNET)
val recipient: Address = Address("tb1qrnfslnrve9uncz9pzpvf83k3ukz22ljgees989", Network.SIGNET)
val psbt: Psbt = TxBuilder()
.addRecipient(recipient.scriptPubkey(), 4200uL)
.addRecipient(recipient.scriptPubkey(), Amount.fromSat(4200uL))
.feeRate(FeeRate.fromSatPerVb(4uL))
.finish(wallet)
@@ -76,7 +83,7 @@ class LiveWalletTest {
println("Txid is: ${tx.txid()}")
val txFee: ULong = wallet.calculateFee(tx)
println("Tx fee is: ${txFee}")
println("Tx fee is: $txFee")
val feeRate: FeeRate = wallet.calculateFeeRate(tx)
println("Tx fee rate is: ${feeRate.toSatPerVbCeil()} sat/vB")

View File

@@ -13,7 +13,7 @@ import kotlin.test.AfterTest
@RunWith(AndroidJUnit4::class)
class OfflineWalletTest {
private val persistenceFilePath = InstrumentationRegistry
.getInstrumentation().targetContext.filesDir.path + "/bdk_persistence.db"
.getInstrumentation().targetContext.filesDir.path + "/bdk_persistence1.db"
@AfterTest
fun cleanup() {
@@ -44,7 +44,7 @@ class OfflineWalletTest {
persistenceFilePath,
Network.TESTNET
)
val addressInfo: AddressInfo = wallet.getAddress(AddressIndex.New)
val addressInfo: AddressInfo = wallet.revealNextAddress(KeychainKind.EXTERNAL)
assertTrue(addressInfo.address.isValidForNetwork(Network.TESTNET), "Address is not valid for testnet network")
assertTrue(addressInfo.address.isValidForNetwork(Network.SIGNET), "Address is not valid for signet network")
@@ -72,7 +72,7 @@ class OfflineWalletTest {
assertEquals(
expected = 0uL,
actual = wallet.getBalance().total
actual = wallet.getBalance().total.toSat()
)
}
}

View File

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

View File

@@ -17,6 +17,11 @@ internal class UniFfiAndroidPlugin : Plugin<Project> {
OS.OTHER -> throw Error("Cannot build Android library from current architecture")
}
// if ANDROID_NDK_ROOT is not set, stop build
if (System.getenv("ANDROID_NDK_ROOT") == null) {
throw IllegalStateException("ANDROID_NDK_ROOT environment variable is not set; cannot build library")
}
// arm64-v8a is the most popular hardware architecture for Android
val buildAndroidAarch64Binary by tasks.register<Exec>("buildAndroidAarch64Binary") {
@@ -26,13 +31,6 @@ internal class UniFfiAndroidPlugin : Plugin<Project> {
executable("cargo")
args(cargoArgs)
// if ANDROID_NDK_ROOT is not set then set it to github actions default
if (System.getenv("ANDROID_NDK_ROOT") == null) {
environment(
Pair("ANDROID_NDK_ROOT", "${System.getenv("ANDROID_SDK_ROOT")}/ndk-bundle")
)
}
environment(
// add build toolchain to PATH
Pair("PATH", "${System.getenv("PATH")}:${System.getenv("ANDROID_NDK_ROOT")}/toolchains/llvm/prebuilt/$llvmArchPath/bin"),
@@ -56,13 +54,6 @@ internal class UniFfiAndroidPlugin : Plugin<Project> {
executable("cargo")
args(cargoArgs)
// if ANDROID_NDK_ROOT is not set then set it to github actions default
if (System.getenv("ANDROID_NDK_ROOT") == null) {
environment(
Pair("ANDROID_NDK_ROOT", "${System.getenv("ANDROID_SDK_ROOT")}/ndk-bundle")
)
}
environment(
// add build toolchain to PATH
Pair("PATH", "${System.getenv("PATH")}:${System.getenv("ANDROID_NDK_ROOT")}/toolchains/llvm/prebuilt/$llvmArchPath/bin"),
@@ -86,13 +77,6 @@ internal class UniFfiAndroidPlugin : Plugin<Project> {
executable("cargo")
args(cargoArgs)
// if ANDROID_NDK_ROOT is not set then set it to github actions default
if (System.getenv("ANDROID_NDK_ROOT") == null) {
environment(
Pair("ANDROID_NDK_ROOT", "${System.getenv("ANDROID_SDK_ROOT")}/ndk-bundle")
)
}
environment(
// add build toolchain to PATH
Pair("PATH", "${System.getenv("PATH")}:${System.getenv("ANDROID_NDK_ROOT")}/toolchains/llvm/prebuilt/$llvmArchPath/bin"),
@@ -139,14 +123,14 @@ internal class UniFfiAndroidPlugin : Plugin<Project> {
val generateAndroidBindings by tasks.register<Exec>("generateAndroidBindings") {
dependsOn(moveNativeAndroidLibs)
// val libraryPath = "${project.projectDir}/../../bdk-ffi/target/aarch64-linux-android/release-smaller/libbdkffi.so"
// workingDir("${project.projectDir}/../../bdk-ffi")
// val cargoArgs: List<String> = listOf("run", "--bin", "uniffi-bindgen", "generate", "--library", libraryPath, "--language", "kotlin", "--out-dir", "../bdk-android/lib/src/main/kotlin", "--no-format")
val libraryPath = "${project.projectDir}/../../bdk-ffi/target/aarch64-linux-android/release-smaller/libbdkffi.so"
workingDir("${project.projectDir}/../../bdk-ffi")
val cargoArgs: List<String> = listOf("run", "--bin", "uniffi-bindgen", "generate", "--library", libraryPath, "--language", "kotlin", "--out-dir", "../bdk-android/lib/src/main/kotlin", "--no-format")
// The code above worked for uniffi 0.24.3 using the --library flag
// The code below works for uniffi 0.23.0
workingDir("${project.projectDir}/../../bdk-ffi")
val cargoArgs: List<String> = listOf("run", "--bin", "uniffi-bindgen", "generate", "src/bdk.udl", "--language", "kotlin", "--config", "uniffi-android.toml", "--out-dir", "../bdk-android/lib/src/main/kotlin", "--no-format")
// workingDir("${project.projectDir}/../../bdk-ffi")
// val cargoArgs: List<String> = listOf("run", "--bin", "uniffi-bindgen", "generate", "src/bdk.udl", "--language", "kotlin", "--config", "uniffi-android.toml", "--out-dir", "../bdk-android/lib/src/main/kotlin", "--no-format")
executable("cargo")
args(cargoArgs)

283
bdk-ffi/Cargo.lock generated
View File

@@ -4,9 +4,9 @@ version = 3
[[package]]
name = "anstream"
version = "0.6.11"
version = "0.6.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e2e1ebcb11de5c03c67de28a7df593d32191b44939c482e97702baaaa6ab6a5"
checksum = "d96bd03f33fe50a863e394ee9718a706f988b9079b20c3784fb726e7678b62fb"
dependencies = [
"anstyle",
"anstyle-parse",
@@ -18,9 +18,9 @@ dependencies = [
[[package]]
name = "anstyle"
version = "1.0.5"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2faccea4cc4ab4a667ce676a30e8ec13922a692c99bb8f5b11f1502c72e04220"
checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc"
[[package]]
name = "anstyle-parse"
@@ -52,9 +52,9 @@ dependencies = [
[[package]]
name = "anyhow"
version = "1.0.79"
version = "1.0.82"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca"
checksum = "f538837af36e6f6a9be0faa67f9a314f8119e4e4b5867c6ab40ed60360142519"
[[package]]
name = "arrayvec"
@@ -111,9 +111,9 @@ checksum = "9b34d609dfbaf33d6889b2b7106d3ca345eacad44200913df5ba02bfd31d2ba9"
[[package]]
name = "autocfg"
version = "1.1.0"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80"
[[package]]
name = "base64"
@@ -129,20 +129,22 @@ checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
[[package]]
name = "basic-toml"
version = "0.1.8"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2db21524cad41c5591204d22d75e1970a2d1f71060214ca931dc7d5afe2c14e5"
checksum = "823388e228f614e9558c6804262db37960ec8821856535f5c3f59913140558f8"
dependencies = [
"serde",
]
[[package]]
name = "bdk"
version = "1.0.0-alpha.9"
version = "1.0.0-alpha.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ffe2761cc729d09bcaf88fe6e283ff5b5af0398c0eee855469295b59d6c6a9d"
checksum = "65c23f2903ac5dbb7b35934ae319aadc946201e4fa51b652440bd1c8fa3080ee"
dependencies = [
"anyhow",
"bdk_chain",
"bdk_persist",
"bip39",
"bitcoin",
"getrandom",
@@ -155,23 +157,23 @@ dependencies = [
[[package]]
name = "bdk-ffi"
version = "1.0.0-alpha.9"
version = "1.0.0-alpha.11"
dependencies = [
"assert_matches",
"bdk",
"bdk_electrum",
"bdk_esplora",
"bdk_file_store",
"bitcoin-internals",
"esplora-client",
"thiserror",
"uniffi",
]
[[package]]
name = "bdk_chain"
version = "0.12.0"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4cd4e915470c6bb196a4c2515c6db3190dcdec482255d8f1022068646641cc8f"
checksum = "440ec5b1c8911f126b540e05c98493b699b497a3cb90c5e9c5eee21cdd8d1e01"
dependencies = [
"bitcoin",
"miniscript",
@@ -179,10 +181,20 @@ dependencies = [
]
[[package]]
name = "bdk_esplora"
version = "0.11.0"
name = "bdk_electrum"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7b5358eb90cbf99f7725ec5b1786e25a869cacba555f74f70cc4bf453921c34"
checksum = "44bbf3b0031651a37a48bdfab0c1d96a305b587f616593d34df9b1ff63efc4ff"
dependencies = [
"bdk_chain",
"electrum-client",
]
[[package]]
name = "bdk_esplora"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9fb5b46f8c256bc083640342bd0d35ec1963971f18800c3fee1a9189eda60ecd"
dependencies = [
"bdk_chain",
"esplora-client",
@@ -190,15 +202,27 @@ dependencies = [
[[package]]
name = "bdk_file_store"
version = "0.9.0"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c19806b6b4e898e351e0bc2d8bd4cb96f7d8e2730c8b277e9e889c72dfb32de"
checksum = "5dfd7e9a5edb8d384ea1836b0bcd4febdd3211815acc058d64c7e284776d69ab"
dependencies = [
"anyhow",
"bdk_chain",
"bdk_persist",
"bincode",
"serde",
]
[[package]]
name = "bdk_persist"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aba103c2108dd0f0b452650043d21c449ae07ce866dbaea29a9c59899a5964f0"
dependencies = [
"anyhow",
"bdk_chain",
]
[[package]]
name = "bech32"
version = "0.10.0-beta"
@@ -269,15 +293,21 @@ dependencies = [
[[package]]
name = "bumpalo"
version = "3.14.0"
version = "3.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec"
checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
[[package]]
name = "byteorder"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "bytes"
version = "1.5.0"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223"
checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9"
[[package]]
name = "camino"
@@ -290,9 +320,9 @@ dependencies = [
[[package]]
name = "cargo-platform"
version = "0.1.6"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ceed8ef69d8518a5dda55c07425450b58a4e1946f4951eab6d7191ee86c2443d"
checksum = "24b1f0365a6c6bb4020cd05806fd0d33c44d38046b8bd7f0e40814b9763cabfc"
dependencies = [
"serde",
]
@@ -313,12 +343,9 @@ dependencies = [
[[package]]
name = "cc"
version = "1.0.83"
version = "1.0.94"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0"
dependencies = [
"libc",
]
checksum = "17f6e324229dc011159fcc089755d1e2e216a90d43a7dea6853ca740b84f35e7"
[[package]]
name = "cfg-if"
@@ -328,9 +355,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "clap"
version = "4.4.18"
version = "4.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e578d6ec4194633722ccf9544794b71b1385c3c027efe0c55db226fc880865c"
checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0"
dependencies = [
"clap_builder",
"clap_derive",
@@ -338,9 +365,9 @@ dependencies = [
[[package]]
name = "clap_builder"
version = "4.4.18"
version = "4.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4df4df40ec50c46000231c914968278b1eb05098cf8f1b3a518a95030e71d1c7"
checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4"
dependencies = [
"anstream",
"anstyle",
@@ -350,11 +377,11 @@ dependencies = [
[[package]]
name = "clap_derive"
version = "4.4.7"
version = "4.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442"
checksum = "528131438037fd55894f62d6e9f068b8f45ac57ffa77517819645d10aed04f64"
dependencies = [
"heck",
"heck 0.5.0",
"proc-macro2",
"quote",
"syn",
@@ -362,9 +389,9 @@ dependencies = [
[[package]]
name = "clap_lex"
version = "0.6.0"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1"
checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce"
[[package]]
name = "colorchoice"
@@ -372,6 +399,23 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
[[package]]
name = "electrum-client"
version = "0.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89008f106be6f303695522f2f4c1f28b40c3e8367ed8b3bb227f1f882cb52cc2"
dependencies = [
"bitcoin",
"byteorder",
"libc",
"log",
"rustls",
"serde",
"serde_json",
"webpki-roots",
"winapi",
]
[[package]]
name = "esplora-client"
version = "0.7.0"
@@ -396,9 +440,9 @@ dependencies = [
[[package]]
name = "getrandom"
version = "0.2.12"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5"
checksum = "94b22e06ecb0110981051723910cbf0b5f5e09a2062dd7663334ee79a9d1286c"
dependencies = [
"cfg-if",
"libc",
@@ -428,6 +472,12 @@ version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
[[package]]
name = "heck"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "hex-conservative"
version = "0.1.1"
@@ -451,36 +501,36 @@ checksum = "3011d1213f159867b13cfd6ac92d2cd5f1345762c63be3554e84092d85a50bbd"
[[package]]
name = "itoa"
version = "1.0.10"
version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c"
checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
[[package]]
name = "js-sys"
version = "0.3.67"
version = "0.3.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a1d36f1235bc969acba30b7f5990b864423a6068a10f7c90ae8f0112e3a59d1"
checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d"
dependencies = [
"wasm-bindgen",
]
[[package]]
name = "libc"
version = "0.2.152"
version = "0.2.153"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7"
checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
[[package]]
name = "log"
version = "0.4.20"
version = "0.4.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c"
[[package]]
name = "memchr"
version = "2.7.1"
version = "2.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149"
checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d"
[[package]]
name = "mime"
@@ -574,18 +624,18 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
[[package]]
name = "proc-macro2"
version = "1.0.78"
version = "1.0.81"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae"
checksum = "3d1597b0c024618f09a9c3b8655b7e430397a36d23fdafec26d6965e9eec3eba"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.35"
version = "1.0.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7"
dependencies = [
"proc-macro2",
]
@@ -637,9 +687,9 @@ dependencies = [
[[package]]
name = "rustls"
version = "0.21.10"
version = "0.21.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f9d5a6813c0759e4609cd494e8e725babae6a2ca7b62a5536a13daaec6fcb7ba"
checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e"
dependencies = [
"log",
"ring",
@@ -659,9 +709,9 @@ dependencies = [
[[package]]
name = "ryu"
version = "1.0.16"
version = "1.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c"
checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1"
[[package]]
name = "scroll"
@@ -716,27 +766,27 @@ dependencies = [
[[package]]
name = "semver"
version = "1.0.21"
version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b97ed7a9823b74f99c7742f5336af7be5ecd3eeafcb1507d1fa93347b1d589b0"
checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca"
dependencies = [
"serde",
]
[[package]]
name = "serde"
version = "1.0.196"
version = "1.0.198"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "870026e60fa08c69f064aa766c10f10b1d62db9ccd4d0abb206472bee0ce3b32"
checksum = "9846a40c979031340571da2545a4e5b7c4163bdae79b301d5f86d03979451fcc"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.196"
version = "1.0.198"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67"
checksum = "e88edab869b01783ba905e7d0153f9fc1a6505a96e4ad3018011eedb838566d9"
dependencies = [
"proc-macro2",
"quote",
@@ -745,9 +795,9 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.113"
version = "1.0.116"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69801b70b1c3dac963ecb03a364ba0ceda9cf60c71cfe475e99864759c8b8a79"
checksum = "3e17db7126d17feb94eb3fad46bf1a96b034e8aacbc2e775fe81505f8b0b2813"
dependencies = [
"itoa",
"ryu",
@@ -780,15 +830,15 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]]
name = "strsim"
version = "0.10.0"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
name = "syn"
version = "2.0.48"
version = "2.0.60"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f"
checksum = "909518bc7b1c9b779f1bbf07f2929d35af9f0f37e47c6e9ef7f9dddc1e1821f3"
dependencies = [
"proc-macro2",
"quote",
@@ -797,9 +847,9 @@ dependencies = [
[[package]]
name = "textwrap"
version = "0.16.0"
version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d"
checksum = "23d434d3f8967a09480fb04132ebe0a3e088c173e6d0ee7897abbdf4eab0f8b9"
dependencies = [
"smawk",
"unicode-linebreak",
@@ -915,7 +965,7 @@ dependencies = [
"fs-err",
"glob",
"goblin",
"heck",
"heck 0.4.1",
"once_cell",
"paste",
"serde",
@@ -1046,9 +1096,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "wasm-bindgen"
version = "0.2.90"
version = "0.2.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1223296a201415c7fad14792dbefaace9bd52b62d33453ade1c5b5f07555406"
checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8"
dependencies = [
"cfg-if",
"wasm-bindgen-macro",
@@ -1056,9 +1106,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.90"
version = "0.2.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fcdc935b63408d58a32f8cc9738a0bffd8f05cc7c002086c6ef20b7312ad9dcd"
checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da"
dependencies = [
"bumpalo",
"log",
@@ -1071,9 +1121,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.90"
version = "0.2.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e4c238561b2d428924c49815533a8b9121c664599558a5d9ec51f8a1740a999"
checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
@@ -1081,9 +1131,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.90"
version = "0.2.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bae1abb6806dc1ad9e560ed242107c0f6c84335f1749dd4e8ddb012ebd5e25a7"
checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7"
dependencies = [
"proc-macro2",
"quote",
@@ -1094,9 +1144,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.90"
version = "0.2.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4d91413b1c31d7539ba5ef2451af3f0b833a005eb27a631cec32bc0635a8602b"
checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96"
[[package]]
name = "webpki-roots"
@@ -1113,6 +1163,28 @@ dependencies = [
"nom",
]
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-sys"
version = "0.52.0"
@@ -1124,13 +1196,14 @@ dependencies = [
[[package]]
name = "windows-targets"
version = "0.52.0"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd"
checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_gnullvm",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
@@ -1139,42 +1212,48 @@ dependencies = [
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.0"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea"
checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.0"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef"
checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6"
[[package]]
name = "windows_i686_gnu"
version = "0.52.0"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313"
checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9"
[[package]]
name = "windows_i686_msvc"
version = "0.52.0"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a"
checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.0"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd"
checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.0"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e"
checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.0"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04"
checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0"

View File

@@ -1,6 +1,6 @@
[package]
name = "bdk-ffi"
version = "1.0.0-alpha.9"
version = "1.0.0-alpha.11"
homepage = "https://bitcoindevkit.org"
repository = "https://github.com/bitcoindevkit/bdk"
edition = "2018"
@@ -18,11 +18,10 @@ path = "uniffi-bindgen.rs"
default = ["uniffi/cli"]
[dependencies]
bdk = { version = "1.0.0-alpha.9", features = ["all-keys", "keys-bip39"] }
bdk_esplora = { version = "0.11.0", default-features = false, features = ["std", "blocking"] }
esplora-client = { version = "0.7.0", default-features = false, features = ["blocking-https-rustls"] }
# bdk_esplora = { version = "0.10.0", default-features = false, features = ["std", "blocking", "async-https-rustls"] }
bdk_file_store = { version = "0.9.0" }
bdk = { version = "1.0.0-alpha.11", features = ["all-keys", "keys-bip39"] }
bdk_esplora = { version = "0.13.0", default-features = false, features = ["std", "blocking", "blocking-https-rustls"] }
bdk_electrum = { version = "0.13.0" }
bdk_file_store = { version = "0.11.0" }
uniffi = { version = "=0.26.1" }
bitcoin-internals = { version = "0.2.0", features = ["alloc"] }

View File

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

View File

@@ -5,8 +5,40 @@ namespace bdk {};
// ------------------------------------------------------------------------
[Error]
enum Alpha3Error {
"Generic"
interface AddressError {
Base58();
Bech32();
WitnessVersion(string error_message);
WitnessProgram(string error_message);
UncompressedPubkey();
ExcessiveScriptSize();
UnrecognizedScript();
NetworkValidation(Network required, Network found, string address);
OtherAddressErr();
};
[Error]
interface Bip32Error {
CannotDeriveFromHardenedKey();
Secp256k1(string error_message);
InvalidChildNumber(u32 child_number);
InvalidChildNumberFormat();
InvalidDerivationPathFormat();
UnknownVersion(string version);
WrongExtendedKeyLength(u32 length);
Base58(string error_message);
Hex(string error_message);
InvalidPublicKeyHexLength(u32 length);
UnknownError(string error_message);
};
[Error]
interface Bip39Error {
BadWordCount(u64 word_count);
UnknownWord(u64 index);
BadEntropyBitCount(u64 bit_count);
InvalidChecksum();
AmbiguousLanguages(string languages);
};
[Error]
@@ -16,20 +48,78 @@ interface CalculateFeeError {
};
[Error]
interface WalletCreationError {
Io(string e);
InvalidMagicBytes(sequence<u8> got, sequence<u8> expected);
Descriptor();
Write();
Load();
NotInitialized();
LoadedGenesisDoesNotMatch();
LoadedNetworkDoesNotMatch(Network expected, Network? got);
interface CannotConnectError {
Include(u32 height);
};
[Error]
interface PersistenceError {
Write(string e);
interface CreateTxError {
Descriptor(string error_message);
Persist(string error_message);
Policy(string error_message);
SpendingPolicyRequired(string kind);
Version0();
Version1Csv();
LockTime(string requested, string required);
RbfSequence();
RbfSequenceCsv(string rbf, string csv);
FeeTooLow(u64 required);
FeeRateTooLow(string required);
NoUtxosSelected();
OutputBelowDustLimit(u64 index);
ChangePolicyDescriptor();
CoinSelection(string error_message);
InsufficientFunds(u64 needed, u64 available);
NoRecipients();
Psbt(string error_message);
MissingKeyOrigin(string key);
UnknownUtxo(string outpoint);
MissingNonWitnessUtxo(string outpoint);
MiniscriptPsbt(string error_message);
};
[Error]
interface DescriptorError {
InvalidHdKeyPath();
InvalidDescriptorChecksum();
HardenedDerivationXpub();
MultiPath();
Key(string error_message);
Policy(string error_message);
InvalidDescriptorCharacter(string char);
Bip32(string error_message);
Base58(string error_message);
Pk(string error_message);
Miniscript(string error_message);
Hex(string error_message);
};
[Error]
interface DescriptorKeyError {
Parse(string error_message);
InvalidKeyType();
Bip32(string error_message);
};
[Error]
interface ElectrumError {
IOError(string error_message);
Json(string error_message);
Hex(string error_message);
Protocol(string error_message);
Bitcoin(string error_message);
AlreadySubscribed();
NotSubscribed();
InvalidResponse(string error_message);
Message(string error_message);
InvalidDNSNameError(string domain);
MissingDomain();
AllAttemptsErrored();
SharedIOError(string error_message);
CouldntLockReader();
Mpsc();
CouldNotCreateConnection(string error_message);
RequestAlreadyConsumed();
};
[Error]
@@ -46,6 +136,15 @@ interface EsploraError {
HeaderHashNotFound();
InvalidHttpHeaderName(string name);
InvalidHttpHeaderValue(string value);
RequestAlreadyConsumed();
};
[Error]
interface ExtractTxError {
AbsurdFeeRate(u64 fee_rate);
MissingInputValue();
SendingTooMuch();
OtherExtractTxErr();
};
[Error]
@@ -54,16 +153,45 @@ enum FeeRateError {
};
[Error]
interface AddressError {
Base58();
Bech32();
WitnessVersion(string error_message);
WitnessProgram(string error_message);
UncompressedPubkey();
ExcessiveScriptSize();
UnrecognizedScript();
NetworkValidation(Network required, Network found, string address);
OtherAddressError();
interface ParseAmountError {
Negative();
TooBig();
TooPrecise();
InvalidFormat();
InputTooLarge();
InvalidCharacter(string error_message);
UnknownDenomination(string error_message);
PossiblyConfusingDenomination(string error_message);
OtherParseAmountErr();
};
[Error]
interface PersistenceError {
Write(string error_message);
};
[Error]
interface PsbtParseError {
PsbtEncoding(string error_message);
Base64Encoding(string error_message);
};
[Error]
interface SignerError {
MissingKey();
InvalidKey();
UserCanceled();
InputIndexOutOfRange();
MissingNonWitnessUtxo();
InvalidNonWitnessUtxo();
MissingWitnessUtxo();
MissingWitnessScript();
MissingHdKeypath();
NonStandardSighash();
InvalidSighash();
SighashError(string error_message);
MiniscriptPsbt(string error_message);
External(string error_message);
};
[Error]
@@ -74,29 +202,7 @@ interface TransactionError {
NonMinimalVarInt();
ParseFailed();
UnsupportedSegwitFlag(u8 flag);
OtherTransactionError();
};
[Error]
interface PsbtParseError {
PsbtEncoding(string e);
Base64Encoding(string e);
};
[Error]
interface DescriptorError {
InvalidHdKeyPath();
InvalidDescriptorChecksum();
HardenedDerivationXpub();
MultiPath();
Key(string e);
Policy(string e);
InvalidDescriptorCharacter(string char);
Bip32(string e);
Base58(string e);
Pk(string e);
Miniscript(string e);
Hex(string e);
OtherTransactionErr();
};
[Error]
@@ -105,11 +211,15 @@ interface TxidParseError {
};
[Error]
interface ExtractTxError {
AbsurdFeeRate(u64 fee_rate);
MissingInputValue();
SendingTooMuch();
OtherExtractTransactionError();
interface WalletCreationError {
Io(string error_message);
InvalidMagicBytes(sequence<u8> got, sequence<u8> expected);
Descriptor();
Persist(string error_message);
NotInitialized();
LoadedGenesisDoesNotMatch(string expected, string got);
LoadedNetworkDoesNotMatch(Network expected, Network? got);
LoadedDescriptorDoesNotMatch(string got, KeychainKind keychain);
};
// ------------------------------------------------------------------------
@@ -127,25 +237,18 @@ dictionary AddressInfo {
KeychainKind keychain;
};
[Enum]
interface AddressIndex {
New();
LastUnused();
Peek(u32 index);
};
dictionary Balance {
u64 immature;
Amount immature;
u64 trusted_pending;
Amount trusted_pending;
u64 untrusted_pending;
Amount untrusted_pending;
u64 confirmed;
Amount confirmed;
u64 trusted_spendable;
Amount trusted_spendable;
u64 total;
Amount total;
};
dictionary LocalOutput {
@@ -171,24 +274,14 @@ dictionary CanonicalTx {
ChainPosition chain_position;
};
interface FullScanRequest {};
interface SyncRequest {};
// ------------------------------------------------------------------------
// bdk crate - wallet module
// ------------------------------------------------------------------------
interface FeeRate {
[Name=from_sat_per_vb, Throws=FeeRateError]
constructor(u64 sat_per_vb);
[Name=from_sat_per_kwu]
constructor(u64 sat_per_kwu);
u64 to_sat_per_vb_ceil();
u64 to_sat_per_vb_floor();
u64 to_sat_per_kwu();
};
enum ChangeSpendPolicy {
"ChangeAllowed",
"OnlyChange",
@@ -199,21 +292,25 @@ interface Wallet {
[Throws=WalletCreationError]
constructor(Descriptor descriptor, Descriptor? change_descriptor, string persistence_backend_path, Network network);
AddressInfo get_address(AddressIndex address_index);
[Name=new_no_persist, Throws=DescriptorError]
constructor(Descriptor descriptor, Descriptor? change_descriptor, Network network);
[Throws=PersistenceError]
AddressInfo try_get_internal_address(AddressIndex address_index);
AddressInfo reveal_next_address(KeychainKind keychain);
Network network();
Balance get_balance();
[Throws=Alpha3Error]
[Throws=CannotConnectError]
void apply_update(Update update);
[Throws=PersistenceError]
boolean commit();
boolean is_mine([ByRef] Script script);
[Throws=Alpha3Error]
[Throws=SignerError]
boolean sign(Psbt psbt);
SentAndReceivedValues sent_and_received([ByRef] Transaction tx);
@@ -232,6 +329,10 @@ interface Wallet {
sequence<LocalOutput> list_unspent();
sequence<LocalOutput> list_output();
FullScanRequest start_full_scan();
SyncRequest start_sync_with_revealed_spks();
};
interface Update {};
@@ -239,7 +340,7 @@ interface Update {};
interface TxBuilder {
constructor();
TxBuilder add_recipient([ByRef] Script script, u64 amount);
TxBuilder add_recipient([ByRef] Script script, Amount amount);
TxBuilder set_recipients(sequence<ScriptAmount> recipients);
@@ -269,20 +370,18 @@ interface TxBuilder {
TxBuilder enable_rbf_with_sequence(u32 nsequence);
[Throws=Alpha3Error]
[Throws=CreateTxError]
Psbt finish([ByRef] Wallet wallet);
};
interface BumpFeeTxBuilder {
constructor(string txid, FeeRate fee_rate);
BumpFeeTxBuilder allow_shrinking(Script script_pubkey);
BumpFeeTxBuilder enable_rbf();
BumpFeeTxBuilder enable_rbf_with_sequence(u32 nsequence);
[Throws=Alpha3Error]
[Throws=CreateTxError]
Psbt finish([ByRef] Wallet wallet);
};
@@ -293,30 +392,30 @@ interface BumpFeeTxBuilder {
interface Mnemonic {
constructor(WordCount word_count);
[Name=from_string, Throws=Alpha3Error]
[Name=from_string, Throws=Bip39Error]
constructor(string mnemonic);
[Name=from_entropy, Throws=Alpha3Error]
[Name=from_entropy, Throws=Bip39Error]
constructor(sequence<u8> entropy);
string as_string();
};
interface DerivationPath {
[Throws=Alpha3Error]
[Throws=Bip32Error]
constructor(string path);
};
interface DescriptorSecretKey {
constructor(Network network, [ByRef] Mnemonic mnemonic, string? password);
[Name=from_string, Throws=Alpha3Error]
[Name=from_string, Throws=DescriptorKeyError]
constructor(string secret_key);
[Throws=Alpha3Error]
[Throws=DescriptorKeyError]
DescriptorSecretKey derive([ByRef] DerivationPath path);
[Throws=Alpha3Error]
[Throws=DescriptorKeyError]
DescriptorSecretKey extend([ByRef] DerivationPath path);
DescriptorPublicKey as_public();
@@ -327,13 +426,13 @@ interface DescriptorSecretKey {
};
interface DescriptorPublicKey {
[Name=from_string, Throws=Alpha3Error]
[Name=from_string, Throws=DescriptorKeyError]
constructor(string public_key);
[Throws=Alpha3Error]
[Throws=DescriptorKeyError]
DescriptorPublicKey derive([ByRef] DerivationPath path);
[Throws=Alpha3Error]
[Throws=DescriptorKeyError]
DescriptorPublicKey extend([ByRef] DerivationPath path);
string as_string();
@@ -380,24 +479,45 @@ interface EsploraClient {
constructor(string url);
[Throws=EsploraError]
Update full_scan(Wallet wallet, u64 stop_gap, u64 parallel_requests);
Update full_scan(FullScanRequest full_scan_request, u64 stop_gap, u64 parallel_requests);
[Throws=EsploraError]
Update sync(SyncRequest sync_request, u64 parallel_requests);
[Throws=EsploraError]
void broadcast([ByRef] Transaction transaction);
};
// ------------------------------------------------------------------------
// bdk_electrum crate
// ------------------------------------------------------------------------
interface ElectrumClient {
[Throws=ElectrumError]
constructor(string url);
[Throws=ElectrumError]
Update full_scan(FullScanRequest full_scan_request, u64 stop_gap, u64 batch_size, boolean fetch_prev_txouts);
[Throws=ElectrumError]
Update sync(SyncRequest sync_request, u64 batch_size, boolean fetch_prev_txouts);
[Throws=ElectrumError]
string broadcast([ByRef] Transaction transaction);
};
// ------------------------------------------------------------------------
// bdk-ffi-defined types
// ------------------------------------------------------------------------
dictionary ScriptAmount {
Script script;
u64 amount;
Amount amount;
};
dictionary SentAndReceivedValues {
u64 sent;
u64 received;
Amount sent;
Amount received;
};
// ------------------------------------------------------------------------
@@ -460,6 +580,14 @@ interface Transaction {
i32 version();
sequence<u8> serialize();
u64 weight();
sequence<TxIn> input();
sequence<TxOut> output();
u32 lock_time();
};
interface Psbt {
@@ -476,3 +604,36 @@ dictionary OutPoint {
string txid;
u32 vout;
};
interface Amount {
[Name=from_sat]
constructor(u64 from_sat);
[Name=from_btc, Throws=ParseAmountError]
constructor(f64 from_btc);
u64 to_sat();
f64 to_btc();
};
interface FeeRate {
[Name=from_sat_per_vb, Throws=FeeRateError]
constructor(u64 sat_per_vb);
[Name=from_sat_per_kwu]
constructor(u64 sat_per_kwu);
u64 to_sat_per_vb_ceil();
u64 to_sat_per_vb_floor();
u64 to_sat_per_kwu();
};
dictionary TxIn {
OutPoint previous_output;
Script script_sig;
u32 sequence;
sequence<sequence<u8>> witness;
};

View File

@@ -1,22 +1,60 @@
use crate::error::{AddressError, PsbtParseError, TransactionError};
use crate::error::{AddressError, FeeRateError, PsbtParseError, TransactionError};
use bdk::bitcoin::address::{NetworkChecked, NetworkUnchecked};
use bdk::bitcoin::amount::ParseAmountError;
use bdk::bitcoin::blockdata::script::ScriptBuf as BdkScriptBuf;
use bdk::bitcoin::blockdata::transaction::TxOut as BdkTxOut;
use bdk::bitcoin::consensus::encode::serialize;
use bdk::bitcoin::consensus::Decodable;
use bdk::bitcoin::psbt::ExtractTxError;
use bdk::bitcoin::Address as BdkAddress;
use bdk::bitcoin::Amount as BdkAmount;
use bdk::bitcoin::FeeRate as BdkFeeRate;
use bdk::bitcoin::Network;
use bdk::bitcoin::OutPoint as BdkOutPoint;
use bdk::bitcoin::Psbt as BdkPsbt;
use bdk::bitcoin::Transaction as BdkTransaction;
use bdk::bitcoin::TxIn as BdkTxIn;
use bdk::bitcoin::Txid;
use std::io::Cursor;
use std::str::FromStr;
use std::sync::{Arc, Mutex};
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Amount(pub(crate) BdkAmount);
impl Amount {
pub fn from_sat(sat: u64) -> Self {
Amount(BdkAmount::from_sat(sat))
}
pub fn from_btc(btc: f64) -> Result<Self, ParseAmountError> {
let bdk_amount = BdkAmount::from_btc(btc).map_err(ParseAmountError::from)?;
Ok(Amount(bdk_amount))
}
pub fn to_sat(&self) -> u64 {
self.0.to_sat()
}
pub fn to_btc(&self) -> f64 {
self.0.to_btc()
}
}
impl From<Amount> for BdkAmount {
fn from(amount: Amount) -> Self {
amount.0
}
}
impl From<BdkAmount> for Amount {
fn from(amount: BdkAmount) -> Self {
Amount(amount)
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Script(pub(crate) BdkScriptBuf);
@@ -38,60 +76,34 @@ impl From<BdkScriptBuf> for Script {
}
#[derive(Debug, PartialEq, Eq)]
pub struct Address {
inner: BdkAddress<NetworkChecked>,
}
pub struct Address(BdkAddress<NetworkChecked>);
impl Address {
pub fn new(address: String, network: Network) -> Result<Self, AddressError> {
let parsed_address = address.parse::<bdk::bitcoin::Address<NetworkUnchecked>>()?;
let network_checked_address = parsed_address.require_network(network)?;
Ok(Address {
inner: network_checked_address,
})
Ok(Address(network_checked_address))
}
/// alternative constructor
// fn from_script(script: Arc<Script>, network: Network) -> Result<Self, BdkError> {
// BdkAddress::from_script(&script.inner, network)
// .map(|a| Address { inner: a })
// .map_err(|e| BdkError::Generic(e.to_string()))
// }
//
// fn payload(&self) -> Payload {
// match &self.inner.payload.clone() {
// BdkPayload::PubkeyHash(pubkey_hash) => Payload::PubkeyHash {
// pubkey_hash: pubkey_hash.to_vec(),
// },
// BdkPayload::ScriptHash(script_hash) => Payload::ScriptHash {
// script_hash: script_hash.to_vec(),
// },
// BdkPayload::WitnessProgram { version, program } => Payload::WitnessProgram {
// version: *version,
// program: program.clone(),
// },
// }
// }
pub fn network(&self) -> Network {
*self.inner.network()
*self.0.network()
}
pub fn script_pubkey(&self) -> Arc<Script> {
Arc::new(Script(self.inner.script_pubkey()))
Arc::new(Script(self.0.script_pubkey()))
}
pub fn to_qr_uri(&self) -> String {
self.inner.to_qr_uri()
self.0.to_qr_uri()
}
pub fn as_string(&self) -> String {
self.inner.to_string()
self.0.to_string()
}
pub fn is_valid_for_network(&self, network: Network) -> bool {
let address_str = self.inner.to_string();
let address_str = self.0.to_string();
if let Ok(unchecked_address) = address_str.parse::<BdkAddress<NetworkUnchecked>>() {
unchecked_address.is_valid_for_network(network)
} else {
@@ -102,166 +114,116 @@ impl Address {
impl From<Address> for BdkAddress {
fn from(address: Address) -> Self {
address.inner
address.0
}
}
impl From<BdkAddress> for Address {
fn from(address: BdkAddress) -> Self {
Address { inner: address }
Address(address)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Transaction {
inner: BdkTransaction,
}
pub struct Transaction(BdkTransaction);
impl Transaction {
pub fn new(transaction_bytes: Vec<u8>) -> Result<Self, TransactionError> {
let mut decoder = Cursor::new(transaction_bytes);
let tx: BdkTransaction = BdkTransaction::consensus_decode(&mut decoder)?;
Ok(Transaction { inner: tx })
Ok(Transaction(tx))
}
pub fn txid(&self) -> String {
self.inner.txid().to_string()
self.0.txid().to_string()
}
// fn weight(&self) -> u64 {
// self.inner.weight() as u64
// }
pub fn weight(&self) -> u64 {
self.0.weight().to_wu()
}
pub fn total_size(&self) -> u64 {
self.inner.total_size() as u64
self.0.total_size() as u64
}
pub fn vsize(&self) -> u64 {
self.inner.vsize() as u64
self.0.vsize() as u64
}
pub fn is_coinbase(&self) -> bool {
self.inner.is_coinbase()
self.0.is_coinbase()
}
pub fn is_explicitly_rbf(&self) -> bool {
self.inner.is_explicitly_rbf()
self.0.is_explicitly_rbf()
}
pub fn is_lock_time_enabled(&self) -> bool {
self.inner.is_lock_time_enabled()
self.0.is_lock_time_enabled()
}
pub fn version(&self) -> i32 {
self.inner.version.0
self.0.version.0
}
pub fn serialize(&self) -> Vec<u8> {
serialize(&self.inner)
serialize(&self.0)
}
// fn lock_time(&self) -> u32 {
// self.inner.lock_time.0
// }
pub fn input(&self) -> Vec<TxIn> {
self.0.input.iter().map(|tx_in| tx_in.into()).collect()
}
// fn input(&self) -> Vec<TxIn> {
// self.inner.input.iter().map(|x| x.into()).collect()
// }
//
// fn output(&self) -> Vec<TxOut> {
// self.inner.output.iter().map(|x| x.into()).collect()
// }
pub fn output(&self) -> Vec<TxOut> {
self.0.output.iter().map(|tx_out| tx_out.into()).collect()
}
pub fn lock_time(&self) -> u32 {
self.0.lock_time.to_consensus_u32()
}
}
impl From<BdkTransaction> for Transaction {
fn from(tx: BdkTransaction) -> Self {
Transaction { inner: tx }
Transaction(tx)
}
}
impl From<&BdkTransaction> for Transaction {
fn from(tx: &BdkTransaction) -> Self {
Transaction { inner: tx.clone() }
Transaction(tx.clone())
}
}
impl From<&Transaction> for BdkTransaction {
fn from(tx: &Transaction) -> Self {
tx.inner.clone()
tx.0.clone()
}
}
pub struct Psbt {
pub(crate) inner: Mutex<BdkPsbt>,
}
pub struct Psbt(pub(crate) Mutex<BdkPsbt>);
impl Psbt {
pub(crate) fn new(psbt_base64: String) -> Result<Self, PsbtParseError> {
let psbt: BdkPsbt = BdkPsbt::from_str(&psbt_base64)?;
Ok(Psbt {
inner: Mutex::new(psbt),
})
Ok(Psbt(Mutex::new(psbt)))
}
pub(crate) fn serialize(&self) -> String {
let psbt = self.inner.lock().unwrap().clone();
let psbt = self.0.lock().unwrap().clone();
psbt.to_string()
}
// pub(crate) fn txid(&self) -> String {
// let tx = self.inner.lock().unwrap().clone().extract_tx();
// let txid = tx.txid();
// txid.to_hex()
// }
pub(crate) fn extract_tx(&self) -> Result<Arc<Transaction>, ExtractTxError> {
let tx: BdkTransaction = self.inner.lock().unwrap().clone().extract_tx()?;
let tx: BdkTransaction = self.0.lock().unwrap().clone().extract_tx()?;
let transaction: Transaction = tx.into();
Ok(Arc::new(transaction))
}
// /// Combines this PartiallySignedTransaction with other PSBT as described by BIP 174.
// ///
// /// In accordance with BIP 174 this function is commutative i.e., `A.combine(B) == B.combine(A)`
// pub(crate) fn combine(
// &self,
// other: Arc<PartiallySignedTransaction>,
// ) -> Result<Arc<PartiallySignedTransaction>, BdkError> {
// let other_psbt = other.inner.lock().unwrap().clone();
// let mut original_psbt = self.inner.lock().unwrap().clone();
//
// original_psbt.combine(other_psbt)?;
// Ok(Arc::new(PartiallySignedTransaction {
// inner: Mutex::new(original_psbt),
// }))
// }
// /// The total transaction fee amount, sum of input amounts minus sum of output amounts, in Sats.
// /// If the PSBT is missing a TxOut for an input returns None.
// pub(crate) fn fee_amount(&self) -> Option<u64> {
// self.inner.lock().unwrap().fee_amount()
// }
// /// The transaction's fee rate. This value will only be accurate if calculated AFTER the
// /// `PartiallySignedTransaction` is finalized and all witness/signature data is added to the
// /// transaction.
// /// If the PSBT is missing a TxOut for an input returns None.
// pub(crate) fn fee_rate(&self) -> Option<Arc<FeeRate>> {
// self.inner.lock().unwrap().fee_rate().map(Arc::new)
// }
// /// Serialize the PSBT data structure as a String of JSON.
// pub(crate) fn json_serialize(&self) -> String {
// let psbt = self.inner.lock().unwrap();
// serde_json::to_string(psbt.deref()).unwrap()
// }
}
impl From<BdkPsbt> for Psbt {
fn from(psbt: BdkPsbt) -> Self {
Psbt {
inner: Mutex::new(psbt),
}
Psbt(Mutex::new(psbt))
}
}
@@ -289,6 +251,28 @@ impl From<&BdkOutPoint> for OutPoint {
}
}
#[derive(Debug, Clone)]
pub struct TxIn {
pub previous_output: OutPoint,
pub script_sig: Arc<Script>,
pub sequence: u32,
pub witness: Vec<Vec<u8>>,
}
impl From<&BdkTxIn> for TxIn {
fn from(tx_in: &BdkTxIn) -> Self {
TxIn {
previous_output: OutPoint {
txid: tx_in.previous_output.txid.to_string(),
vout: tx_in.previous_output.vout,
},
script_sig: Arc::new(Script(tx_in.script_sig.clone())),
sequence: tx_in.sequence.0,
witness: tx_in.witness.to_vec(),
}
}
}
#[derive(Debug, Clone)]
pub struct TxOut {
pub value: u64,
@@ -304,6 +288,35 @@ impl From<&BdkTxOut> for TxOut {
}
}
#[derive(Clone, Debug)]
pub struct FeeRate(pub BdkFeeRate);
impl FeeRate {
pub fn from_sat_per_vb(sat_per_vb: u64) -> Result<Self, FeeRateError> {
let fee_rate: Option<BdkFeeRate> = BdkFeeRate::from_sat_per_vb(sat_per_vb);
match fee_rate {
Some(fee_rate) => Ok(FeeRate(fee_rate)),
None => Err(FeeRateError::ArithmeticOverflow),
}
}
pub fn from_sat_per_kwu(sat_per_kwu: u64) -> Self {
FeeRate(BdkFeeRate::from_sat_per_kwu(sat_per_kwu))
}
pub fn to_sat_per_vb_ceil(&self) -> u64 {
self.0.to_sat_per_vb_ceil()
}
pub fn to_sat_per_vb_floor(&self) -> u64 {
self.0.to_sat_per_vb_floor()
}
pub fn to_sat_per_kwu(&self) -> u64 {
self.0.to_sat_per_kwu()
}
}
#[cfg(test)]
mod tests {
use crate::bitcoin::Address;

View File

@@ -37,7 +37,7 @@ impl Descriptor {
keychain_kind: KeychainKind,
network: Network,
) -> Self {
let derivable_key = &secret_key.inner;
let derivable_key = &secret_key.0;
match derivable_key {
BdkDescriptorSecretKey::Single(_) => {
@@ -65,7 +65,7 @@ impl Descriptor {
network: Network,
) -> Self {
let fingerprint = Fingerprint::from_str(fingerprint.as_str()).unwrap();
let derivable_key = &public_key.inner;
let derivable_key = &public_key.0;
match derivable_key {
BdkDescriptorPublicKey::Single(_) => {
@@ -94,7 +94,7 @@ impl Descriptor {
keychain_kind: KeychainKind,
network: Network,
) -> Self {
let derivable_key = &secret_key.inner;
let derivable_key = &secret_key.0;
match derivable_key {
BdkDescriptorSecretKey::Single(_) => {
@@ -122,7 +122,7 @@ impl Descriptor {
network: Network,
) -> Self {
let fingerprint = Fingerprint::from_str(fingerprint.as_str()).unwrap();
let derivable_key = &public_key.inner;
let derivable_key = &public_key.0;
match derivable_key {
BdkDescriptorPublicKey::Single(_) => {
@@ -151,7 +151,7 @@ impl Descriptor {
keychain_kind: KeychainKind,
network: Network,
) -> Self {
let derivable_key = &secret_key.inner;
let derivable_key = &secret_key.0;
match derivable_key {
BdkDescriptorSecretKey::Single(_) => {
@@ -179,7 +179,7 @@ impl Descriptor {
network: Network,
) -> Self {
let fingerprint = Fingerprint::from_str(fingerprint.as_str()).unwrap();
let derivable_key = &public_key.inner;
let derivable_key = &public_key.0;
match derivable_key {
BdkDescriptorPublicKey::Single(_) => {
@@ -208,7 +208,7 @@ impl Descriptor {
keychain_kind: KeychainKind,
network: Network,
) -> Self {
let derivable_key = &secret_key.inner;
let derivable_key = &secret_key.0;
match derivable_key {
BdkDescriptorSecretKey::Single(_) => {
@@ -236,7 +236,7 @@ impl Descriptor {
network: Network,
) -> Self {
let fingerprint = Fingerprint::from_str(fingerprint.as_str()).unwrap();
let derivable_key = &public_key.inner;
let derivable_key = &public_key.0;
match derivable_key {
BdkDescriptorPublicKey::Single(_) => {

95
bdk-ffi/src/electrum.rs Normal file
View File

@@ -0,0 +1,95 @@
use crate::bitcoin::Transaction;
use crate::error::ElectrumError;
use crate::types::{FullScanRequest, SyncRequest};
use crate::wallet::Update;
use bdk::bitcoin::Transaction as BdkTransaction;
use bdk::chain::spk_client::FullScanRequest as BdkFullScanRequest;
use bdk::chain::spk_client::FullScanResult as BdkFullScanResult;
use bdk::chain::spk_client::SyncRequest as BdkSyncRequest;
use bdk::chain::spk_client::SyncResult as BdkSyncResult;
use bdk::KeychainKind;
use bdk_electrum::electrum_client::{Client as BdkBlockingClient, ElectrumApi};
use bdk_electrum::{ElectrumExt, ElectrumFullScanResult, ElectrumSyncResult};
use std::collections::BTreeMap;
use std::sync::Arc;
pub struct ElectrumClient(BdkBlockingClient);
impl ElectrumClient {
pub fn new(url: String) -> Result<Self, ElectrumError> {
let client = BdkBlockingClient::new(url.as_str())?;
Ok(Self(client))
}
pub fn full_scan(
&self,
request: Arc<FullScanRequest>,
stop_gap: u64,
batch_size: u64,
fetch_prev_txouts: bool,
) -> Result<Arc<Update>, ElectrumError> {
// using option and take is not ideal but the only way to take full ownership of the request
let request: BdkFullScanRequest<KeychainKind> = request
.0
.lock()
.unwrap()
.take()
.ok_or(ElectrumError::RequestAlreadyConsumed)?;
let electrum_result: ElectrumFullScanResult<KeychainKind> = self.0.full_scan(
request,
stop_gap as usize,
batch_size as usize,
fetch_prev_txouts,
)?;
let full_scan_result: BdkFullScanResult<KeychainKind> =
electrum_result.with_confirmation_time_height_anchor(&self.0)?;
let update = bdk::wallet::Update {
last_active_indices: full_scan_result.last_active_indices,
graph: full_scan_result.graph_update,
chain: Some(full_scan_result.chain_update),
};
Ok(Arc::new(Update(update)))
}
pub fn sync(
&self,
request: Arc<SyncRequest>,
batch_size: u64,
fetch_prev_txouts: bool,
) -> Result<Arc<Update>, ElectrumError> {
// using option and take is not ideal but the only way to take full ownership of the request
let request: BdkSyncRequest = request
.0
.lock()
.unwrap()
.take()
.ok_or(ElectrumError::RequestAlreadyConsumed)?;
let electrum_result: ElectrumSyncResult =
self.0
.sync(request, batch_size as usize, fetch_prev_txouts)?;
let sync_result: BdkSyncResult =
electrum_result.with_confirmation_time_height_anchor(&self.0)?;
let update = bdk::wallet::Update {
last_active_indices: BTreeMap::default(),
graph: sync_result.graph_update,
chain: Some(sync_result.chain_update),
};
Ok(Arc::new(Update(update)))
}
pub fn broadcast(&self, transaction: &Transaction) -> Result<String, ElectrumError> {
let bdk_transaction: BdkTransaction = transaction.into();
self.0
.transaction_broadcast(&bdk_transaction)
.map_err(ElectrumError::from)
.map(|txid| txid.to_string())
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,9 +1,15 @@
use crate::error::EsploraError;
use crate::wallet::{Update, Wallet};
use crate::bitcoin::Transaction;
use crate::error::EsploraError;
use crate::types::{FullScanRequest, SyncRequest};
use crate::wallet::Update;
use std::collections::BTreeMap;
use bdk::bitcoin::Transaction as BdkTransaction;
use bdk::wallet::Update as BdkUpdate;
use bdk::chain::spk_client::FullScanRequest as BdkFullScanRequest;
use bdk::chain::spk_client::FullScanResult as BdkFullScanResult;
use bdk::chain::spk_client::SyncRequest as BdkSyncRequest;
use bdk::chain::spk_client::SyncResult as BdkSyncResult;
use bdk::KeychainKind;
use bdk_esplora::esplora_client::{BlockingClient, Builder};
use bdk_esplora::EsploraExt;
@@ -17,40 +23,56 @@ impl EsploraClient {
Self(client)
}
// This is a temporary solution for scanning. The long-term solution involves not passing
// the wallet to the client at all.
pub fn full_scan(
&self,
wallet: Arc<Wallet>,
request: Arc<FullScanRequest>,
stop_gap: u64,
parallel_requests: u64,
) -> Result<Arc<Update>, EsploraError> {
let wallet = wallet.get_wallet();
let previous_tip = wallet.latest_checkpoint();
let keychain_spks = wallet.all_unbounded_spk_iters().into_iter().collect();
let (update_graph, last_active_indices) = self
// using option and take is not ideal but the only way to take full ownership of the request
let request: BdkFullScanRequest<KeychainKind> = request
.0
.full_scan(keychain_spks, stop_gap as usize, parallel_requests as usize)
.map_err(|e| EsploraError::from(*e))?;
.lock()
.unwrap()
.take()
.ok_or(EsploraError::RequestAlreadyConsumed)?;
let missing_heights = update_graph.missing_heights(wallet.local_chain());
let chain_update = self
.0
.update_local_chain(previous_tip, missing_heights)
.map_err(|e| EsploraError::from(*e))?;
let result: BdkFullScanResult<KeychainKind> =
self.0
.full_scan(request, stop_gap as usize, parallel_requests as usize)?;
let update = BdkUpdate {
last_active_indices,
graph: update_graph,
chain: Some(chain_update),
let update = bdk::wallet::Update {
last_active_indices: result.last_active_indices,
graph: result.graph_update,
chain: Some(result.chain_update),
};
Ok(Arc::new(Update(update)))
}
// pub fn sync();
pub fn sync(
&self,
request: Arc<SyncRequest>,
parallel_requests: u64,
) -> Result<Arc<Update>, EsploraError> {
// using option and take is not ideal but the only way to take full ownership of the request
let request: BdkSyncRequest = request
.0
.lock()
.unwrap()
.take()
.ok_or(EsploraError::RequestAlreadyConsumed)?;
let result: BdkSyncResult = self.0.sync(request, parallel_requests as usize)?;
let update = bdk::wallet::Update {
last_active_indices: BTreeMap::default(),
graph: result.graph_update,
chain: Some(result.chain_update),
};
Ok(Arc::new(Update(update)))
}
pub fn broadcast(&self, transaction: &Transaction) -> Result<(), EsploraError> {
let bdk_transaction: BdkTransaction = transaction.into();
@@ -58,6 +80,4 @@ impl EsploraClient {
.broadcast(&bdk_transaction)
.map_err(EsploraError::from)
}
// pub fn estimate_fee();
}

View File

@@ -1,4 +1,4 @@
use crate::error::Alpha3Error;
use crate::error::{Bip32Error, Bip39Error, DescriptorKeyError};
use bdk::bitcoin::bip32::DerivationPath as BdkDerivationPath;
use bdk::bitcoin::key::Secp256k1;
@@ -18,9 +18,7 @@ use std::ops::Deref;
use std::str::FromStr;
use std::sync::{Arc, Mutex};
pub(crate) struct Mnemonic {
inner: BdkMnemonic,
}
pub(crate) struct Mnemonic(pub(crate) BdkMnemonic);
impl Mnemonic {
pub(crate) fn new(word_count: WordCount) -> Self {
@@ -32,23 +30,23 @@ impl Mnemonic {
let generated_key: GeneratedKey<_, BareCtx> =
BdkMnemonic::generate_with_entropy((word_count, Language::English), entropy).unwrap();
let mnemonic = BdkMnemonic::parse_in(Language::English, generated_key.to_string()).unwrap();
Mnemonic { inner: mnemonic }
Mnemonic(mnemonic)
}
pub(crate) fn from_string(mnemonic: String) -> Result<Self, Alpha3Error> {
pub(crate) fn from_string(mnemonic: String) -> Result<Self, Bip39Error> {
BdkMnemonic::from_str(&mnemonic)
.map(|m| Mnemonic { inner: m })
.map_err(|_| Alpha3Error::Generic)
.map(Mnemonic)
.map_err(Bip39Error::from)
}
pub(crate) fn from_entropy(entropy: Vec<u8>) -> Result<Self, Alpha3Error> {
pub(crate) fn from_entropy(entropy: Vec<u8>) -> Result<Self, Bip39Error> {
BdkMnemonic::from_entropy(entropy.as_slice())
.map(|m| Mnemonic { inner: m })
.map_err(|_| Alpha3Error::Generic)
.map(Mnemonic)
.map_err(Bip39Error::from)
}
pub(crate) fn as_string(&self) -> String {
self.inner.to_string()
self.0.to_string()
}
}
@@ -57,23 +55,21 @@ pub(crate) struct DerivationPath {
}
impl DerivationPath {
pub(crate) fn new(path: String) -> Result<Self, Alpha3Error> {
pub(crate) fn new(path: String) -> Result<Self, Bip32Error> {
BdkDerivationPath::from_str(&path)
.map(|x| DerivationPath {
inner_mutex: Mutex::new(x),
})
.map_err(|_| Alpha3Error::Generic)
.map_err(Bip32Error::from)
}
}
#[derive(Debug)]
pub struct DescriptorSecretKey {
pub(crate) inner: BdkDescriptorSecretKey,
}
pub struct DescriptorSecretKey(pub(crate) BdkDescriptorSecretKey);
impl DescriptorSecretKey {
pub(crate) fn new(network: Network, mnemonic: &Mnemonic, password: Option<String>) -> Self {
let mnemonic = mnemonic.inner.clone();
let mnemonic = mnemonic.0.clone();
let xkey: ExtendedKey = (mnemonic, password).into_extended_key().unwrap();
let descriptor_secret_key = BdkDescriptorSecretKey::XPrv(DescriptorXKey {
origin: None,
@@ -81,27 +77,26 @@ impl DescriptorSecretKey {
derivation_path: BdkDerivationPath::master(),
wildcard: Wildcard::Unhardened,
});
Self {
inner: descriptor_secret_key,
}
Self(descriptor_secret_key)
}
pub(crate) fn from_string(private_key: String) -> Result<Self, Alpha3Error> {
pub(crate) fn from_string(private_key: String) -> Result<Self, DescriptorKeyError> {
let descriptor_secret_key = BdkDescriptorSecretKey::from_str(private_key.as_str())
.map_err(|_| Alpha3Error::Generic)?;
Ok(Self {
inner: descriptor_secret_key,
})
.map_err(DescriptorKeyError::from)?;
Ok(Self(descriptor_secret_key))
}
pub(crate) fn derive(&self, path: &DerivationPath) -> Result<Arc<Self>, Alpha3Error> {
pub(crate) fn derive(&self, path: &DerivationPath) -> Result<Arc<Self>, DescriptorKeyError> {
let secp = Secp256k1::new();
let descriptor_secret_key = &self.inner;
let descriptor_secret_key = &self.0;
let path = path.inner_mutex.lock().unwrap().deref().clone();
match descriptor_secret_key {
BdkDescriptorSecretKey::Single(_) => Err(Alpha3Error::Generic),
BdkDescriptorSecretKey::Single(_) => Err(DescriptorKeyError::InvalidKeyType),
BdkDescriptorSecretKey::XPrv(descriptor_x_key) => {
let derived_xprv = descriptor_x_key.xkey.derive_priv(&secp, &path)?;
let derived_xprv = descriptor_x_key
.xkey
.derive_priv(&secp, &path)
.map_err(DescriptorKeyError::from)?;
let key_source = match descriptor_x_key.origin.clone() {
Some((fingerprint, origin_path)) => (fingerprint, origin_path.extend(path)),
None => (descriptor_x_key.xkey.fingerprint(&secp), path),
@@ -112,19 +107,17 @@ impl DescriptorSecretKey {
derivation_path: BdkDerivationPath::default(),
wildcard: descriptor_x_key.wildcard,
});
Ok(Arc::new(Self {
inner: derived_descriptor_secret_key,
}))
Ok(Arc::new(Self(derived_descriptor_secret_key)))
}
BdkDescriptorSecretKey::MultiXPrv(_) => Err(Alpha3Error::Generic),
BdkDescriptorSecretKey::MultiXPrv(_) => Err(DescriptorKeyError::InvalidKeyType),
}
}
pub(crate) fn extend(&self, path: &DerivationPath) -> Result<Arc<Self>, Alpha3Error> {
let descriptor_secret_key = &self.inner;
pub(crate) fn extend(&self, path: &DerivationPath) -> Result<Arc<Self>, DescriptorKeyError> {
let descriptor_secret_key = &self.0;
let path = path.inner_mutex.lock().unwrap().deref().clone();
match descriptor_secret_key {
BdkDescriptorSecretKey::Single(_) => Err(Alpha3Error::Generic),
BdkDescriptorSecretKey::Single(_) => Err(DescriptorKeyError::InvalidKeyType),
BdkDescriptorSecretKey::XPrv(descriptor_x_key) => {
let extended_path = descriptor_x_key.derivation_path.extend(path);
let extended_descriptor_secret_key = BdkDescriptorSecretKey::XPrv(DescriptorXKey {
@@ -133,24 +126,20 @@ impl DescriptorSecretKey {
derivation_path: extended_path,
wildcard: descriptor_x_key.wildcard,
});
Ok(Arc::new(Self {
inner: extended_descriptor_secret_key,
}))
Ok(Arc::new(Self(extended_descriptor_secret_key)))
}
BdkDescriptorSecretKey::MultiXPrv(_) => Err(Alpha3Error::Generic),
BdkDescriptorSecretKey::MultiXPrv(_) => Err(DescriptorKeyError::InvalidKeyType),
}
}
pub(crate) fn as_public(&self) -> Arc<DescriptorPublicKey> {
let secp = Secp256k1::new();
let descriptor_public_key = self.inner.to_public(&secp).unwrap();
Arc::new(DescriptorPublicKey {
inner: descriptor_public_key,
})
let descriptor_public_key = self.0.to_public(&secp).unwrap();
Arc::new(DescriptorPublicKey(descriptor_public_key))
}
pub(crate) fn secret_bytes(&self) -> Vec<u8> {
let inner = &self.inner;
let inner = &self.0;
let secret_bytes: Vec<u8> = match inner {
BdkDescriptorSecretKey::Single(_) => {
unreachable!()
@@ -167,33 +156,32 @@ impl DescriptorSecretKey {
}
pub(crate) fn as_string(&self) -> String {
self.inner.to_string()
self.0.to_string()
}
}
#[derive(Debug)]
pub struct DescriptorPublicKey {
pub(crate) inner: BdkDescriptorPublicKey,
}
pub struct DescriptorPublicKey(pub(crate) BdkDescriptorPublicKey);
impl DescriptorPublicKey {
pub(crate) fn from_string(public_key: String) -> Result<Self, Alpha3Error> {
pub(crate) fn from_string(public_key: String) -> Result<Self, DescriptorKeyError> {
let descriptor_public_key = BdkDescriptorPublicKey::from_str(public_key.as_str())
.map_err(|_| Alpha3Error::Generic)?;
Ok(Self {
inner: descriptor_public_key,
})
.map_err(DescriptorKeyError::from)?;
Ok(Self(descriptor_public_key))
}
pub(crate) fn derive(&self, path: &DerivationPath) -> Result<Arc<Self>, Alpha3Error> {
pub(crate) fn derive(&self, path: &DerivationPath) -> Result<Arc<Self>, DescriptorKeyError> {
let secp = Secp256k1::new();
let descriptor_public_key = &self.inner;
let descriptor_public_key = &self.0;
let path = path.inner_mutex.lock().unwrap().deref().clone();
match descriptor_public_key {
BdkDescriptorPublicKey::Single(_) => Err(Alpha3Error::Generic),
BdkDescriptorPublicKey::Single(_) => Err(DescriptorKeyError::InvalidKeyType),
BdkDescriptorPublicKey::XPub(descriptor_x_key) => {
let derived_xpub = descriptor_x_key.xkey.derive_pub(&secp, &path)?;
let derived_xpub = descriptor_x_key
.xkey
.derive_pub(&secp, &path)
.map_err(DescriptorKeyError::from)?;
let key_source = match descriptor_x_key.origin.clone() {
Some((fingerprint, origin_path)) => (fingerprint, origin_path.extend(path)),
None => (descriptor_x_key.xkey.fingerprint(), path),
@@ -204,19 +192,17 @@ impl DescriptorPublicKey {
derivation_path: BdkDerivationPath::default(),
wildcard: descriptor_x_key.wildcard,
});
Ok(Arc::new(Self {
inner: derived_descriptor_public_key,
}))
Ok(Arc::new(Self(derived_descriptor_public_key)))
}
BdkDescriptorPublicKey::MultiXPub(_) => Err(Alpha3Error::Generic),
BdkDescriptorPublicKey::MultiXPub(_) => Err(DescriptorKeyError::InvalidKeyType),
}
}
pub(crate) fn extend(&self, path: &DerivationPath) -> Result<Arc<Self>, Alpha3Error> {
let descriptor_public_key = &self.inner;
pub(crate) fn extend(&self, path: &DerivationPath) -> Result<Arc<Self>, DescriptorKeyError> {
let descriptor_public_key = &self.0;
let path = path.inner_mutex.lock().unwrap().deref().clone();
match descriptor_public_key {
BdkDescriptorPublicKey::Single(_) => Err(Alpha3Error::Generic),
BdkDescriptorPublicKey::Single(_) => Err(DescriptorKeyError::InvalidKeyType),
BdkDescriptorPublicKey::XPub(descriptor_x_key) => {
let extended_path = descriptor_x_key.derivation_path.extend(path);
let extended_descriptor_public_key = BdkDescriptorPublicKey::XPub(DescriptorXKey {
@@ -225,16 +211,14 @@ impl DescriptorPublicKey {
derivation_path: extended_path,
wildcard: descriptor_x_key.wildcard,
});
Ok(Arc::new(Self {
inner: extended_descriptor_public_key,
}))
Ok(Arc::new(Self(extended_descriptor_public_key)))
}
BdkDescriptorPublicKey::MultiXPub(_) => Err(Alpha3Error::Generic),
BdkDescriptorPublicKey::MultiXPub(_) => Err(DescriptorKeyError::InvalidKeyType),
}
}
pub(crate) fn as_string(&self) -> String {
self.inner.to_string()
self.0.to_string()
}
}
@@ -242,7 +226,7 @@ impl DescriptorPublicKey {
mod test {
use crate::keys::{DerivationPath, DescriptorPublicKey, DescriptorSecretKey, Mnemonic};
// use bdk::bitcoin::hashes::hex::ToHex;
use crate::error::Alpha3Error;
use crate::error::DescriptorKeyError;
use bdk::bitcoin::Network;
use std::sync::Arc;
@@ -254,7 +238,7 @@ mod test {
fn derive_dsk(
key: &DescriptorSecretKey,
path: &str,
) -> Result<Arc<DescriptorSecretKey>, Alpha3Error> {
) -> Result<Arc<DescriptorSecretKey>, DescriptorKeyError> {
let path = DerivationPath::new(path.to_string()).unwrap();
key.derive(&path)
}
@@ -262,7 +246,7 @@ mod test {
fn extend_dsk(
key: &DescriptorSecretKey,
path: &str,
) -> Result<Arc<DescriptorSecretKey>, Alpha3Error> {
) -> Result<Arc<DescriptorSecretKey>, DescriptorKeyError> {
let path = DerivationPath::new(path.to_string()).unwrap();
key.extend(&path)
}
@@ -270,7 +254,7 @@ mod test {
fn derive_dpk(
key: &DescriptorPublicKey,
path: &str,
) -> Result<Arc<DescriptorPublicKey>, Alpha3Error> {
) -> Result<Arc<DescriptorPublicKey>, DescriptorKeyError> {
let path = DerivationPath::new(path.to_string()).unwrap();
key.derive(&path)
}
@@ -278,7 +262,7 @@ mod test {
fn extend_dpk(
key: &DescriptorPublicKey,
path: &str,
) -> Result<Arc<DescriptorPublicKey>, Alpha3Error> {
) -> Result<Arc<DescriptorPublicKey>, DescriptorKeyError> {
let path = DerivationPath::new(path.to_string()).unwrap();
key.extend(&path)
}

View File

@@ -1,5 +1,6 @@
mod bitcoin;
mod descriptor;
mod electrum;
mod error;
mod esplora;
mod keys;
@@ -7,21 +8,32 @@ mod types;
mod wallet;
use crate::bitcoin::Address;
use crate::bitcoin::Amount;
use crate::bitcoin::FeeRate;
use crate::bitcoin::OutPoint;
use crate::bitcoin::Psbt;
use crate::bitcoin::Script;
use crate::bitcoin::Transaction;
use crate::bitcoin::TxIn;
use crate::bitcoin::TxOut;
use crate::descriptor::Descriptor;
use crate::electrum::ElectrumClient;
use crate::error::AddressError;
use crate::error::Alpha3Error;
use crate::error::Bip32Error;
use crate::error::Bip39Error;
use crate::error::CalculateFeeError;
use crate::error::CannotConnectError;
use crate::error::CreateTxError;
use crate::error::DescriptorError;
use crate::error::DescriptorKeyError;
use crate::error::ElectrumError;
use crate::error::EsploraError;
use crate::error::ExtractTxError;
use crate::error::FeeRateError;
use crate::error::ParseAmountError;
use crate::error::PersistenceError;
use crate::error::PsbtParseError;
use crate::error::SignerError;
use crate::error::TransactionError;
use crate::error::TxidParseError;
use crate::error::WalletCreationError;
@@ -30,14 +42,14 @@ use crate::keys::DerivationPath;
use crate::keys::DescriptorPublicKey;
use crate::keys::DescriptorSecretKey;
use crate::keys::Mnemonic;
use crate::types::AddressIndex;
use crate::types::AddressInfo;
use crate::types::Balance;
use crate::types::CanonicalTx;
use crate::types::ChainPosition;
use crate::types::FeeRate;
use crate::types::FullScanRequest;
use crate::types::LocalOutput;
use crate::types::ScriptAmount;
use crate::types::SyncRequest;
use crate::wallet::BumpFeeTxBuilder;
use crate::wallet::SentAndReceivedValues;
use crate::wallet::TxBuilder;
@@ -50,5 +62,3 @@ use bdk::wallet::tx_builder::ChangeSpendPolicy;
use bdk::KeychainKind;
uniffi::include_scaffolding!("bdk");
// TODO: TxIn, Payload

View File

@@ -1,17 +1,17 @@
use crate::error::FeeRateError;
use crate::bitcoin::{Address, OutPoint, Script, Transaction, TxOut};
use bdk::bitcoin::FeeRate as BdkFeeRate;
use bdk::chain::spk_client::FullScanRequest as BdkFullScanRequest;
use bdk::chain::spk_client::SyncRequest as BdkSyncRequest;
use bdk::chain::tx_graph::CanonicalTx as BdkCanonicalTx;
use bdk::chain::{ChainPosition as BdkChainPosition, ConfirmationTimeHeightAnchor};
use bdk::wallet::AddressIndex as BdkAddressIndex;
use bdk::wallet::AddressInfo as BdkAddressInfo;
use bdk::wallet::Balance as BdkBalance;
use bdk::KeychainKind;
use bdk::LocalOutput as BdkLocalOutput;
use std::sync::Arc;
use std::sync::{Arc, Mutex};
use crate::bitcoin::Amount;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ChainPosition {
@@ -45,38 +45,9 @@ impl From<BdkCanonicalTx<'_, Arc<bdk::bitcoin::Transaction>, ConfirmationTimeHei
}
}
#[derive(Clone, Debug)]
pub struct FeeRate(pub BdkFeeRate);
impl FeeRate {
pub fn from_sat_per_vb(sat_per_vb: u64) -> Result<Self, FeeRateError> {
let fee_rate: Option<BdkFeeRate> = BdkFeeRate::from_sat_per_vb(sat_per_vb);
match fee_rate {
Some(fee_rate) => Ok(FeeRate(fee_rate)),
None => Err(FeeRateError::ArithmeticOverflow),
}
}
pub fn from_sat_per_kwu(sat_per_kwu: u64) -> Self {
FeeRate(BdkFeeRate::from_sat_per_kwu(sat_per_kwu))
}
pub fn to_sat_per_vb_ceil(&self) -> u64 {
self.0.to_sat_per_vb_ceil()
}
pub fn to_sat_per_vb_floor(&self) -> u64 {
self.0.to_sat_per_vb_floor()
}
pub fn to_sat_per_kwu(&self) -> u64 {
self.0.to_sat_per_kwu()
}
}
pub struct ScriptAmount {
pub script: Arc<Script>,
pub amount: u64,
pub amount: Arc<Amount>,
}
pub struct AddressInfo {
@@ -95,71 +66,24 @@ impl From<BdkAddressInfo> for AddressInfo {
}
}
pub enum AddressIndex {
New,
LastUnused,
Peek { index: u32 },
}
impl From<AddressIndex> for BdkAddressIndex {
fn from(address_index: AddressIndex) -> Self {
match address_index {
AddressIndex::New => BdkAddressIndex::New,
AddressIndex::LastUnused => BdkAddressIndex::LastUnused,
AddressIndex::Peek { index } => BdkAddressIndex::Peek(index),
}
}
}
impl From<BdkAddressIndex> for AddressIndex {
fn from(address_index: BdkAddressIndex) -> Self {
match address_index {
BdkAddressIndex::New => AddressIndex::New,
BdkAddressIndex::LastUnused => AddressIndex::LastUnused,
_ => panic!("Mmmm not working"),
}
}
}
// TODO 9: Peek is not correctly implemented
impl From<&AddressIndex> for BdkAddressIndex {
fn from(address_index: &AddressIndex) -> Self {
match address_index {
AddressIndex::New => BdkAddressIndex::New,
AddressIndex::LastUnused => BdkAddressIndex::LastUnused,
AddressIndex::Peek { index } => BdkAddressIndex::Peek(*index),
}
}
}
impl From<&BdkAddressIndex> for AddressIndex {
fn from(address_index: &BdkAddressIndex) -> Self {
match address_index {
BdkAddressIndex::New => AddressIndex::New,
BdkAddressIndex::LastUnused => AddressIndex::LastUnused,
_ => panic!("Mmmm not working"),
}
}
}
pub struct Balance {
pub immature: u64,
pub trusted_pending: u64,
pub untrusted_pending: u64,
pub confirmed: u64,
pub trusted_spendable: u64,
pub total: u64,
pub immature: Arc<Amount>,
pub trusted_pending: Arc<Amount>,
pub untrusted_pending: Arc<Amount>,
pub confirmed: Arc<Amount>,
pub trusted_spendable: Arc<Amount>,
pub total: Arc<Amount>,
}
impl From<BdkBalance> for Balance {
fn from(bdk_balance: BdkBalance) -> Self {
Balance {
immature: bdk_balance.immature,
trusted_pending: bdk_balance.trusted_pending,
untrusted_pending: bdk_balance.untrusted_pending,
confirmed: bdk_balance.confirmed,
trusted_spendable: bdk_balance.trusted_spendable(),
total: bdk_balance.total(),
immature: Arc::new(bdk_balance.immature.into()),
trusted_pending: Arc::new(bdk_balance.trusted_pending.into()),
untrusted_pending: Arc::new(bdk_balance.untrusted_pending.into()),
confirmed: Arc::new(bdk_balance.confirmed.into()),
trusted_spendable: Arc::new(bdk_balance.trusted_spendable().into()),
total: Arc::new(bdk_balance.total().into()),
}
}
}
@@ -187,3 +111,7 @@ impl From<BdkLocalOutput> for LocalOutput {
}
}
}
pub struct FullScanRequest(pub(crate) Mutex<Option<BdkFullScanRequest<KeychainKind>>>);
pub struct SyncRequest(pub(crate) Mutex<Option<BdkSyncRequest>>);

View File

@@ -1,20 +1,23 @@
use crate::bitcoin::{OutPoint, Psbt, Script, Transaction};
use crate::bitcoin::Amount;
use crate::bitcoin::{FeeRate, OutPoint, Psbt, Script, Transaction};
use crate::descriptor::Descriptor;
use crate::error::{
Alpha3Error, CalculateFeeError, PersistenceError, TxidParseError, WalletCreationError,
CalculateFeeError, CannotConnectError, CreateTxError, DescriptorError, PersistenceError,
SignerError, TxidParseError, WalletCreationError,
};
use crate::types::{
AddressIndex, AddressInfo, Balance, CanonicalTx, FeeRate, LocalOutput, ScriptAmount,
AddressInfo, Balance, CanonicalTx, FullScanRequest, LocalOutput, ScriptAmount, SyncRequest,
};
use bdk::bitcoin::amount::Amount as BdkAmount;
use bdk::bitcoin::blockdata::script::ScriptBuf as BdkScriptBuf;
use bdk::bitcoin::Network;
use bdk::bitcoin::Psbt as BdkPsbt;
use bdk::bitcoin::{OutPoint as BdkOutPoint, Sequence, Txid};
use bdk::wallet::tx_builder::ChangeSpendPolicy;
use bdk::wallet::{ChangeSet, Update as BdkUpdate};
use bdk::SignOptions;
use bdk::Wallet as BdkWallet;
use bdk::{KeychainKind, SignOptions};
use bdk_file_store::Store;
use std::collections::HashSet;
@@ -24,7 +27,7 @@ use std::sync::{Arc, Mutex, MutexGuard};
const MAGIC_BYTES: &[u8] = "bdkffi".as_bytes();
pub struct Wallet {
inner_mutex: Mutex<BdkWallet<Store<ChangeSet>>>,
inner_mutex: Mutex<BdkWallet>,
}
impl Wallet {
@@ -38,7 +41,7 @@ impl Wallet {
let change_descriptor = change_descriptor.map(|d| d.as_string_private());
let db = Store::<ChangeSet>::open_or_create_new(MAGIC_BYTES, persistence_backend_path)?;
let wallet: bdk::wallet::Wallet<Store<ChangeSet>> =
let wallet: BdkWallet =
BdkWallet::new_or_load(&descriptor, change_descriptor.as_ref(), db, network)?;
Ok(Wallet {
@@ -46,34 +49,50 @@ impl Wallet {
})
}
pub(crate) fn get_wallet(&self) -> MutexGuard<BdkWallet<Store<ChangeSet>>> {
pub fn new_no_persist(
descriptor: Arc<Descriptor>,
change_descriptor: Option<Arc<Descriptor>>,
network: Network,
) -> Result<Self, DescriptorError> {
let descriptor = descriptor.as_string_private();
let change_descriptor = change_descriptor.map(|d| d.as_string_private());
let wallet: BdkWallet =
BdkWallet::new_no_persist(&descriptor, change_descriptor.as_ref(), network)?;
Ok(Wallet {
inner_mutex: Mutex::new(wallet),
})
}
pub(crate) fn get_wallet(&self) -> MutexGuard<BdkWallet> {
self.inner_mutex.lock().expect("wallet")
}
pub fn get_address(&self, address_index: AddressIndex) -> AddressInfo {
pub fn reveal_next_address(
&self,
keychain_kind: KeychainKind,
) -> Result<AddressInfo, PersistenceError> {
self.get_wallet()
.try_get_address(address_index.into())
.unwrap()
.into()
.reveal_next_address(keychain_kind)
.map(|address_info| address_info.into())
.map_err(|e| PersistenceError::Write {
error_message: e.to_string(),
})
}
pub fn apply_update(&self, update: Arc<Update>) -> Result<(), Alpha3Error> {
pub fn apply_update(&self, update: Arc<Update>) -> Result<(), CannotConnectError> {
self.get_wallet()
.apply_update(update.0.clone())
.map_err(|_| Alpha3Error::Generic)
.map_err(CannotConnectError::from)
}
// TODO: This is the fallible version of get_internal_address; should I rename it to get_internal_address?
// It's a slight change of the API, the other option is to rename the get_address to try_get_address
pub fn try_get_internal_address(
&self,
address_index: AddressIndex,
) -> Result<AddressInfo, PersistenceError> {
let address_info = self
.get_wallet()
.try_get_internal_address(address_index.into())?
.into();
Ok(address_info)
pub fn commit(&self) -> Result<bool, PersistenceError> {
self.get_wallet()
.commit()
.map_err(|e| PersistenceError::Write {
error_message: e.to_string(),
})
}
pub fn network(&self) -> Network {
@@ -81,7 +100,7 @@ impl Wallet {
}
pub fn get_balance(&self) -> Balance {
let bdk_balance: bdk::wallet::Balance = self.get_wallet().get_balance();
let bdk_balance = self.get_wallet().get_balance();
Balance::from(bdk_balance)
}
@@ -93,16 +112,19 @@ impl Wallet {
&self,
psbt: Arc<Psbt>,
// sign_options: Option<SignOptions>,
) -> Result<bool, Alpha3Error> {
let mut psbt = psbt.inner.lock().unwrap();
) -> Result<bool, SignerError> {
let mut psbt = psbt.0.lock().unwrap();
self.get_wallet()
.sign(&mut psbt, SignOptions::default())
.map_err(|_| Alpha3Error::Generic)
.map_err(SignerError::from)
}
pub fn sent_and_received(&self, tx: &Transaction) -> SentAndReceivedValues {
let (sent, received): (u64, u64) = self.get_wallet().sent_and_received(&tx.into());
SentAndReceivedValues { sent, received }
let (sent, received) = self.get_wallet().sent_and_received(&tx.into());
SentAndReceivedValues {
sent: Arc::new(sent.into()),
received: Arc::new(received.into()),
}
}
pub fn transactions(&self) -> Vec<CanonicalTx> {
@@ -138,199 +160,28 @@ impl Wallet {
pub fn list_output(&self) -> Vec<LocalOutput> {
self.get_wallet().list_output().map(|o| o.into()).collect()
}
pub fn start_full_scan(&self) -> Arc<FullScanRequest> {
let request = self.get_wallet().start_full_scan();
Arc::new(FullScanRequest(Mutex::new(Some(request))))
}
pub fn start_sync_with_revealed_spks(&self) -> Arc<SyncRequest> {
let request = self.get_wallet().start_sync_with_revealed_spks();
Arc::new(SyncRequest(Mutex::new(Some(request))))
}
}
pub struct SentAndReceivedValues {
pub sent: u64,
pub received: u64,
pub sent: Arc<Amount>,
pub received: Arc<Amount>,
}
pub struct Update(pub(crate) BdkUpdate);
// /// A Bitcoin wallet.
// /// The Wallet acts as a way of coherently interfacing with output descriptors and related transactions. Its main components are:
// /// 1. Output descriptors from which it can derive addresses.
// /// 2. A Database where it tracks transactions and utxos related to the descriptors.
// /// 3. Signers that can contribute signatures to addresses instantiated from the descriptors.
// impl Wallet {
// pub fn new(
// descriptor: Arc<Descriptor>,
// change_descriptor: Option<Arc<Descriptor>>,
// network: Network,
// ) -> Result<Self, BdkError> {
// let wallet = BdkWallet::new_no_persist()?;
// Ok(Wallet {
// inner: wallet,
// })
// }
// }
// /// Return whether or not a script is part of this wallet (either internal or external).
// pub(crate) fn is_mine(&self, script: Arc<Script>) -> bool {
// self.inner.is_mine(&script.inner)
// }
//
// /// Sync the internal database with the blockchain.
// // pub(crate) fn sync(
// // &self,
// // blockchain: &Blockchain,
// // progress: Option<Box<dyn Progress>>,
// // ) -> Result<(), BdkError> {
// // let bdk_sync_opts = BdkSyncOptions {
// // progress: progress.map(|p| {
// // Box::new(ProgressHolder { progress: p })
// // as Box<(dyn bdk::blockchain::Progress + 'static)>
// // }),
// // };
// //
// // let blockchain = blockchain.get_blockchain();
// // self.get_wallet().sync(blockchain.deref(), bdk_sync_opts)
// // }
//
// /// Return a derived address using the external descriptor, see AddressIndex for available address index selection
// /// strategies. If none of the keys in the descriptor are derivable (i.e. the descriptor does not end with a * character)
// /// then the same address will always be returned for any AddressIndex.
// /// MIGRATION 1.0: The wallet needs to be mutated for this method to work... does that mean I should bring back the Mutex?
// /// Is this thread-safe?
// pub(crate) fn get_address(&mut self, address_index: AddressIndex) -> AddressInfo {
// AddressInfo::from(self.inner.get_address(address_index.into()))
// }
//
// /// Return a derived address using the internal (change) descriptor.
// ///
// /// If the wallet doesn't have an internal descriptor it will use the external descriptor.
// ///
// /// see [`AddressIndex`] for available address index selection strategies. If none of the keys
// /// in the descriptor are derivable (i.e. does not end with /*) then the same address will always
// /// be returned for any [`AddressIndex`].
// pub(crate) fn get_internal_address(&mut self, address_index: AddressIndex, ) -> AddressInfo {
// AddressInfo::from(self.inner.get_internal_address(address_index.into()))
// }
//
// /// Return the balance, meaning the sum of this 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) -> Balance {
// Balance::from(self.inner.get_balance())
// }
//
// /// Sign a transaction with all the wallet's signers, in the order specified by every signer's
// /// [`SignerOrdering`]. This function returns the `Result` type with an encapsulated `bool` that
// /// has the value true if the PSBT was finalized, or false otherwise.
// ///
// /// The [`SignOptions`] can be used to tweak the behavior of the software signers, and the way
// /// the transaction is finalized at the end. Note that it can't be guaranteed that *every*
// /// signers will follow the options, but the "software signers" (WIF keys and `xprv`) defined
// /// in this library will.
// pub(crate) fn sign(
// &self,
// psbt: &PartiallySignedTransaction,
// sign_options: Option<SignOptions>,
// ) -> Result<bool, BdkError> {
// let mut psbt = psbt.inner.lock().unwrap();
// self.inner.sign(
// &mut psbt,
// sign_options.map(SignOptions::into).unwrap_or_default(),
// )
// }
//
// /// Return the list of transactions made and received by the wallet. Note that this method only operate on the internal database, which first needs to be [Wallet.sync] manually.
// pub(crate) fn list_transactions(
// &self,
// include_raw: bool,
// ) -> Result<Vec<TransactionDetails>, BdkError> {
// let transaction_details = self.inner.list_transactions(include_raw)?;
// Ok(transaction_details
// .into_iter()
// .map(TransactionDetails::from)
// .collect())
// }
//
// /// Return the list of unspent outputs of this wallet. Note that this method only operates on the internal database,
// /// which first needs to be Wallet.sync manually.
// pub(crate) fn list_unspent(&self) -> Result<Vec<LocalUtxo>, BdkError> {
// let unspents: Vec<BdkLocalUtxo> = self.inner.list_unspent()?;
// Ok(unspents.into_iter().map(LocalUtxo::from).collect())
// }
// }
//
// /// Options for a software signer
// ///
// /// Adjust the behavior of our software signers and the way a transaction is finalized
// #[derive(Debug, Clone, Default)]
// pub struct SignOptions {
// /// Whether the signer should trust the `witness_utxo`, if the `non_witness_utxo` hasn't been
// /// provided
// ///
// /// Defaults to `false` to mitigate the "SegWit bug" which should trick the wallet into
// /// paying a fee larger than expected.
// ///
// /// Some wallets, especially if relatively old, might not provide the `non_witness_utxo` for
// /// SegWit transactions in the PSBT they generate: in those cases setting this to `true`
// /// should correctly produce a signature, at the expense of an increased trust in the creator
// /// of the PSBT.
// ///
// /// For more details see: <https://blog.trezor.io/details-of-firmware-updates-for-trezor-one-version-1-9-1-and-trezor-model-t-version-2-3-1-1eba8f60f2dd>
// pub trust_witness_utxo: bool,
//
// /// Whether the wallet should assume a specific height has been reached when trying to finalize
// /// a transaction
// ///
// /// The wallet will only "use" a timelock to satisfy the spending policy of an input if the
// /// timelock height has already been reached. This option allows overriding the "current height" to let the
// /// wallet use timelocks in the future to spend a coin.
// pub assume_height: Option<u32>,
//
// /// Whether the signer should use the `sighash_type` set in the PSBT when signing, no matter
// /// what its value is
// ///
// /// Defaults to `false` which will only allow signing using `SIGHASH_ALL`.
// pub allow_all_sighashes: bool,
//
// /// Whether to remove partial signatures from the PSBT inputs while finalizing PSBT.
// ///
// /// Defaults to `true` which will remove partial signatures during finalization.
// pub remove_partial_sigs: bool,
//
// /// Whether to try finalizing the PSBT after the inputs are signed.
// ///
// /// Defaults to `true` which will try finalizing PSBT after inputs are signed.
// pub try_finalize: bool,
//
// // Specifies which Taproot script-spend leaves we should sign for. This option is
// // ignored if we're signing a non-taproot PSBT.
// //
// // Defaults to All, i.e., the wallet will sign all the leaves it has a key for.
// // TODO pub tap_leaves_options: TapLeavesOptions,
// /// Whether we should try to sign a taproot transaction with the taproot internal key
// /// or not. This option is ignored if we're signing a non-taproot PSBT.
// ///
// /// Defaults to `true`, i.e., we always try to sign with the taproot internal key.
// pub sign_with_tap_internal_key: bool,
//
// /// Whether we should grind ECDSA signature to ensure signing with low r
// /// or not.
// /// Defaults to `true`, i.e., we always grind ECDSA signature to sign with low r.
// pub allow_grinding: bool,
// }
//
// impl From<SignOptions> for BdkSignOptions {
// fn from(sign_options: SignOptions) -> Self {
// BdkSignOptions {
// trust_witness_utxo: sign_options.trust_witness_utxo,
// assume_height: sign_options.assume_height,
// allow_all_sighashes: sign_options.allow_all_sighashes,
// remove_partial_sigs: sign_options.remove_partial_sigs,
// try_finalize: sign_options.try_finalize,
// tap_leaves_options: Default::default(),
// sign_with_tap_internal_key: sign_options.sign_with_tap_internal_key,
// allow_grinding: sign_options.allow_grinding,
// }
// }
// }
#[derive(Clone, Debug)]
pub struct TxBuilder {
pub(crate) recipients: Vec<(BdkScriptBuf, u64)>,
pub(crate) recipients: Vec<(BdkScriptBuf, BdkAmount)>,
pub(crate) utxos: Vec<OutPoint>,
pub(crate) unspendable: HashSet<OutPoint>,
pub(crate) change_policy: ChangeSpendPolicy,
@@ -360,9 +211,9 @@ impl TxBuilder {
}
}
pub(crate) fn add_recipient(&self, script: &Script, amount: u64) -> Arc<Self> {
let mut recipients: Vec<(BdkScriptBuf, u64)> = self.recipients.clone();
recipients.append(&mut vec![(script.0.clone(), amount)]);
pub(crate) fn add_recipient(&self, script: &Script, amount: Arc<Amount>) -> Arc<Self> {
let mut recipients: Vec<(BdkScriptBuf, BdkAmount)> = self.recipients.clone();
recipients.append(&mut vec![(script.0.clone(), amount.0)]);
Arc::new(TxBuilder {
recipients,
@@ -373,7 +224,7 @@ impl TxBuilder {
pub(crate) fn set_recipients(&self, recipients: Vec<ScriptAmount>) -> Arc<Self> {
let recipients = recipients
.iter()
.map(|script_amount| (script_amount.script.0.clone(), script_amount.amount))
.map(|script_amount| (script_amount.script.0.clone(), script_amount.amount.0)) //;
.collect();
Arc::new(TxBuilder {
recipients,
@@ -480,15 +331,7 @@ impl TxBuilder {
})
}
/// Add data as an output using OP_RETURN.
// pub(crate) fn add_data(&self, data: Vec<u8>) -> Arc<Self> {
// Arc::new(TxBuilder {
// data,
// ..self.clone()
// })
// }
pub(crate) fn finish(&self, wallet: &Arc<Wallet>) -> Result<Arc<Psbt>, Alpha3Error> {
pub(crate) fn finish(&self, wallet: &Arc<Wallet>) -> Result<Arc<Psbt>, CreateTxError> {
// TODO: I had to change the wallet here to be mutable. Why is that now required with the 1.0 API?
let mut wallet = wallet.get_wallet();
let mut tx_builder = wallet.build_tx();
@@ -498,8 +341,9 @@ impl TxBuilder {
tx_builder.change_policy(self.change_policy);
if !self.utxos.is_empty() {
let bdk_utxos: Vec<BdkOutPoint> = self.utxos.iter().map(BdkOutPoint::from).collect();
let utxos: &[BdkOutPoint] = &bdk_utxos;
tx_builder.add_utxos(utxos)?;
tx_builder
.add_utxos(&bdk_utxos)
.map_err(CreateTxError::from)?;
}
if !self.unspendable.is_empty() {
let bdk_unspendable: Vec<BdkOutPoint> =
@@ -531,11 +375,8 @@ impl TxBuilder {
}
}
}
// if !&self.data.is_empty() {
// tx_builder.add_data(self.data.as_slice());
// }
let psbt = tx_builder.finish()?;
let psbt = tx_builder.finish().map_err(CreateTxError::from)?;
Ok(Arc::new(psbt.into()))
}
@@ -545,7 +386,6 @@ impl TxBuilder {
pub(crate) struct BumpFeeTxBuilder {
pub(crate) txid: String,
pub(crate) fee_rate: Arc<FeeRate>,
pub(crate) allow_shrinking: Option<Arc<Script>>,
pub(crate) rbf: Option<RbfValue>,
}
@@ -554,18 +394,10 @@ impl BumpFeeTxBuilder {
Self {
txid,
fee_rate,
allow_shrinking: None,
rbf: None,
}
}
pub(crate) fn allow_shrinking(&self, script_pubkey: Arc<Script>) -> Arc<Self> {
Arc::new(Self {
allow_shrinking: Some(script_pubkey),
..self.clone()
})
}
pub(crate) fn enable_rbf(&self) -> Arc<Self> {
Arc::new(Self {
rbf: Some(RbfValue::Default),
@@ -580,14 +412,13 @@ impl BumpFeeTxBuilder {
})
}
pub(crate) fn finish(&self, wallet: &Wallet) -> Result<Arc<Psbt>, Alpha3Error> {
let txid = Txid::from_str(self.txid.as_str()).map_err(|_| Alpha3Error::Generic)?;
pub(crate) fn finish(&self, wallet: &Wallet) -> Result<Arc<Psbt>, CreateTxError> {
let txid = Txid::from_str(self.txid.as_str()).map_err(|_| CreateTxError::UnknownUtxo {
outpoint: self.txid.clone(),
})?;
let mut wallet = wallet.get_wallet();
let mut tx_builder = wallet.build_fee_bump(txid)?;
let mut tx_builder = wallet.build_fee_bump(txid).map_err(CreateTxError::from)?;
tx_builder.fee_rate(self.fee_rate.0);
if let Some(allow_shrinking) = &self.allow_shrinking {
tx_builder.allow_shrinking(allow_shrinking.0.clone())?;
}
if let Some(rbf) = &self.rbf {
match *rbf {
RbfValue::Default => {
@@ -598,7 +429,7 @@ impl BumpFeeTxBuilder {
}
}
}
let psbt: BdkPsbt = tx_builder.finish().map_err(|_| Alpha3Error::Generic)?;
let psbt: BdkPsbt = tx_builder.finish()?;
Ok(Arc::new(psbt.into()))
}

View File

@@ -36,10 +36,10 @@ curl -s "https://get.sdkman.io" | bash
source "$HOME/.sdkman/bin/sdkman-init.sh"
sdk install java 17.0.2-tem
```
2. Install Rust (note that we are currently building using Rust 1.73.0):
2. Install Rust (note that we are currently building using Rust 1.77.1):
```shell
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
rustup default 1.73.0
rustup default 1.77.1
```
3. Clone this repository.
```shell

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -5,6 +5,9 @@ import kotlin.test.AfterTest
import kotlin.test.Test
import kotlin.test.assertTrue
private const val SIGNET_ESPLORA_URL = "http://signet.bitcoindevkit.net"
private const val TESTNET_ESPLORA_URL = "https://esplora.testnet.kuutamo.cloud"
class LiveTxBuilderTest {
private val persistenceFilePath = run {
val currentDirectory = System.getProperty("user.dir")
@@ -22,17 +25,21 @@ class LiveTxBuilderTest {
@Test
fun testTxBuilder() {
val descriptor = Descriptor("wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)", Network.TESTNET)
val wallet = Wallet(descriptor, null, persistenceFilePath, Network.TESTNET)
val esploraClient = EsploraClient("https://esplora.testnet.kuutamo.cloud/")
val update = esploraClient.fullScan(wallet, 10uL, 1uL)
val wallet = Wallet(descriptor, null, persistenceFilePath, Network.SIGNET)
val esploraClient = EsploraClient(SIGNET_ESPLORA_URL)
val fullScanRequest: FullScanRequest = wallet.startFullScan()
val update = esploraClient.fullScan(fullScanRequest, 10uL, 1uL)
wallet.applyUpdate(update)
println("Balance: ${wallet.getBalance().total}")
wallet.commit()
println("Balance: ${wallet.getBalance().total.toSat()}")
assert(wallet.getBalance().total > 0uL)
assert(wallet.getBalance().total.toSat() > 0uL) {
"Wallet balance must be greater than 0! Please send funds to ${wallet.revealNextAddress(KeychainKind.EXTERNAL).address.asString()} and try again."
}
val recipient: Address = Address("tb1qrnfslnrve9uncz9pzpvf83k3ukz22ljgees989", Network.TESTNET)
val recipient: Address = Address("tb1qrnfslnrve9uncz9pzpvf83k3ukz22ljgees989", Network.SIGNET)
val psbt: Psbt = TxBuilder()
.addRecipient(recipient.scriptPubkey(), 4200uL)
.addRecipient(recipient.scriptPubkey(), Amount.fromSat(4200uL))
.feeRate(FeeRate.fromSatPerVb(2uL))
.finish(wallet)
@@ -45,19 +52,23 @@ class LiveTxBuilderTest {
fun complexTxBuilder() {
val externalDescriptor = Descriptor("wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)", Network.TESTNET)
val changeDescriptor = Descriptor("wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/1/*)", Network.TESTNET)
val wallet = Wallet(externalDescriptor, changeDescriptor, persistenceFilePath, Network.TESTNET)
val esploraClient = EsploraClient("https://esplora.testnet.kuutamo.cloud/")
val update = esploraClient.fullScan(wallet, 10uL, 1uL)
val wallet = Wallet(externalDescriptor, changeDescriptor, persistenceFilePath, Network.SIGNET)
val esploraClient = EsploraClient(SIGNET_ESPLORA_URL)
val fullScanRequest: FullScanRequest = wallet.startFullScan()
val update = esploraClient.fullScan(fullScanRequest, 10uL, 1uL)
wallet.applyUpdate(update)
println("Balance: ${wallet.getBalance().total}")
wallet.commit()
println("Balance: ${wallet.getBalance().total.toSat()}")
assert(wallet.getBalance().total > 0uL)
assert(wallet.getBalance().total.toSat() > 0uL) {
"Wallet balance must be greater than 0! Please send funds to ${wallet.revealNextAddress(KeychainKind.EXTERNAL).address.asString()} and try again."
}
val recipient1: Address = Address("tb1qrnfslnrve9uncz9pzpvf83k3ukz22ljgees989", Network.TESTNET)
val recipient2: Address = Address("tb1qw2c3lxufxqe2x9s4rdzh65tpf4d7fssjgh8nv6", Network.TESTNET)
val recipient1: Address = Address("tb1qrnfslnrve9uncz9pzpvf83k3ukz22ljgees989", Network.SIGNET)
val recipient2: Address = Address("tb1qw2c3lxufxqe2x9s4rdzh65tpf4d7fssjgh8nv6", Network.SIGNET)
val allRecipients: List<ScriptAmount> = listOf(
ScriptAmount(recipient1.scriptPubkey(), 4200uL),
ScriptAmount(recipient2.scriptPubkey(), 4200uL),
ScriptAmount(recipient1.scriptPubkey(), Amount.fromSat(4200uL)),
ScriptAmount(recipient2.scriptPubkey(), Amount.fromSat(4200uL)),
)
val psbt: Psbt = TxBuilder()

View File

@@ -5,6 +5,9 @@ import kotlin.test.AfterTest
import kotlin.test.Test
import kotlin.test.assertTrue
private const val SIGNET_ESPLORA_URL = "http://signet.bitcoindevkit.net"
private const val TESTNET_ESPLORA_URL = "https://esplora.testnet.kuutamo.cloud"
class LiveWalletTest {
private val persistenceFilePath = run {
val currentDirectory = System.getProperty("user.dir")
@@ -21,16 +24,18 @@ class LiveWalletTest {
@Test
fun testSyncedBalance() {
val descriptor: Descriptor = Descriptor("wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)", Network.TESTNET)
val wallet: Wallet = Wallet(descriptor, null, persistenceFilePath, Network.TESTNET)
val esploraClient: EsploraClient = EsploraClient("https://esplora.testnet.kuutamo.cloud/")
// val esploraClient: EsploraClient = EsploraClient("https://mempool.space/testnet/api")
// val esploraClient = EsploraClient("https://blockstream.info/testnet/api")
val update = esploraClient.fullScan(wallet, 10uL, 1uL)
val descriptor: Descriptor = Descriptor("wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)", Network.SIGNET)
val wallet: Wallet = Wallet(descriptor, null, persistenceFilePath, Network.SIGNET)
val esploraClient: EsploraClient = EsploraClient(SIGNET_ESPLORA_URL)
val fullScanRequest: FullScanRequest = wallet.startFullScan()
val update = esploraClient.fullScan(fullScanRequest, 10uL, 1uL)
wallet.applyUpdate(update)
println("Balance: ${wallet.getBalance().total}")
wallet.commit()
println("Balance: ${wallet.getBalance().total.toSat()}")
assert(wallet.getBalance().total > 0uL)
assert(wallet.getBalance().total.toSat() > 0uL) {
"Wallet balance must be greater than 0! Please send funds to ${wallet.revealNextAddress(KeychainKind.EXTERNAL).address.asString()} and try again."
}
println("Transactions count: ${wallet.transactions().count()}")
val transactions = wallet.transactions().take(3)
@@ -44,23 +49,23 @@ class LiveWalletTest {
@Test
fun testBroadcastTransaction() {
val descriptor = Descriptor("wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)", Network.TESTNET)
val wallet: Wallet = Wallet(descriptor, null, persistenceFilePath, Network.TESTNET)
val esploraClient = EsploraClient("https://esplora.testnet.kuutamo.cloud/")
val update = esploraClient.fullScan(wallet, 10uL, 1uL)
val descriptor = Descriptor("wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)", Network.SIGNET)
val wallet: Wallet = Wallet(descriptor, null, persistenceFilePath, Network.SIGNET)
val esploraClient = EsploraClient(SIGNET_ESPLORA_URL)
val fullScanRequest: FullScanRequest = wallet.startFullScan()
val update = esploraClient.fullScan(fullScanRequest, 10uL, 1uL)
wallet.applyUpdate(update)
println("Balance: ${wallet.getBalance().total}")
println("New address: ${wallet.getAddress(AddressIndex.New).address.asString()}")
wallet.commit()
println("Balance: ${wallet.getBalance().total.toSat()}")
assert(wallet.getBalance().total > 0uL) {
"Wallet balance must be greater than 0! Please send funds to ${wallet.getAddress(AddressIndex.New).address} and try again."
assert(wallet.getBalance().total.toSat() > 0uL) {
"Wallet balance must be greater than 0! Please send funds to ${wallet.revealNextAddress(KeychainKind.EXTERNAL).address.asString()} and try again."
}
val recipient: Address = Address("tb1qrnfslnrve9uncz9pzpvf83k3ukz22ljgees989", Network.TESTNET)
val recipient: Address = Address("tb1qrnfslnrve9uncz9pzpvf83k3ukz22ljgees989", Network.SIGNET)
val psbt: Psbt = TxBuilder()
.addRecipient(recipient.scriptPubkey(), 4200uL)
.addRecipient(recipient.scriptPubkey(), Amount.fromSat(4200uL))
.feeRate(FeeRate.fromSatPerVb(2uL))
.finish(wallet)

View File

@@ -42,7 +42,7 @@ class OfflineWalletTest {
persistenceFilePath,
Network.TESTNET
)
val addressInfo: AddressInfo = wallet.getAddress(AddressIndex.New)
val addressInfo: AddressInfo = wallet.revealNextAddress(KeychainKind.EXTERNAL)
assertTrue(addressInfo.address.isValidForNetwork(Network.TESTNET), "Address is not valid for testnet network")
assertTrue(addressInfo.address.isValidForNetwork(Network.SIGNET), "Address is not valid for signet network")
@@ -70,7 +70,7 @@ class OfflineWalletTest {
assertEquals(
expected = 0uL,
actual = wallet.getBalance().total
actual = wallet.getBalance().total.toSat()
)
}
}

View File

@@ -112,20 +112,20 @@ internal class UniFfiJvmPlugin : Plugin<Project> {
// TODO 2: Is the Windows name the correct one?
// TODO 3: This will not work on mac Intel (x86_64 architecture)
// val libraryPath = when (operatingSystem) {
// OS.LINUX -> "./target/x86_64-unknown-linux-gnu/release-smaller/libbdkffi.so"
// OS.MAC -> "./target/aarch64-apple-darwin/release-smaller/libbdkffi.dylib"
// OS.WINDOWS -> "./target/x86_64-pc-windows-msvc/release-smaller/bdkffi.dll"
// else -> throw Exception("Unsupported OS")
// }
val libraryPath = when (operatingSystem) {
OS.LINUX -> "./target/x86_64-unknown-linux-gnu/release-smaller/libbdkffi.so"
OS.MAC -> "./target/aarch64-apple-darwin/release-smaller/libbdkffi.dylib"
OS.WINDOWS -> "./target/x86_64-pc-windows-msvc/release-smaller/bdkffi.dll"
else -> throw Exception("Unsupported OS")
}
// workingDir("${project.projectDir}/../../bdk-ffi/")
// val cargoArgs: List<String> = listOf("run", "--bin", "uniffi-bindgen", "generate", "--library", libraryPath, "--language", "kotlin", "--out-dir", "../bdk-jvm/lib/src/main/kotlin/", "--no-format")
workingDir("${project.projectDir}/../../bdk-ffi/")
val cargoArgs: List<String> = listOf("run", "--bin", "uniffi-bindgen", "generate", "--library", libraryPath, "--language", "kotlin", "--out-dir", "../bdk-jvm/lib/src/main/kotlin/", "--no-format")
// The code above was for the migration to uniffi 0.24.3 using the --library flag
// The code below works with uniffi 0.23.0
workingDir("${project.projectDir}/../../bdk-ffi/")
val cargoArgs: List<String> = listOf("run", "--bin", "uniffi-bindgen", "generate", "src/bdk.udl", "--language", "kotlin", "--out-dir", "../bdk-jvm/lib/src/main/kotlin", "--no-format")
// workingDir("${project.projectDir}/../../bdk-ffi/")
// val cargoArgs: List<String> = listOf("run", "--bin", "uniffi-bindgen", "generate", "src/bdk.udl", "--language", "kotlin", "--out-dir", "../bdk-jvm/lib/src/main/kotlin", "--no-format")
executable("cargo")
args(cargoArgs)

View File

@@ -1,7 +1,7 @@
test:
python -m unittest --verbose
default:
just --list
maclocalbuild:
build-local-mac:
bash ./scripts/generate-macos-arm64.sh && python3 setup.py bdist_wheel --verbose
clean:
@@ -9,3 +9,6 @@ clean:
rm -rf ./bdkpython.egg-info/
rm -rf ./build/
rm -rf ./dist/
test:
python3 -m unittest --verbose

View File

@@ -4,14 +4,15 @@ set -euo pipefail
${PYBIN}/python --version
${PYBIN}/pip install -r requirements.txt
echo "Generating bdk.py..."
cd ../bdk-ffi/
cargo run --bin uniffi-bindgen generate src/bdk.udl --language python --out-dir ../bdk-python/src/bdkpython/ --no-format
rustup default 1.77.1
echo "Generating native binaries..."
rustup default 1.73.0
cargo build --profile release-smaller
echo "Generating bdk.py..."
cargo run --bin uniffi-bindgen generate --library ./target/release-smaller/libbdkffi.so --language python --out-dir ../bdk-python/src/bdkpython/ --no-format
echo "Copying linux libbdkffi.so..."
cp ./target/release-smaller/libbdkffi.so ../bdk-python/src/bdkpython/libbdkffi.so

View File

@@ -4,15 +4,16 @@ set -euo pipefail
python3 --version
pip install -r requirements.txt
echo "Generating bdk.py..."
cd ../bdk-ffi/
cargo run --bin uniffi-bindgen generate src/bdk.udl --language python --out-dir ../bdk-python/src/bdkpython/ --no-format
rustup default 1.77.1
rustup target add aarch64-apple-darwin
echo "Generating native binaries..."
rustup default 1.73.0
rustup target add aarch64-apple-darwin
cargo build --profile release-smaller --target aarch64-apple-darwin
echo "Generating bdk.py..."
cargo run --bin uniffi-bindgen generate --library ./target/aarch64-apple-darwin/release-smaller/libbdkffi.dylib --language python --out-dir ../bdk-python/src/bdkpython/ --no-format
echo "Copying libraries libbdkffi.dylib..."
cp ./target/aarch64-apple-darwin/release-smaller/libbdkffi.dylib ../bdk-python/src/bdkpython/libbdkffi.dylib

View File

@@ -4,15 +4,16 @@ set -euo pipefail
python3 --version
pip install -r requirements.txt
echo "Generating bdk.py..."
cd ../bdk-ffi/
cargo run --bin uniffi-bindgen generate src/bdk.udl --language python --out-dir ../bdk-python/src/bdkpython/ --no-format
rustup default 1.77.1
rustup target add x86_64-apple-darwin
echo "Generating native binaries..."
rustup default 1.73.0
rustup target add x86_64-apple-darwin
cargo build --profile release-smaller --target x86_64-apple-darwin
echo "Generating bdk.py..."
cargo run --bin uniffi-bindgen generate --library ./target/x86_64-apple-darwin/release-smaller/libbdkffi.dylib --language python --out-dir ../bdk-python/src/bdkpython/ --no-format
echo "Copying libraries libbdkffi.dylib..."
cp ./target/x86_64-apple-darwin/release-smaller/libbdkffi.dylib ../bdk-python/src/bdkpython/libbdkffi.dylib

View File

@@ -4,15 +4,16 @@ set -euo pipefail
python3 --version
pip install -r requirements.txt
echo "Generating bdk.py..."
cd ../bdk-ffi/
cargo run --bin uniffi-bindgen generate src/bdk.udl --language python --out-dir ../bdk-python/src/bdkpython/ --no-format
rustup default 1.77.1
rustup target add x86_64-pc-windows-msvc
echo "Generating native binaries..."
rustup default 1.73.0
rustup target add x86_64-pc-windows-msvc
cargo build --profile release-smaller --target x86_64-pc-windows-msvc
echo "Generating bdk.py..."
cargo run --bin uniffi-bindgen generate --library ./target/x86_64-pc-windows-msvc/release-smaller/bdkffi.dll --language python --out-dir ../bdk-python/src/bdkpython/ --no-format
echo "Copying libraries bdkffi.dll..."
cp ./target/x86_64-pc-windows-msvc/release-smaller/bdkffi.dll ../bdk-python/src/bdkpython/bdkffi.dll

View File

@@ -18,7 +18,7 @@ import bdkpython as bdk
setup(
name="bdkpython",
version="1.0.0a8.dev",
version="1.0.0a11",
description="The Python language bindings for the Bitcoin Development Kit",
long_description=LONG_DESCRIPTION,
long_description_content_type="text/markdown",

View File

@@ -2,6 +2,9 @@ import bdkpython as bdk
import unittest
import os
SIGNET_ESPLORA_URL = "http://signet.bitcoindevkit.net"
TESTNET_ESPLORA_URL = "https://esplora.testnet.kuutamo.cloud"
class LiveTxBuilderTest(unittest.TestCase):
def tearDown(self) -> None:
@@ -10,66 +13,74 @@ class LiveTxBuilderTest(unittest.TestCase):
def test_tx_builder(self):
descriptor: bdk.Descriptor = bdk.Descriptor(
"wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)",
bdk.Network.TESTNET
"wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)",
bdk.Network.SIGNET
)
wallet: bdk.Wallet = bdk.Wallet(
descriptor,
None,
"./bdk_persistence.db",
bdk.Network.TESTNET
bdk.Network.SIGNET
)
esploraClient: bdk.EsploraClient = bdk.EsploraClient(url = "https://esplora.testnet.kuutamo.cloud/")
update = esploraClient.full_scan(
wallet = wallet,
stop_gap = 10,
parallel_requests = 1
esplora_client: bdk.EsploraClient = bdk.EsploraClient(url = SIGNET_ESPLORA_URL)
full_scan_request: bdk.FullScanRequest = wallet.start_full_scan()
update = esplora_client.full_scan(
full_scan_request=full_scan_request,
stop_gap=10,
parallel_requests=1
)
wallet.apply_update(update)
wallet.commit()
self.assertGreater(wallet.get_balance().total, 0)
recipient = bdk.Address(
address = "tb1qrnfslnrve9uncz9pzpvf83k3ukz22ljgees989",
network = bdk.Network.TESTNET
self.assertGreater(
wallet.get_balance().total.to_sat(),
0,
f"Wallet balance must be greater than 0! Please send funds to {wallet.reveal_next_address(bdk.KeychainKind.EXTERNAL).address.as_string()} and try again."
)
psbt = bdk.TxBuilder().add_recipient(script=recipient.script_pubkey(), amount=4200).fee_rate(fee_rate=bdk.FeeRate.from_sat_per_vb(2)).finish(wallet)
recipient = bdk.Address(
address="tb1qrnfslnrve9uncz9pzpvf83k3ukz22ljgees989",
network=bdk.Network.SIGNET
)
psbt = bdk.TxBuilder().add_recipient(script=recipient.script_pubkey(), amount=bdk.Amount.from_sat(4200)).fee_rate(fee_rate=bdk.FeeRate.from_sat_per_vb(2)).finish(wallet)
self.assertTrue(psbt.serialize().startswith("cHNi"), "The PSBT should start with cHNi")
def complex_tx_builder(self):
descriptor: bdk.Descriptor = bdk.Descriptor(
"wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)",
bdk.Network.TESTNET
)
change_descriptor: bdk.Descriptor = bdk.Descriptor(
"wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)",
bdk.Network.TESTNET
"wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)",
bdk.Network.SIGNET
)
wallet: bdk.Wallet = bdk.Wallet(
descriptor,
change_descriptor,
None,
"./bdk_persistence.db",
bdk.Network.TESTNET
bdk.Network.SIGNET
)
esploraClient: bdk.EsploraClient = bdk.EsploraClient(url = "https://esplora.testnet.kuutamo.cloud/")
update = esploraClient.full_scan(
wallet = wallet,
stop_gap = 10,
parallel_requests = 1
esplora_client: bdk.EsploraClient = bdk.EsploraClient(url = SIGNET_ESPLORA_URL)
full_scan_request: bdk.FullScanRequest = wallet.start_full_scan()
update = esplora_client.full_scan(
full_scan_request=full_scan_request,
stop_gap=10,
parallel_requests=1
)
wallet.apply_update(update)
wallet.commit()
self.assertGreater(wallet.get_balance().total, 0)
self.assertGreater(
wallet.get_balance().total.to_sat(),
0,
f"Wallet balance must be greater than 0! Please send funds to {wallet.reveal_next_address(bdk.KeychainKind.EXTERNAL).address.as_string()} and try again."
)
recipient1 = bdk.Address(
address = "tb1qrnfslnrve9uncz9pzpvf83k3ukz22ljgees989",
network = bdk.Network.TESTNET
address="tb1qrnfslnrve9uncz9pzpvf83k3ukz22ljgees989",
network=bdk.Network.SIGNET
)
recipient2 = bdk.Address(
address = "tb1qw2c3lxufxqe2x9s4rdzh65tpf4d7fssjgh8nv6",
network = bdk.Network.TESTNET
address="tb1qw2c3lxufxqe2x9s4rdzh65tpf4d7fssjgh8nv6",
network=bdk.Network.SIGNET
)
all_recipients = list(
bdk.ScriptAmount(recipient1.script_pubkey, 4200),

View File

@@ -2,6 +2,9 @@ import bdkpython as bdk
import unittest
import os
SIGNET_ESPLORA_URL = "http://signet.bitcoindevkit.net"
TESTNET_ESPLORA_URL = "https://esplora.testnet.kuutamo.cloud"
class LiveWalletTest(unittest.TestCase):
def tearDown(self) -> None:
@@ -11,61 +14,72 @@ class LiveWalletTest(unittest.TestCase):
def test_synced_balance(self):
descriptor: bdk.Descriptor = bdk.Descriptor(
"wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)",
bdk.Network.TESTNET
bdk.Network.SIGNET
)
wallet: bdk.Wallet = bdk.Wallet(
descriptor,
None,
"./bdk_persistence.db",
bdk.Network.TESTNET
bdk.Network.SIGNET
)
esploraClient: bdk.EsploraClient = bdk.EsploraClient(url = "https://esplora.testnet.kuutamo.cloud/")
update = esploraClient.full_scan(
wallet = wallet,
stop_gap = 10,
parallel_requests = 1
esplora_client: bdk.EsploraClient = bdk.EsploraClient(url = SIGNET_ESPLORA_URL)
full_scan_request: bdk.FullScanRequest = wallet.start_full_scan()
update = esplora_client.full_scan(
full_scan_request=full_scan_request,
stop_gap=10,
parallel_requests=1
)
wallet.apply_update(update)
wallet.commit()
self.assertGreater(wallet.get_balance().total, 0)
self.assertGreater(
wallet.get_balance().total.to_sat(),
0,
f"Wallet balance must be greater than 0! Please send funds to {wallet.reveal_next_address(bdk.KeychainKind.EXTERNAL).address.as_string()} and try again."
)
print(f"Transactions count: {len(wallet.transactions())}")
transactions = wallet.transactions()[:3]
for tx in transactions:
sent_and_received = wallet.sent_and_received(tx.transaction)
print(f"Transaction: {tx.transaction.txid()}")
print(f"Sent {sent_and_received.sent}")
print(f"Received {sent_and_received.received}")
print(f"Sent {sent_and_received.sent.to_sat()}")
print(f"Received {sent_and_received.received.to_sat()}")
def test_broadcast_transaction(self):
descriptor: bdk.Descriptor = bdk.Descriptor(
"wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)",
bdk.Network.TESTNET
bdk.Network.SIGNET
)
wallet: bdk.Wallet = bdk.Wallet(
descriptor,
None,
"./bdk_persistence.db",
bdk.Network.TESTNET
bdk.Network.SIGNET
)
esploraClient: bdk.EsploraClient = bdk.EsploraClient(url = "https://esplora.testnet.kuutamo.cloud/")
update = esploraClient.full_scan(
wallet = wallet,
stop_gap = 10,
parallel_requests = 1
esplora_client: bdk.EsploraClient = bdk.EsploraClient(url = SIGNET_ESPLORA_URL)
full_scan_request: bdk.FullScanRequest = wallet.start_full_scan()
update = esplora_client.full_scan(
full_scan_request=full_scan_request,
stop_gap=10,
parallel_requests=1
)
wallet.apply_update(update)
wallet.commit()
self.assertGreater(wallet.get_balance().total, 0)
recipient = bdk.Address(
address = "tb1qrnfslnrve9uncz9pzpvf83k3ukz22ljgees989",
network = bdk.Network.TESTNET
self.assertGreater(
wallet.get_balance().total.to_sat(),
0,
f"Wallet balance must be greater than 0! Please send funds to {wallet.reveal_next_address(bdk.KeychainKind.EXTERNAL).address.as_string()} and try again."
)
psbt: bdk.Psbt = bdk.TxBuilder().add_recipient(script=recipient.script_pubkey(), amount=4200).fee_rate(fee_rate=bdk.FeeRate.from_sat_per_vb(2)).finish(wallet)
# print(psbt.serialize())
recipient = bdk.Address(
address="tb1qrnfslnrve9uncz9pzpvf83k3ukz22ljgees989",
network=bdk.Network.SIGNET
)
psbt: bdk.Psbt = bdk.TxBuilder().add_recipient(script=recipient.script_pubkey(), amount=bdk.Amount.from_sat(4200)).fee_rate(fee_rate=bdk.FeeRate.from_sat_per_vb(2)).finish(wallet)
self.assertTrue(psbt.serialize().startswith("cHNi"), "The PSBT should start with cHNi")
walletDidSign = wallet.sign(psbt)
@@ -77,7 +91,7 @@ class LiveWalletTest(unittest.TestCase):
fee_rate = wallet.calculate_fee_rate(tx)
print(f"Transaction Fee Rate: {fee_rate.to_sat_per_vb_ceil()} sat/vB")
esploraClient.broadcast(tx)
esplora_client.broadcast(tx)
if __name__ == '__main__':

View File

@@ -19,7 +19,7 @@ class OfflineWalletTest(unittest.TestCase):
"./bdk_persistence.db",
bdk.Network.TESTNET
)
address_info: bdk.AddressInfo = wallet.get_address(bdk.AddressIndex.NEW())
address_info: bdk.AddressInfo = wallet.reveal_next_address(bdk.KeychainKind.EXTERNAL)
self.assertTrue(address_info.address.is_valid_for_network(bdk.Network.TESTNET), "Address is not valid for testnet network")
self.assertTrue(address_info.address.is_valid_for_network(bdk.Network.SIGNET), "Address is not valid for signet network")
@@ -40,7 +40,7 @@ class OfflineWalletTest(unittest.TestCase):
bdk.Network.TESTNET
)
self.assertEqual(wallet.get_balance().total, 0)
self.assertEqual(wallet.get_balance().total.to_sat(), 0)
if __name__ == '__main__':
unittest.main()

View File

@@ -0,0 +1,44 @@
import XCTest
@testable import BitcoinDevKit
private let SIGNET_ELECTRUM_URL = "ssl://mempool.space:60602"
final class LiveElectrumClientTests: XCTestCase {
func testSyncedBalance() throws {
let descriptor = try Descriptor(
descriptor: "wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)",
network: Network.signet
)
let wallet = try Wallet.newNoPersist(
descriptor: descriptor,
changeDescriptor: nil,
network: .signet
)
let electrumClient: ElectrumClient = try ElectrumClient(url: SIGNET_ELECTRUM_URL)
let fullScanRequest: FullScanRequest = wallet.startFullScan()
let update = try electrumClient.fullScan(
fullScanRequest: fullScanRequest,
stopGap: 10,
batchSize: 10,
fetchPrevTxouts: false
)
try wallet.applyUpdate(update: update)
let _ = try wallet.commit()
let address = try wallet.revealNextAddress(keychain: KeychainKind.external).address.asString()
XCTAssertGreaterThan(
wallet.getBalance().total.toSat(),
UInt64(0),
"Wallet must have positive balance, please send funds to \(address)"
)
print("Transactions count: \(wallet.transactions().count)")
let transactions = wallet.transactions().prefix(3)
for tx in transactions {
let sentAndReceived = wallet.sentAndReceived(tx: tx.transaction)
print("Transaction: \(tx.transaction.txid())")
print("Sent \(sentAndReceived.sent.toSat())")
print("Received \(sentAndReceived.received.toSat())")
}
}
}

View File

@@ -0,0 +1,44 @@
import XCTest
@testable import BitcoinDevKit
private let SIGNET_ESPLORA_URL = "http://signet.bitcoindevkit.net"
private let TESTNET_ESPLORA_URL = "https://esplora.testnet.kuutamo.cloud"
final class LiveMemoryWalletTests: XCTestCase {
func testSyncedBalance() throws {
let descriptor = try Descriptor(
descriptor: "wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)",
network: Network.signet
)
let wallet = try Wallet.newNoPersist(
descriptor: descriptor,
changeDescriptor: nil,
network: .signet
)
let esploraClient = EsploraClient(url: SIGNET_ESPLORA_URL)
let fullScanRequest: FullScanRequest = wallet.startFullScan()
let update = try esploraClient.fullScan(
fullScanRequest: fullScanRequest,
stopGap: 10,
parallelRequests: 1
)
try wallet.applyUpdate(update: update)
let _ = try wallet.commit()
let address = try wallet.revealNextAddress(keychain: KeychainKind.external).address.asString()
XCTAssertGreaterThan(
wallet.getBalance().total.toSat(),
UInt64(0),
"Wallet must have positive balance, please send funds to \(address)"
)
print("Transactions count: \(wallet.transactions().count)")
let transactions = wallet.transactions().prefix(3)
for tx in transactions {
let sentAndReceived = wallet.sentAndReceived(tx: tx.transaction)
print("Transaction: \(tx.transaction.txid())")
print("Sent \(sentAndReceived.sent.toSat())")
print("Received \(sentAndReceived.received.toSat())")
}
}
}

View File

@@ -0,0 +1,51 @@
import XCTest
@testable import BitcoinDevKit
private let SIGNET_ESPLORA_URL = "http://signet.bitcoindevkit.net"
private let TESTNET_ESPLORA_URL = "https://esplora.testnet.kuutamo.cloud"
final class LiveTransactionTests: XCTestCase {
func testSyncedBalance() throws {
let descriptor = try Descriptor(
descriptor: "wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)",
network: Network.signet
)
let wallet = try Wallet.newNoPersist(
descriptor: descriptor,
changeDescriptor: nil,
network: .signet
)
let esploraClient = EsploraClient(url: SIGNET_ESPLORA_URL)
let fullScanRequest: FullScanRequest = wallet.startFullScan()
let update = try esploraClient.fullScan(
fullScanRequest: fullScanRequest,
stopGap: 10,
parallelRequests: 1
)
try wallet.applyUpdate(update: update)
let _ = try wallet.commit()
let address = try wallet.revealNextAddress(keychain: KeychainKind.external).address.asString()
XCTAssertGreaterThan(
wallet.getBalance().total.toSat(),
UInt64(0),
"Wallet must have positive balance, please send funds to \(address)"
)
guard let transaction = wallet.transactions().first?.transaction else {
print("No transactions found")
return
}
print("First transaction:")
print("Txid: \(transaction.txid())")
print("Version: \(transaction.version())")
print("Total size: \(transaction.totalSize())")
print("Vsize: \(transaction.vsize())")
print("Weight: \(transaction.weight())")
print("Coinbase transaction: \(transaction.isCoinbase())")
print("Is explicitly RBF: \(transaction.isExplicitlyRbf())")
print("Inputs: \(transaction.input())")
print("Outputs: \(transaction.output())")
}
}

View File

@@ -1,6 +1,9 @@
import XCTest
@testable import BitcoinDevKit
private let SIGNET_ESPLORA_URL = "http://signet.bitcoindevkit.net"
private let TESTNET_ESPLORA_URL = "https://esplora.testnet.kuutamo.cloud"
final class LiveTxBuilderTests: XCTestCase {
var dbFilePath: URL!
@@ -26,27 +29,34 @@ final class LiveTxBuilderTests: XCTestCase {
func testTxBuilder() throws {
let descriptor = try Descriptor(
descriptor: "wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)",
network: Network.testnet
network: Network.signet
)
let wallet = try Wallet(
descriptor: descriptor,
changeDescriptor: nil,
persistenceBackendPath: dbFilePath.path,
network: .testnet
network: .signet
)
let esploraClient = EsploraClient(url: "https://esplora.testnet.kuutamo.cloud/")
let esploraClient = EsploraClient(url: SIGNET_ESPLORA_URL)
let fullScanRequest: FullScanRequest = wallet.startFullScan()
let update = try esploraClient.fullScan(
wallet: wallet,
fullScanRequest: fullScanRequest,
stopGap: 10,
parallelRequests: 1
)
try wallet.applyUpdate(update: update)
let _ = try wallet.commit()
let address = try wallet.revealNextAddress(keychain: KeychainKind.external).address.asString()
XCTAssertGreaterThan(wallet.getBalance().total, UInt64(0), "Wallet must have positive balance, please add funds")
XCTAssertGreaterThan(
wallet.getBalance().total.toSat(),
UInt64(0),
"Wallet must have positive balance, please send funds to \(address)"
)
let recipient: Address = try Address(address: "tb1qrnfslnrve9uncz9pzpvf83k3ukz22ljgees989", network: .testnet)
let recipient: Address = try Address(address: "tb1qrnfslnrve9uncz9pzpvf83k3ukz22ljgees989", network: .signet)
let psbt: Psbt = try TxBuilder()
.addRecipient(script: recipient.scriptPubkey(), amount: 4200)
.addRecipient(script: recipient.scriptPubkey(), amount: Amount.fromSat(fromSat: 4200))
.feeRate(feeRate: FeeRate.fromSatPerVb(satPerVb: 2))
.finish(wallet: wallet)
@@ -57,33 +67,40 @@ final class LiveTxBuilderTests: XCTestCase {
func testComplexTxBuilder() throws {
let descriptor = try Descriptor(
descriptor: "wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)",
network: Network.testnet
network: Network.signet
)
let changeDescriptor = try Descriptor(
descriptor: "wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/1/*)",
network: Network.testnet
network: Network.signet
)
let wallet = try Wallet(
descriptor: descriptor,
changeDescriptor: changeDescriptor,
persistenceBackendPath: dbFilePath.path,
network: .testnet
network: .signet
)
let esploraClient = EsploraClient(url: "https://esplora.testnet.kuutamo.cloud/")
let esploraClient = EsploraClient(url: SIGNET_ESPLORA_URL)
let fullScanRequest: FullScanRequest = wallet.startFullScan()
let update = try esploraClient.fullScan(
wallet: wallet,
fullScanRequest: fullScanRequest,
stopGap: 10,
parallelRequests: 1
)
try wallet.applyUpdate(update: update)
let _ = try wallet.commit()
let address = try wallet.revealNextAddress(keychain: KeychainKind.external).address.asString()
XCTAssertGreaterThan(wallet.getBalance().total, UInt64(0), "Wallet must have positive balance, please add funds")
XCTAssertGreaterThan(
wallet.getBalance().total.toSat(),
UInt64(0),
"Wallet must have positive balance, please send funds to \(address)"
)
let recipient1: Address = try Address(address: "tb1qrnfslnrve9uncz9pzpvf83k3ukz22ljgees989", network: .testnet)
let recipient2: Address = try Address(address: "tb1qw2c3lxufxqe2x9s4rdzh65tpf4d7fssjgh8nv6", network: .testnet)
let recipient1: Address = try Address(address: "tb1qrnfslnrve9uncz9pzpvf83k3ukz22ljgees989", network: .signet)
let recipient2: Address = try Address(address: "tb1qw2c3lxufxqe2x9s4rdzh65tpf4d7fssjgh8nv6", network: .signet)
let allRecipients: [ScriptAmount] = [
ScriptAmount(script: recipient1.scriptPubkey(), amount: 4200),
ScriptAmount(script: recipient2.scriptPubkey(), amount: 4200)
ScriptAmount(script: recipient1.scriptPubkey(), amount: Amount.fromSat(fromSat: 4200)),
ScriptAmount(script: recipient2.scriptPubkey(), amount: Amount.fromSat(fromSat: 4200))
]
let psbt: Psbt = try TxBuilder()
@@ -93,7 +110,7 @@ final class LiveTxBuilderTests: XCTestCase {
.enableRbf()
.finish(wallet: wallet)
try! wallet.sign(psbt: psbt)
let _ = try! wallet.sign(psbt: psbt)
XCTAssertTrue(psbt.serialize().hasPrefix("cHNi"), "PSBT should start with cHNI")
}

View File

@@ -1,6 +1,9 @@
import XCTest
@testable import BitcoinDevKit
private let SIGNET_ESPLORA_URL = "http://signet.bitcoindevkit.net"
private let TESTNET_ESPLORA_URL = "https://esplora.testnet.kuutamo.cloud"
final class LiveWalletTests: XCTestCase {
var dbFilePath: URL!
@@ -26,61 +29,75 @@ final class LiveWalletTests: XCTestCase {
func testSyncedBalance() throws {
let descriptor = try Descriptor(
descriptor: "wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)",
network: Network.testnet
network: Network.signet
)
let wallet = try Wallet(
descriptor: descriptor,
changeDescriptor: nil,
persistenceBackendPath: dbFilePath.path,
network: .testnet
network: .signet
)
let esploraClient = EsploraClient(url: "https://esplora.testnet.kuutamo.cloud/")
let esploraClient = EsploraClient(url: SIGNET_ESPLORA_URL)
let fullScanRequest: FullScanRequest = wallet.startFullScan()
let update = try esploraClient.fullScan(
wallet: wallet,
fullScanRequest: fullScanRequest,
stopGap: 10,
parallelRequests: 1
)
try wallet.applyUpdate(update: update)
let _ = try wallet.commit()
let address = try wallet.revealNextAddress(keychain: KeychainKind.external).address.asString()
XCTAssertGreaterThan(wallet.getBalance().total, UInt64(0))
XCTAssertGreaterThan(
wallet.getBalance().total.toSat(),
UInt64(0),
"Wallet must have positive balance, please send funds to \(address)"
)
print("Transactions count: \(wallet.transactions().count)")
let transactions = wallet.transactions().prefix(3)
for tx in transactions {
let sentAndReceived = wallet.sentAndReceived(tx: tx.transaction)
print("Transaction: \(tx.transaction.txid())")
print("Sent \(sentAndReceived.sent)")
print("Received \(sentAndReceived.received)")
print("Sent \(sentAndReceived.sent.toSat())")
print("Received \(sentAndReceived.received.toSat())")
}
}
func testBroadcastTransaction() throws {
let descriptor = try Descriptor(
descriptor: "wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)",
network: Network.testnet
network: Network.signet
)
let wallet = try Wallet(
descriptor: descriptor,
changeDescriptor: nil,
persistenceBackendPath: dbFilePath.path,
network: .testnet
network: .signet
)
let esploraClient = EsploraClient(url: "https://esplora.testnet.kuutamo.cloud/")
let esploraClient = EsploraClient(url: SIGNET_ESPLORA_URL)
let fullScanRequest: FullScanRequest = wallet.startFullScan()
let update = try esploraClient.fullScan(
wallet: wallet,
fullScanRequest: fullScanRequest,
stopGap: 10,
parallelRequests: 1
)
try wallet.applyUpdate(update: update)
XCTAssertGreaterThan(wallet.getBalance().total, UInt64(0), "Wallet must have positive balance, please add funds")
let _ = try wallet.commit()
let address = try wallet.revealNextAddress(keychain: KeychainKind.external).address.asString()
XCTAssertGreaterThan(
wallet.getBalance().total.toSat(),
UInt64(0),
"Wallet must have positive balance, please send funds to \(address)"
)
print("Balance: \(wallet.getBalance().total)")
let recipient: Address = try Address(address: "tb1qrnfslnrve9uncz9pzpvf83k3ukz22ljgees989", network: .testnet)
let recipient: Address = try Address(address: "tb1qrnfslnrve9uncz9pzpvf83k3ukz22ljgees989", network: .signet)
let psbt: Psbt = try
TxBuilder()
.addRecipient(script: recipient.scriptPubkey(), amount: 4200)
.addRecipient(script: recipient.scriptPubkey(), amount: Amount.fromSat(fromSat: 4200))
.feeRate(feeRate: FeeRate.fromSatPerVb(satPerVb: 2))
.finish(wallet: wallet)

View File

@@ -34,7 +34,7 @@ final class OfflineWalletTests: XCTestCase {
persistenceBackendPath: dbFilePath.path,
network: .testnet
)
let addressInfo: AddressInfo = wallet.getAddress(addressIndex: AddressIndex.new)
let addressInfo: AddressInfo = try wallet.revealNextAddress(keychain: KeychainKind.external)
XCTAssertTrue(addressInfo.address.isValidForNetwork(network: Network.testnet),
"Address is not valid for testnet network")
@@ -60,6 +60,6 @@ final class OfflineWalletTests: XCTestCase {
network: .testnet
)
XCTAssertEqual(wallet.getBalance().total, 0)
XCTAssertEqual(wallet.getBalance().total.toSat(), 0)
}
}

View File

@@ -3,7 +3,7 @@
# The results of this script can be used for locally testing your SPM package adding a local package
# to your application pointing at the bdk-swift directory.
rustup install 1.73.0
rustup default 1.77.1
rustup component add rust-src
rustup target add aarch64-apple-ios # iOS arm64
rustup target add x86_64-apple-ios # iOS x86_64
@@ -12,7 +12,6 @@ rustup target add aarch64-apple-darwin # mac M1
rustup target add x86_64-apple-darwin # mac x86_64
cd ../bdk-ffi/ || exit
cargo run --bin uniffi-bindgen generate src/bdk.udl --language swift --out-dir ../bdk-swift/Sources/BitcoinDevKit --no-format
cargo build --package bdk-ffi --profile release-smaller --target x86_64-apple-darwin
cargo build --package bdk-ffi --profile release-smaller --target aarch64-apple-darwin
@@ -20,6 +19,8 @@ cargo build --package bdk-ffi --profile release-smaller --target x86_64-apple-io
cargo build --package bdk-ffi --profile release-smaller --target aarch64-apple-ios
cargo build --package bdk-ffi --profile release-smaller --target aarch64-apple-ios-sim
cargo run --bin uniffi-bindgen generate --library ./target/aarch64-apple-ios/release-smaller/libbdkffi.dylib --language swift --out-dir ../bdk-swift/Sources/BitcoinDevKit --no-format
mkdir -p target/lipo-ios-sim/release-smaller
lipo target/aarch64-apple-ios-sim/release-smaller/libbdkffi.a target/x86_64-apple-ios/release-smaller/libbdkffi.a -create -output target/lipo-ios-sim/release-smaller/libbdkffi.a
mkdir -p target/lipo-macos/release-smaller

View File

@@ -1,11 +1,14 @@
default:
just --list
build:
bash ./build-local-swift.sh
clean:
rm -rf ../bdk-ffi/target/
test:
swift test
offlinetests:
swift test --skip LiveWalletTests --skip LiveTxBuilderTests
clean:
rm -rf ../bdk-ffi/target/
test-offline:
swift test --skip LiveWalletTests --skip LiveTxBuilderTests