Compare commits

...

45 Commits

Author SHA1 Message Date
Steve Myers
30e54ac067 Bump version to 0.6.0 2022-05-10 22:41:48 -07:00
Steve Myers
71583eca7f Merge bitcoindevkit/bdk-ffi#150: Add new BumpFeeTxBuilder interface
0787d9c446 Fix order of BumpFeeTxBuilder parameters (Steve Myers)
390d12703e Change TxBuilder and BumpFeeTxBuilder build() to finish() (Steve Myers)
9f903932dc Add BumpFeeTxBuilder (Steve Myers)

Pull request description:

  Add BumpFeeTxBuilder to bump the fee on an unconfirmed tx created by the Wallet. The structure of the new interface is:

  ```udl
  interface BumpFeeTxBuilder {
    constructor(string txid, float new_fee_rate);
    BumpFeeTxBuilder allow_shrinking(string address);
    BumpFeeTxBuilder enable_rbf();
    BumpFeeTxBuilder enable_rbf_with_sequence(u32 nsequence);
    [Throws=BdkError]
    PartiallySignedBitcoinTransaction build([ByRef] Wallet wallet);
  };
  ```
  Fixes #150

Top commit has no ACKs.

Tree-SHA512: a24ed41f7b897a0e091ef55d4c1347b3973cbe628b7d1bd69c95e663bc6f049de8f7e3b019115d763cff3fa6d5cb4d31ed474022c3087e8404a2af37f380d56d
2022-05-10 22:35:32 -07:00
Steve Myers
0787d9c446 Fix order of BumpFeeTxBuilder parameters 2022-05-06 09:59:07 -07:00
Steve Myers
390d12703e Change TxBuilder and BumpFeeTxBuilder build() to finish() 2022-05-06 09:52:20 -07:00
Steve Myers
9f903932dc Add BumpFeeTxBuilder 2022-05-05 20:22:55 -07:00
Steve Myers
3b243efefd Merge bitcoindevkit/bdk-ffi#149: Update to bdk 0.18.0
f38f4c6197 Update CHANGELOG (Steve Myers)
11ba16ec1b Move txid getter to PartiallySignedBitcoinTransaction (Steve Myers)
4665c551dd Update EsploraConfig, Blockchain broadcast, Wallet sync (Steve Myers)
907540d214 Update bdk to 0.18.0 with sqlite-bundled feature (Steve Myers)

