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);
}
}