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