491 lines
16 KiB
Rust
491 lines
16 KiB
Rust
mod blockchain;
|
|
mod database;
|
|
mod descriptor;
|
|
mod keys;
|
|
mod psbt;
|
|
mod wallet;
|
|
|
|
use crate::blockchain::{
|
|
Auth, Blockchain, BlockchainConfig, ElectrumConfig, EsploraConfig, RpcConfig, RpcSyncParams,
|
|
};
|
|
use crate::database::DatabaseConfig;
|
|
use crate::descriptor::Descriptor;
|
|
use crate::keys::DerivationPath;
|
|
use crate::keys::{DescriptorPublicKey, DescriptorSecretKey, Mnemonic};
|
|
use crate::psbt::PartiallySignedTransaction;
|
|
use crate::wallet::SignOptions;
|
|
use crate::wallet::{BumpFeeTxBuilder, TxBuilder, Wallet};
|
|
use bdk::bitcoin::blockdata::script::Script as BdkScript;
|
|
use bdk::bitcoin::blockdata::transaction::TxIn as BdkTxIn;
|
|
use bdk::bitcoin::blockdata::transaction::TxOut as BdkTxOut;
|
|
use bdk::bitcoin::consensus::Decodable;
|
|
use bdk::bitcoin::psbt::serialize::Serialize;
|
|
use bdk::bitcoin::util::address::{Payload as BdkPayload, WitnessVersion};
|
|
use bdk::bitcoin::{
|
|
Address as BdkAddress, Network, OutPoint as BdkOutPoint, Transaction as BdkTransaction, Txid,
|
|
};
|
|
use bdk::blockchain::Progress as BdkProgress;
|
|
use bdk::database::any::{SledDbConfiguration, SqliteDbConfiguration};
|
|
use bdk::keys::bip39::WordCount;
|
|
use bdk::wallet::AddressIndex as BdkAddressIndex;
|
|
use bdk::wallet::AddressInfo as BdkAddressInfo;
|
|
use bdk::LocalUtxo as BdkLocalUtxo;
|
|
use bdk::TransactionDetails as BdkTransactionDetails;
|
|
use bdk::{Balance as BdkBalance, BlockTime, Error as BdkError, FeeRate, KeychainKind};
|
|
use std::convert::From;
|
|
use std::fmt;
|
|
use std::fmt::Debug;
|
|
use std::io::Cursor;
|
|
use std::str::FromStr;
|
|
use std::sync::Arc;
|
|
|
|
uniffi::include_scaffolding!("bdk");
|
|
|
|
/// A output script and an amount of satoshis.
|
|
pub struct ScriptAmount {
|
|
pub script: Arc<Script>,
|
|
pub amount: u64,
|
|
}
|
|
|
|
/// A derived address and the index it was found at.
|
|
pub struct AddressInfo {
|
|
/// Child index of this address.
|
|
pub index: u32,
|
|
/// Address.
|
|
pub address: Arc<Address>,
|
|
/// Type of keychain.
|
|
pub keychain: KeychainKind,
|
|
}
|
|
|
|
impl From<BdkAddressInfo> for AddressInfo {
|
|
fn from(address_info: BdkAddressInfo) -> Self {
|
|
AddressInfo {
|
|
index: address_info.index,
|
|
address: Arc::new(Address::from(address_info.address)),
|
|
keychain: address_info.keychain,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// The address index selection strategy to use to derived an address from the wallet's external
|
|
/// descriptor.
|
|
pub enum AddressIndex {
|
|
/// Return a new address after incrementing the current descriptor index.
|
|
New,
|
|
/// Return the address for the current descriptor index if it has not been used in a received
|
|
/// transaction. Otherwise return a new address as with AddressIndex::New.
|
|
/// Use with caution, if the wallet has not yet detected an address has been used it could
|
|
/// return an already used address. This function is primarily meant for situations where the
|
|
/// caller is untrusted; for example when deriving donation addresses on-demand for a public
|
|
/// web page.
|
|
LastUnused,
|
|
/// Return the address for a specific descriptor index. Does not change the current descriptor
|
|
/// index used by `AddressIndex::New` and `AddressIndex::LastUsed`.
|
|
/// Use with caution, if an index is given that is less than the current descriptor index
|
|
/// then the returned address may have already been used.
|
|
Peek { index: u32 },
|
|
/// Return the address for a specific descriptor index and reset the current descriptor index
|
|
/// used by `AddressIndex::New` and `AddressIndex::LastUsed` to this value.
|
|
/// Use with caution, if an index is given that is less than the current descriptor index
|
|
/// then the returned address and subsequent addresses returned by calls to `AddressIndex::New`
|
|
/// and `AddressIndex::LastUsed` may have already been used. Also if the index is reset to a
|
|
/// value earlier than the [`Blockchain`] stop_gap (default is 20) then a
|
|
/// larger stop_gap should be used to monitor for all possibly used addresses.
|
|
Reset { index: u32 },
|
|
}
|
|
|
|
impl From<AddressIndex> for BdkAddressIndex {
|
|
fn from(address_index: AddressIndex) -> Self {
|
|
match address_index {
|
|
AddressIndex::New => BdkAddressIndex::New,
|
|
AddressIndex::LastUnused => BdkAddressIndex::LastUnused,
|
|
AddressIndex::Peek { index } => BdkAddressIndex::Peek(index),
|
|
AddressIndex::Reset { index } => BdkAddressIndex::Reset(index),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// A wallet transaction
|
|
#[derive(Debug, Clone, PartialEq, Eq, Default)]
|
|
pub struct TransactionDetails {
|
|
pub transaction: Option<Arc<Transaction>>,
|
|
/// Transaction id.
|
|
pub txid: String,
|
|
/// Received value (sats)
|
|
/// Sum of owned outputs of this transaction.
|
|
pub received: u64,
|
|
/// Sent value (sats)
|
|
/// Sum of owned inputs of this transaction.
|
|
pub sent: u64,
|
|
/// Fee value (sats) if confirmed.
|
|
/// The availability of the fee depends on the backend. It's never None with an Electrum
|
|
/// Server backend, but it could be None with a Bitcoin RPC node without txindex that receive
|
|
/// funds while offline.
|
|
pub fee: Option<u64>,
|
|
/// If the transaction is confirmed, contains height and timestamp of the block containing the
|
|
/// transaction, unconfirmed transaction contains `None`.
|
|
pub confirmation_time: Option<BlockTime>,
|
|
}
|
|
|
|
impl From<BdkTransactionDetails> for TransactionDetails {
|
|
fn from(tx_details: BdkTransactionDetails) -> Self {
|
|
let optional_tx: Option<Arc<Transaction>> =
|
|
tx_details.transaction.map(|tx| Arc::new(tx.into()));
|
|
|
|
TransactionDetails {
|
|
transaction: optional_tx,
|
|
fee: tx_details.fee,
|
|
txid: tx_details.txid.to_string(),
|
|
received: tx_details.received,
|
|
sent: tx_details.sent,
|
|
confirmation_time: tx_details.confirmation_time,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// A reference to a transaction output.
|
|
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
|
pub struct OutPoint {
|
|
/// The referenced transaction's txid.
|
|
txid: String,
|
|
/// The index of the referenced output in its transaction's vout.
|
|
vout: u32,
|
|
}
|
|
|
|
impl From<&OutPoint> for BdkOutPoint {
|
|
fn from(outpoint: &OutPoint) -> Self {
|
|
BdkOutPoint {
|
|
txid: Txid::from_str(&outpoint.txid).unwrap(),
|
|
vout: outpoint.vout,
|
|
}
|
|
}
|
|
}
|
|
|
|
pub struct Balance {
|
|
// All coinbase outputs not yet matured
|
|
pub immature: u64,
|
|
/// Unconfirmed UTXOs generated by a wallet tx
|
|
pub trusted_pending: u64,
|
|
/// Unconfirmed UTXOs received from an external wallet
|
|
pub untrusted_pending: u64,
|
|
/// Confirmed and immediately spendable balance
|
|
pub confirmed: u64,
|
|
/// Get sum of trusted_pending and confirmed coins
|
|
pub spendable: u64,
|
|
/// Get the whole balance visible to the wallet
|
|
pub total: u64,
|
|
}
|
|
|
|
impl From<BdkBalance> for Balance {
|
|
fn from(bdk_balance: BdkBalance) -> Self {
|
|
Balance {
|
|
immature: bdk_balance.immature,
|
|
trusted_pending: bdk_balance.trusted_pending,
|
|
untrusted_pending: bdk_balance.untrusted_pending,
|
|
confirmed: bdk_balance.confirmed,
|
|
spendable: bdk_balance.get_spendable(),
|
|
total: bdk_balance.get_total(),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// A transaction output, which defines new coins to be created from old ones.
|
|
#[derive(Debug, Clone)]
|
|
pub struct TxOut {
|
|
/// The value of the output, in satoshis.
|
|
value: u64,
|
|
/// The address of the output.
|
|
script_pubkey: Arc<Script>,
|
|
}
|
|
|
|
impl From<&BdkTxOut> for TxOut {
|
|
fn from(tx_out: &BdkTxOut) -> Self {
|
|
TxOut {
|
|
value: tx_out.value,
|
|
script_pubkey: Arc::new(Script {
|
|
script: tx_out.script_pubkey.clone(),
|
|
}),
|
|
}
|
|
}
|
|
}
|
|
|
|
pub struct LocalUtxo {
|
|
outpoint: OutPoint,
|
|
txout: TxOut,
|
|
keychain: KeychainKind,
|
|
is_spent: bool,
|
|
}
|
|
|
|
impl From<BdkLocalUtxo> for LocalUtxo {
|
|
fn from(local_utxo: BdkLocalUtxo) -> Self {
|
|
LocalUtxo {
|
|
outpoint: OutPoint {
|
|
txid: local_utxo.outpoint.txid.to_string(),
|
|
vout: local_utxo.outpoint.vout,
|
|
},
|
|
txout: TxOut {
|
|
value: local_utxo.txout.value,
|
|
script_pubkey: Arc::new(Script {
|
|
script: local_utxo.txout.script_pubkey,
|
|
}),
|
|
},
|
|
keychain: local_utxo.keychain,
|
|
is_spent: local_utxo.is_spent,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Trait that logs at level INFO every update received (if any).
|
|
pub trait Progress: Send + Sync + 'static {
|
|
/// Send a new progress update. The progress value should be in the range 0.0 - 100.0, and the message value is an
|
|
/// optional text message that can be displayed to the user.
|
|
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<(), BdkError> {
|
|
self.progress.update(progress, message);
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl Debug for ProgressHolder {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
f.debug_struct("ProgressHolder").finish_non_exhaustive()
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct TxIn {
|
|
pub previous_output: OutPoint,
|
|
pub script_sig: Arc<Script>,
|
|
pub sequence: u32,
|
|
pub witness: Vec<Vec<u8>>,
|
|
}
|
|
|
|
impl From<&BdkTxIn> for TxIn {
|
|
fn from(tx_in: &BdkTxIn) -> Self {
|
|
TxIn {
|
|
previous_output: OutPoint {
|
|
txid: tx_in.previous_output.txid.to_string(),
|
|
vout: tx_in.previous_output.vout,
|
|
},
|
|
script_sig: Arc::new(Script {
|
|
script: tx_in.script_sig.clone(),
|
|
}),
|
|
sequence: tx_in.sequence.0,
|
|
witness: tx_in.witness.to_vec(),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// A Bitcoin transaction.
|
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
pub struct Transaction {
|
|
internal: BdkTransaction,
|
|
}
|
|
|
|
impl Transaction {
|
|
fn new(transaction_bytes: Vec<u8>) -> Result<Self, BdkError> {
|
|
let mut decoder = Cursor::new(transaction_bytes);
|
|
let tx: BdkTransaction = BdkTransaction::consensus_decode(&mut decoder)?;
|
|
Ok(Transaction { internal: tx })
|
|
}
|
|
|
|
fn txid(&self) -> String {
|
|
self.internal.txid().to_string()
|
|
}
|
|
|
|
fn weight(&self) -> u64 {
|
|
self.internal.weight() as u64
|
|
}
|
|
|
|
fn size(&self) -> u64 {
|
|
self.internal.size() as u64
|
|
}
|
|
|
|
fn vsize(&self) -> u64 {
|
|
self.internal.vsize() as u64
|
|
}
|
|
|
|
fn serialize(&self) -> Vec<u8> {
|
|
self.internal.serialize()
|
|
}
|
|
|
|
fn is_coin_base(&self) -> bool {
|
|
self.internal.is_coin_base()
|
|
}
|
|
|
|
fn is_explicitly_rbf(&self) -> bool {
|
|
self.internal.is_explicitly_rbf()
|
|
}
|
|
|
|
fn is_lock_time_enabled(&self) -> bool {
|
|
self.internal.is_lock_time_enabled()
|
|
}
|
|
|
|
fn version(&self) -> i32 {
|
|
self.internal.version
|
|
}
|
|
|
|
fn lock_time(&self) -> u32 {
|
|
self.internal.lock_time.0
|
|
}
|
|
|
|
fn input(&self) -> Vec<TxIn> {
|
|
self.internal.input.iter().map(|x| x.into()).collect()
|
|
}
|
|
|
|
fn output(&self) -> Vec<TxOut> {
|
|
self.internal.output.iter().map(|x| x.into()).collect()
|
|
}
|
|
}
|
|
|
|
impl From<BdkTransaction> for Transaction {
|
|
fn from(tx: BdkTransaction) -> Self {
|
|
Transaction { internal: tx }
|
|
}
|
|
}
|
|
|
|
/// A Bitcoin address.
|
|
#[derive(Debug, PartialEq, Eq)]
|
|
pub struct Address {
|
|
address: BdkAddress,
|
|
}
|
|
|
|
impl Address {
|
|
fn new(address: String) -> Result<Self, BdkError> {
|
|
BdkAddress::from_str(address.as_str())
|
|
.map(|a| Address { address: a })
|
|
.map_err(|e| BdkError::Generic(e.to_string()))
|
|
}
|
|
|
|
fn payload(&self) -> Payload {
|
|
match &self.address.payload.clone() {
|
|
BdkPayload::PubkeyHash(pubkey_hash) => Payload::PubkeyHash {
|
|
pubkey_hash: pubkey_hash.to_vec(),
|
|
},
|
|
BdkPayload::ScriptHash(script_hash) => Payload::ScriptHash {
|
|
script_hash: script_hash.to_vec(),
|
|
},
|
|
BdkPayload::WitnessProgram { version, program } => Payload::WitnessProgram {
|
|
version: *version,
|
|
program: program.clone(),
|
|
},
|
|
}
|
|
}
|
|
|
|
fn network(&self) -> Network {
|
|
self.address.network
|
|
}
|
|
|
|
fn script_pubkey(&self) -> Arc<Script> {
|
|
Arc::new(Script {
|
|
script: self.address.script_pubkey(),
|
|
})
|
|
}
|
|
|
|
fn to_qr_uri(&self) -> String {
|
|
self.address.to_qr_uri()
|
|
}
|
|
|
|
fn as_string(&self) -> String {
|
|
self.address.to_string()
|
|
}
|
|
}
|
|
|
|
impl From<BdkAddress> for Address {
|
|
fn from(address: BdkAddress) -> Self {
|
|
Address { address }
|
|
}
|
|
}
|
|
|
|
/// The method used to produce an address.
|
|
#[derive(Debug)]
|
|
pub enum Payload {
|
|
/// P2PKH address.
|
|
PubkeyHash { pubkey_hash: Vec<u8> },
|
|
/// P2SH address.
|
|
ScriptHash { script_hash: Vec<u8> },
|
|
/// Segwit address.
|
|
WitnessProgram {
|
|
/// The witness program version.
|
|
version: WitnessVersion,
|
|
/// The witness program.
|
|
program: Vec<u8>,
|
|
},
|
|
}
|
|
|
|
/// A Bitcoin script.
|
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
pub struct Script {
|
|
script: BdkScript,
|
|
}
|
|
|
|
impl Script {
|
|
fn new(raw_output_script: Vec<u8>) -> Self {
|
|
let script: BdkScript = BdkScript::from(raw_output_script);
|
|
Script { script }
|
|
}
|
|
}
|
|
|
|
impl From<BdkScript> for Script {
|
|
fn from(bdk_script: BdkScript) -> Self {
|
|
Script { script: bdk_script }
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
enum RbfValue {
|
|
Default,
|
|
Value(u32),
|
|
}
|
|
|
|
/// The result after calling the TxBuilder finish() function. Contains unsigned PSBT and
|
|
/// transaction details.
|
|
pub struct TxBuilderResult {
|
|
pub(crate) psbt: Arc<PartiallySignedTransaction>,
|
|
pub transaction_details: TransactionDetails,
|
|
}
|
|
|
|
uniffi::deps::static_assertions::assert_impl_all!(Wallet: Sync, Send);
|
|
|
|
// The goal of these tests to to ensure `bdk-ffi` intermediate code correctly calls `bdk` APIs.
|
|
// These tests should not be used to verify `bdk` behavior that is already tested in the `bdk`
|
|
// crate.
|
|
#[cfg(test)]
|
|
mod test {
|
|
use super::Transaction;
|
|
use crate::Network::Regtest;
|
|
use crate::{Address, Payload};
|
|
use assert_matches::assert_matches;
|
|
use bdk::bitcoin::hashes::hex::FromHex;
|
|
use bdk::bitcoin::util::address::WitnessVersion;
|
|
|
|
// Verify that bdk-ffi Transaction can be created from valid bytes and serialized back into the same bytes.
|
|
#[test]
|
|
fn test_transaction_serde() {
|
|
let test_tx_bytes = Vec::from_hex("020000000001031cfbc8f54fbfa4a33a30068841371f80dbfe166211242213188428f437445c91000000006a47304402206fbcec8d2d2e740d824d3d36cc345b37d9f65d665a99f5bd5c9e8d42270a03a8022013959632492332200c2908459547bf8dbf97c65ab1a28dec377d6f1d41d3d63e012103d7279dfb90ce17fe139ba60a7c41ddf605b25e1c07a4ddcb9dfef4e7d6710f48feffffff476222484f5e35b3f0e43f65fc76e21d8be7818dd6a989c160b1e5039b7835fc00000000171600140914414d3c94af70ac7e25407b0689e0baa10c77feffffffa83d954a62568bbc99cc644c62eb7383d7c2a2563041a0aeb891a6a4055895570000000017160014795d04cc2d4f31480d9a3710993fbd80d04301dffeffffff06fef72f000000000017a91476fd7035cd26f1a32a5ab979e056713aac25796887a5000f00000000001976a914b8332d502a529571c6af4be66399cd33379071c588ac3fda0500000000001976a914fc1d692f8de10ae33295f090bea5fe49527d975c88ac522e1b00000000001976a914808406b54d1044c429ac54c0e189b0d8061667e088ac6eb68501000000001976a914dfab6085f3a8fb3e6710206a5a959313c5618f4d88acbba20000000000001976a914eb3026552d7e3f3073457d0bee5d4757de48160d88ac0002483045022100bee24b63212939d33d513e767bc79300051f7a0d433c3fcf1e0e3bf03b9eb1d70220588dc45a9ce3a939103b4459ce47500b64e23ab118dfc03c9caa7d6bfc32b9c601210354fd80328da0f9ae6eef2b3a81f74f9a6f66761fadf96f1d1d22b1fd6845876402483045022100e29c7e3a5efc10da6269e5fc20b6a1cb8beb92130cc52c67e46ef40aaa5cac5f0220644dd1b049727d991aece98a105563416e10a5ac4221abac7d16931842d5c322012103960b87412d6e169f30e12106bdf70122aabb9eb61f455518322a18b920a4dfa887d30700").unwrap();
|
|
let new_tx_from_bytes = Transaction::new(test_tx_bytes.clone()).unwrap();
|
|
let serialized_tx_to_bytes = new_tx_from_bytes.serialize();
|
|
assert_eq!(test_tx_bytes, serialized_tx_to_bytes);
|
|
}
|
|
|
|
// Verify that bdk-ffi Address.payload includes expected WitnessProgram variant, version and program bytes.
|
|
#[test]
|
|
fn test_address_witness_program() {
|
|
let address =
|
|
Address::new("bcrt1qqjn9gky9mkrm3c28e5e87t5akd3twg6xezp0tv".to_string()).unwrap();
|
|
let payload = address.payload();
|
|
assert_matches!(payload, Payload::WitnessProgram { version, program } => {
|
|
assert_eq!(version,WitnessVersion::V0);
|
|
assert_eq!(program, Vec::from_hex("04a6545885dd87b8e147cd327f2e9db362b72346").unwrap());
|
|
});
|
|
assert_eq!(address.network(), Regtest);
|
|
}
|
|
}
|