lwk_bindings 0.14.0

Liquid Wallet Kit - Bindings for other languages
Documentation
use std::sync::{Arc, Mutex};

use lwk_wollet::{
    asyncr,
    clients::blocking::{self, BlockchainBackend},
};

use crate::{BlockHeader, LwkError, Network, Transaction, Txid, Update, Wollet};

/// A blockchain backend implementation based on the
/// [esplora HTTP API](https://github.com/blockstream/esplora/blob/master/API.md)
/// But can also use the [waterfalls](https://github.com/RCasatta/waterfalls) endpoint to
/// speed up the scan if supported by the server.
#[derive(uniffi::Object, Debug)]
pub struct EsploraClient {
    pub(crate) inner: Mutex<blocking::EsploraClient>,

    /// The builder used to create the client, used to create a new client with the same configuration.
    pub(crate) builder: lwk_wollet::clients::EsploraClientBuilder,
}

/// A builder for the `EsploraClient`
#[derive(uniffi::Record)]
pub struct EsploraClientBuilder {
    base_url: String,
    network: Arc<Network>,
    #[uniffi(default = false)]
    waterfalls: bool,
    #[uniffi(default = None)]
    concurrency: Option<u32>,
    #[uniffi(default = None)]
    timeout: Option<u8>,
    #[uniffi(default = false)]
    utxo_only: bool,
}

impl From<EsploraClientBuilder> for lwk_wollet::clients::EsploraClientBuilder {
    fn from(builder: EsploraClientBuilder) -> Self {
        let mut result = lwk_wollet::clients::EsploraClientBuilder::new(
            &builder.base_url,
            (*builder.network.as_ref()).into(),
        );
        if builder.waterfalls {
            result = result.waterfalls(true);
        }
        if let Some(concurrency) = builder.concurrency {
            result = result.concurrency(concurrency as usize);
        }
        if let Some(timeout) = builder.timeout {
            result = result.timeout(timeout);
        }
        if builder.utxo_only {
            result = result.utxo_only(true);
        }
        result
    }
}

#[uniffi::export]
impl EsploraClient {
    /// Construct an Esplora Client
    #[uniffi::constructor]
    pub fn new(url: &str, network: &Network) -> Result<Arc<Self>, LwkError> {
        let builder = lwk_wollet::clients::EsploraClientBuilder::new(url, network.into());
        let client = builder.clone().build_blocking()?;
        Ok(Arc::new(Self {
            inner: Mutex::new(client),
            builder,
        }))
    }

    /// Construct an Esplora Client using Waterfalls endpoint
    #[uniffi::constructor]
    pub fn new_waterfalls(url: &str, network: &Network) -> Result<Arc<Self>, LwkError> {
        let builder =
            lwk_wollet::clients::EsploraClientBuilder::new(url, network.into()).waterfalls(true);
        let client = builder.clone().build_blocking()?;
        Ok(Arc::new(Self {
            inner: Mutex::new(client),
            builder,
        }))
    }

    /// Construct an Esplora Client from an `EsploraClientBuilder`
    #[uniffi::constructor]
    pub fn from_builder(builder: EsploraClientBuilder) -> Result<Arc<Self>, LwkError> {
        let builder = lwk_wollet::clients::EsploraClientBuilder::from(builder);
        let client = builder.clone().build_blocking()?;
        Ok(Arc::new(Self {
            inner: Mutex::new(client),
            builder,
        }))
    }

    /// Broadcast a transaction to the network so that a miner can include it in a block.
    pub fn broadcast(&self, tx: &Transaction) -> Result<Arc<Txid>, LwkError> {
        Ok(Arc::new(self.inner.lock()?.broadcast(tx.as_ref())?.into()))
    }

    /// Scan the blockchain for the scripts generated by a watch-only wallet
    ///
    /// This method scans both external and internal address chains, stopping after finding
    /// 20 consecutive unused addresses (the gap limit) as recommended by
    /// [BIP44](https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki#address-gap-limit).
    ///
    /// Returns `Some(Update)` if any changes were found during scanning, or `None` if no changes
    /// were detected.
    ///
    /// To scan beyond the gap limit use `full_scan_to_index()` instead.
    pub fn full_scan(&self, wollet: &Wollet) -> Result<Option<Arc<Update>>, LwkError> {
        self.full_scan_to_index(wollet, 0)
    }

    /// Scan the blockchain for the scripts generated by a watch-only wallet up to a specified derivation index
    ///
    /// While `full_scan()` stops after finding 20 consecutive unused addresses (the gap limit),
    /// this method will scan at least up to the given derivation index. This is useful to prevent
    /// missing funds in cases where outputs exist beyond the gap limit.
    ///
    /// Will scan both external and internal address chains up to the given index for maximum safety,
    /// even though internal addresses may not need such deep scanning.
    ///
    /// If transactions are found beyond the gap limit during this scan, subsequent calls to
    /// `full_scan()` will automatically scan up to the highest used index, preventing any
    /// previously-found transactions from being missed.
    pub fn full_scan_to_index(
        &self,
        wollet: &Wollet,
        index: u32,
    ) -> Result<Option<Arc<Update>>, LwkError> {
        let wollet = wollet.inner_wollet()?;
        let update: Option<lwk_wollet::Update> = self
            .inner
            .lock()?
            .full_scan_to_index(&wollet.state(), index)?;
        Ok(update.map(Into::into).map(Arc::new))
    }

    /// See [`BlockchainBackend::tip`]
    pub fn tip(&self) -> Result<Arc<BlockHeader>, LwkError> {
        let tip = self.inner.lock()?.tip()?;
        Ok(Arc::new(tip.into()))
    }
}

impl EsploraClient {
    /// Create a new esplora blocking client with the same connection parameters
    #[allow(unused)] // TODO remove once lwk_boltz is integrated
    pub(crate) fn clone_blocking_client(&self) -> Result<blocking::EsploraClient, LwkError> {
        Ok(self.builder.clone().build_blocking()?)
    }

    /// Create a new esplora async client with the same connection parameters
    #[allow(unused)] // TODO remove once lwk_boltz is integrated
    pub(crate) fn clone_async_client(&self) -> Result<asyncr::EsploraClient, LwkError> {
        Ok(self.builder.clone().build()?)
    }
}