lwk/
electrum_client.rs

1use std::sync::{Arc, Mutex};
2
3use lwk_wollet::clients::blocking::BlockchainBackend;
4
5use crate::{BlockHeader, LwkError, Transaction, Txid, Update, Wollet};
6
7/// A client to issue TCP requests to an electrum server.
8#[derive(uniffi::Object, Debug)]
9pub struct ElectrumClient {
10    inner: Mutex<lwk_wollet::ElectrumClient>,
11}
12
13#[uniffi::export]
14impl ElectrumClient {
15    /// Construct an Electrum client
16    #[uniffi::constructor]
17    pub fn new(
18        electrum_url: &str,
19        tls: bool,
20        validate_domain: bool,
21    ) -> Result<Arc<Self>, LwkError> {
22        let url = lwk_wollet::ElectrumUrl::new(electrum_url, tls, validate_domain)
23            .map_err(lwk_wollet::Error::Url)?;
24        let client = lwk_wollet::ElectrumClient::new(&url)?;
25        Ok(Arc::new(Self {
26            inner: Mutex::new(client),
27        }))
28    }
29
30    /// Ping the Electrum server
31    pub fn ping(&self) -> Result<(), LwkError> {
32        Ok(self.inner.lock()?.ping()?)
33    }
34
35    /// Broadcast a transaction to the network so that a miner can include it in a block.
36    pub fn broadcast(&self, tx: &Transaction) -> Result<Arc<Txid>, LwkError> {
37        Ok(Arc::new(self.inner.lock()?.broadcast(tx.as_ref())?.into()))
38    }
39
40    /// Scan the blockchain for the scripts generated by a watch-only wallet
41    ///
42    /// This method scans both external and internal address chains, stopping after finding
43    /// 20 consecutive unused addresses (the gap limit) as recommended by
44    /// [BIP44](https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki#address-gap-limit).
45    ///
46    /// Returns `Some(Update)` if any changes were found during scanning, or `None` if no changes
47    /// were detected.
48    ///
49    /// To scan beyond the gap limit use `full_scan_to_index()` instead.
50    pub fn full_scan(&self, wollet: &Wollet) -> Result<Option<Arc<Update>>, LwkError> {
51        self.full_scan_to_index(wollet, 0)
52    }
53
54    /// Scan the blockchain for the scripts generated by a watch-only wallet up to a specified derivation index
55    ///
56    /// While `full_scan()` stops after finding 20 consecutive unused addresses (the gap limit),
57    /// this method will scan at least up to the given derivation index. This is useful to prevent
58    /// missing funds in cases where outputs exist beyond the gap limit.
59    ///
60    /// Will scan both external and internal address chains up to the given index for maximum safety,
61    /// even though internal addresses may not need such deep scanning.
62    ///
63    /// If transactions are found beyond the gap limit during this scan, subsequent calls to
64    /// `full_scan()` will automatically scan up to the highest used index, preventing any
65    /// previously-found transactions from being missed.
66    pub fn full_scan_to_index(
67        &self,
68        wollet: &Wollet,
69        index: u32,
70    ) -> Result<Option<Arc<Update>>, LwkError> {
71        let wollet = wollet.inner_wollet()?;
72        let update: Option<lwk_wollet::Update> = self
73            .inner
74            .lock()?
75            .full_scan_to_index(&wollet.state(), index)?;
76        Ok(update.map(Into::into).map(Arc::new))
77    }
78
79    /// Fetch the transaction with the given id
80    pub fn get_tx(&self, txid: &Txid) -> Result<Arc<Transaction>, LwkError> {
81        let err = || LwkError::Generic {
82            msg: "tx not found".to_string(),
83        };
84        let mut tx = self.inner.lock()?.get_transactions(&[txid.into()])?;
85        Ok(Arc::new(Transaction::from(tx.pop().ok_or_else(err)?)))
86    }
87
88    /// Return the current tip of the blockchain
89    pub fn tip(&self) -> Result<Arc<BlockHeader>, LwkError> {
90        let tip = self.inner.lock()?.tip()?;
91        Ok(Arc::new(tip.into()))
92    }
93}