Compare commits
10 Commits
v0.2.0
...
release/0.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
89d58db02a | ||
|
|
cda682b634 | ||
|
|
d17ea4b90c | ||
|
|
76fa9b9521 | ||
|
|
cafa8dacab | ||
|
|
c039281ffc | ||
|
|
1f0b053872 | ||
|
|
97f1011748 | ||
|
|
edfcde1cc6 | ||
|
|
15c0dac622 |
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
|
||||||
16
Cargo.toml
16
Cargo.toml
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "bdk-ffi"
|
name = "bdk-ffi"
|
||||||
version = "0.2.0"
|
version = "0.3.1"
|
||||||
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"
|
||||||
|
|
||||||
@@ -11,10 +11,18 @@ name = "bdkffi"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
bdk = { version = "0.14", features = ["all-keys", "use-esplora-ureq"] }
|
bdk = { version = "0.14", features = ["all-keys", "use-esplora-ureq"] }
|
||||||
uniffi_macros = "0.16.0"
|
uniffi_macros = { version = "0.16.0", features = ["builtin-bindgen"] }
|
||||||
uniffi = "0.16.0"
|
uniffi = { version = "0.16.0", features = ["builtin-bindgen"] }
|
||||||
thiserror = "1.0"
|
thiserror = "1.0"
|
||||||
anyhow = "=1.0.45" # remove after upgrading to next version of uniffi
|
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 = "0.16.0"
|
uniffi_build = { version = "0.16.0", features = ["builtin-bindgen"] }
|
||||||
|
|
||||||
|
[features]
|
||||||
|
generate-python = ["uniffi_bindgen"]
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "generate"
|
||||||
|
|||||||
@@ -131,6 +131,9 @@ interface Wallet {
|
|||||||
interface PartiallySignedBitcoinTransaction {
|
interface PartiallySignedBitcoinTransaction {
|
||||||
[Throws=BdkError]
|
[Throws=BdkError]
|
||||||
constructor([ByRef] Wallet wallet, string recipient, u64 amount, float? fee_rate);
|
constructor([ByRef] Wallet wallet, string recipient, u64 amount, float? fee_rate);
|
||||||
|
[Name=deserialize,Throws=BdkError]
|
||||||
|
constructor(string psbt_base64);
|
||||||
|
string serialize();
|
||||||
};
|
};
|
||||||
|
|
||||||
dictionary ExtendedKeyInfo {
|
dictionary ExtendedKeyInfo {
|
||||||
|
|||||||
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(())
|
||||||
|
}
|
||||||
35
src/lib.rs
35
src/lib.rs
@@ -12,7 +12,7 @@ use bdk::keys::bip39::{Language, Mnemonic, WordCount};
|
|||||||
use bdk::keys::{DerivableKey, ExtendedKey, GeneratableKey, GeneratedKey};
|
use bdk::keys::{DerivableKey, ExtendedKey, GeneratableKey, GeneratedKey};
|
||||||
use bdk::miniscript::BareCtx;
|
use bdk::miniscript::BareCtx;
|
||||||
use bdk::wallet::AddressIndex;
|
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::{Mutex, MutexGuard};
|
||||||
@@ -135,7 +135,7 @@ trait WalletOperations<B>: WalletHolder<B> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct Wallet {
|
struct Wallet {
|
||||||
_wallet: Mutex<BdkWallet<AnyBlockchain, AnyDatabase>>,
|
wallet_mutex: Mutex<BdkWallet<AnyBlockchain, AnyDatabase>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait BdkProgress: Send + Sync {
|
pub trait BdkProgress: Send + Sync {
|
||||||
@@ -155,7 +155,6 @@ impl Progress for BdkProgressHolder {
|
|||||||
|
|
||||||
struct PartiallySignedBitcoinTransaction {
|
struct PartiallySignedBitcoinTransaction {
|
||||||
internal: Mutex<PartiallySignedTransaction>,
|
internal: Mutex<PartiallySignedTransaction>,
|
||||||
details: bdk::TransactionDetails,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PartiallySignedBitcoinTransaction {
|
impl PartiallySignedBitcoinTransaction {
|
||||||
@@ -168,7 +167,7 @@ impl PartiallySignedBitcoinTransaction {
|
|||||||
let wallet = wallet.get_wallet();
|
let wallet = wallet.get_wallet();
|
||||||
match Address::from_str(&recipient) {
|
match Address::from_str(&recipient) {
|
||||||
Ok(address) => {
|
Ok(address) => {
|
||||||
let (psbt, details) = {
|
let (psbt, _details) = {
|
||||||
let mut builder = wallet.build_tx();
|
let mut builder = wallet.build_tx();
|
||||||
builder.add_recipient(address.script_pubkey(), amount);
|
builder.add_recipient(address.script_pubkey(), amount);
|
||||||
if let Some(sat_per_vb) = fee_rate {
|
if let Some(sat_per_vb) = fee_rate {
|
||||||
@@ -178,7 +177,6 @@ impl PartiallySignedBitcoinTransaction {
|
|||||||
};
|
};
|
||||||
Ok(PartiallySignedBitcoinTransaction {
|
Ok(PartiallySignedBitcoinTransaction {
|
||||||
internal: Mutex::new(psbt),
|
internal: Mutex::new(psbt),
|
||||||
details,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
Err(..) => Err(BdkError::Generic(
|
Err(..) => Err(BdkError::Generic(
|
||||||
@@ -186,11 +184,23 @@ impl PartiallySignedBitcoinTransaction {
|
|||||||
)),
|
)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn deserialize(psbt_base64: String) -> Result<Self, Error> {
|
||||||
|
let psbt: PartiallySignedTransaction = PartiallySignedTransaction::from_str(&psbt_base64)?;
|
||||||
|
Ok(PartiallySignedBitcoinTransaction {
|
||||||
|
internal: Mutex::new(psbt),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn serialize(&self) -> String {
|
||||||
|
let psbt = self.internal.lock().unwrap().clone();
|
||||||
|
psbt.to_string()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WalletHolder<AnyBlockchain> for Wallet {
|
impl WalletHolder<AnyBlockchain> for Wallet {
|
||||||
fn get_wallet(&self) -> MutexGuard<BdkWallet<AnyBlockchain, AnyDatabase>> {
|
fn get_wallet(&self) -> MutexGuard<BdkWallet<AnyBlockchain, AnyDatabase>> {
|
||||||
self._wallet.lock().unwrap()
|
self.wallet_mutex.lock().unwrap()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -230,14 +240,14 @@ impl Wallet {
|
|||||||
};
|
};
|
||||||
let database = AnyDatabase::from_config(&any_database_config)?;
|
let database = AnyDatabase::from_config(&any_database_config)?;
|
||||||
let blockchain = AnyBlockchain::from_config(&any_blockchain_config)?;
|
let blockchain = AnyBlockchain::from_config(&any_blockchain_config)?;
|
||||||
let _wallet = Mutex::new(BdkWallet::new(
|
let wallet_mutex = Mutex::new(BdkWallet::new(
|
||||||
&descriptor,
|
&descriptor,
|
||||||
change_descriptor.to_owned().as_ref(),
|
change_descriptor.to_owned().as_ref(),
|
||||||
network,
|
network,
|
||||||
database,
|
database,
|
||||||
blockchain,
|
blockchain,
|
||||||
)?);
|
)?);
|
||||||
Ok(Wallet { _wallet })
|
Ok(Wallet { wallet_mutex })
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_network(&self) -> Network {
|
fn get_network(&self) -> Network {
|
||||||
@@ -249,18 +259,15 @@ impl Wallet {
|
|||||||
progress_update: Box<dyn BdkProgress>,
|
progress_update: Box<dyn BdkProgress>,
|
||||||
max_address_param: Option<u32>,
|
max_address_param: Option<u32>,
|
||||||
) -> Result<(), BdkError> {
|
) -> Result<(), BdkError> {
|
||||||
progress_update.update(21.0, Some("message".to_string()));
|
|
||||||
self.get_wallet()
|
self.get_wallet()
|
||||||
.sync(BdkProgressHolder { progress_update }, max_address_param)
|
.sync(BdkProgressHolder { progress_update }, max_address_param)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn broadcast(
|
fn broadcast(&self, psbt: &PartiallySignedBitcoinTransaction) -> Result<Transaction, Error> {
|
||||||
&self,
|
|
||||||
psbt: &PartiallySignedBitcoinTransaction,
|
|
||||||
) -> Result<Transaction, Error> {
|
|
||||||
let tx = psbt.internal.lock().unwrap().clone().extract_tx();
|
let tx = psbt.internal.lock().unwrap().clone().extract_tx();
|
||||||
self.get_wallet().broadcast(&tx)?;
|
self.get_wallet().broadcast(&tx)?;
|
||||||
Ok(Transaction::from(&psbt.details))
|
let tx_details = self.get_wallet().get_tx(&tx.txid(), true)?;
|
||||||
|
Ok(Transaction::from(&tx_details.unwrap()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user