Compare commits

...

18 Commits

Author SHA1 Message Date
Steve Myers
d17ea4b90c Bump version to 0.3.0 2022-02-27 21:18:05 -08:00
Steve Myers
76fa9b9521 Add CHANGELOG.md 2022-02-27 21:17:37 -08:00
Steve Myers
cafa8dacab Merge bitcoindevkit/bdk-ffi#104: Add PSBT deserialize and serialize functions, remove details
c039281ffc Add PSBT deserialize and serialize functions, remove details (Steve Myers)
1f0b053872 Fix bin/generate with no features (Steve Myers)

Pull request description:

  1. Fix bin/generate with no features
  2. Add `PartiallySignedBitcoinTransaction::deserialize` function as named constructor to decode from a string per [BIP 0174]
  3. Add `PartiallySignedBitcoinTransaction::serialize` function to encode to a string per [BIP 0174]
  4. Remove `PartiallySignedBitcoinTransaction.details` struct field

  [BIP 0174]:https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki#encoding

  Fixes #103

Top commit has no ACKs.

Tree-SHA512: 0ba34d96625d71434d41573089a150d09fcfb6439648a7eed6e36dcdddd2682c969525b7c6efda898b2f979a7ca6ce51dc2158acf65da7f1f4c554d98b60f4ff
2022-02-25 14:47:47 -08:00
Steve Myers
c039281ffc Add PSBT deserialize and serialize functions, remove details 2022-02-24 20:49:59 -08:00
Steve Myers
1f0b053872 Fix bin/generate with no features 2022-02-24 17:17:05 -08:00
Alekos Filini
97f1011748 Add a binary to generate bindings 2022-02-08 22:15:10 +01:00
Alekos Filini
edfcde1cc6 Generate bindings for Python in build.rs 2022-02-08 21:04:12 +01:00
Sudarsan Balaji
15c0dac622 Rename field to wallet_mutex 2022-01-30 21:22:40 +00:00
Sudarsan Balaji
a99e022756 Rename OnlineWallet to Wallet
and remove OfflineWallet
2022-01-24 20:32:49 +00:00
Steve Myers
672131ca0f Fix uniffi_macros and uniffi_build versions 2022-01-24 10:58:48 -08:00
Steve Myers
72f90f1d63 Upgrade bdk to 0.14.0 2021-12-21 22:16:29 -08:00
Steve Myers
5240cd895e Update uniffi-rs to 0.16.0 2021-12-21 22:16:27 -08:00
Steve Myers
55462fb426 Remove bindings and examples, update README.md 2021-12-21 22:16:25 -08:00
Steve Myers
9188dec2f2 Remove swift related files and -s option in build.sh
Build script and files to create a bdkFFI binary xcframework and BitcoinDevKit swift package
have been moved to the bdk-swift repo.
2021-11-24 12:23:19 -08:00
Steve Myers
a9a01950ee Fix IOSBdkAppSample to work with git hosted BitcoinDevKit swift package 2021-11-23 10:05:19 -08:00
Steve Myers
39cc3b3bfa Update build.sh to create swift xcframework 2021-11-23 10:05:19 -08:00
Steve Myers
c08fe99ad6 Pin anyhow version to "=1.0.45"
This change can be removed after upgrading to the next version of uniffi.
See: https://github.com/mozilla/uniffi-rs/issues/1109
2021-11-22 15:59:12 -08:00
thunderbiscuit
d53eb793ea Refactor transaction 'id' property to 'txid' 2021-11-12 12:50:40 -05:00
9 changed files with 192 additions and 371 deletions

3
.gitignore vendored
View File

@@ -2,9 +2,6 @@ target
build
Cargo.lock
/bindings/bdk-kotlin/local.properties
/bindings/bdk-swift
/bindings/bdk-swift.swiftdoc
/bindings/bdk-swift.swiftsourceinfo
.gradle
wallet_db
bdk_ffi_test

23
CHANGELOG.md Normal file
View File

