1pub mod coinset;
23pub mod drift;
24pub mod types;
25
26#[cfg(all(target_arch = "wasm32", feature = "coinset", not(feature = "native")))]
28pub mod wasm_api;
29
30#[cfg(feature = "native")]
34pub mod peer;
35#[cfg(feature = "native")]
36pub mod provider_registry;
37#[cfg(feature = "native")]
38pub mod router;
39
40pub use types::*;
41
42#[cfg(feature = "native")]
46mod native_client {
47 use std::collections::HashMap;
48 use std::path::PathBuf;
49 use std::time::Duration;
50
51 use serde_json::Value;
52
53 use crate::types::*;
54 use crate::{coinset, peer, router};
55
56 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
61 pub enum NetworkType {
62 Mainnet,
63 Testnet11,
64 }
65
66 impl NetworkType {
67 pub fn network_id(self) -> &'static str {
68 match self {
69 Self::Mainnet => "mainnet",
70 Self::Testnet11 => "testnet11",
71 }
72 }
73 }
74
75 #[derive(Debug, Clone, PartialEq, Eq)]
84 pub enum TlsIdentity {
85 Generated,
93
94 Files {
96 cert_path: PathBuf,
97 key_path: PathBuf,
98 },
99 }
100
101 pub struct ChiaQueryConfig {
102 pub network: NetworkType,
103 pub max_peers: usize,
104 pub coinset_base_url: String,
105 pub coinset_fallback_enabled: bool,
106 pub tls_identity: TlsIdentity,
107 pub peer_connect_timeout: Duration,
108 pub peer_request_timeout: Duration,
109 pub coinset_request_timeout: Duration,
110 }
111
112 impl Default for ChiaQueryConfig {
113 fn default() -> Self {
114 Self {
115 network: NetworkType::Mainnet,
116 max_peers: 5,
117 coinset_base_url: "https://api.coinset.org".into(),
118 coinset_fallback_enabled: true,
119 tls_identity: TlsIdentity::Generated,
120 peer_connect_timeout: Duration::from_secs(8),
121 peer_request_timeout: Duration::from_secs(30),
122 coinset_request_timeout: Duration::from_secs(30),
123 }
124 }
125 }
126
127 pub struct ChiaQuery {
132 router: router::QueryRouter,
133 }
134
135 impl ChiaQuery {
136 pub async fn new(cfg: ChiaQueryConfig) -> Result<Self, ChiaQueryError> {
145 let tls = match &cfg.tls_identity {
146 TlsIdentity::Generated => peer::connect::create_generated_tls()?,
147 TlsIdentity::Files {
148 cert_path,
149 key_path,
150 } => peer::connect::create_tls(cert_path, key_path)?,
151 };
152
153 let peer_requirement = if cfg.coinset_fallback_enabled {
157 peer::PeerRequirement::Optional
158 } else {
159 peer::PeerRequirement::Required
160 };
161
162 let peer_backend = peer::PeerBackend::new(
163 cfg.network,
164 tls,
165 cfg.max_peers,
166 peer_requirement,
167 cfg.peer_connect_timeout,
168 cfg.peer_request_timeout,
169 )
170 .await?;
171
172 let coinset_client =
173 coinset::CoinsetClient::new(&cfg.coinset_base_url, cfg.coinset_request_timeout)?;
174
175 Ok(Self {
176 router: router::QueryRouter {
177 peer: peer_backend,
178 coinset: coinset_client,
179 coinset_fallback_enabled: cfg.coinset_fallback_enabled,
180 },
181 })
182 }
183
184 pub async fn get_additions_and_removals(
189 &self,
190 header_hash: &str,
191 ) -> Result<AdditionsAndRemovals, ChiaQueryError> {
192 self.router.get_additions_and_removals(header_hash).await
193 }
194
195 pub async fn get_block(&self, header_hash: &str) -> Result<FullBlock, ChiaQueryError> {
196 self.router.get_block(header_hash).await
197 }
198
199 pub async fn get_block_by_height(&self, height: u32) -> Result<FullBlock, ChiaQueryError> {
201 self.router.get_block_by_height(height).await
202 }
203
204 pub async fn get_block_count_metrics(&self) -> Result<BlockCountMetrics, ChiaQueryError> {
205 self.router.get_block_count_metrics().await
206 }
207
208 pub async fn get_block_record(
209 &self,
210 header_hash: &str,
211 ) -> Result<BlockRecord, ChiaQueryError> {
212 self.router.get_block_record(header_hash).await
213 }
214
215 pub async fn get_block_record_by_height(
216 &self,
217 height: u32,
218 ) -> Result<BlockRecord, ChiaQueryError> {
219 self.router.get_block_record_by_height(height).await
220 }
221
222 pub async fn get_block_records(
223 &self,
224 start: u32,
225 end: u32,
226 ) -> Result<Vec<BlockRecord>, ChiaQueryError> {
227 self.router.get_block_records(start, end).await
228 }
229
230 pub async fn get_block_spends(
231 &self,
232 header_hash: &str,
233 ) -> Result<Vec<CoinSpend>, ChiaQueryError> {
234 self.router.get_block_spends(header_hash).await
235 }
236
237 pub async fn get_block_spends_with_conditions(
238 &self,
239 header_hash: &str,
240 ) -> Result<Vec<CoinSpendWithConditions>, ChiaQueryError> {
241 self.router
242 .get_block_spends_with_conditions(header_hash)
243 .await
244 }
245
246 pub async fn get_blocks(
247 &self,
248 start: u32,
249 end: u32,
250 exclude_header_hash: bool,
251 exclude_reorged: bool,
252 ) -> Result<Vec<FullBlock>, ChiaQueryError> {
253 self.router
254 .get_blocks(start, end, exclude_header_hash, exclude_reorged)
255 .await
256 }
257
258 pub async fn get_unfinished_block_headers(
259 &self,
260 ) -> Result<Vec<UnfinishedBlockHeader>, ChiaQueryError> {
261 self.router.get_unfinished_block_headers().await
262 }
263
264 pub async fn get_coin_record_by_name(
269 &self,
270 name: &str,
271 ) -> Result<CoinRecord, ChiaQueryError> {
272 self.router.get_coin_record_by_name(name).await
273 }
274
275 pub async fn get_coin_record_by_name_opt(
280 &self,
281 name: &str,
282 ) -> Result<Option<CoinRecord>, ChiaQueryError> {
283 self.router.get_coin_record_by_name_opt(name).await
284 }
285
286 pub async fn get_coin_spend_opt(
289 &self,
290 coin_id: &str,
291 ) -> Result<Option<CoinSpend>, ChiaQueryError> {
292 self.router.get_coin_spend_opt(coin_id).await
293 }
294
295 pub async fn peak_height_opt(&self) -> Result<Option<u32>, ChiaQueryError> {
297 self.router.peak_height_opt().await
298 }
299
300 pub async fn block_timestamp_opt(
302 &self,
303 height: u32,
304 ) -> Result<Option<u64>, ChiaQueryError> {
305 self.router.block_timestamp_opt(height).await
306 }
307
308 pub async fn get_coin_records_by_hint(
309 &self,
310 hint: &str,
311 start_height: Option<u32>,
312 end_height: Option<u32>,
313 include_spent_coins: bool,
314 ) -> Result<Vec<CoinRecord>, ChiaQueryError> {
315 self.router
316 .get_coin_records_by_hint(hint, start_height, end_height, include_spent_coins)
317 .await
318 }
319
320 pub async fn get_coin_records_by_hints(
321 &self,
322 hints: &[String],
323 start_height: Option<u32>,
324 end_height: Option<u32>,
325 include_spent_coins: bool,
326 ) -> Result<Vec<CoinRecord>, ChiaQueryError> {
327 self.router
328 .get_coin_records_by_hints(hints, start_height, end_height, include_spent_coins)
329 .await
330 }
331
332 pub async fn get_coin_records_by_names(
333 &self,
334 names: &[String],
335 start_height: Option<u32>,
336 end_height: Option<u32>,
337 include_spent_coins: bool,
338 ) -> Result<Vec<CoinRecord>, ChiaQueryError> {
339 self.router
340 .get_coin_records_by_names(names, start_height, end_height, include_spent_coins)
341 .await
342 }
343
344 pub async fn get_coin_records_by_parent_ids(
345 &self,
346 parent_ids: &[String],
347 start_height: Option<u32>,
348 end_height: Option<u32>,
349 include_spent_coins: bool,
350 ) -> Result<Vec<CoinRecord>, ChiaQueryError> {
351 self.router
352 .get_coin_records_by_parent_ids(
353 parent_ids,
354 start_height,
355 end_height,
356 include_spent_coins,
357 )
358 .await
359 }
360
361 pub async fn get_coin_records_by_puzzle_hash(
362 &self,
363 puzzle_hash: &str,
364 start_height: Option<u32>,
365 end_height: Option<u32>,
366 include_spent_coins: bool,
367 ) -> Result<Vec<CoinRecord>, ChiaQueryError> {
368 self.router
369 .get_coin_records_by_puzzle_hash(
370 puzzle_hash,
371 start_height,
372 end_height,
373 include_spent_coins,
374 )
375 .await
376 }
377
378 pub async fn get_coin_records_by_puzzle_hashes(
379 &self,
380 puzzle_hashes: &[String],
381 start_height: Option<u32>,
382 end_height: Option<u32>,
383 include_spent_coins: bool,
384 ) -> Result<Vec<CoinRecord>, ChiaQueryError> {
385 self.router
386 .get_coin_records_by_puzzle_hashes(
387 puzzle_hashes,
388 start_height,
389 end_height,
390 include_spent_coins,
391 )
392 .await
393 }
394
395 pub async fn get_memos_by_coin_name(&self, name: &str) -> Result<Value, ChiaQueryError> {
396 self.router.get_memos_by_coin_name(name).await
397 }
398
399 pub async fn get_puzzle_and_solution(
400 &self,
401 coin_id: &str,
402 height: Option<u32>,
403 ) -> Result<CoinSpend, ChiaQueryError> {
404 self.router.get_puzzle_and_solution(coin_id, height).await
405 }
406
407 pub async fn get_puzzle_and_solution_with_conditions(
408 &self,
409 coin_id: &str,
410 height: Option<u32>,
411 ) -> Result<CoinSpendWithConditions, ChiaQueryError> {
412 self.router
413 .get_puzzle_and_solution_with_conditions(coin_id, height)
414 .await
415 }
416
417 pub async fn push_tx(
418 &self,
419 spend_bundle: &SpendBundle,
420 ) -> Result<TxStatus, ChiaQueryError> {
421 self.router.push_tx(spend_bundle).await
422 }
423
424 pub async fn get_fee_estimate(
429 &self,
430 spend_bundle: Option<&SpendBundle>,
431 target_times: Option<&[u64]>,
432 spend_count: Option<u64>,
433 ) -> Result<FeeEstimate, ChiaQueryError> {
434 self.router
435 .get_fee_estimate(spend_bundle, target_times, spend_count)
436 .await
437 }
438
439 pub async fn get_aggsig_additional_data(&self) -> Result<String, ChiaQueryError> {
444 self.router.get_aggsig_additional_data().await
445 }
446
447 pub async fn get_network_info(&self) -> Result<NetworkInfo, ChiaQueryError> {
448 self.router.get_network_info().await
449 }
450
451 pub async fn get_blockchain_state(&self) -> Result<BlockchainState, ChiaQueryError> {
452 self.router.get_blockchain_state().await
453 }
454
455 pub async fn get_network_space(
456 &self,
457 newer_block_header_hash: &str,
458 older_block_header_hash: &str,
459 ) -> Result<u64, ChiaQueryError> {
460 self.router
461 .get_network_space(newer_block_header_hash, older_block_header_hash)
462 .await
463 }
464
465 pub async fn get_all_mempool_items(
470 &self,
471 ) -> Result<HashMap<String, MempoolItem>, ChiaQueryError> {
472 self.router.get_all_mempool_items().await
473 }
474
475 pub async fn get_all_mempool_tx_ids(&self) -> Result<Vec<String>, ChiaQueryError> {
476 self.router.get_all_mempool_tx_ids().await
477 }
478
479 pub async fn get_mempool_item_by_tx_id(
480 &self,
481 tx_id: &str,
482 ) -> Result<MempoolItem, ChiaQueryError> {
483 self.router.get_mempool_item_by_tx_id(tx_id).await
484 }
485
486 pub async fn get_mempool_items_by_coin_name(
487 &self,
488 coin_name: &str,
489 include_spent_coins: Option<bool>,
490 ) -> Result<Vec<MempoolItem>, ChiaQueryError> {
491 self.router
492 .get_mempool_items_by_coin_name(coin_name, include_spent_coins)
493 .await
494 }
495
496 pub async fn wait_for_confirmation(
522 &self,
523 coin_id: &str,
524 poll_interval: Duration,
525 timeout: Duration,
526 ) -> Result<CoinRecord, ChiaQueryError> {
527 let deadline = tokio::time::Instant::now() + timeout;
528
529 loop {
530 match self.get_coin_record_by_name(coin_id).await {
531 Ok(record) if record.confirmed_block_index > 0 => {
532 return Ok(record);
533 }
534 Ok(_) => {
535 }
538 Err(ChiaQueryError::PeerRejection(_))
539 | Err(ChiaQueryError::CoinsetApiError(_)) => {
540 }
542 Err(e) => {
543 log::debug!("wait_for_confirmation poll error: {e}");
545 }
546 }
547
548 if tokio::time::Instant::now() + poll_interval > deadline {
549 return Err(ChiaQueryError::PeerConnection(format!(
550 "coin {coin_id} not confirmed within {timeout:?}"
551 )));
552 }
553
554 tokio::time::sleep(poll_interval).await;
555 }
556 }
557 }
558} #[cfg(feature = "native")]
561pub use native_client::{ChiaQuery, ChiaQueryConfig, NetworkType, TlsIdentity};