1#[cfg(feature = "resolver-electrum")]
26mod electrum;
27#[cfg(any(feature = "resolver-esplora", feature = "resolver-esplora-async",))]
28mod esplora;
29use core::iter;
33#[cfg(feature = "std")]
34use std::process::exit;
35
36use amplify::IoError;
37use bpstd::psbt::Utxo;
38use bpstd::{ScriptPubkey, Terminal, Tx, Txid, UnsignedTx};
39use rgb::WitnessStatus;
40
41#[cfg(feature = "resolver-electrum")]
44pub use self::electrum::ElectrumResolver;
45#[cfg(feature = "resolver-esplora-async")]
46pub use self::esplora::EsploraAsyncResolver;
47#[cfg(feature = "resolver-esplora")]
48pub use self::esplora::EsploraResolver;
49
50#[cfg(not(feature = "async"))]
51pub trait Resolver {
52 fn resolve_tx(&self, txid: Txid) -> Result<Option<UnsignedTx>, ResolverError>;
53 fn resolve_tx_status(&self, txid: Txid) -> Result<WitnessStatus, ResolverError>;
54
55 fn resolve_utxos(
56 &self,
57 iter: impl IntoIterator<Item = (Terminal, ScriptPubkey)>,
58 ) -> impl Iterator<Item = Result<Utxo, ResolverError>>;
59
60 fn last_block_height(&self) -> Result<u64, ResolverError>;
61
62 fn broadcast(&self, tx: &Tx) -> Result<(), ResolverError>;
63}
64
65#[cfg(feature = "async")]
66pub trait Resolver {
67 async fn resolve_tx_async(&self, txid: Txid) -> Result<Option<UnsignedTx>, ResolverError>;
68 async fn resolve_tx_status_async(&self, txid: Txid) -> Result<WitnessStatus, ResolverError>;
69
70 async fn resolve_utxos_async(
71 &self,
72 iter: impl IntoIterator<Item = (Terminal, ScriptPubkey)>,
73 ) -> impl Iterator<Item = Result<Utxo, ResolverError>>;
74
75 async fn last_block_height_async(&self) -> Result<u64, ResolverError>;
76
77 async fn broadcast_async(&self, tx: &Tx) -> Result<(), ResolverError>;
78}
79
80#[derive(Default)]
81pub struct MultiResolver {
82 #[cfg(feature = "resolver-electrum")]
83 electrum: Option<ElectrumResolver>,
84 #[cfg(feature = "resolver-esplora")]
85 esplora: Option<EsploraResolver>,
86 #[cfg(feature = "resolver-esplora-async")]
93 esplora: Option<EsploraAsyncResolver>,
94 }
97
98#[derive(Copy, Clone)]
99pub struct NoResolver;
100
101impl NoResolver {
102 fn call(&self) -> ! {
103 #[cfg(feature = "std")]
104 {
105 eprintln!(
106 "Error: no blockchain indexer specified; use either --esplora or --electrum \
107 argument"
108 );
109 exit(1);
110 }
111 #[cfg(not(feature = "std"))]
112 panic!(
113 "Error: no blockchain indexer, you need to use one of resolver-* features during the \
114 compilation"
115 );
116 }
117}
118
119#[cfg(not(feature = "async"))]
120impl Resolver for NoResolver {
121 fn resolve_tx(&self, _txid: Txid) -> Result<Option<UnsignedTx>, ResolverError> { self.call() }
122 fn resolve_tx_status(&self, _txid: Txid) -> Result<WitnessStatus, ResolverError> { self.call() }
123 fn resolve_utxos(
124 &self,
125 _iter: impl IntoIterator<Item = (Terminal, ScriptPubkey)>,
126 ) -> impl Iterator<Item = Result<Utxo, ResolverError>> {
127 self.call();
128 #[allow(unreachable_code)]
129 iter::empty()
130 }
131 fn last_block_height(&self) -> Result<u64, ResolverError> { self.call() }
132 fn broadcast(&self, _tx: &Tx) -> Result<(), ResolverError> { self.call() }
133}
134
135#[cfg(feature = "async")]
136impl Resolver for NoResolver {
137 async fn resolve_tx_async(&self, _txid: Txid) -> Result<Option<UnsignedTx>, ResolverError> {
138 self.call()
139 }
140 async fn resolve_tx_status_async(&self, _txid: Txid) -> Result<WitnessStatus, ResolverError> {
141 self.call()
142 }
143 async fn resolve_utxos_async(
144 &self,
145 _iter: impl IntoIterator<Item = (Terminal, ScriptPubkey)>,
146 ) -> impl Iterator<Item = Result<Utxo, ResolverError>> {
147 self.call();
148 #[allow(unreachable_code)]
149 iter::empty()
150 }
151 async fn last_block_height_async(&self) -> Result<u64, ResolverError> { self.call() }
152 async fn broadcast_async(&self, _tx: &Tx) -> Result<(), ResolverError> { self.call() }
153}
154
155impl MultiResolver {
156 #[cfg(feature = "resolver-electrum")]
157 pub fn new_electrum(url: &str) -> Result<Self, ResolverError> {
158 Ok(Self { electrum: Some(ElectrumResolver::new(url)?), ..default!() })
159 }
160 #[cfg(feature = "resolver-esplora")]
161 pub fn new_esplora(url: &str) -> Result<Self, ResolverError> {
162 Ok(Self { esplora: Some(EsploraResolver::new(url)?), ..default!() })
163 }
164 #[allow(clippy::needless_update)]
165 #[cfg(feature = "resolver-esplora-async")]
166 pub fn new_esplora(url: &str) -> Result<Self, ResolverError> {
167 Ok(Self { esplora: Some(EsploraAsyncResolver::new(url)?), ..default!() })
168 }
169 pub fn new_absent() -> Result<Self, ResolverError> { Ok(Self::default()) }
172}
173
174#[cfg(not(feature = "async"))]
175impl Resolver for MultiResolver {
176 fn resolve_tx(&self, txid: Txid) -> Result<Option<UnsignedTx>, ResolverError> {
177 #[cfg(feature = "resolver-esplora")]
178 if let Some(resolver) = &self.esplora {
179 return resolver.resolve_tx(txid);
180 }
181 #[cfg(feature = "resolver-electrum")]
182 if let Some(resolver) = &self.electrum {
183 return resolver.resolve_tx(txid);
184 }
185 NoResolver.call()
190 }
191
192 fn resolve_tx_status(&self, txid: Txid) -> Result<WitnessStatus, ResolverError> {
193 #[cfg(feature = "resolver-esplora")]
194 if let Some(resolver) = &self.esplora {
195 return resolver.resolve_tx_status(txid);
196 }
197 #[cfg(feature = "resolver-electrum")]
198 if let Some(resolver) = &self.electrum {
199 return resolver.resolve_tx_status(txid);
200 }
201 NoResolver.call()
206 }
207
208 #[allow(unreachable_code)]
209 fn resolve_utxos(
210 &self,
211 iter: impl IntoIterator<Item = (Terminal, ScriptPubkey)>,
212 ) -> impl Iterator<Item = Result<Utxo, ResolverError>> {
213 #[cfg(feature = "resolver-esplora")]
214 if let Some(resolver) = &self.esplora {
215 return resolver.resolve_utxos(iter).collect::<Vec<_>>().into_iter();
216 }
217 #[cfg(feature = "resolver-electrum")]
218 if let Some(resolver) = &self.electrum {
219 return resolver.resolve_utxos(iter).collect::<Vec<_>>().into_iter();
220 }
221 NoResolver.call();
226 vec![].into_iter()
227 }
228
229 fn last_block_height(&self) -> Result<u64, ResolverError> {
230 #[cfg(feature = "resolver-esplora")]
231 if let Some(resolver) = &self.esplora {
232 return resolver.last_block_height();
233 }
234 #[cfg(feature = "resolver-electrum")]
235 if let Some(resolver) = &self.electrum {
236 return resolver.last_block_height();
237 }
238 NoResolver.call()
243 }
244
245 fn broadcast(&self, tx: &Tx) -> Result<(), ResolverError> {
246 #[cfg(feature = "resolver-esplora")]
247 if let Some(resolver) = &self.esplora {
248 return resolver.broadcast(tx);
249 }
250 #[cfg(feature = "resolver-electrum")]
251 if let Some(resolver) = &self.electrum {
252 return resolver.broadcast(tx);
253 }
254 NoResolver.call()
259 }
260}
261
262#[cfg(feature = "async")]
263impl Resolver for MultiResolver {
264 async fn resolve_tx_async(&self, txid: Txid) -> Result<Option<UnsignedTx>, ResolverError> {
265 #[cfg(feature = "resolver-esplora-async")]
266 if let Some(resolver) = &self.esplora {
267 return resolver.resolve_tx_async(txid).await;
268 }
269 NoResolver.call()
278 }
279
280 async fn resolve_tx_status_async(&self, txid: Txid) -> Result<WitnessStatus, ResolverError> {
281 #[cfg(feature = "resolver-esplora-async")]
282 if let Some(resolver) = &self.esplora {
283 return resolver.resolve_tx_status_async(txid).await;
284 }
285 NoResolver.call()
294 }
295
296 #[allow(unreachable_code)]
297 async fn resolve_utxos_async(
298 &self,
299 iter: impl IntoIterator<Item = (Terminal, ScriptPubkey)>,
300 ) -> impl Iterator<Item = Result<Utxo, ResolverError>> {
301 #[cfg(feature = "resolver-esplora-async")]
302 if let Some(resolver) = &self.esplora {
303 return resolver
304 .resolve_utxos_async(iter)
305 .await
306 .collect::<Vec<_>>()
307 .into_iter();
308 }
309 NoResolver.call();
326 vec![].into_iter()
327 }
328
329 async fn last_block_height_async(&self) -> Result<u64, ResolverError> {
330 #[cfg(feature = "resolver-esplora-async")]
331 if let Some(resolver) = &self.esplora {
332 return resolver.last_block_height_async().await;
333 }
334 NoResolver.call()
343 }
344
345 async fn broadcast_async(&self, tx: &Tx) -> Result<(), ResolverError> {
346 #[cfg(feature = "resolver-esplora-async")]
347 if let Some(resolver) = &self.esplora {
348 return resolver.broadcast_async(tx).await;
349 }
350 NoResolver.call()
359 }
360}
361
362#[derive(Debug, Display, Error, From)]
363#[display(inner)]
364pub enum ResolverError {
365 Io(IoError),
366
367 Connectivity,
369
370 Local,
372
373 Protocol,
375
376 Logic,
378
379 ServerSide(String),
381}