@@ -0,0 +1,23 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
## [v0.3.0]
- Move bdk-kotlin bindings and ios example to separate repos
- Add bin to generate Python bindings
- Add `PartiallySignedBitcoinTransaction::deserialize` function as named constructor to decode from a string per [BIP 0174]
- Add `PartiallySignedBitcoinTransaction::serialize` function to encode to a string per [BIP 0174]
- Remove `PartiallySignedBitcoinTransaction.details` struct field
[BIP 0174]:https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki#encoding
## [v0.2.0]
[v0.2.0]: https://github.com/bitcoindevkit/bdk-ffi/compare/v0.0.0...v0.2.0
[v0.3.0]: https://github.com/bitcoindevkit/bdk-ffi/compare/v0.2.0...v0.3.0
[unreleased]: https://github.com/bitcoindevkit/bdk-ffi/compare/v0.3.0...HEAD

View File

@@ -1,6 +1,6 @@
[package]
name = "bdk-ffi"
version = "0.1.0"
version = "0.3.0"
authors = ["Steve Myers <steve@notmandatory.org>", "Sudarsan Balaji <sudarsan.balaji@artfuldev.com>"]
edition = "2018"
@@ -10,10 +10,19 @@ crate-type = ["staticlib", "cdylib"]
name = "bdkffi"
[dependencies]
bdk = { version = "0.13", features = ["all-keys", "use-esplora-ureq"] }
uniffi_macros = "0.14.1"
uniffi = "0.14.1"
bdk = { version = "0.14", features = ["all-keys", "use-esplora-ureq"] }
uniffi_macros = { version = "0.16.0", features = ["builtin-bindgen"] }
uniffi = { version = "0.16.0", features = ["builtin-bindgen"] }
thiserror = "1.0"
anyhow = "=1.0.45" # remove after upgrading to next version of uniffi
uniffi_bindgen = { version = "0.16.0", optional = true }
[build-dependencies]
uniffi_build = "0.14.1"
uniffi_build = { version = "0.16.0", features = ["builtin-bindgen"] }
[features]
generate-python = ["uniffi_bindgen"]
[[bin]]
name = "generate"

105
README.md
View File

