Compare commits
24 Commits
release/0.
...
v0.5.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8a556d0ba0 | ||
|
|
d7c5f24fe8 | ||
|
|
f1431c3073 | ||
|
|
e797efea57 | ||
|
|
a41d628b14 | ||
|
|
b207464fe6 | ||
|
|
fca5d1602b | ||
|
|
f4e097c4ac | ||
|
|
c66dfdd52a | ||
|
|
ce848725b4 | ||
|
|
5512b31969 | ||
|
|
a48f9b4387 | ||
|
|
87a0a15ea7 | ||
|
|
ee91ad5b31 | ||
|
|
ba68103be1 | ||
|
|
bc43d2eb1a | ||
|
|
adc3f68e31 | ||
|
|
dd5622f724 | ||
|
|
e5aa51c3f8 | ||
|
|
a39fc787d5 | ||
|
|
51603e06d9 | ||
|
|
a1b89adf84 | ||
|
|
b1d483463f | ||
|
|
851f61296a |
10
CHANGELOG.md
10
CHANGELOG.md
@@ -6,7 +6,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
## [v0.5.0]
|
||||||
|
|
||||||
- Fix Wallet.broadcast function, now returns a tx id as a hex string
|
- 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]
|
## [v0.4.0]
|
||||||
|
|
||||||
@@ -30,7 +37,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
## [v0.2.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.5.0...HEAD
|
||||||
|
[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.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.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
|
[v0.3.0]: https://github.com/bitcoindevkit/bdk-ffi/compare/v0.2.0...v0.3.0
|
||||||
|
|||||||
16
Cargo.toml
16
Cargo.toml
@@ -1,9 +1,13 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "bdk-ffi"
|
name = "bdk-ffi"
|
||||||
version = "0.4.1"
|
version = "0.5.0"
|
||||||
authors = ["Steve Myers <steve@notmandatory.org>", "Sudarsan Balaji <sudarsan.balaji@artfuldev.com>"]
|
authors = ["Steve Myers <steve@notmandatory.org>", "Sudarsan Balaji <sudarsan.balaji@artfuldev.com>"]
|
||||||
edition = "2018"
|
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
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
[lib]
|
[lib]
|
||||||
crate-type = ["staticlib", "cdylib"]
|
crate-type = ["staticlib", "cdylib"]
|
||||||
@@ -17,16 +21,6 @@ rusqlite = { version = "0.25.3", features = ["bundled"] }
|
|||||||
|
|
||||||
uniffi_macros = { version = "0.16.0", features = ["builtin-bindgen"] }
|
uniffi_macros = { version = "0.16.0", features = ["builtin-bindgen"] }
|
||||||
uniffi = { 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]
|
[build-dependencies]
|
||||||
uniffi_build = { version = "0.16.0", features = ["builtin-bindgen"] }
|
uniffi_build = { version = "0.16.0", features = ["builtin-bindgen"] }
|
||||||
|
|
||||||
[features]
|
|
||||||
generate-python = ["uniffi_bindgen"]
|
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = "generate"
|
|
||||||
|
|||||||
23
README.md
23
README.md
@@ -1,9 +1,10 @@
|
|||||||
# Native language bindings for BDK
|
# Native language bindings for BDK
|
||||||
|
|
||||||
This repository contains source code for generating native language bindings for the rust based
|
The workspace in this repository creates the `libbdkffi` multi-language library for the rust based
|
||||||
[bdk] library which is the central artifact of the [Bitcoin Dev Kit] project.
|
[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
|
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.
|
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] |
|
| Swift | iOS, macOS | [bdk-swift] |
|
||||||
| Python | linux, macOS | [bdk-python] |
|
| 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
|
[bdk]: https://github.com/bitcoindevkit/bdk
|
||||||
[Bitcoin Dev Kit]: https://github.com/bitcoindevkit
|
[Bitcoin Dev Kit]: https://github.com/bitcoindevkit
|
||||||
[git submodule]: https://git-scm.com/book/en/v2/Git-Tools-Submodules
|
[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
|
## 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
|
### Adding new structs and functions
|
||||||
|
|
||||||
See the [UniFFI User Guide](https://mozilla.github.io/uniffi-rs/)
|
See the [UniFFI User Guide](https://mozilla.github.io/uniffi-rs/)
|
||||||
|
|||||||
11
bdk-ffi-bindgen/Cargo.toml
Normal file
11
bdk-ffi-bindgen/Cargo.toml
Normal 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"
|
||||||
136
bdk-ffi-bindgen/src/main.rs
Normal file
136
bdk-ffi-bindgen/src/main.rs
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
use std::fmt;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use std::str::FromStr;
|
||||||
|
use structopt::StructOpt;
|
||||||
|
use uniffi_bindgen;
|
||||||
|
|
||||||
|
#[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: &PathBuf,
|
||||||
|
lib_name: &PathBuf,
|
||||||
|
) -> 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).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.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(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(())
|
||||||
|
}
|
||||||
12
src/bdk.udl
12
src/bdk.udl
@@ -136,12 +136,20 @@ interface Wallet {
|
|||||||
|
|
||||||
interface PartiallySignedBitcoinTransaction {
|
interface PartiallySignedBitcoinTransaction {
|
||||||
[Throws=BdkError]
|
[Throws=BdkError]
|
||||||
constructor([ByRef] Wallet wallet, string recipient, u64 amount, float? fee_rate);
|
|
||||||
[Name=deserialize,Throws=BdkError]
|
|
||||||
constructor(string psbt_base64);
|
constructor(string psbt_base64);
|
||||||
string serialize();
|
string serialize();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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);
|
||||||
|
[Throws=BdkError]
|
||||||
|
PartiallySignedBitcoinTransaction build([ByRef] Wallet wallet);
|
||||||
|
};
|
||||||
|
|
||||||
dictionary ExtendedKeyInfo {
|
dictionary ExtendedKeyInfo {
|
||||||
string mnemonic;
|
string mnemonic;
|
||||||
string xprv;
|
string xprv;
|
||||||
|
|||||||
@@ -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(())
|
|
||||||
}
|
|
||||||
215
src/lib.rs
215
src/lib.rs
@@ -1,7 +1,7 @@
|
|||||||
use bdk::bitcoin::hashes::hex::ToHex;
|
use bdk::bitcoin::hashes::hex::ToHex;
|
||||||
use bdk::bitcoin::secp256k1::Secp256k1;
|
use bdk::bitcoin::secp256k1::Secp256k1;
|
||||||
use bdk::bitcoin::util::psbt::PartiallySignedTransaction;
|
use bdk::bitcoin::util::psbt::PartiallySignedTransaction;
|
||||||
use bdk::bitcoin::{Address, Network};
|
use bdk::bitcoin::{Address, Network, Script};
|
||||||
use bdk::blockchain::any::{AnyBlockchain, AnyBlockchainConfig};
|
use bdk::blockchain::any::{AnyBlockchain, AnyBlockchainConfig};
|
||||||
use bdk::blockchain::Progress;
|
use bdk::blockchain::Progress;
|
||||||
use bdk::blockchain::{
|
use bdk::blockchain::{
|
||||||
@@ -16,7 +16,7 @@ use bdk::wallet::AddressIndex;
|
|||||||
use bdk::{BlockTime, Error, FeeRate, SignOptions, Wallet as BdkWallet};
|
use bdk::{BlockTime, Error, FeeRate, SignOptions, Wallet as BdkWallet};
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::sync::{Mutex, MutexGuard};
|
use std::sync::{Arc, Mutex, MutexGuard};
|
||||||
|
|
||||||
uniffi_macros::include_scaffolding!("bdk");
|
uniffi_macros::include_scaffolding!("bdk");
|
||||||
|
|
||||||
@@ -49,10 +49,6 @@ pub enum BlockchainConfig {
|
|||||||
Esplora { config: EsploraConfig },
|
Esplora { config: EsploraConfig },
|
||||||
}
|
}
|
||||||
|
|
||||||
trait WalletHolder<B> {
|
|
||||||
fn get_wallet(&self) -> MutexGuard<BdkWallet<B, AnyDatabase>>;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Default)]
|
#[derive(Debug, Clone, PartialEq, Eq, Default)]
|
||||||
pub struct TransactionDetails {
|
pub struct TransactionDetails {
|
||||||
pub fees: Option<u64>,
|
pub fees: Option<u64>,
|
||||||
@@ -97,45 +93,6 @@ impl From<&bdk::TransactionDetails> for Transaction {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
struct Wallet {
|
||||||
wallet_mutex: Mutex<BdkWallet<AnyBlockchain, AnyDatabase>>,
|
wallet_mutex: Mutex<BdkWallet<AnyBlockchain, AnyDatabase>>,
|
||||||
}
|
}
|
||||||
@@ -155,59 +112,25 @@ impl Progress for BdkProgressHolder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
struct PartiallySignedBitcoinTransaction {
|
struct PartiallySignedBitcoinTransaction {
|
||||||
internal: Mutex<PartiallySignedTransaction>,
|
internal: Mutex<PartiallySignedTransaction>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PartiallySignedBitcoinTransaction {
|
impl PartiallySignedBitcoinTransaction {
|
||||||
fn new(
|
fn new(psbt_base64: String) -> Result<Self, Error> {
|
||||||
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)?;
|
let psbt: PartiallySignedTransaction = PartiallySignedTransaction::from_str(&psbt_base64)?;
|
||||||
Ok(PartiallySignedBitcoinTransaction {
|
Ok(PartiallySignedBitcoinTransaction {
|
||||||
internal: Mutex::new(psbt),
|
internal: Mutex::new(psbt),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn serialize(&self) -> String {
|
fn serialize(&self) -> String {
|
||||||
let psbt = self.internal.lock().unwrap().clone();
|
let psbt = self.internal.lock().unwrap().clone();
|
||||||
psbt.to_string()
|
psbt.to_string()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WalletHolder<AnyBlockchain> for Wallet {
|
|
||||||
fn get_wallet(&self) -> MutexGuard<BdkWallet<AnyBlockchain, AnyDatabase>> {
|
|
||||||
self.wallet_mutex.lock().expect("wallet")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl WalletOperations<AnyBlockchain> for Wallet {}
|
|
||||||
|
|
||||||
impl Wallet {
|
impl Wallet {
|
||||||
fn new(
|
fn new(
|
||||||
descriptor: String,
|
descriptor: String,
|
||||||
@@ -245,7 +168,7 @@ impl Wallet {
|
|||||||
let blockchain = AnyBlockchain::from_config(&any_blockchain_config)?;
|
let blockchain = AnyBlockchain::from_config(&any_blockchain_config)?;
|
||||||
let wallet_mutex = Mutex::new(BdkWallet::new(
|
let wallet_mutex = Mutex::new(BdkWallet::new(
|
||||||
&descriptor,
|
&descriptor,
|
||||||
change_descriptor.to_owned().as_ref(),
|
change_descriptor.as_ref(),
|
||||||
network,
|
network,
|
||||||
database,
|
database,
|
||||||
blockchain,
|
blockchain,
|
||||||
@@ -253,6 +176,10 @@ impl Wallet {
|
|||||||
Ok(Wallet { wallet_mutex })
|
Ok(Wallet { wallet_mutex })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_wallet(&self) -> MutexGuard<BdkWallet<AnyBlockchain, AnyDatabase>> {
|
||||||
|
self.wallet_mutex.lock().expect("wallet")
|
||||||
|
}
|
||||||
|
|
||||||
fn get_network(&self) -> Network {
|
fn get_network(&self) -> Network {
|
||||||
self.get_wallet().network()
|
self.get_wallet().network()
|
||||||
}
|
}
|
||||||
@@ -266,6 +193,43 @@ impl Wallet {
|
|||||||
.sync(BdkProgressHolder { progress_update }, max_address_param)
|
.sync(BdkProgressHolder { progress_update }, max_address_param)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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())
|
||||||
|
}
|
||||||
|
|
||||||
fn broadcast(&self, psbt: &PartiallySignedBitcoinTransaction) -> Result<String, Error> {
|
fn broadcast(&self, psbt: &PartiallySignedBitcoinTransaction) -> Result<String, Error> {
|
||||||
let tx = psbt.internal.lock().unwrap().clone().extract_tx();
|
let tx = psbt.internal.lock().unwrap().clone().extract_tx();
|
||||||
let txid = self.get_wallet().broadcast(&tx)?;
|
let txid = self.get_wallet().broadcast(&tx)?;
|
||||||
@@ -313,4 +277,89 @@ 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()))
|
||||||
|
}
|
||||||
|
|
||||||
|
struct TxBuilder {
|
||||||
|
recipients: Vec<(String, u64)>,
|
||||||
|
fee_rate: Option<f32>,
|
||||||
|
drain_wallet: bool,
|
||||||
|
drain_to: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TxBuilder {
|
||||||
|
fn new() -> Self {
|
||||||
|
TxBuilder {
|
||||||
|
recipients: Vec::new(),
|
||||||
|
fee_rate: None,
|
||||||
|
drain_wallet: false,
|
||||||
|
drain_to: 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(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
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(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
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(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
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),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build(&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)?);
|
||||||
|
}
|
||||||
|
tx_builder
|
||||||
|
.finish()
|
||||||
|
.map(|(psbt, _)| PartiallySignedBitcoinTransaction {
|
||||||
|
internal: Mutex::new(psbt),
|
||||||
|
})
|
||||||
|
.map(Arc::new)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
uniffi::deps::static_assertions::assert_impl_all!(Wallet: Sync, Send);
|
uniffi::deps::static_assertions::assert_impl_all!(Wallet: Sync, Send);
|
||||||
|
|||||||
Reference in New Issue
Block a user