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
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
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;
}
}