chia_query/lib.rs
1//! # chia-query
2//!
3//! Query the Chia blockchain through decentralized peer connections with
4//! automatic fallback to the [coinset.org](https://api.coinset.org) HTTP API.
5//!
6//! No Chia installation is required. The peer TLS client identity is generated in
7//! memory by default ([`TlsIdentity::Generated`]), so a client works from a service
8//! account with no home directory of its own.
9//!
10//! ```rust,no_run
11//! use chia_query::{ChiaQuery, ChiaQueryConfig};
12//!
13//! #[tokio::main]
14//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
15//! let client = ChiaQuery::new(ChiaQueryConfig::default()).await?;
16//! let record = client.get_coin_record_by_name("0xabc...").await?;
17//! println!("{:?}", record);
18//! Ok(())
19//! }
20//! ```
21
22pub mod coinset;
23pub mod drift;
24pub mod types;
25
26// The `@dignetwork/chia-query-wasm` bindings — only for the wasm coinset build.
27#[cfg(all(target_arch = "wasm32", feature = "coinset", not(feature = "native")))]
28pub mod wasm_api;
29
30// The peer WebSocket backend + the routing/CLVM layer are native-only: they
31// pull in `chia-wallet-sdk` (native-tls), `clvmr`, and tokio networking, none
32// of which belong in the wasm coinset-only build.
33#[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// Everything below — the full `ChiaQuery` client that races peers against the
43// coinset fallback — is the native surface. A wasm consumer uses
44// [`coinset::CoinsetClient`] directly with an injected `fetch` transport.
45#[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 // ---------------------------------------------------------------------------
57 // NetworkType
58 // ---------------------------------------------------------------------------
59
60 #[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 // ---------------------------------------------------------------------------
76 // Configuration
77 // ---------------------------------------------------------------------------
78
79 /// Where the peer-protocol TLS client identity comes from.
80 ///
81 /// Modelled as one choice rather than a pair of optional paths so a half-configured
82 /// identity — a certificate without its key — cannot be expressed.
83 #[derive(Debug, Clone, PartialEq, Eq)]
84 pub enum TlsIdentity {
85 /// Generate a fresh self-signed certificate in memory (the default).
86 ///
87 /// Chia full nodes accept any well-formed client certificate, so nothing is
88 /// gained by requiring one on disk and a great deal is lost: a service account
89 /// has no populated `~/.chia`, which is what made every balance read fail in
90 /// dig_ecosystem#2210. See [`peer::connect::create_generated_tls`] for why the
91 /// certificate is not persisted.
92 Generated,
93
94 /// Load an existing certificate/key pair, e.g. a real Chia node's wallet cert.
95 Files {
96 cert_path: PathBuf,
97 key_path: PathBuf,
98 },
99 }
100
101 pub struct ChiaQueryConfig {
102 pub network: NetworkType,
103 /// How many peer sessions the pool holds.
104 ///
105 /// Defaults to [`peer::plurality::default_max_peers`], which is DERIVED from the sample a
106 /// corroborated read needs to leave standing rather than picked. Lowering it below that
107 /// does not make corroboration weaker quietly — the pool refuses rather than degrading —
108 /// but it does make it unavailable.
109 pub max_peers: usize,
110 pub coinset_base_url: String,
111 pub coinset_fallback_enabled: bool,
112 pub tls_identity: TlsIdentity,
113 pub peer_connect_timeout: Duration,
114 pub peer_request_timeout: Duration,
115 pub coinset_request_timeout: Duration,
116 }
117
118 impl Default for ChiaQueryConfig {
119 fn default() -> Self {
120 Self {
121 network: NetworkType::Mainnet,
122 max_peers: peer::plurality::default_max_peers(),
123 coinset_base_url: "https://api.coinset.org".into(),
124 coinset_fallback_enabled: true,
125 tls_identity: TlsIdentity::Generated,
126 peer_connect_timeout: Duration::from_secs(8),
127 peer_request_timeout: Duration::from_secs(30),
128 coinset_request_timeout: Duration::from_secs(30),
129 }
130 }
131 }
132
133 // ---------------------------------------------------------------------------
134 // ChiaQuery -- the public entry-point
135 // ---------------------------------------------------------------------------
136
137 pub struct ChiaQuery {
138 router: router::QueryRouter,
139 }
140
141 impl ChiaQuery {
142 /// Create a new client. This will:
143 /// 1. Establish the peer TLS identity (generated by default — no files needed).
144 /// 2. Discover peers via DNS and connect up to `max_peers` concurrently.
145 /// 3. Initialise the coinset.org HTTP client.
146 ///
147 /// Peer discovery failing is fatal ([`ChiaQueryError::PeerDiscoveryFailed`])
148 /// ONLY when the coinset fallback is disabled; with the fallback enabled the
149 /// client is usable immediately and the peer pool refills in the background.
150 pub async fn new(cfg: ChiaQueryConfig) -> Result<Self, ChiaQueryError> {
151 let tls = match &cfg.tls_identity {
152 TlsIdentity::Generated => peer::connect::create_generated_tls()?,
153 TlsIdentity::Files {
154 cert_path,
155 key_path,
156 } => peer::connect::create_tls(cert_path, key_path)?,
157 };
158
159 // The coinset tier is plain HTTP and needs neither a credential nor a peer,
160 // so a peer-tier problem must not deny a reader the fallback that exists
161 // for exactly that case (dig_ecosystem#2210).
162 let peer_requirement = if cfg.coinset_fallback_enabled {
163 peer::PeerRequirement::Optional
164 } else {
165 peer::PeerRequirement::Required
166 };
167
168 let peer_backend = peer::PeerBackend::new(
169 cfg.network,
170 tls,
171 cfg.max_peers,
172 peer_requirement,
173 cfg.peer_connect_timeout,
174 cfg.peer_request_timeout,
175 )
176 .await?;
177
178 let coinset_client =
179 coinset::CoinsetClient::new(&cfg.coinset_base_url, cfg.coinset_request_timeout)?;
180
181 Ok(Self {
182 router: router::QueryRouter {
183 peer: std::sync::Arc::new(peer_backend),
184 coinset: coinset_client,
185 coinset_fallback_enabled: cfg.coinset_fallback_enabled,
186 },
187 })
188 }
189
190 // =======================================================================
191 // Blocks
192 // =======================================================================
193
194 pub async fn get_additions_and_removals(
195 &self,
196 header_hash: &str,
197 ) -> Result<AdditionsAndRemovals, ChiaQueryError> {
198 self.router.get_additions_and_removals(header_hash).await
199 }
200
201 pub async fn get_block(&self, header_hash: &str) -> Result<FullBlock, ChiaQueryError> {
202 self.router.get_block(header_hash).await
203 }
204
205 /// Fetch a full block by height. Peer-backed via `RequestBlock`.
206 pub async fn get_block_by_height(&self, height: u32) -> Result<FullBlock, ChiaQueryError> {
207 self.router.get_block_by_height(height).await
208 }
209
210 pub async fn get_block_count_metrics(&self) -> Result<BlockCountMetrics, ChiaQueryError> {
211 self.router.get_block_count_metrics().await
212 }
213
214 pub async fn get_block_record(
215 &self,
216 header_hash: &str,
217 ) -> Result<BlockRecord, ChiaQueryError> {
218 self.router.get_block_record(header_hash).await
219 }
220
221 pub async fn get_block_record_by_height(
222 &self,
223 height: u32,
224 ) -> Result<BlockRecord, ChiaQueryError> {
225 self.router.get_block_record_by_height(height).await
226 }
227
228 pub async fn get_block_records(
229 &self,
230 start: u32,
231 end: u32,
232 ) -> Result<Vec<BlockRecord>, ChiaQueryError> {
233 self.router.get_block_records(start, end).await
234 }
235
236 pub async fn get_block_spends(
237 &self,
238 header_hash: &str,
239 ) -> Result<Vec<CoinSpend>, ChiaQueryError> {
240 self.router.get_block_spends(header_hash).await
241 }
242
243 pub async fn get_block_spends_with_conditions(
244 &self,
245 header_hash: &str,
246 ) -> Result<Vec<CoinSpendWithConditions>, ChiaQueryError> {
247 self.router
248 .get_block_spends_with_conditions(header_hash)
249 .await
250 }
251
252 pub async fn get_blocks(
253 &self,
254 start: u32,
255 end: u32,
256 exclude_header_hash: bool,
257 exclude_reorged: bool,
258 ) -> Result<Vec<FullBlock>, ChiaQueryError> {
259 self.router
260 .get_blocks(start, end, exclude_header_hash, exclude_reorged)
261 .await
262 }
263
264 pub async fn get_unfinished_block_headers(
265 &self,
266 ) -> Result<Vec<UnfinishedBlockHeader>, ChiaQueryError> {
267 self.router.get_unfinished_block_headers().await
268 }
269
270 // =======================================================================
271 // Coins
272 // =======================================================================
273
274 pub async fn get_coin_record_by_name(
275 &self,
276 name: &str,
277 ) -> Result<CoinRecord, ChiaQueryError> {
278 self.router.get_coin_record_by_name(name).await
279 }
280
281 /// Absence-aware [`get_coin_record_by_name`](Self::get_coin_record_by_name).
282 ///
283 /// `Ok(None)` means **corroborated absence**: two independent sources were asked and both
284 /// reported no such coin. Absence that only one source will vouch for is
285 /// [`UncorroboratedAbsence`](ChiaQueryError::UncorroboratedAbsence), and two sources that
286 /// contradict each other are [`SourcesDisagree`](ChiaQueryError::SourcesDisagree) — both
287 /// errors, because neither is a fact about the chain (dig_ecosystem#2456).
288 ///
289 /// `Ok(Some(record))` means **corroborated presence**, and means it for the same reason:
290 /// the coin-id binding authenticates the coin's identity only, so `confirmed_block_index`
291 /// and `spent_block_index` are put to a second independent source before they are
292 /// reported. A record only one source will vouch for is
293 /// [`UncorroboratedPresence`](ChiaQueryError::UncorroboratedPresence)
294 /// (dig_ecosystem#2462).
295 ///
296 /// Used by the [`ChainSource`](dig_chainsource_interface::ChainSource) facade to honour the
297 /// fail-closed `Ok(None)`-vs-`Err` contract.
298 pub async fn get_coin_record_by_name_opt(
299 &self,
300 name: &str,
301 ) -> Result<Option<CoinRecord>, ChiaQueryError> {
302 self.router.get_coin_record_by_name_opt(name).await
303 }
304
305 /// Absence-aware read of the spend that spent `coin_id`.
306 ///
307 /// `Ok(None)` when two independent sources agree the coin is unspent or unknown,
308 /// `Ok(Some(spend))` when two agree on the spend; `Err` on failure, and on an answer in
309 /// either direction that only one source will vouch for — see
310 /// [`get_coin_record_by_name_opt`](Self::get_coin_record_by_name_opt).
311 pub async fn get_coin_spend_opt(
312 &self,
313 coin_id: &str,
314 ) -> Result<Option<CoinSpend>, ChiaQueryError> {
315 self.router.get_coin_spend_opt(coin_id).await
316 }
317
318 /// The current peak height (`Ok(None)` when unavailable), `Err` on failure.
319 pub async fn peak_height_opt(&self) -> Result<Option<u32>, ChiaQueryError> {
320 self.router.peak_height_opt().await
321 }
322
323 /// How many Chia full-node peers this client HOLDS right now.
324 ///
325 /// Exposed because a consumer that presents itself as a light client has to be able to
326 /// SAY how many peers it is a client of, and until now the pool's size was observable
327 /// only as the boolean [`has_peers`](peer::PeerBackend::has_peers). A count is not
328 /// derivable from that, and a consumer with no way to read it is left either silent or
329 /// quoting [`ChiaQueryConfig::max_peers`] — an intention presented as a measurement.
330 ///
331 /// It is the LIVE count, never the target: a filling pool reports the smaller number.
332 /// See [`peer::pool::PeerPool::peer_count`] for what "held" means with respect to a peer
333 /// that has died without being used since.
334 pub async fn peer_count(&self) -> usize {
335 self.router.peer.peer_count().await
336 }
337
338 /// How many held peers count as INDEPENDENT opinions about the chain.
339 ///
340 /// Never larger than [`peer_count`](Self::peer_count), and smaller by the peers reached
341 /// from a preferred address — an operator's `TRUSTED_FULLNODE`, or a full node on this
342 /// machine. Those are the fastest peers to read from and the worst possible witnesses to
343 /// each other: a local process is a source a local attacker can supply, so a count of
344 /// agreeing sources that includes one is not a count of independent sources.
345 ///
346 /// Use this, not `peer_count`, for any decision of the form "do enough separate sources
347 /// agree" (dig_ecosystem#2648). Use `peer_count` to tell a user how many peers are held.
348 pub async fn independent_peer_count(&self) -> usize {
349 self.router.peer.independent_peer_count().await
350 }
351
352 /// The peak height this client's OWN peers have reported, or `None` when they have
353 /// reported none yet.
354 ///
355 /// Distinct from [`peak_height_opt`](Self::peak_height_opt), which answers "what is the
356 /// chain's peak" and consults coinset FIRST — so its figure is a third party's view of
357 /// the chain even on a client holding peers. This one answers "what have MY peers told
358 /// me", which is the only form of the question a light client can demonstrate, and it
359 /// makes no network call at all: the pool tracks it from inbound `NewPeakWallet`
360 /// messages.
361 ///
362 /// `None` is UNKNOWN, never height zero. The pool spells an unobserved peak `0`
363 /// internally, and every block is trivially above zero, so returning it would silently
364 /// satisfy any "is this buried yet" comparison a caller makes.
365 pub async fn peer_peak_height(&self) -> Option<u32> {
366 observed_peak(self.router.peer.peak_height())
367 }
368
369 /// A subscribing light client over THIS client's peer pool.
370 ///
371 /// The returned [`ChiaLightClient`](peer::ChiaLightClient) borrows the same held sessions
372 /// this client reads through, so a consumer that needs both coin subscriptions and ordinary
373 /// queries holds ONE set of peers with ONE notion of the peak. Building a second
374 /// `ChiaQuery` to get a light client would recreate exactly the split this replaces.
375 ///
376 /// `request_timeout` bounds each of the light client's own requests; it does not affect
377 /// this client's.
378 pub async fn light_client(&self, request_timeout: Duration) -> peer::ChiaLightClient {
379 peer::ChiaLightClient::new(self.router.peer.clone(), request_timeout).await
380 }
381
382 /// The Unix timestamp of the block at `height` (`Ok(None)` when absent), `Err` on failure.
383 pub async fn block_timestamp_opt(
384 &self,
385 height: u32,
386 ) -> Result<Option<u64>, ChiaQueryError> {
387 self.router.block_timestamp_opt(height).await
388 }
389
390 pub async fn get_coin_records_by_hint(
391 &self,
392 hint: &str,
393 start_height: Option<u32>,
394 end_height: Option<u32>,
395 include_spent_coins: bool,
396 ) -> Result<Vec<CoinRecord>, ChiaQueryError> {
397 self.router
398 .get_coin_records_by_hint(hint, start_height, end_height, include_spent_coins)
399 .await
400 }
401
402 pub async fn get_coin_records_by_hints(
403 &self,
404 hints: &[String],
405 start_height: Option<u32>,
406 end_height: Option<u32>,
407 include_spent_coins: bool,
408 ) -> Result<Vec<CoinRecord>, ChiaQueryError> {
409 self.router
410 .get_coin_records_by_hints(hints, start_height, end_height, include_spent_coins)
411 .await
412 }
413
414 pub async fn get_coin_records_by_names(
415 &self,
416 names: &[String],
417 start_height: Option<u32>,
418 end_height: Option<u32>,
419 include_spent_coins: bool,
420 ) -> Result<Vec<CoinRecord>, ChiaQueryError> {
421 self.router
422 .get_coin_records_by_names(names, start_height, end_height, include_spent_coins)
423 .await
424 }
425
426 pub async fn get_coin_records_by_parent_ids(
427 &self,
428 parent_ids: &[String],
429 start_height: Option<u32>,
430 end_height: Option<u32>,
431 include_spent_coins: bool,
432 ) -> Result<Vec<CoinRecord>, ChiaQueryError> {
433 self.router
434 .get_coin_records_by_parent_ids(
435 parent_ids,
436 start_height,
437 end_height,
438 include_spent_coins,
439 )
440 .await
441 }
442
443 pub async fn get_coin_records_by_puzzle_hash(
444 &self,
445 puzzle_hash: &str,
446 start_height: Option<u32>,
447 end_height: Option<u32>,
448 include_spent_coins: bool,
449 ) -> Result<Vec<CoinRecord>, ChiaQueryError> {
450 self.router
451 .get_coin_records_by_puzzle_hash(
452 puzzle_hash,
453 start_height,
454 end_height,
455 include_spent_coins,
456 )
457 .await
458 }
459
460 pub async fn get_coin_records_by_puzzle_hashes(
461 &self,
462 puzzle_hashes: &[String],
463 start_height: Option<u32>,
464 end_height: Option<u32>,
465 include_spent_coins: bool,
466 ) -> Result<Vec<CoinRecord>, ChiaQueryError> {
467 self.router
468 .get_coin_records_by_puzzle_hashes(
469 puzzle_hashes,
470 start_height,
471 end_height,
472 include_spent_coins,
473 )
474 .await
475 }
476
477 pub async fn get_memos_by_coin_name(&self, name: &str) -> Result<Value, ChiaQueryError> {
478 self.router.get_memos_by_coin_name(name).await
479 }
480
481 pub async fn get_puzzle_and_solution(
482 &self,
483 coin_id: &str,
484 height: Option<u32>,
485 ) -> Result<CoinSpend, ChiaQueryError> {
486 self.router.get_puzzle_and_solution(coin_id, height).await
487 }
488
489 pub async fn get_puzzle_and_solution_with_conditions(
490 &self,
491 coin_id: &str,
492 height: Option<u32>,
493 ) -> Result<CoinSpendWithConditions, ChiaQueryError> {
494 self.router
495 .get_puzzle_and_solution_with_conditions(coin_id, height)
496 .await
497 }
498
499 pub async fn push_tx(
500 &self,
501 spend_bundle: &SpendBundle,
502 ) -> Result<TxStatus, ChiaQueryError> {
503 self.router.push_tx(spend_bundle).await
504 }
505
506 // =======================================================================
507 // Fees
508 // =======================================================================
509
510 pub async fn get_fee_estimate(
511 &self,
512 spend_bundle: Option<&SpendBundle>,
513 target_times: Option<&[u64]>,
514 spend_count: Option<u64>,
515 ) -> Result<FeeEstimate, ChiaQueryError> {
516 self.router
517 .get_fee_estimate(spend_bundle, target_times, spend_count)
518 .await
519 }
520
521 // =======================================================================
522 // Full node / network
523 // =======================================================================
524
525 pub async fn get_aggsig_additional_data(&self) -> Result<String, ChiaQueryError> {
526 self.router.get_aggsig_additional_data().await
527 }
528
529 pub async fn get_network_info(&self) -> Result<NetworkInfo, ChiaQueryError> {
530 self.router.get_network_info().await
531 }
532
533 pub async fn get_blockchain_state(&self) -> Result<BlockchainState, ChiaQueryError> {
534 self.router.get_blockchain_state().await
535 }
536
537 pub async fn get_network_space(
538 &self,
539 newer_block_header_hash: &str,
540 older_block_header_hash: &str,
541 ) -> Result<u64, ChiaQueryError> {
542 self.router
543 .get_network_space(newer_block_header_hash, older_block_header_hash)
544 .await
545 }
546
547 // =======================================================================
548 // Mempool
549 // =======================================================================
550
551 pub async fn get_all_mempool_items(
552 &self,
553 ) -> Result<HashMap<String, MempoolItem>, ChiaQueryError> {
554 self.router.get_all_mempool_items().await
555 }
556
557 pub async fn get_all_mempool_tx_ids(&self) -> Result<Vec<String>, ChiaQueryError> {
558 self.router.get_all_mempool_tx_ids().await
559 }
560
561 pub async fn get_mempool_item_by_tx_id(
562 &self,
563 tx_id: &str,
564 ) -> Result<MempoolItem, ChiaQueryError> {
565 self.router.get_mempool_item_by_tx_id(tx_id).await
566 }
567
568 pub async fn get_mempool_items_by_coin_name(
569 &self,
570 coin_name: &str,
571 include_spent_coins: Option<bool>,
572 ) -> Result<Vec<MempoolItem>, ChiaQueryError> {
573 self.router
574 .get_mempool_items_by_coin_name(coin_name, include_spent_coins)
575 .await
576 }
577
578 // =======================================================================
579 // Convenience helpers
580 // =======================================================================
581
582 /// Poll the blockchain until a coin appears on-chain (confirmed) or the
583 /// timeout elapses.
584 ///
585 /// Returns the [`CoinRecord`] once the coin is found with a non-zero
586 /// `confirmed_block_index`. Returns an error if the timeout expires
587 /// before the coin is confirmed.
588 ///
589 /// ```rust,no_run
590 /// # use chia_query::{ChiaQuery, ChiaQueryConfig};
591 /// # use std::time::Duration;
592 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
593 /// let client = ChiaQuery::new(ChiaQueryConfig::default()).await?;
594 /// let record = client.wait_for_confirmation(
595 /// "0xabc...",
596 /// Duration::from_secs(5), // poll every 5 seconds
597 /// Duration::from_secs(300), // give up after 5 minutes
598 /// ).await?;
599 /// println!("confirmed at height {}", record.confirmed_block_index);
600 /// # Ok(())
601 /// # }
602 /// ```
603 pub async fn wait_for_confirmation(
604 &self,
605 coin_id: &str,
606 poll_interval: Duration,
607 timeout: Duration,
608 ) -> Result<CoinRecord, ChiaQueryError> {
609 let deadline = tokio::time::Instant::now() + timeout;
610
611 loop {
612 match self.get_coin_record_by_name(coin_id).await {
613 Ok(record) if record.confirmed_block_index > 0 => {
614 return Ok(record);
615 }
616 Ok(_) => {
617 // Coin exists but confirmed_block_index is 0 -- not
618 // confirmed yet, keep polling.
619 }
620 Err(ChiaQueryError::PeerRejection(_))
621 | Err(ChiaQueryError::CoinsetApiError(_)) => {
622 // Coin not found yet -- keep polling.
623 }
624 Err(e) => {
625 // Transient connection errors -- log and keep trying.
626 log::debug!("wait_for_confirmation poll error: {e}");
627 }
628 }
629
630 if tokio::time::Instant::now() + poll_interval > deadline {
631 return Err(ChiaQueryError::PeerConnection(format!(
632 "coin {coin_id} not confirmed within {timeout:?}"
633 )));
634 }
635
636 tokio::time::sleep(poll_interval).await;
637 }
638 }
639 }
640
641 /// The pool's peak sentinel as an honest optional height.
642 ///
643 /// Kept as a named pure function rather than inlined, because the rule it encodes — an
644 /// unobserved peak is UNKNOWN and not height zero — is the whole reason
645 /// [`ChiaQuery::peer_peak_height`] returns an `Option`, and inline it is unreachable from a
646 /// test on a machine with no peers.
647 fn observed_peak(raw: u32) -> Option<u32> {
648 (raw != 0).then_some(raw)
649 }
650
651 #[cfg(test)]
652 mod tests {
653 use super::observed_peak;
654
655 /// **An unobserved peak is unknown, never zero.** The pool spells "no peer has told me a
656 /// peak" as `0`, and every block is trivially above zero — so a caller asking "is this
657 /// coin buried yet" against a leaked `0` gets a confident yes about a chain nobody has
658 /// looked at.
659 #[test]
660 fn an_unobserved_peak_is_unknown_and_a_real_height_survives() {
661 assert_eq!(observed_peak(0), None);
662 assert_eq!(observed_peak(1), Some(1));
663 assert_eq!(observed_peak(9_139_211), Some(9_139_211));
664 }
665 }
666} // mod native_client
667
668#[cfg(feature = "native")]
669pub use native_client::{ChiaQuery, ChiaQueryConfig, NetworkType, TlsIdentity};