Compare commits
150 Commits
v0.0.0
...
release/0.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
89d58db02a | ||
|
|
cda682b634 | ||
|
|
d17ea4b90c | ||
|
|
76fa9b9521 | ||
|
|
cafa8dacab | ||
|
|
c039281ffc | ||
|
|
1f0b053872 | ||
|
|
97f1011748 | ||
|
|
edfcde1cc6 | ||
|
|
15c0dac622 | ||
|
|
a99e022756 | ||
|
|
672131ca0f | ||
|
|
72f90f1d63 | ||
|
|
5240cd895e | ||
|
|
55462fb426 | ||
|
|
9188dec2f2 | ||
|
|
a9a01950ee | ||
|
|
39cc3b3bfa | ||
|
|
c08fe99ad6 | ||
|
|
d53eb793ea | ||
|
|
a68a8bee7d | ||
|
|
e250d4ae1f | ||
|
|
3e0ae31890 | ||
|
|
d197e17eaa | ||
|
|
63cbcb1aa3 | ||
|
|
be8b31684f | ||
|
|
862658ce96 | ||
|
|
6257911095 | ||
|
|
01da0137ef | ||
|
|
f86a9df594 | ||
|
|
379cbe0b59 | ||
|
|
4fd4a7ee6f | ||
|
|
c6c4446092 | ||
|
|
358cc35b60 | ||
|
|
c58a31f711 | ||
|
|
8a9e025e2f | ||
|
|
2ac26fa060 | ||
|
|
7e61659cb7 | ||
|
|
947a5cb8e0 | ||
|
|
f6b099aa76 | ||
|
|
0467e12aae | ||
|
|
94b07b9fb9 | ||
|
|
9ee31d97c7 | ||
|
|
2c30bdff56 | ||
|
|
97c59b4cad | ||
|
|
c5f18ca998 | ||
|
|
c79f1b6326 | ||
|
|
334bafcc7c | ||
|
|
1148a2e6d7 | ||
|
|
80510381de | ||
|
|
a4370332df | ||
|
|
693a75e6d1 | ||
|
|
b6fa3539e4 | ||
|
|
5e7a42df07 | ||
|
|
3b05c57794 | ||
|
|
aabb09ccb8 | ||
|
|
ab301f075b | ||
|
|
9681f30e19 | ||
|
|
ef73e38154 | ||
|
|
e98298090d | ||
|
|
d1b24026e6 | ||
|
|
4ad9b89e25 | ||
|
|
a8d9864825 | ||
|
|
71531b7abb | ||
|
|
a7a45a036c | ||
|
|
7e02dbc97a | ||
|
|
1b30ce14a3 | ||
|
|
e0937b73db | ||
|
|
a8b161569c | ||
|
|
04eec7bf56 | ||
|
|
f2bbe668b1 | ||
|
|
f1c2118b02 | ||
|
|
f34e59e289 | ||
|
|
892bfe868f | ||
|
|
fe251c12f3 | ||
|
|
ee59dbe543 | ||
|
|
2f83a9ed05 | ||
|
|
4a7d665f7c | ||
|
|
25b8a8841d | ||
|
|
38b8589526 | ||
|
|
3693e99372 | ||
|
|
d97a13d186 | ||
|
|
4e1c6bd62b | ||
|
|
04eee046cb | ||
|
|
69a676ba36 | ||
|
|
a528a76c5d | ||
|
|
653773a638 | ||
|
|
857d99030b | ||
|
|
2db59de659 | ||
|
|
c15993310d | ||
|
|
b2f2f9135d | ||
|
|
3a7e7baf51 | ||
|
|
d307b281d7 | ||
|
|
41afeafcd3 | ||
|
|
47651f3681 | ||
|
|
9b7a9ded56 | ||
|
|
178fd6f010 | ||
|
|
31710ac77b | ||
|
|
6d7939c88f | ||
|
|
598d08b3bc | ||
|
|
b6d6a0d092 | ||
|
|
6093a8750b | ||
|
|
279f304d5d | ||
|
|
64fbf60a6a | ||
|
|
ce93e4e954 | ||
|
|
cf56619157 | ||
|
|
caff93c945 | ||
|
|
01bfe5d10e | ||
|
|
091c9994fa | ||
|
|
f30558d55c | ||
|
|
4efbfc1e9c | ||
|
|
3491a4548d | ||
|
|
0cc2f0a5be | ||
|
|
784f754cd9 | ||
|
|
8faba58cd4 | ||
|
|
0b265a7c26 | ||
|
|
f9e3bdfdb2 | ||
|
|
39e5efe5c0 | ||
|
|
342c4c4aa8 | ||
|
|
bb9fdd35cf | ||
|
|
a1aea54c53 | ||
|
|
a33a09f2a3 | ||
|
|
6361b41f33 | ||
|
|
2abe7205cb | ||
|
|
3e31e9aca3 | ||
|
|
a056c0dd59 | ||
|
|
060e54a718 | ||
|
|
59fe24818a | ||
|
|
91ae8dd537 | ||
|
|
13e7217ffd | ||
|
|
af828efa93 | ||
|
|
0f7a4aebf8 | ||
|
|
9f29eb0e86 | ||
|
|
3a5d4816ac | ||
|
|
2b40718875 | ||
|
|
36333b9fb7 | ||
|
|
f6c10da805 | ||
|
|
76087b9aec | ||
|
|
43ddf0a3b1 | ||
|
|
5303de9593 | ||
|
|
333f694d55 | ||
|
|
308d4af4f1 | ||
|
|
2aad5d4428 | ||
|
|
f31ebaa1e6 | ||
|
|
43425c8875 | ||
|
|
cb54405aed | ||
|
|
49126a8943 | ||
|
|
ec9d2ea284 | ||
|
|
f07f0248ef | ||
|
|
d14ea9a059 |
29
.editorconfig
Normal file
29
.editorconfig
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
charset = utf-8
|
||||||
|
end_of_line = lf
|
||||||
|
indent_size = 2
|
||||||
|
indent_style = space
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
insert_final_newline = true
|
||||||
|
|
||||||
|
[*.md]
|
||||||
|
trim_trailing_whitespace = false
|
||||||
|
|
||||||
|
[*.rs]
|
||||||
|
indent_size = 4
|
||||||
|
|
||||||
|
[*.kt]
|
||||||
|
indent_size = 4
|
||||||
|
|
||||||
|
[*.gradle]
|
||||||
|
indent_size = 4
|
||||||
|
|
||||||
|
[tests/**/*.rs]
|
||||||
|
charset = utf-8
|
||||||
|
end_of_line = unset
|
||||||
|
indent_size = unset
|
||||||
|
indent_style = unset
|
||||||
|
trim_trailing_whitespace = unset
|
||||||
|
insert_final_newline = unset
|
||||||
20
.gitignore
vendored
20
.gitignore
vendored
@@ -1,4 +1,16 @@
|
|||||||
/target
|
target
|
||||||
*.h
|
build
|
||||||
/main
|
Cargo.lock
|
||||||
/Cargo.lock
|
/bindings/bdk-kotlin/local.properties
|
||||||
|
.gradle
|
||||||
|
wallet_db
|
||||||
|
bdk_ffi_test
|
||||||
|
local.properties
|
||||||
|
*.log
|
||||||
|
*.dylib
|
||||||
|
*.so
|
||||||
|
.DS_Store
|
||||||
|
testdb
|
||||||
|
xcuserdata
|
||||||
|
.lsp
|
||||||
|
.clj-kondo
|
||||||
|
|||||||
23
CHANGELOG.md
Normal file
23
CHANGELOG.md
Normal 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
|
||||||
26
Cargo.toml
26
Cargo.toml
@@ -1,16 +1,28 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "bdk_ffi"
|
name = "bdk-ffi"
|
||||||
version = "0.1.0"
|
version = "0.3.1"
|
||||||
authors = ["Steve Myers <steve@notmandatory.org>"]
|
authors = ["Steve Myers <steve@notmandatory.org>", "Sudarsan Balaji <sudarsan.balaji@artfuldev.com>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
[lib]
|
[lib]
|
||||||
crate-type = ["staticlib"]
|
crate-type = ["staticlib", "cdylib"]
|
||||||
|
name = "bdkffi"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
bdk = { version = "^0.7", feature = ["all-keys"] }
|
bdk = { version = "0.14", features = ["all-keys", "use-esplora-ureq"] }
|
||||||
safer-ffi = { version = "*", features = ["proc_macros"]}
|
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 = { version = "0.16.0", features = ["builtin-bindgen"] }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
c-headers = ["safer-ffi/headers"]
|
generate-python = ["uniffi_bindgen"]
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "generate"
|
||||||
|
|||||||
67
README.md
Normal file
67
README.md
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
# Native language bindings for BDK
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
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] |
|
||||||
|
|
||||||
|
[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
|
||||||
|
|
||||||
|
[bdk-kotlin]: https://github.com/bitcoindevkit/bdk-kotlin
|
||||||
|
[bdk-swift]: https://github.com/bitcoindevkit/bdk-swift
|
||||||
|
[bdk-python]: https://github.com/thunderbiscuit/bdk-python
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
### Install uniffi-bindgen cli tool
|
||||||
|
|
||||||
|
Install the uniffi-bindgen binary on your system using:
|
||||||
|
|
||||||
|
`cargo install uniffi_bindgen`
|
||||||
|
|
||||||
|
The version must be the same as the `uniffi` dependency in `Cargo.toml`.
|
||||||
|
|
||||||
|
### Adding new structs and functions
|
||||||
|
|
||||||
|
See the [UniFFI User Guide](https://mozilla.github.io/uniffi-rs/)
|
||||||
|
|
||||||
|
#### For pass by value objects
|
||||||
|
|
||||||
|
1. create new rust struct with only fields that are supported UniFFI types
|
||||||
|
1. update mapping `bdk.udl` file with new `dictionary`
|
||||||
|
|
||||||
|
#### For pass by reference values
|
||||||
|
|
||||||
|
1. create wrapper rust struct/impl with only fields that are `Sync + Send`
|
||||||
|
1. update mapping `bdk.udl` file with new `interface`
|
||||||
|
|
||||||
|
## Goals
|
||||||
|
|
||||||
|
1. Language bindings should feel idiomatic in target languages/platforms
|
||||||
|
1. Adding new targets should be easy
|
||||||
|
1. Getting up and running should be easy
|
||||||
|
1. Contributing should be easy
|
||||||
|
1. Get it right, then automate
|
||||||
|
|
||||||
|
## Thanks
|
||||||
|
|
||||||
|
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
|
||||||
3
build.rs
Normal file
3
build.rs
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
fn main() {
|
||||||
|
uniffi_build::generate_scaffolding("src/bdk.udl").unwrap();
|
||||||
|
}
|
||||||
13
main.c
13
main.c
@@ -1,13 +0,0 @@
|
|||||||
#include <stdlib.h>
|
|
||||||
|
|
||||||
#include "bdk_ffi.h"
|
|
||||||
|
|
||||||
int main (int argc, char const * const argv[])
|
|
||||||
{
|
|
||||||
Point_t * a = new_point(84,45);
|
|
||||||
Point_t * b = new_point(0.0,39.0);
|
|
||||||
Point_t * m = mid_point(a, b);
|
|
||||||
print_point(m);
|
|
||||||
print_point(NULL);
|
|
||||||
return EXIT_SUCCESS;
|
|
||||||
}
|
|
||||||
151
src/bdk.udl
Normal file
151
src/bdk.udl
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
namespace bdk {
|
||||||
|
[Throws=BdkError]
|
||||||
|
ExtendedKeyInfo generate_extended_key(Network network, WordCount word_count, string? password);
|
||||||
|
[Throws=BdkError]
|
||||||
|
ExtendedKeyInfo restore_extended_key(Network network, string mnemonic, string? password);
|
||||||
|
};
|
||||||
|
|
||||||
|
[Error]
|
||||||
|
enum BdkError {
|
||||||
|
"InvalidU32Bytes",
|
||||||
|
"Generic",
|
||||||
|
"ScriptDoesntHaveAddressForm",
|
||||||
|
"NoRecipients",
|
||||||
|
"NoUtxosSelected",
|
||||||
|
"OutputBelowDustLimit",
|
||||||
|
"InsufficientFunds",
|
||||||
|
"BnBTotalTriesExceeded",
|
||||||
|
"BnBNoExactMatch",
|
||||||
|
"UnknownUtxo",
|
||||||
|
"TransactionNotFound",
|
||||||
|
"TransactionConfirmed",
|
||||||
|
"IrreplaceableTransaction",
|
||||||
|
"FeeRateTooLow",
|
||||||
|
"FeeTooLow",
|
||||||
|
"FeeRateUnavailable",
|
||||||
|
"MissingKeyOrigin",
|
||||||
|
"Key",
|
||||||
|
"ChecksumMismatch",
|
||||||
|
"SpendingPolicyRequired",
|
||||||
|
"InvalidPolicyPathError",
|
||||||
|
"Signer",
|
||||||
|
"InvalidNetwork",
|
||||||
|
"InvalidProgressValue",
|
||||||
|
"ProgressUpdateError",
|
||||||
|
"InvalidOutpoint",
|
||||||
|
"Descriptor",
|
||||||
|
"AddressValidator",
|
||||||
|
"Encode",
|
||||||
|
"Miniscript",
|
||||||
|
"Bip32",
|
||||||
|
"Secp256k1",
|
||||||
|
"Json",
|
||||||
|
"Hex",
|
||||||
|
"Psbt",
|
||||||
|
"PsbtParse",
|
||||||
|
"Electrum",
|
||||||
|
"Esplora",
|
||||||
|
"Sled",
|
||||||
|
};
|
||||||
|
|
||||||
|
enum Network {
|
||||||
|
"Bitcoin",
|
||||||
|
"Testnet",
|
||||||
|
"Signet",
|
||||||
|
"Regtest",
|
||||||
|
};
|
||||||
|
|
||||||
|
dictionary SledDbConfiguration {
|
||||||
|
string path;
|
||||||
|
string tree_name;
|
||||||
|
};
|
||||||
|
|
||||||
|
[Enum]
|
||||||
|
interface DatabaseConfig {
|
||||||
|
Memory(string junk);
|
||||||
|
Sled(SledDbConfiguration config);
|
||||||
|
};
|
||||||
|
|
||||||
|
dictionary TransactionDetails {
|
||||||
|
u64? fees;
|
||||||
|
u64 received;
|
||||||
|
u64 sent;
|
||||||
|
string txid;
|
||||||
|
};
|
||||||
|
|
||||||
|
dictionary BlockTime {
|
||||||
|
u32 height;
|
||||||
|
u64 timestamp;
|
||||||
|
};
|
||||||
|
|
||||||
|
[Enum]
|
||||||
|
interface Transaction {
|
||||||
|
Unconfirmed(TransactionDetails details);
|
||||||
|
Confirmed(TransactionDetails details, BlockTime confirmation);
|
||||||
|
};
|
||||||
|
|
||||||
|
dictionary ElectrumConfig {
|
||||||
|
string url;
|
||||||
|
string? socks5;
|
||||||
|
u8 retry;
|
||||||
|
u8? timeout;
|
||||||
|
u64 stop_gap;
|
||||||
|
};
|
||||||
|
|
||||||
|
dictionary EsploraConfig {
|
||||||
|
string base_url;
|
||||||
|
string? proxy;
|
||||||
|
u64 timeout_read;
|
||||||
|
u64 timeout_write;
|
||||||
|
u64 stop_gap;
|
||||||
|
};
|
||||||
|
|
||||||
|
[Enum]
|
||||||
|
interface BlockchainConfig {
|
||||||
|
Electrum(ElectrumConfig config);
|
||||||
|
Esplora(EsploraConfig config);
|
||||||
|
};
|
||||||
|
|
||||||
|
callback interface BdkProgress {
|
||||||
|
void update(f32 progress, string? message);
|
||||||
|
};
|
||||||
|
|
||||||
|
interface Wallet {
|
||||||
|
[Throws=BdkError]
|
||||||
|
constructor(string descriptor, string? change_descriptor, Network network, DatabaseConfig database_config, BlockchainConfig blockchain_config);
|
||||||
|
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();
|
||||||
|
Network get_network();
|
||||||
|
[Throws=BdkError]
|
||||||
|
void sync(BdkProgress progress_update, u32? max_address_param);
|
||||||
|
[Throws=BdkError]
|
||||||
|
Transaction broadcast([ByRef] PartiallySignedBitcoinTransaction psbt);
|
||||||
|
};
|
||||||
|
|
||||||
|
interface PartiallySignedBitcoinTransaction {
|
||||||
|
[Throws=BdkError]
|
||||||
|
constructor([ByRef] Wallet wallet, string recipient, u64 amount, float? fee_rate);
|
||||||
|
[Name=deserialize,Throws=BdkError]
|
||||||
|
constructor(string psbt_base64);
|
||||||
|
string serialize();
|
||||||
|
};
|
||||||
|
|
||||||
|
dictionary ExtendedKeyInfo {
|
||||||
|
string mnemonic;
|
||||||
|
string xprv;
|
||||||
|
string fingerprint;
|
||||||
|
};
|
||||||
|
|
||||||
|
enum WordCount {
|
||||||
|
"Words12",
|
||||||
|
"Words15",
|
||||||
|
"Words18",
|
||||||
|
"Words21",
|
||||||
|
"Words24",
|
||||||
|
};
|
||||||
67
src/bin/generate.rs
Normal file
67
src/bin/generate.rs
Normal 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(())
|
||||||
|
}
|
||||||
345
src/lib.rs
345
src/lib.rs
@@ -1,47 +1,314 @@
|
|||||||
use ::safer_ffi::prelude::*;
|
use bdk::bitcoin::secp256k1::Secp256k1;
|
||||||
|
use bdk::bitcoin::util::psbt::PartiallySignedTransaction;
|
||||||
|
use bdk::bitcoin::{Address, Network};
|
||||||
|
use bdk::blockchain::any::{AnyBlockchain, AnyBlockchainConfig};
|
||||||
|
use bdk::blockchain::Progress;
|
||||||
|
use bdk::blockchain::{
|
||||||
|
electrum::ElectrumBlockchainConfig, esplora::EsploraBlockchainConfig, ConfigurableBlockchain,
|
||||||
|
};
|
||||||
|
use bdk::database::any::{AnyDatabase, SledDbConfiguration};
|
||||||
|
use bdk::database::{AnyDatabaseConfig, ConfigurableDatabase};
|
||||||
|
use bdk::keys::bip39::{Language, Mnemonic, WordCount};
|
||||||
|
use bdk::keys::{DerivableKey, ExtendedKey, GeneratableKey, GeneratedKey};
|
||||||
|
use bdk::miniscript::BareCtx;
|
||||||
|
use bdk::wallet::AddressIndex;
|
||||||
|
use bdk::{BlockTime, Error, FeeRate, SignOptions, Wallet as BdkWallet};
|
||||||
|
use std::convert::TryFrom;
|
||||||
|
use std::str::FromStr;
|
||||||
|
use std::sync::{Mutex, MutexGuard};
|
||||||
|
|
||||||
/// A `struct` usable from both Rust and C
|
uniffi_macros::include_scaffolding!("bdk");
|
||||||
#[derive_ReprC]
|
|
||||||
#[repr(C)]
|
type BdkError = Error;
|
||||||
#[derive(Debug, Clone, Copy)]
|
|
||||||
pub struct Point {
|
pub enum DatabaseConfig {
|
||||||
x: f64,
|
Memory { junk: String },
|
||||||
y: f64,
|
Sled { config: SledDbConfiguration },
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Export a Rust function to the C world. */
|
pub struct ElectrumConfig {
|
||||||
/// Returns the middle point of `[a, b]`.
|
pub url: String,
|
||||||
#[ffi_export]
|
pub socks5: Option<String>,
|
||||||
fn mid_point(a: Option<repr_c::Box<Point>>, b: Option<repr_c::Box<Point>>) -> repr_c::Box<Point> {
|
pub retry: u8,
|
||||||
let a = a.unwrap();
|
pub timeout: Option<u8>,
|
||||||
let b = b.unwrap();
|
pub stop_gap: u64,
|
||||||
repr_c::Box::new(Point {
|
}
|
||||||
x: (a.x + b.x) / 2.,
|
|
||||||
y: (a.y + b.y) / 2.,
|
pub struct EsploraConfig {
|
||||||
|
pub base_url: String,
|
||||||
|
pub proxy: Option<String>,
|
||||||
|
pub timeout_read: u64,
|
||||||
|
pub timeout_write: u64,
|
||||||
|
pub stop_gap: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum BlockchainConfig {
|
||||||
|
Electrum { config: ElectrumConfig },
|
||||||
|
Esplora { config: EsploraConfig },
|
||||||
|
}
|
||||||
|
|
||||||
|
trait WalletHolder<B> {
|
||||||
|
fn get_wallet(&self) -> MutexGuard<BdkWallet<B, AnyDatabase>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Default)]
|
||||||
|
pub struct TransactionDetails {
|
||||||
|
pub fees: Option<u64>,
|
||||||
|
pub received: u64,
|
||||||
|
pub sent: u64,
|
||||||
|
pub txid: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub enum Transaction {
|
||||||
|
Unconfirmed {
|
||||||
|
details: TransactionDetails,
|
||||||
|
},
|
||||||
|
Confirmed {
|
||||||
|
details: TransactionDetails,
|
||||||
|
confirmation: BlockTime,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&bdk::TransactionDetails> for TransactionDetails {
|
||||||
|
fn from(x: &bdk::TransactionDetails) -> TransactionDetails {
|
||||||
|
TransactionDetails {
|
||||||
|
fees: x.fee,
|
||||||
|
txid: x.txid.to_string(),
|
||||||
|
received: x.received,
|
||||||
|
sent: x.sent,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&bdk::TransactionDetails> for Transaction {
|
||||||
|
fn from(x: &bdk::TransactionDetails) -> Transaction {
|
||||||
|
match x.confirmation_time.clone() {
|
||||||
|
Some(block_time) => Transaction::Confirmed {
|
||||||
|
details: TransactionDetails::from(x),
|
||||||
|
confirmation: block_time,
|
||||||
|
},
|
||||||
|
None => Transaction::Unconfirmed {
|
||||||
|
details: TransactionDetails::from(x),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
trait WalletOperations<B>: WalletHolder<B> {
|
||||||
|
fn get_new_address(&self) -> String {
|
||||||
|
self.get_wallet()
|
||||||
|
.get_address(AddressIndex::New)
|
||||||
|
.unwrap()
|
||||||
|
.address
|
||||||
|
.to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_last_unused_address(&self) -> String {
|
||||||
|
self.get_wallet()
|
||||||
|
.get_address(AddressIndex::LastUnused)
|
||||||
|
.unwrap()
|
||||||
|
.address
|
||||||
|
.to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_balance(&self) -> Result<u64, Error> {
|
||||||
|
self.get_wallet().get_balance()
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
true => Ok(()),
|
||||||
|
false => Err(BdkError::Generic(format!(
|
||||||
|
"transaction signing not finalized {:?}",
|
||||||
|
psbt
|
||||||
|
))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_transactions(&self) -> Result<Vec<Transaction>, Error> {
|
||||||
|
let transactions = self.get_wallet().list_transactions(true)?;
|
||||||
|
Ok(transactions.iter().map(Transaction::from).collect())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Wallet {
|
||||||
|
wallet_mutex: Mutex<BdkWallet<AnyBlockchain, AnyDatabase>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait BdkProgress: Send + Sync {
|
||||||
|
fn update(&self, progress: f32, message: Option<String>);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct BdkProgressHolder {
|
||||||
|
progress_update: Box<dyn BdkProgress>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Progress for BdkProgressHolder {
|
||||||
|
fn update(&self, progress: f32, message: Option<String>) -> Result<(), Error> {
|
||||||
|
self.progress_update.update(progress, message);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct PartiallySignedBitcoinTransaction {
|
||||||
|
internal: Mutex<PartiallySignedTransaction>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartiallySignedBitcoinTransaction {
|
||||||
|
fn new(
|
||||||
|
wallet: &Wallet,
|
||||||
|
recipient: String,
|
||||||
|
amount: u64,
|
||||||
|
fee_rate: Option<f32>, // satoshis per vbyte
|
||||||
|
) -> Result<Self, Error> {
|
||||||
|
let wallet = wallet.get_wallet();
|
||||||
|
match Address::from_str(&recipient) {
|
||||||
|
Ok(address) => {
|
||||||
|
let (psbt, _details) = {
|
||||||
|
let mut builder = wallet.build_tx();
|
||||||
|
builder.add_recipient(address.script_pubkey(), amount);
|
||||||
|
if let Some(sat_per_vb) = fee_rate {
|
||||||
|
builder.fee_rate(FeeRate::from_sat_per_vb(sat_per_vb));
|
||||||
|
}
|
||||||
|
builder.finish()?
|
||||||
|
};
|
||||||
|
Ok(PartiallySignedBitcoinTransaction {
|
||||||
|
internal: Mutex::new(psbt),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
Err(..) => Err(BdkError::Generic(
|
||||||
|
"failed to read wallet address".to_string(),
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 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>,
|
||||||
|
network: Network,
|
||||||
|
database_config: DatabaseConfig,
|
||||||
|
blockchain_config: BlockchainConfig,
|
||||||
|
) -> Result<Self, BdkError> {
|
||||||
|
let any_database_config = match database_config {
|
||||||
|
DatabaseConfig::Memory { .. } => AnyDatabaseConfig::Memory(()),
|
||||||
|
DatabaseConfig::Sled { config } => AnyDatabaseConfig::Sled(config),
|
||||||
|
};
|
||||||
|
let any_blockchain_config = match blockchain_config {
|
||||||
|
BlockchainConfig::Electrum { config } => {
|
||||||
|
AnyBlockchainConfig::Electrum(ElectrumBlockchainConfig {
|
||||||
|
retry: config.retry,
|
||||||
|
socks5: config.socks5,
|
||||||
|
timeout: config.timeout,
|
||||||
|
url: config.url,
|
||||||
|
stop_gap: usize::try_from(config.stop_gap).unwrap(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
BlockchainConfig::Esplora { config } => {
|
||||||
|
AnyBlockchainConfig::Esplora(EsploraBlockchainConfig {
|
||||||
|
base_url: config.base_url,
|
||||||
|
proxy: config.proxy,
|
||||||
|
timeout_read: config.timeout_read,
|
||||||
|
timeout_write: config.timeout_write,
|
||||||
|
stop_gap: usize::try_from(config.stop_gap).unwrap(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let database = AnyDatabase::from_config(&any_database_config)?;
|
||||||
|
let blockchain = AnyBlockchain::from_config(&any_blockchain_config)?;
|
||||||
|
let wallet_mutex = Mutex::new(BdkWallet::new(
|
||||||
|
&descriptor,
|
||||||
|
change_descriptor.to_owned().as_ref(),
|
||||||
|
network,
|
||||||
|
database,
|
||||||
|
blockchain,
|
||||||
|
)?);
|
||||||
|
Ok(Wallet { wallet_mutex })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_network(&self) -> Network {
|
||||||
|
self.get_wallet().network()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sync(
|
||||||
|
&self,
|
||||||
|
progress_update: Box<dyn BdkProgress>,
|
||||||
|
max_address_param: Option<u32>,
|
||||||
|
) -> Result<(), BdkError> {
|
||||||
|
self.get_wallet()
|
||||||
|
.sync(BdkProgressHolder { progress_update }, max_address_param)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn broadcast(&self, psbt: &PartiallySignedBitcoinTransaction) -> Result<Transaction, Error> {
|
||||||
|
let tx = psbt.internal.lock().unwrap().clone().extract_tx();
|
||||||
|
self.get_wallet().broadcast(&tx)?;
|
||||||
|
let tx_details = self.get_wallet().get_tx(&tx.txid(), true)?;
|
||||||
|
Ok(Transaction::from(&tx_details.unwrap()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ExtendedKeyInfo {
|
||||||
|
mnemonic: String,
|
||||||
|
xprv: String,
|
||||||
|
fingerprint: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generate_extended_key(
|
||||||
|
network: Network,
|
||||||
|
word_count: WordCount,
|
||||||
|
password: Option<String>,
|
||||||
|
) -> Result<ExtendedKeyInfo, Error> {
|
||||||
|
let mnemonic: GeneratedKey<_, BareCtx> =
|
||||||
|
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();
|
||||||
|
let fingerprint = xprv.fingerprint(&Secp256k1::new());
|
||||||
|
Ok(ExtendedKeyInfo {
|
||||||
|
mnemonic: mnemonic.to_string(),
|
||||||
|
xprv: xprv.to_string(),
|
||||||
|
fingerprint: fingerprint.to_string(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Pretty-prints a point using Rust's formatting logic.
|
fn restore_extended_key(
|
||||||
#[ffi_export]
|
network: Network,
|
||||||
fn print_point(point: Option<repr_c::Box<Point>>) {
|
mnemonic: String,
|
||||||
println!("{:?}", point);
|
password: Option<String>,
|
||||||
|
) -> Result<ExtendedKeyInfo, Error> {
|
||||||
|
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());
|
||||||
|
Ok(ExtendedKeyInfo {
|
||||||
|
mnemonic: mnemonic.to_string(),
|
||||||
|
xprv: xprv.to_string(),
|
||||||
|
fingerprint: fingerprint.to_string(),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[ffi_export]
|
uniffi::deps::static_assertions::assert_impl_all!(Wallet: Sync, Send);
|
||||||
fn new_point(x: f64, y: f64) -> repr_c::Box<Point> {
|
|
||||||
repr_c::Box::new(Point { x, y })
|
|
||||||
}
|
|
||||||
|
|
||||||
#[ffi_export]
|
|
||||||
fn free_point(point: Option<repr_c::Box<Point>>) {
|
|
||||||
drop(point)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The following test function is necessary for the header generation.
|
|
||||||
#[::safer_ffi::cfg_headers]
|
|
||||||
#[test]
|
|
||||||
fn generate_headers() -> ::std::io::Result<()> {
|
|
||||||
::safer_ffi::headers::builder()
|
|
||||||
.to_file("bdk_ffi.h")?
|
|
||||||
.generate()
|
|
||||||
}
|
|
||||||
|
|||||||
12
uniffi.toml
Normal file
12
uniffi.toml
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
[bindings.kotlin]
|
||||||
|
package_name = "org.bitcoindevkit"
|
||||||
|
cdylib_name = "bdkffi"
|
||||||
|
|
||||||
|
[bindings.python]
|
||||||
|
cdylib_name = "bdkffi"
|
||||||
|
|
||||||
|
[bindings.ruby]
|
||||||
|
cdylib_name = "bdkffi"
|
||||||
|
|
||||||
|
[bindings.swift]
|
||||||
|
cdylib_name = "bdkffi"
|
||||||
Reference in New Issue
Block a user