@@ -1,89 +1,42 @@
# Foreign language bindings for BDK (bdk-ffi)
# Native language bindings for BDK
This repository contains source code for generating foreign language bindings
for the rust library bdk for the Bitcoin Dev Kit (BDK) project.
This repository contains source code for generating native language bindings for the rust based
[bdk] library which is the central artifact of the [Bitcoin Dev Kit] project.
Each supported language has it's own repository that includes this project as a [git submodule].
The rust code in this project is a wrapper around the [bdk] library to expose it's APIs in a
uniform way using the [mozilla/uniffi-rs] bindings generator for each supported target language.
## Supported target languages and platforms
| Language | Platform | Status |
| --- | --- | --- |
| Kotlin | JVM | WIP |
| Kotlin | Android | WIP |
| Swift | iOS | WIP |
The below repositories include instructions for using, building, and publishing the native
language binding for [bdk] supported by this project.
| Language | Platform | Repository |
| -------- | ------------ | ------------ |
| Kotlin | jvm | [bdk-kotlin] |
| Kotlin | android | [bdk-kotlin] |
| Swift | iOS, macOS | [bdk-swift] |
| Python | linux, macOS | [bdk-python] |
## Getting Started (User)
[bdk]: https://github.com/bitcoindevkit/bdk
[Bitcoin Dev Kit]: https://github.com/bitcoindevkit
[git submodule]: https://git-scm.com/book/en/v2/Git-Tools-Submodules
[uniffi-rs]: https://github.com/mozilla/uniffi-rs
If you just want to consume the language bindings:
[bdk-kotlin]: https://github.com/bitcoindevkit/bdk-kotlin
[bdk-swift]: https://github.com/bitcoindevkit/bdk-swift
[bdk-python]: https://github.com/thunderbiscuit/bdk-python
### Kotlin (JVM)
## Contributing
Just add the dependency `org.bitcoindevkit:bdk-jvm:0.1.1`. The package is `org.bitcoindevkit.bdk`.
### Install uniffi-bindgen cli tool
### Kotlin (Android)
Install the uniffi-bindgen binary on your system using:
Just add the dependency `org.bitcoindevkit:bdk-android:0.1.1`. The package is `org.bitcoindevkit.bdk`.
`cargo install uniffi_bindgen`
## Getting Started (Developer)
This project uses rust. A basic knowledge of the rust ecosystem is helpful.
### General
1. Install `uniffi-bindgen`
```sh
cargo install uniffi_bindgen
```
1. See the [UniFFI User Guide](https://mozilla.github.io/uniffi-rs/) for more info
### Kotlin Bindings for JVM (OSX / Linux)
1. Install required targets
```sh
rustup target add x86_64-apple-darwin x86_64-unknown-linux-gnu
```
1. Build kotlin (JVM) bindings
```sh
./build.sh -k
```
1. Generated kotlin bindings are available at `/bindings/bdk-kotlin/`
1. A demo app is available at `/bindings/bdk-kotlin/demo/`. It uses stdin for
inputs and can be run from gradle.
```sh
cd bindings/bdk-kotlin
./gradlew :demo:run
```
### Kotlin bindings for Android
1. Install required targets
```sh
rustup target add x86_64-linux-android aarch64-linux-android
armv7-linux-androideabi i686-linux-android
```
1. Install Android SDK and Build-Tools for API level 30+
1. Setup `$ANDROID_NDK_HOME` and `$ANDROID_SDK_ROOT` path variables (which are
required by the build scripts)
1. Build kotlin (Android) bindings
```sh
./build.sh -a
```
2. A demo android app is available at [notmandatory/bdk-sample-app](https://github.com/notmandatory/bitcoindevkit-android-sample-app/tree/upgrade-to-bdk-ffi/)
### Swift bindings for iOS
1. Install the latest version of xcode, download and install the advanced tools.
1. Ensure Swift is installed
1. Install required targets
```sh
rustup target add aarch64-apple-ios x86_64-apple-ios
```
1. Build swift (iOS) bindings
```sh
./build.sh -s
```
1. Example iOS app can be found in `/examples/iOS` which can be run by xcode.
## Notes
The version must be the same as the `uniffi` dependency in `Cargo.toml`.
### Adding new structs and functions
@@ -109,4 +62,6 @@ See the [UniFFI User Guide](https://mozilla.github.io/uniffi-rs/)
## Thanks
This project is made possible thanks to the wonderful work on [mozilla/uniffi-rs](https://github.com/mozilla/uniffi-rs)
This project is made possible thanks to the wonderful work by the [mozilla/uniffi-rs] team.
[mozilla/uniffi-rs]: https://github.com/mozilla/uniffi-rs

145
build.sh
View File

@@ -1,145 +0,0 @@
#!/usr/bin/env bash
set -eo pipefail
# functions
## help
help()
{
# Display Help
echo "Build bdk-ffi and related libraries."
echo
echo "Syntax: build [-a|h|k|s]"
echo "options:"
echo "-a Android."
echo "-h Print this Help."
echo "-k Kotlin."
echo "-s Swift."
echo
}
## rust
build_rust() {
echo "Build Rust library"
cargo fmt
cargo build
cargo test
}
## copy to bdk-bdk-kotlin
copy_lib_kotlin() {
echo -n "Copy "
case $OS in
"Darwin")
echo -n "darwin "
mkdir -p bindings/bdk-kotlin/jvm/src/main/resources/darwin-x86-64
cp target/debug/libbdkffi.dylib bindings/bdk-kotlin/jvm/src/main/resources/darwin-x86-64
;;
"Linux")
echo -n "linux "
mkdir -p bindings/bdk-kotlin/jvm/src/main/resources/linux-x86-64
cp target/debug/libbdkffi.so bindings/bdk-kotlin/jvm/src/main/resources/linux-x86-64
;;
esac
echo "libs to kotlin sub-project"
}
## bdk-bdk-kotlin jar
build_kotlin() {
copy_lib_kotlin
uniffi-bindgen generate src/bdk.udl --no-format --out-dir bindings/bdk-kotlin/jvm/src/main/kotlin --language kotlin
}
## bdk swift
build_swift() {
uniffi-bindgen generate src/bdk.udl --no-format --out-dir bindings/bdk-swift/ --language swift
swiftc -module-name bdk -emit-library -o libbdkffi.dylib -emit-module -emit-module-path ./bindings/bdk-swift/ -parse-as-library -L ./target/debug/ -lbdkffi -Xcc -fmodule-map-file=./bindings/bdk-swift/bdkFFI.modulemap ./bindings/bdk-swift/bdk.swift
TARGETDIR=target
RELDIR=debug
STATIC_LIB_NAME=libbdkffi.a
# We can't use cargo lipo because we can't link to universal libraries :(
# https://github.com/rust-lang/rust/issues/55235
LIBS_ARCHS=("x86_64" "arm64")
IOS_TRIPLES=("x86_64-apple-ios" "aarch64-apple-ios")
for i in "${!LIBS_ARCHS[@]}"; do
cargo build --target "${IOS_TRIPLES[${i}]}"
done
UNIVERSAL_BINARY=./${TARGETDIR}/ios/universal/${RELDIR}/${STATIC_LIB_NAME}
NEED_LIPO=
# if the universal binary doesnt exist, or if it's older than the static libs,
# we need to run `lipo` again.
if [[ ! -f "${UNIVERSAL_BINARY}" ]]; then
NEED_LIPO=1
elif [[ "$(stat -f "%m" "./${TARGETDIR}/x86_64-apple-ios/${RELDIR}/${STATIC_LIB_NAME}")" -gt "$(stat -f "%m" "${UNIVERSAL_BINARY}")" ]]; then
NEED_LIPO=1
elif [[ "$(stat -f "%m" "./${TARGETDIR}/aarch64-apple-ios/${RELDIR}/${STATIC_LIB_NAME}")" -gt "$(stat -f "%m" "${UNIVERSAL_BINARY}")" ]]; then
NEED_LIPO=1
fi
if [[ "${NEED_LIPO}" = "1" ]]; then
mkdir -p "${TARGETDIR}/ios/universal/${RELDIR}"
lipo -create -output "${UNIVERSAL_BINARY}" \
"${TARGETDIR}/x86_64-apple-ios/${RELDIR}/${STATIC_LIB_NAME}" \
"${TARGETDIR}/aarch64-apple-ios/${RELDIR}/${STATIC_LIB_NAME}"
fi
}
## rust android
build_android() {
build_kotlin
# If ANDROID_NDK_HOME is not set then set it to github actions default
[ -z "$ANDROID_NDK_HOME" ] && export ANDROID_NDK_HOME=$ANDROID_HOME/ndk-bundle
# Update this line accordingly if you are not building *from* darwin-x86_64 or linux-x86_64
export PATH=$PATH:$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/`uname | tr '[:upper:]' '[:lower:]'`-x86_64/bin
# Required for 'ring' dependency to cross-compile to Android platform, must be at least 21
export CFLAGS="-D__ANDROID_API__=21"
# IMPORTANT: make sure every target is not a substring of a different one. We check for them with grep later on
BUILD_TARGETS="${BUILD_TARGETS:-aarch64,x86_64,i686}"
mkdir -p bindings/bdk-kotlin/android/src/main/jniLibs/ bindings/bdk-kotlin/android/src/main/jniLibs/arm64-v8a bindings/bdk-kotlin/android/src/main/jniLibs/x86_64 bindings/bdk-kotlin/android/src/main/jniLibs/x86
if echo $BUILD_TARGETS | grep "aarch64"; then
CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER="aarch64-linux-android21-clang" CC="aarch64-linux-android21-clang" cargo build --target=aarch64-linux-android
cp target/aarch64-linux-android/debug/libbdkffi.so bindings/bdk-kotlin/android/src/main/jniLibs/arm64-v8a
fi
if echo $BUILD_TARGETS | grep "x86_64"; then
CARGO_TARGET_X86_64_LINUX_ANDROID_LINKER="x86_64-linux-android21-clang" CC="x86_64-linux-android21-clang" cargo build --target=x86_64-linux-android
cp target/x86_64-linux-android/debug/libbdkffi.so bindings/bdk-kotlin/android/src/main/jniLibs/x86_64
fi
if echo $BUILD_TARGETS | grep "i686"; then
CARGO_TARGET_I686_LINUX_ANDROID_LINKER="i686-linux-android21-clang" CC="i686-linux-android21-clang" cargo build --target=i686-linux-android
cp target/i686-linux-android/debug/libbdkffi.so bindings/bdk-kotlin/android/src/main/jniLibs/x86
fi
# copy sources
cp -R bindings/bdk-kotlin/jvm/src/main/kotlin bindings/bdk-kotlin/android/src/main
# bdk-kotlin aar
(cd bindings/bdk-kotlin && ./gradlew :android:build)
}
OS=$(uname)
if [ "$1" == "-h" ]
then
help
else
build_rust
while [ -n "$1" ]; do # while loop starts
case "$1" in
-a) build_android ;;
-k) build_kotlin ;;
-s) build_swift ;;
-h) help ;;
*) echo "Option $1 not recognized" ;;
esac
shift
done
fi