Pull request description:

  Changes that were needed to match updated bdk APIs:
  * new bdk-ffi `Blockchain`  interface with `constructo(BlockchainConfig)` and `broadcast()` functions
  * `Blockchain.broadcast()` function is now void (doesn't return txid)
  * added `PartiallySignedBitcoinTransaction.txid()` getter function
  * `Wallet.sync()` function now takes a `Blockchain` and optional `Progress` callback interface

  Tests performed:
  * from `bdk-kotlin/bdk-ffi` added local remote to this branch, ran build.sh script
  * fixed broken `bdk-kotlin` jvm and android tests and confirmed tests all pass

Top commit has no ACKs.

Tree-SHA512: d50633bbc8fd8a0d141597b30122c72957d2a0d64fc1537b649eeb8a5df1b1fb9a78ee1f03b9c606f47dee3952c9a91ae09eb47eb7a66d5f0fcb5545c86d906b
2022-05-05 09:29:37 -07:00
Steve Myers
f38f4c6197 Update CHANGELOG 2022-05-02 16:17:27 -07:00
Steve Myers
11ba16ec1b Move txid getter to PartiallySignedBitcoinTransaction 2022-04-25 21:31:40 -07:00
Steve Myers
4665c551dd Update EsploraConfig, Blockchain broadcast, Wallet sync 2022-04-25 21:31:38 -07:00
Steve Myers
907540d214 Update bdk to 0.18.0 with sqlite-bundled feature 2022-04-25 21:31:36 -07:00
Steve Myers
e6a6be5b60 Merge bitcoindevkit/bdk-ffi#145: Fix fee parameter typo in TransactionDetails
c722223b49 Fix fee parameter typo in TransactionDetails (dhruvbaliyan)

Pull request description:

  Solves issue #136
  Generated Kotlin file now have "fee" in TransactionDetails as parameter instead of "fees"
  ```
  data class TransactionDetails (
      var fee: ULong?,
      var received: ULong,
      var sent: ULong,
      var txid: String
  ) {
      // ...
  }
  ```

ACKs for top commit:
  thunderbiscuit:
    Tested ACK c722223. Works as expected in my apps. Thanks for the quick fix!

Tree-SHA512: c55a6e77ca5a0cd19758fc628fc48ed997b3c86247a1eadf5be77771818e3aa5f4db10025e7aa30d05be573e94d7439b15c7fc1f3d6dad752487f7f1ad455367
2022-04-25 21:30:17 -07:00
dhruvbaliyan
c722223b49 Fix fee parameter typo in TransactionDetails 2022-04-20 06:13:49 +05:30
Steve Myers
236360e8c4 Merge bitcoindevkit/bdk-ffi#140: Add RBF methods to TxBuilder
220835cffd Add RBF to TxBuilder (Sudarsan Balaji)
b3c93b0435 Expose functions (Sudarsan Balaji)

Pull request description:

  Fix #133

  We need to create another `RbfValue` enum because the actual type is only visible within the crate in `bdk`.

ACKs for top commit:
  notmandatory:
    tACK 220835cffd

Tree-SHA512: 648ea26a9742c8a395876f38c7299ff0dabb4ccad64e2f6a47d29ceecf44d9e54d845410fa68665e4d4d03d8eda1e51f680d0b89df307b003de49cf7b98e8701
2022-04-16 22:36:34 -07:00
Sudarsan Balaji
220835cffd Add RBF to TxBuilder 2022-04-15 21:04:21 +01:00
Sudarsan Balaji
b3c93b0435 Expose functions 2022-04-15 21:04:04 +01:00
Sudarsan Balaji
a12e5ed396 Use Path instead of PathBuf 2022-04-04 11:07:51 +01:00
Sudarsan Balaji
fc00d0d38c Use unwrap_or_else panic instead of expect 2022-04-04 11:01:50 +01:00
Sudarsan Balaji
7ea5e75bc4 Use write_all when not writing partially 2022-04-04 10:59:19 +01:00
Sudarsan Balaji
a5bd16db4d Enforce rust naming conventions 2022-04-04 10:58:01 +01:00
Steve Myers
d72905168b Merge bitcoindevkit/bdk-ffi#128: Release/0.5
8a556d0ba0 Bump bdk-ffi version to 0.5.0 (Steve Myers)
d7c5f24fe8 Bump bdk-ffi-bindgen version to 0.2.0 (Steve Myers)
f1431c3073 Update CHANGELOG.md (Steve Myers)

Pull request description:

Top commit has no ACKs.

Tree-SHA512: 2d612936740b93148c90acf512005c82e1fe38b4708710952abeac03d361e7dca6c6bdea4e82981a87dbba1cb6d37c0bd48b4ab252467798aa60aca463af5e5e
2022-04-01 19:24:48 -07:00
Steve Myers
8a556d0ba0 Bump bdk-ffi version to 0.5.0 2022-04-01 19:16:53 -07:00
Steve Myers
d7c5f24fe8 Bump bdk-ffi-bindgen version to 0.2.0 2022-04-01 19:16:29 -07:00
Steve Myers
f1431c3073 Update CHANGELOG.md 2022-04-01 19:15:58 -07:00
Steve Myers
e797efea57 Merge bitcoindevkit/bdk-ffi#126: Add workspace and bdk-ffi-bindgen binary cli tool
b207464fe6 Update README.md with bdk-ffi-bindgen info (Steve Myers)
fca5d1602b Add workspace and move bin to bdk-ffi-bindgen package (Steve Myers)
f4e097c4ac Only print python fix up lib path if used (Steve Myers)
c66dfdd52a Use structopt to capture generate options (Steve Myers)
ce848725b4 Add binary to remove the need for uniffi-bindgen cli tool (thunderbiscuit)

Pull request description:

  This PR is based on the mozilla/application-services [embedded-uniffi-bindgen](https://github.com/mozilla/application-services/tree/main/tools/embedded-uniffi-bindgen) tool. The purpose is to keep the bdk-ffi and bdk-ffi-bindgen tool in sync with the same version of uniffi-rs.

  Fixes #124, this PR replaces #122.

  The `bdkffi` library code remains unchanged but the `bin/generate` and `bin/generate-bindings` bins are combined and put in a new workspace binary package called `bdk-ffi-bindgen`.  The `bdk-ffi-bindgen` binary uses the following options, defaults, and environment variables:

  ```shell
  % cargo run -p bdk-ffi-bindgen -- --help

  bdk-ffi-bindgen 0.1.0
  A tool to generate bdk-ffi language bindings

  USAGE:
      bdk-ffi-bindgen [OPTIONS] --language <language> --out-dir <out-dir>

  FLAGS:
      -h, --help       Prints help information
      -V, --version    Prints version information

  OPTIONS:
      -l, --language <language>
              Language to generate bindings for [env: BDKFFI_BINDGEN_LANGUAGE=]  [possible values: kotlin, swift, python]

      -o, --out-dir <out-dir>
              Output directory to put generated language bindings [env: BDKFFI_BINDGEN_OUTPUT_DIR=]

      -p, --python-fixup-path <python-fixup-path>    Python fix up lib path [env: BDKFFI_BINDGEN_PYTHON_FIXUP_PATH=]
      -u, --udl-file <udl-file>                      UDL file [env: BDKFFI_BINDGEN_UDL=]  [default: src/bdk.udl]

  ```

Top commit has no ACKs.

Tree-SHA512: fa1a1c097fe5d0e704d76078c10f82c466dad5d045c8c93d579c2d13c448c52fb6a4f99dfd3dbc46be30471477ae2d1f9264201e14bae7948b408c8e0b3c9b81
2022-04-01 18:58:18 -07:00
Sudarsan Balaji
a41d628b14 Fix typo 2022-04-01 13:56:43 +05:30
Steve Myers
b207464fe6 Update README.md with bdk-ffi-bindgen info 2022-03-31 20:18:31 -07:00
Steve Myers
fca5d1602b Add workspace and move bin to bdk-ffi-bindgen package 2022-03-31 19:29:05 -07:00
Steve Myers
f4e097c4ac Only print python fix up lib path if used 2022-03-31 19:29:03 -07:00
Steve Myers
c66dfdd52a Use structopt to capture generate options 2022-03-31 19:29:01 -07:00
thunderbiscuit
ce848725b4 Add binary to remove the need for uniffi-bindgen cli tool 2022-03-31 19:28:59 -07:00
Sudarsan Balaji
5512b31969 Simplify 2022-03-31 17:26:32 +01:00
Sudarsan Balaji
a48f9b4387 Simplify 2022-03-31 17:22:52 +01:00
Sudarsan Balaji
87a0a15ea7 Fix formatting
cargo fmt
2022-03-31 17:18:03 +01:00
Sudarsan Balaji
ee91ad5b31 Add TxBuilder::drain_to 2022-03-31 17:17:43 +01:00
Sudarsan Balaji
ba68103be1 Add TxBuilder::drain_wallet 2022-03-31 17:17:24 +01:00
Steve Myers
bc43d2eb1a Map TxBuilder address error to BdkError::Generic 2022-03-28 17:30:40 -07:00
Sudarsan Balaji
adc3f68e31 Remove unnecessary map_err 2022-03-28 20:04:22 +01:00
Sudarsan Balaji
dd5622f724 Remove PSBT constructor 2022-03-25 17:39:25 +00:00
Sudarsan Balaji
e5aa51c3f8 Use TxBuilder in PSBT constructor 2022-03-25 17:37:25 +00:00
Sudarsan Balaji
a39fc787d5 Add initial version of TxBuilder 2022-03-25 17:24:21 +00:00
Sudarsan Balaji
51603e06d9 Remove to_owned() 2022-03-25 15:18:25 +00:00
Steve Myers
a1b89adf84 Remove unneeded WalletHolder and WalletOperations traits 2022-03-20 19:59:20 -05:00
Steve Myers
b1d483463f Merge bitcoindevkit/bdk-ffi#120: Fix Wallet.broadcast function, now returns a tx id as a hex string
851f61296a Fix Wallet.broadcast function, now returns a tx id as a hex string (Steve Myers)

Pull request description:

ACKs for top commit:
  thunderbiscuit:
    Tested ACK 851f612.

Tree-SHA512: 86e1d39029924e4fa3a0c21e9f45c1ba0694f4db9d1cfd8dee25a5675d5a8b7851a7c712ce57fb74382a7428fcecbe8ecee4b7b87b9245672bbd6ccea63dfc13
2022-03-16 16:53:20 -05:00
Steve Myers
851f61296a Fix Wallet.broadcast function, now returns a tx id as a hex string 2022-03-15 20:50:12 -05:00
Steve Myers
5128ce8d5b Bump version to 0.4.1 2022-03-14 14:46:31 -05:00
8 changed files with 568 additions and 255 deletions

View File

@@ -6,6 +6,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
## [v0.6.0]
- Update BDK to version 0.18.0
- Add BumpFeeTxBuilder to bump the fee on an unconfirmed tx created by the Wallet
- Change TxBuilder.build() to TxBuilder.finish() to align with bdk function name
## [v0.5.0]
- Fix Wallet.broadcast function, now returns a tx id as a hex string
- Remove creating a new spending Transaction via the PartiallySignedBitcoinTransaction constructor
- Add TxBuilder for creating new spending PartiallySignedBitcoinTransaction
- Add TxBuilder .add_recipient, .fee_rate, and .build functions
- Add TxBuilder .drain_wallet and .drain_to functions
- Update generate cli tool to generate all binding languages and rename to bdk-ffi-bindgen
## [v0.4.0]
- Add dual license MIT and Apache 2.0
@@ -28,7 +43,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [v0.2.0]
[unreleased]: https://github.com/bitcoindevkit/bdk-ffi/compare/v0.4.0...HEAD
[unreleased]: https://github.com/bitcoindevkit/bdk-ffi/compare/v0.6.0...HEAD
[v0.6.0]: https://github.com/bitcoindevkit/bdk-ffi/compare/v0.5.0...v0.6.0
[v0.5.0]: https://github.com/bitcoindevkit/bdk-ffi/compare/v0.4.0...v0.5.0
[v0.4.0]: https://github.com/bitcoindevkit/bdk-ffi/compare/v0.3.1...v0.4.0
[v0.3.1]: https://github.com/bitcoindevkit/bdk-ffi/compare/v0.3.0...v0.3.1
[v0.3.0]: https://github.com/bitcoindevkit/bdk-ffi/compare/v0.2.0...v0.3.0

View File

@@ -1,32 +1,23 @@
[package]
name = "bdk-ffi"
version = "0.4.0"
version = "0.6.0"
authors = ["Steve Myers <steve@notmandatory.org>", "Sudarsan Balaji <sudarsan.balaji@artfuldev.com>"]
edition = "2018"
[workspace]
members = [".","bdk-ffi-bindgen"]
default-members = [".", "bdk-ffi-bindgen"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
crate-type = ["staticlib", "cdylib"]
name = "bdkffi"
[dependencies]
bdk = { version = "0.14", features = ["all-keys", "use-esplora-ureq", "sqlite"] }
# TODO remove when bdk "sqlite-bundled" feature added
rusqlite = { version = "0.25.3", features = ["bundled"] }
bdk = { version = "0.18", features = ["all-keys", "use-esplora-ureq", "sqlite-bundled"] }
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]
generate-python = ["uniffi_bindgen"]
[[bin]]
name = "generate"

View File

@@ -1,9 +1,10 @@
# 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.
The workspace in this repository creates the `libbdkffi` multi-language library for the rust based
[bdk] library from the [Bitcoin Dev Kit] project. The `bdk-ffi-bindgen` package builds a tool for
generating the actual language binding code used to access the `libbdkffi` library.
Each supported language has it's own repository that includes this project as a [git submodule].
Each supported language has its 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.
@@ -19,6 +20,14 @@ language binding for [bdk] supported by this project.
| Swift | iOS, macOS | [bdk-swift] |
| Python | linux, macOS | [bdk-python] |
## Language bindings generator tool
Use the `bdk-ffi-bindgen` tool to generate language binding code for the above supported languages.
To run `bdk-ffi-bindgen` and see the available options use the command:
```shell
cargo run -p bdk-ffi-bindgen -- --help
```
[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
@@ -30,14 +39,6 @@ language binding for [bdk] supported by this project.
## 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/)

View File

@@ -0,0 +1,11 @@
[package]
name = "bdk-ffi-bindgen"
version = "0.2.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
anyhow = "=1.0.45" # remove after upgrading to next version of uniffi
structopt = "0.3"
uniffi_bindgen = "0.16.0"

134
bdk-ffi-bindgen/src/main.rs Normal file
View File

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

View File

@@ -73,7 +73,7 @@ interface DatabaseConfig {
};
dictionary TransactionDetails {
u64? fees;
u64? fee;
u64 received;
u64 sent;
string txid;
@@ -101,9 +101,9 @@ dictionary ElectrumConfig {
dictionary EsploraConfig {
string base_url;
string? proxy;
u64 timeout_read;
u64 timeout_write;
u8? concurrency;
u64 stop_gap;
u64? timeout;
};
[Enum]
@@ -112,13 +112,20 @@ interface BlockchainConfig {
Esplora(EsploraConfig config);
};
callback interface BdkProgress {
interface Blockchain {
[Throws=BdkError]
constructor(BlockchainConfig config);
[Throws=BdkError]
void broadcast([ByRef] PartiallySignedBitcoinTransaction psbt);
};
callback interface Progress {
void update(f32 progress, string? message);
};
interface Wallet {
[Throws=BdkError]
constructor(string descriptor, string? change_descriptor, Network network, DatabaseConfig database_config, BlockchainConfig blockchain_config);
constructor(string descriptor, string? change_descriptor, Network network, DatabaseConfig database_config);
string get_new_address();
string get_last_unused_address();
[Throws=BdkError]
@@ -129,17 +136,35 @@ interface Wallet {
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);
void sync([ByRef] Blockchain blockchain, Progress? progress);
};
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();
string txid();
};
interface TxBuilder {
constructor();
TxBuilder add_recipient(string address, u64 amount);
TxBuilder fee_rate(float sat_per_vbyte);
TxBuilder drain_wallet();
TxBuilder drain_to(string address);
TxBuilder enable_rbf();
TxBuilder enable_rbf_with_sequence(u32 nsequence);
[Throws=BdkError]
PartiallySignedBitcoinTransaction finish([ByRef] Wallet wallet);
};
interface BumpFeeTxBuilder {
constructor(string txid, float new_fee_rate);
BumpFeeTxBuilder allow_shrinking(string address);
BumpFeeTxBuilder enable_rbf();
BumpFeeTxBuilder enable_rbf_with_sequence(u32 nsequence);
[Throws=BdkError]
PartiallySignedBitcoinTransaction finish([ByRef] Wallet wallet);
};
dictionary ExtendedKeyInfo {

View File

@@ -1,67 +0,0 @@
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

@@ -1,21 +1,26 @@
use bdk::bitcoin::hashes::hex::ToHex;
use bdk::bitcoin::secp256k1::Secp256k1;
use bdk::bitcoin::util::psbt::PartiallySignedTransaction;
use bdk::bitcoin::{Address, Network};
use bdk::bitcoin::{Address, Network, Script, Txid};
use bdk::blockchain::any::{AnyBlockchain, AnyBlockchainConfig};
use bdk::blockchain::Progress;
use bdk::blockchain::{
electrum::ElectrumBlockchainConfig, esplora::EsploraBlockchainConfig, ConfigurableBlockchain,
};
use bdk::blockchain::{Blockchain as BdkBlockchain, Progress as BdkProgress};
use bdk::database::any::{AnyDatabase, SledDbConfiguration, SqliteDbConfiguration};
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 bdk::{
BlockTime, Error, FeeRate, SignOptions, SyncOptions as BdkSyncOptions, Wallet as BdkWallet,
};
use std::convert::TryFrom;
use std::fmt;
use std::ops::Deref;
use std::str::FromStr;
use std::sync::{Mutex, MutexGuard};
use std::sync::{Arc, Mutex, MutexGuard};
uniffi_macros::include_scaffolding!("bdk");
@@ -38,9 +43,9 @@ pub struct ElectrumConfig {
pub struct EsploraConfig {
pub base_url: String,
pub proxy: Option<String>,
pub timeout_read: u64,
pub timeout_write: u64,
pub concurrency: Option<u8>,
pub stop_gap: u64,
pub timeout: Option<u64>,
}
pub enum BlockchainConfig {
@@ -48,13 +53,9 @@ pub enum BlockchainConfig {
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 fee: Option<u64>,
pub received: u64,
pub sent: u64,
pub txid: String,
@@ -74,7 +75,7 @@ pub enum Transaction {
impl From<&bdk::TransactionDetails> for TransactionDetails {
fn from(x: &bdk::TransactionDetails) -> TransactionDetails {
TransactionDetails {
fees: x.fee,
fee: x.fee,
txid: x.txid.to_string(),
received: x.received,
sent: x.sent,
@@ -96,7 +97,144 @@ impl From<&bdk::TransactionDetails> for Transaction {
}
}
trait WalletOperations<B>: WalletHolder<B> {
struct Blockchain {
blockchain_mutex: Mutex<AnyBlockchain>,
}
impl Blockchain {
fn new(blockchain_config: BlockchainConfig) -> Result<Self, BdkError> {
let any_blockchain_config = match blockchain_config {
BlockchainConfig::Electrum { config } => {
AnyBlockchainConfig::Electrum(ElectrumBlockchainConfig {
retry: config.retry,
socks5: config.socks5,
timeout: config.timeout,
url: config.url,
stop_gap: usize::try_from(config.stop_gap).unwrap(),
})
}
BlockchainConfig::Esplora { config } => {
AnyBlockchainConfig::Esplora(EsploraBlockchainConfig {
base_url: config.base_url,
proxy: config.proxy,
concurrency: config.concurrency,
stop_gap: usize::try_from(config.stop_gap).unwrap(),
timeout: config.timeout,
})
}
};
let blockchain = AnyBlockchain::from_config(&any_blockchain_config)?;
Ok(Self {
blockchain_mutex: Mutex::new(blockchain),
})
}
fn get_blockchain(&self) -> MutexGuard<AnyBlockchain> {
self.blockchain_mutex.lock().expect("blockchain")
}
fn broadcast(&self, psbt: &PartiallySignedBitcoinTransaction) -> Result<(), Error> {
let tx = psbt.internal.lock().unwrap().clone().extract_tx();
self.get_blockchain().broadcast(&tx)
}
}
struct Wallet {
wallet_mutex: Mutex<BdkWallet<AnyDatabase>>,
}
pub trait Progress: Send + Sync + 'static {
fn update(&self, progress: f32, message: Option<String>);
}
struct ProgressHolder {
progress: Box<dyn Progress>,
}
impl BdkProgress for ProgressHolder {
fn update(&self, progress: f32, message: Option<String>) -> Result<(), Error> {
self.progress.update(progress, message);
Ok(())
}
}
impl fmt::Debug for ProgressHolder {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ProgressHolder").finish_non_exhaustive()
}
}
#[derive(Debug)]
struct PartiallySignedBitcoinTransaction {
internal: Mutex<PartiallySignedTransaction>,
}
impl PartiallySignedBitcoinTransaction {
fn new(psbt_base64: String) -> Result<Self, Error> {
let psbt: PartiallySignedTransaction = PartiallySignedTransaction::from_str(&psbt_base64)?;
Ok(PartiallySignedBitcoinTransaction {
internal: Mutex::new(psbt),
})
}
fn serialize(&self) -> String {
let psbt = self.internal.lock().unwrap().clone();
psbt.to_string()
}
fn txid(&self) -> String {
let tx = self.internal.lock().unwrap().clone().extract_tx();
let txid = tx.txid();
txid.to_hex()
}
}
impl Wallet {
fn new(
descriptor: String,
change_descriptor: Option<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),
DatabaseConfig::Sqlite { config } => AnyDatabaseConfig::Sqlite(config),
};
let database = AnyDatabase::from_config(&any_database_config)?;
let wallet_mutex = Mutex::new(BdkWallet::new(
&descriptor,
change_descriptor.as_ref(),
network,
database,
)?);
Ok(Wallet { wallet_mutex })
}
fn get_wallet(&self) -> MutexGuard<BdkWallet<AnyDatabase>> {
self.wallet_mutex.lock().expect("wallet")
}
fn get_network(&self) -> Network {
self.get_wallet().network()
}
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)
}
fn get_new_address(&self) -> String {
self.get_wallet()
.get_address(AddressIndex::New)
@@ -135,144 +273,6 @@ trait WalletOperations<B>: WalletHolder<B> {
}
}
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),
DatabaseConfig::Sqlite { config } => AnyDatabaseConfig::Sqlite(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,
@@ -313,4 +313,205 @@ fn restore_extended_key(
})
}
fn to_script_pubkey(address: &str) -> Result<Script, BdkError> {
Address::from_str(address)
.map(|x| x.script_pubkey())
.map_err(|e| BdkError::Generic(e.to_string()))
}
#[derive(Clone)]
enum RbfValue {
Default,
Value(u32),
}
struct TxBuilder {
recipients: Vec<(String, u64)>,
fee_rate: Option<f32>,
drain_wallet: bool,
drain_to: Option<String>,
rbf: Option<RbfValue>,
}
impl TxBuilder {
fn new() -> Self {
TxBuilder {
recipients: Vec::new(),
fee_rate: None,
drain_wallet: false,
drain_to: None,
rbf: None,
}
}
fn add_recipient(&self, recipient: String, amount: u64) -> Arc<Self> {
let mut recipients = self.recipients.to_vec();
recipients.append(&mut vec![(recipient, amount)]);
Arc::new(TxBuilder {
recipients,
fee_rate: self.fee_rate,
drain_wallet: self.drain_wallet,
drain_to: self.drain_to.clone(),
rbf: self.rbf.clone(),
})
}
fn fee_rate(&self, sat_per_vb: f32) -> Arc<Self> {
Arc::new(TxBuilder {
recipients: self.recipients.to_vec(),
fee_rate: Some(sat_per_vb),
drain_wallet: self.drain_wallet,
drain_to: self.drain_to.clone(),
rbf: self.rbf.clone(),
})
}
fn drain_wallet(&self) -> Arc<Self> {
Arc::new(TxBuilder {
recipients: self.recipients.to_vec(),
fee_rate: self.fee_rate,
drain_wallet: true,
drain_to: self.drain_to.clone(),
rbf: self.rbf.clone(),
})
}
fn drain_to(&self, address: String) -> Arc<Self> {
Arc::new(TxBuilder {
recipients: self.recipients.to_vec(),
fee_rate: self.fee_rate,
drain_wallet: self.drain_wallet,
drain_to: Some(address),
rbf: self.rbf.clone(),
})
}
fn enable_rbf(&self) -> Arc<Self> {
Arc::new(TxBuilder {
recipients: self.recipients.to_vec(),
fee_rate: self.fee_rate,
drain_wallet: self.drain_wallet,
drain_to: self.drain_to.clone(),
rbf: Some(RbfValue::Default),
})
}
fn enable_rbf_with_sequence(&self, nsequence: u32) -> Arc<Self> {
Arc::new(TxBuilder {
recipients: self.recipients.to_vec(),
fee_rate: self.fee_rate,
drain_wallet: self.drain_wallet,
drain_to: self.drain_to.clone(),
rbf: Some(RbfValue::Value(nsequence)),
})
}
fn finish(&self, wallet: &Wallet) -> Result<Arc<PartiallySignedBitcoinTransaction>, Error> {
let wallet = wallet.get_wallet();
let mut tx_builder = wallet.build_tx();
for (address, amount) in &self.recipients {
tx_builder.add_recipient(to_script_pubkey(address)?, *amount);
}
if let Some(sat_per_vb) = self.fee_rate {
tx_builder.fee_rate(FeeRate::from_sat_per_vb(sat_per_vb));
}
if self.drain_wallet {
tx_builder.drain_wallet();
}
if let Some(address) = &self.drain_to {
tx_builder.drain_to(to_script_pubkey(address)?);
}
if let Some(rbf) = &self.rbf {
match *rbf {
RbfValue::Default => {
tx_builder.enable_rbf();
}
RbfValue::Value(nsequence) => {
tx_builder.enable_rbf_with_sequence(nsequence);
}
}
}
tx_builder
.finish()
.map(|(psbt, _)| PartiallySignedBitcoinTransaction {
internal: Mutex::new(psbt),
})
.map(Arc::new)
}
}
struct BumpFeeTxBuilder {
txid: String,
fee_rate: f32,
allow_shrinking: Option<String>,
rbf: Option<RbfValue>,
}
impl BumpFeeTxBuilder {
fn new(txid: String, fee_rate: f32) -> Self {
Self {
txid,
fee_rate,
allow_shrinking: None,
rbf: None,
}
}
fn allow_shrinking(&self, address: String) -> Arc<Self> {
Arc::new(Self {
txid: self.txid.clone(),
fee_rate: self.fee_rate,
allow_shrinking: Some(address),
rbf: self.rbf.clone(),
})
}
fn enable_rbf(&self) -> Arc<Self> {
Arc::new(Self {
txid: self.txid.clone(),
fee_rate: self.fee_rate,
allow_shrinking: self.allow_shrinking.clone(),
rbf: Some(RbfValue::Default),
})
}
fn enable_rbf_with_sequence(&self, nsequence: u32) -> Arc<Self> {
Arc::new(Self {
txid: self.txid.clone(),
fee_rate: self.fee_rate,
allow_shrinking: self.allow_shrinking.clone(),
rbf: Some(RbfValue::Value(nsequence)),
})
}
fn finish(&self, wallet: &Wallet) -> Result<Arc<PartiallySignedBitcoinTransaction>, Error> {
let wallet = wallet.get_wallet();
let txid = Txid::from_str(self.txid.as_str())?;
let mut tx_builder = wallet.build_fee_bump(txid)?;
tx_builder.fee_rate(FeeRate::from_sat_per_vb(self.fee_rate));
if let Some(allow_shrinking) = &self.allow_shrinking {
let address =
Address::from_str(allow_shrinking).map_err(|e| Error::Generic(e.to_string()))?;
let script = address.script_pubkey();
tx_builder.allow_shrinking(script)?;
}
if let Some(rbf) = &self.rbf {
match *rbf {
RbfValue::Default => {
tx_builder.enable_rbf();
}
RbfValue::Value(nsequence) => {
tx_builder.enable_rbf_with_sequence(nsequence);
}
}
}
tx_builder
.finish()
.map(|(psbt, _)| PartiallySignedBitcoinTransaction {
internal: Mutex::new(psbt),
})
.map(Arc::new)
}
}
uniffi::deps::static_assertions::assert_impl_all!(Wallet: Sync, Send);