lwk_wasm 0.14.0

Liquid Wallet Kit - WASM
Documentation
use crate::{Error, Network, Pset, Transaction, Txid, Update, Wollet, WolletDescriptor};
use lwk_wollet::{age, clients::asyncr};
use wasm_bindgen::prelude::*;

/// Response from the last_used_index endpoint
///
/// Returns the highest derivation index that has been used (has transaction history)
/// for both external and internal chains. This is useful for quickly determining
/// the next unused address without downloading full transaction history.
#[wasm_bindgen]
pub struct LastUsedIndexResponse {
    inner: asyncr::LastUsedIndexResponse,
}

#[wasm_bindgen]
impl LastUsedIndexResponse {
    /// Last used index on the external (receive) chain, or undefined if no addresses have been used.
    #[wasm_bindgen(getter)]
    pub fn external(&self) -> Option<u32> {
        self.inner.external
    }

    /// Last used index on the internal (change) chain, or undefined if no addresses have been used.
    #[wasm_bindgen(getter)]
    pub fn internal(&self) -> Option<u32> {
        self.inner.internal
    }

    /// Current blockchain tip hash for reference.
    #[wasm_bindgen(getter)]
    pub fn tip(&self) -> Option<String> {
        self.inner.tip.map(|t| t.to_string())
    }
}

impl From<asyncr::LastUsedIndexResponse> for LastUsedIndexResponse {
    fn from(inner: asyncr::LastUsedIndexResponse) -> Self {
        Self { inner }
    }
}

/// 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.
#[wasm_bindgen]
pub struct EsploraClient {
    inner: asyncr::EsploraClient,
    /// The builder used to create the client, stored for recreation
    builder: asyncr::EsploraClientBuilder,
}

impl AsRef<asyncr::EsploraClient> for EsploraClient {
    fn as_ref(&self) -> &asyncr::EsploraClient {
        &self.inner
    }
}

impl EsploraClient {
    /// Create a new async client with the same connection parameters
    pub(crate) fn clone_async_client(&self) -> Result<asyncr::EsploraClient, crate::Error> {
        self.builder
            .clone()
            .build()
            .map_err(|e| Error::Generic(e.to_string()))
    }
}

#[wasm_bindgen]
impl EsploraClient {
    /// Creates an Esplora client with the given options
    #[wasm_bindgen(constructor)]
    pub fn new(
        network: &Network,
        url: &str,
        waterfalls: bool,
        concurrency: usize,
        utxo_only: bool,
    ) -> Result<Self, Error> {
        let builder = asyncr::EsploraClientBuilder::new(url, network.into())
            .waterfalls(waterfalls)
            .concurrency(concurrency)
            .utxo_only(utxo_only);
        let inner = builder
            .clone()
            .build()
            .map_err(|e| Error::Generic(e.to_string()))?;
        Ok(Self { inner, builder })
    }

    /// 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.
    #[wasm_bindgen(js_name = fullScan)]
    pub async fn full_scan(&mut self, wollet: &Wollet) -> Result<Option<Update>, Error> {
        let update: Option<lwk_wollet::Update> = self.inner.full_scan(wollet.as_ref()).await?;
        Ok(update.map(Into::into))
    }

    /// 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.
    ///
    /// See `full_scan_to_index()` for a blocking version of this method.
    #[wasm_bindgen(js_name = fullScanToIndex)]
    pub async fn full_scan_to_index(
        &mut self,
        wollet: &Wollet,
        index: u32,
    ) -> Result<Option<Update>, Error> {
        let update: Option<lwk_wollet::Update> = self
            .inner
            .full_scan_to_index(wollet.as_ref(), index)
            .await?;
        Ok(update.map(Into::into))
    }

    /// Broadcast a transaction to the network so that a miner can include it in a block.
    #[wasm_bindgen(js_name = broadcastTx)]
    pub async fn broadcast_tx(&mut self, tx: &Transaction) -> Result<Txid, Error> {
        let txid = self.inner.broadcast(tx.as_ref()).await?;
        Ok(txid.into())
    }

    /// Broadcast a PSET by extracting the transaction from the PSET and broadcasting it.
    pub async fn broadcast(&mut self, pset: &Pset) -> Result<Txid, Error> {
        let tx = pset.extract_tx()?;
        self.broadcast_tx(&tx).await
    }

    /// Set the waterfalls server recipient key. This is used to encrypt the descriptor when calling the waterfalls endpoint.
    #[wasm_bindgen(js_name = setWaterfallsServerRecipient)]
    pub async fn set_waterfalls_server_recipient(&mut self, recipient: &str) -> Result<(), Error> {
        let recipient: age::x25519::Recipient = recipient
            .parse()
            .map_err(|e: &str| Error::Generic(e.to_string()))?;
        self.inner.set_waterfalls_server_recipient(recipient);
        Ok(())
    }

    /// Query the last used derivation index for a wallet's descriptor from the waterfalls server.
    ///
    /// This method queries the waterfalls `/v1/last_used_index` endpoint to get the last used
    /// derivation index for both external and internal chains of the wallet's descriptor.
    ///
    /// Returns `LastUsedIndexResponse` containing the last used indexes and the tip block hash.
    ///
    /// # Errors
    ///
    /// Returns an error if this client was not configured with waterfalls support,
    /// if the descriptor does not contain a wildcard,
    /// or if the descriptor uses ELIP151 blinding.
    #[wasm_bindgen(js_name = lastUsedIndex)]
    pub async fn last_used_index(
        &mut self,
        descriptor: &WolletDescriptor,
    ) -> Result<LastUsedIndexResponse, Error> {
        let result = self.inner.last_used_index(descriptor.as_ref()).await?;
        Ok(result.into())
    }
}

#[cfg(all(test, target_arch = "wasm32"))]
mod tests {

    use wasm_bindgen_test::*;

    wasm_bindgen_test_configure!(run_in_browser);

    #[wasm_bindgen_test]
    async fn test_sleep() {
        lwk_wollet::clients::asyncr::async_sleep(1).await;
    }
}