1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
use std::sync::{Arc, Mutex};
use lwk_wollet::clients::blocking::BlockchainBackend;
use crate::{BlockHeader, LwkError, Transaction, Txid, Update, Wollet};
/// A client to issue TCP requests to an electrum server.
#[derive(uniffi::Object, Debug)]
pub struct ElectrumClient {
inner: Arc<Mutex<lwk_wollet::ElectrumClient>>,
url: lwk_wollet::ElectrumUrl,
}
#[uniffi::export]
impl ElectrumClient {
/// Construct an Electrum client
#[uniffi::constructor]
pub fn new(
electrum_url: &str,
tls: bool,
validate_domain: bool,
) -> Result<Arc<Self>, LwkError> {
let url = lwk_wollet::ElectrumUrl::new(electrum_url, tls, validate_domain)
.map_err(lwk_wollet::Error::Url)?;
let client = lwk_wollet::ElectrumClient::new(&url)?;
Ok(Arc::new(Self {
inner: Arc::new(Mutex::new(client)),
url,
}))
}
#[uniffi::constructor]
/// Construct an electrum client from an Electrum URL
pub fn from_url(electrum_url: &str) -> Result<Arc<Self>, LwkError> {
let url = electrum_url
.parse::<lwk_wollet::ElectrumUrl>()
.map_err(|e| LwkError::Generic { msg: e.to_string() })?;
let client = lwk_wollet::ElectrumClient::new(&url)?;
Ok(Arc::new(Self {
inner: Arc::new(Mutex::new(client)),
url,
}))
}
/// Ping the Electrum server
pub fn ping(&self) -> Result<(), LwkError> {
Ok(self.inner.lock()?.ping()?)
}
/// 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))
}
/// Fetch the transaction with the given id
pub fn get_tx(&self, txid: &Txid) -> Result<Arc<Transaction>, LwkError> {
let err = || LwkError::Generic {
msg: "tx not found".to_string(),
};
let mut tx = self.inner.lock()?.get_transactions(&[txid.into()])?;
Ok(Arc::new(Transaction::from(tx.pop().ok_or_else(err)?)))
}
/// Return the current tip of the blockchain
pub fn tip(&self) -> Result<Arc<BlockHeader>, LwkError> {
let tip = self.inner.lock()?.tip()?;
Ok(Arc::new(tip.into()))
}
}
impl ElectrumClient {
/// Create a new electrum client with the same connection parameters
#[allow(unused)] // TODO remove once lwk_boltz is integrated
pub(crate) fn clone_client(&self) -> Result<lwk_wollet::ElectrumClient, LwkError> {
Ok(lwk_wollet::ElectrumClient::new(&self.url)?)
}
}