View File

@@ -1,6 +1,6 @@
namespace bdk {
[Throws=BdkError]
ExtendedKeyInfo generate_extended_key(Network network, MnemonicType mnemonic_type, string? password);
ExtendedKeyInfo generate_extended_key(Network network, WordCount word_count, string? password);
[Throws=BdkError]
ExtendedKeyInfo restore_extended_key(Network network, string mnemonic, string? password);
};
@@ -70,10 +70,10 @@ dictionary TransactionDetails {
u64? fees;
u64 received;
u64 sent;
string id;
string txid;
};
dictionary Confirmation {
dictionary BlockTime {
u32 height;
u64 timestamp;
};
@@ -81,22 +81,7 @@ dictionary Confirmation {
[Enum]
interface Transaction {
Unconfirmed(TransactionDetails details);
Confirmed(TransactionDetails details, Confirmation confirmation);
};
interface OfflineWallet {
[Throws=BdkError]
constructor(string descriptor, Network network, DatabaseConfig database_config);
// OfflineWalletOperations
string get_new_address();
string get_last_unused_address();
[Throws=BdkError]
u64 get_balance();
[Throws=BdkError]
void sign([ByRef] PartiallySignedBitcoinTransaction psbt);
[Throws=BdkError]
sequence<Transaction> get_transactions();
Confirmed(TransactionDetails details, BlockTime confirmation);
};
dictionary ElectrumConfig {
@@ -125,11 +110,9 @@ callback interface BdkProgress {
void update(f32 progress, string? message);
};
interface OnlineWallet {
interface Wallet {
[Throws=BdkError]
constructor(string descriptor, string? change_descriptor, Network network, DatabaseConfig database_config, BlockchainConfig blockchain_config);
// OfflineWalletOperations
string get_new_address();
string get_last_unused_address();
[Throws=BdkError]
@@ -138,8 +121,6 @@ interface OnlineWallet {
void sign([ByRef] PartiallySignedBitcoinTransaction psbt);
[Throws=BdkError]
sequence<Transaction> get_transactions();
// OnlineWalletInterface
Network get_network();
[Throws=BdkError]
void sync(BdkProgress progress_update, u32? max_address_param);
@@ -149,7 +130,10 @@ interface OnlineWallet {
interface PartiallySignedBitcoinTransaction {
[Throws=BdkError]
constructor([ByRef] OnlineWallet wallet, string recipient, u64 amount, float? fee_rate);
constructor([ByRef] Wallet wallet, string recipient, u64 amount, float? fee_rate);
[Name=deserialize,Throws=BdkError]
constructor(string psbt_base64);
string serialize();
};
dictionary ExtendedKeyInfo {
@@ -158,7 +142,7 @@ dictionary ExtendedKeyInfo {
string fingerprint;
};
enum MnemonicType {
enum WordCount {
"Words12",
"Words15",
"Words18",

67
src/bin/generate.rs Normal file
View File

@@ -0,0 +1,67 @@
pub const BDK_UDL: &str = "src/bdk.udl";
#[cfg(feature = "generate-python")]
fn fixup_python_lib_path<O: AsRef<std::path::Path>>(
out_dir: O,
lib_name: &str,
) -> Result<(), Box<dyn std::error::Error>> {
use std::fs;
use std::io::Write;
const LOAD_INDIRECT_DEF: &str = "def loadIndirect():";
let bindings_file = out_dir.as_ref().join("bdk.py");
let mut data = fs::read_to_string(&bindings_file)?;
let pos = data.find(LOAD_INDIRECT_DEF).expect(&format!(
"loadIndirect not found in `{}`",
bindings_file.display()
));
let range = pos..pos + LOAD_INDIRECT_DEF.len();
let replacement = format!(
r#"
def loadIndirect():
import glob
return getattr(ctypes.cdll, glob.glob(os.path.join(os.path.dirname(os.path.abspath(__file__)), '{}.*'))[0])
def _loadIndirectOld():"#,
lib_name
);
data.replace_range(range, &replacement);
let mut file = fs::OpenOptions::new()
.write(true)
.truncate(true)
.open(&bindings_file)?;
file.write(data.as_bytes())?;
Ok(())
}
#[cfg(feature = "generate-python")]
fn generate_python() -> Result<(), Box<dyn std::error::Error>> {
use std::env;
let out_path = env::var("GENERATE_PYTHON_BINDINGS_OUT")
.map_err(|_| String::from("`GENERATE_PYTHON_BINDINGS_OUT` env variable missing"))?;
uniffi_bindgen::generate_bindings(
&format!("{}/{}", env!("CARGO_MANIFEST_DIR"), BDK_UDL),
None,
vec!["python"],
Some(&out_path),
false,
)?;
if let Some(name) = env::var("GENERATE_PYTHON_BINDINGS_FIXUP_LIB_PATH").ok() {
fixup_python_lib_path(&out_path, &name)?;
}
Ok(())
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
#[cfg(feature = "generate-python")]
generate_python()?;
Ok(())
}

View File

@@ -8,11 +8,11 @@ use bdk::blockchain::{
};
use bdk::database::any::{AnyDatabase, SledDbConfiguration};
use bdk::database::{AnyDatabaseConfig, ConfigurableDatabase};
use bdk::keys::bip39::{Language, Mnemonic, MnemonicType};
use bdk::keys::bip39::{Language, Mnemonic, WordCount};
use bdk::keys::{DerivableKey, ExtendedKey, GeneratableKey, GeneratedKey};
use bdk::miniscript::BareCtx;
use bdk::wallet::AddressIndex;
use bdk::{ConfirmationTime, Error, FeeRate, SignOptions, Wallet};
use bdk::{BlockTime, Error, FeeRate, SignOptions, Wallet as BdkWallet};
use std::convert::TryFrom;
use std::str::FromStr;
use std::sync::{Mutex, MutexGuard};
@@ -48,17 +48,7 @@ pub enum BlockchainConfig {
}
trait WalletHolder<B> {
fn get_wallet(&self) -> MutexGuard<Wallet<B, AnyDatabase>>;
}
struct OfflineWallet {
wallet: Mutex<Wallet<(), AnyDatabase>>,
}
impl WalletHolder<()> for OfflineWallet {
fn get_wallet(&self) -> MutexGuard<Wallet<(), AnyDatabase>> {
self.wallet.lock().unwrap()
}
fn get_wallet(&self) -> MutexGuard<BdkWallet<B, AnyDatabase>>;
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
@@ -66,11 +56,9 @@ pub struct TransactionDetails {
pub fees: Option<u64>,
pub received: u64,
pub sent: u64,
pub id: String,
pub txid: String,
}
type Confirmation = ConfirmationTime;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Transaction {
Unconfirmed {
@@ -78,7 +66,7 @@ pub enum Transaction {
},
Confirmed {
details: TransactionDetails,
confirmation: Confirmation,
confirmation: BlockTime,
},
}
@@ -86,7 +74,7 @@ impl From<&bdk::TransactionDetails> for TransactionDetails {
fn from(x: &bdk::TransactionDetails) -> TransactionDetails {
TransactionDetails {
fees: x.fee,
id: x.txid.to_string(),
txid: x.txid.to_string(),
received: x.received,
sent: x.sent,
}
@@ -96,9 +84,9 @@ impl From<&bdk::TransactionDetails> for TransactionDetails {
impl From<&bdk::TransactionDetails> for Transaction {
fn from(x: &bdk::TransactionDetails) -> Transaction {
match x.confirmation_time.clone() {
Some(confirmation) => Transaction::Confirmed {
Some(block_time) => Transaction::Confirmed {
details: TransactionDetails::from(x),
confirmation,
confirmation: block_time,
},
None => Transaction::Unconfirmed {
details: TransactionDetails::from(x),
@@ -107,7 +95,7 @@ impl From<&bdk::TransactionDetails> for Transaction {
}
}
trait OfflineWalletOperations<B>: WalletHolder<B> {
trait WalletOperations<B>: WalletHolder<B> {
fn get_new_address(&self) -> String {
self.get_wallet()
.get_address(AddressIndex::New)
@@ -128,7 +116,7 @@ trait OfflineWalletOperations<B>: WalletHolder<B> {
self.get_wallet().get_balance()
}
fn sign<'a>(&self, psbt: &'a PartiallySignedBitcoinTransaction) -> Result<(), Error> {
fn sign(&self, psbt: &PartiallySignedBitcoinTransaction) -> Result<(), Error> {
let mut psbt = psbt.internal.lock().unwrap();
let finalized = self.get_wallet().sign(&mut psbt, SignOptions::default())?;
match finalized {
@@ -146,26 +134,8 @@ trait OfflineWalletOperations<B>: WalletHolder<B> {
}
}
impl OfflineWallet {
fn new(
descriptor: String,
network: Network,
database_config: DatabaseConfig,
) -> Result<Self, BdkError> {
let any_database_config = match database_config {
DatabaseConfig::Memory { .. } => AnyDatabaseConfig::Memory(()),
DatabaseConfig::Sled { config } => AnyDatabaseConfig::Sled(config),
};
let database = AnyDatabase::from_config(&any_database_config)?;
let wallet = Mutex::new(Wallet::new_offline(&descriptor, None, network, database)?);
Ok(OfflineWallet { wallet })
}
}
impl OfflineWalletOperations<()> for OfflineWallet {}
struct OnlineWallet {
wallet: Mutex<Wallet<AnyBlockchain, AnyDatabase>>,
struct Wallet {
wallet_mutex: Mutex<BdkWallet<AnyBlockchain, AnyDatabase>>,
}
pub trait BdkProgress: Send + Sync {
@@ -185,20 +155,19 @@ impl Progress for BdkProgressHolder {
struct PartiallySignedBitcoinTransaction {
internal: Mutex<PartiallySignedTransaction>,
details: bdk::TransactionDetails,
}
impl PartiallySignedBitcoinTransaction {
fn new(
online_wallet: &OnlineWallet,
wallet: &Wallet,
recipient: String,
amount: u64,
fee_rate: Option<f32>, // satoshis per vbyte
) -> Result<Self, Error> {
let wallet = online_wallet.get_wallet();
let wallet = wallet.get_wallet();
match Address::from_str(&recipient) {
Ok(address) => {
let (psbt, details) = {
let (psbt, _details) = {
let mut builder = wallet.build_tx();
builder.add_recipient(address.script_pubkey(), amount);
if let Some(sat_per_vb) = fee_rate {
@@ -208,7 +177,6 @@ impl PartiallySignedBitcoinTransaction {
};
Ok(PartiallySignedBitcoinTransaction {
internal: Mutex::new(psbt),
details,
})
}
Err(..) => Err(BdkError::Generic(
@@ -216,9 +184,29 @@ impl PartiallySignedBitcoinTransaction {
)),
}
}
pub fn deserialize(psbt_base64: String) -> Result<Self, Error> {
let psbt: PartiallySignedTransaction = PartiallySignedTransaction::from_str(&psbt_base64)?;
Ok(PartiallySignedBitcoinTransaction {
internal: Mutex::new(psbt),
})
}
pub fn serialize(&self) -> String {
let psbt = self.internal.lock().unwrap().clone();
psbt.to_string()
}
}
impl OnlineWallet {
impl WalletHolder<AnyBlockchain> for Wallet {
fn get_wallet(&self) -> MutexGuard<BdkWallet<AnyBlockchain, AnyDatabase>> {
self.wallet_mutex.lock().unwrap()
}
}
impl WalletOperations<AnyBlockchain> for Wallet {}
impl Wallet {
fn new(
descriptor: String,
change_descriptor: Option<String>,
@@ -252,18 +240,18 @@ impl OnlineWallet {
};
let database = AnyDatabase::from_config(&any_database_config)?;
let blockchain = AnyBlockchain::from_config(&any_blockchain_config)?;
let wallet = Mutex::new(Wallet::new(
let wallet_mutex = Mutex::new(BdkWallet::new(
&descriptor,
change_descriptor.to_owned().as_ref(),
network,
database,
blockchain,
)?);
Ok(OnlineWallet { wallet })
Ok(Wallet { wallet_mutex })
}
fn get_network(&self) -> Network {
self.wallet.lock().unwrap().network()
self.get_wallet().network()
}
fn sync(
@@ -272,30 +260,18 @@ impl OnlineWallet {
max_address_param: Option<u32>,
) -> Result<(), BdkError> {
progress_update.update(21.0, Some("message".to_string()));
self.wallet
.lock()
.unwrap()
self.get_wallet()
.sync(BdkProgressHolder { progress_update }, max_address_param)
}
fn broadcast<'a>(
&self,
psbt: &'a PartiallySignedBitcoinTransaction,
) -> Result<Transaction, Error> {
fn broadcast(&self, psbt: &PartiallySignedBitcoinTransaction) -> Result<Transaction, Error> {
let tx = psbt.internal.lock().unwrap().clone().extract_tx();
self.get_wallet().broadcast(tx)?;
Ok(Transaction::from(&psbt.details))
self.get_wallet().broadcast(&tx)?;
let tx_details = self.get_wallet().get_tx(&tx.txid(), true)?;
Ok(Transaction::from(&tx_details.unwrap()))
}
}
impl WalletHolder<AnyBlockchain> for OnlineWallet {
fn get_wallet(&self) -> MutexGuard<Wallet<AnyBlockchain, AnyDatabase>> {
self.wallet.lock().unwrap()
}
}
impl OfflineWalletOperations<AnyBlockchain> for OnlineWallet {}
pub struct ExtendedKeyInfo {
mnemonic: String,
xprv: String,
@@ -304,11 +280,11 @@ pub struct ExtendedKeyInfo {
fn generate_extended_key(
network: Network,
mnemonic_type: MnemonicType,
word_count: WordCount,
password: Option<String>,
) -> Result<ExtendedKeyInfo, Error> {
let mnemonic: GeneratedKey<_, BareCtx> =
Mnemonic::generate((mnemonic_type, Language::English)).unwrap();
Mnemonic::generate((word_count, Language::English)).unwrap();
let mnemonic = mnemonic.into_key();
let xkey: ExtendedKey = (mnemonic.clone(), password).into_extended_key()?;
let xprv = xkey.into_xprv(network).unwrap();
@@ -325,7 +301,7 @@ fn restore_extended_key(
mnemonic: String,
password: Option<String>,
) -> Result<ExtendedKeyInfo, Error> {
let mnemonic = Mnemonic::from_phrase(mnemonic.as_ref(), Language::English).unwrap();
let mnemonic = Mnemonic::parse_in(Language::English, mnemonic).unwrap();
let xkey: ExtendedKey = (mnemonic.clone(), password).into_extended_key()?;
let xprv = xkey.into_xprv(network).unwrap();
let fingerprint = xprv.fingerprint(&Secp256k1::new());
@@ -336,5 +312,4 @@ fn restore_extended_key(
})
}
uniffi::deps::static_assertions::assert_impl_all!(OfflineWallet: Sync, Send);
uniffi::deps::static_assertions::assert_impl_all!(OnlineWallet: Sync, Send);
uniffi::deps::static_assertions::assert_impl_all!(Wallet: Sync, Send);

44
test.sh
View File

@@ -1,44 +0,0 @@
#!/usr/bin/env bash
set -eo pipefail
# functions
## help
help()
{
# Display Help
echo "Test bdk-uniffi and related libraries."
echo
echo "Syntax: build [-a|h|k]"
echo "options:"
echo "-a Android connected device tests."
echo "-h Print this Help."
echo "-k Kotlin tests."
echo
}
test_kotlin() {
(cd bindings/bdk-kotlin && ./gradlew :jvm:test -Djna.debug_load=true)
}
test_android() {
(cd bindings/bdk-kotlin && ./gradlew :android:connectedDebugAndroidTest)
}
if [ $1 = "-h" ]
then
help
else
cargo test
# optional tests
while [ -n "$1" ]; do # while loop starts
case "$1" in
-a) test_android ;;
-h) help ;;
-k) test_kotlin ;;
*) echo "Option $1 not recognized" ;;
esac
shift
done
fi