1pub mod coinset;
19pub mod drift;
20pub mod types;
21
22#[cfg(all(target_arch = "wasm32", feature = "coinset", not(feature = "native")))]
24pub mod wasm_api;
25
26#[cfg(feature = "native")]
30pub mod peer;
31#[cfg(feature = "native")]
32pub mod provider_registry;
33#[cfg(feature = "native")]
34pub mod router;
35
36pub use types::*;
37
38#[cfg(feature = "native")]
42mod native_client {
43 use std::collections::HashMap;
44 use std::path::PathBuf;
45 use std::time::Duration;
46
47 use serde_json::Value;
48
49 use crate::types::*;
50 use crate::{coinset, peer, router};
51
52 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
57 pub enum NetworkType {
58 Mainnet,
59 Testnet11,
60 }
61
62 impl NetworkType {
63 pub fn network_id(self) -> &'static str {
64 match self {
65 Self::Mainnet => "mainnet",
66 Self::Testnet11 => "testnet11",
67 }
68 }
69
70 fn default_cert_path(self) -> PathBuf {
71 let base = dirs_home().join(".chia");
72 match self {
73 Self::Mainnet => base.join("mainnet/config/ssl/wallet/wallet_node.crt"),
74 Self::Testnet11 => base.join("testnet11/config/ssl/wallet/wallet_node.crt"),
75 }
76 }
77
78 fn default_key_path(self) -> PathBuf {
79 let base = dirs_home().join(".chia");
80 match self {
81 Self::Mainnet => base.join("mainnet/config/ssl/wallet/wallet_node.key"),
82 Self::Testnet11 => base.join("testnet11/config/ssl/wallet/wallet_node.key"),
83 }
84 }
85 }
86
87 fn dirs_home() -> PathBuf {
88 #[cfg(target_os = "windows")]
89 {
90 std::env::var("USERPROFILE")
91 .map(PathBuf::from)
92 .unwrap_or_else(|_| PathBuf::from("C:\\"))
93 }
94 #[cfg(not(target_os = "windows"))]
95 {
96 std::env::var("HOME")
97 .map(PathBuf::from)
98 .unwrap_or_else(|_| PathBuf::from("/"))
99 }
100 }
101
102 pub struct ChiaQueryConfig {
107 pub network: NetworkType,
108 pub max_peers: usize,
109 pub coinset_base_url: String,
110 pub coinset_fallback_enabled: bool,
111 pub cert_path: PathBuf,
112 pub key_path: PathBuf,
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 let network = NetworkType::Mainnet;
121 Self {
122 network,
123 max_peers: 5,
124 coinset_base_url: "https://api.coinset.org".into(),
125 coinset_fallback_enabled: true,
126 cert_path: network.default_cert_path(),
127 key_path: network.default_key_path(),
128 peer_connect_timeout: Duration::from_secs(8),
129 peer_request_timeout: Duration::from_secs(30),
130 coinset_request_timeout: Duration::from_secs(30),
131 }
132 }
133 }
134
135 pub struct ChiaQuery {
140 router: router::QueryRouter,
141 }
142
143 impl ChiaQuery {
144 pub async fn new(cfg: ChiaQueryConfig) -> Result<Self, ChiaQueryError> {
152 let tls = peer::connect::create_tls(&cfg.cert_path, &cfg.key_path)?;
153
154 let peer_backend = peer::PeerBackend::new(
155 cfg.network,
156 tls,
157 cfg.max_peers,
158 cfg.peer_connect_timeout,
159 cfg.peer_request_timeout,
160 )
161 .await?;
162
163 let coinset_client =
164 coinset::CoinsetClient::new(&cfg.coinset_base_url, cfg.coinset_request_timeout)?;
165
166 Ok(Self {
167 router: router::QueryRouter {
168 peer: peer_backend,
169 coinset: coinset_client,
170 coinset_fallback_enabled: cfg.coinset_fallback_enabled,
171 },
172 })
173 }
174
175 pub async fn get_additions_and_removals(
180 &self,
181 header_hash: &str,
182 ) -> Result<AdditionsAndRemovals, ChiaQueryError> {
183 self.router.get_additions_and_removals(header_hash).await
184 }
185
186 pub async fn get_block(&self, header_hash: &str) -> Result<FullBlock, ChiaQueryError> {
187 self.router.get_block(header_hash).await
188 }
189
190 pub async fn get_block_by_height(&self, height: u32) -> Result<FullBlock, ChiaQueryError> {
192 self.router.get_block_by_height(height).await
193 }
194
195 pub async fn get_block_count_metrics(&self) -> Result<BlockCountMetrics, ChiaQueryError> {
196 self.router.get_block_count_metrics().await
197 }
198
199 pub async fn get_block_record(
200 &self,
201 header_hash: &str,
202 ) -> Result<BlockRecord, ChiaQueryError> {
203 self.router.get_block_record(header_hash).await
204 }
205
206 pub async fn get_block_record_by_height(
207 &self,
208 height: u32,
209 ) -> Result<BlockRecord, ChiaQueryError> {
210 self.router.get_block_record_by_height(height).await
211 }
212
213 pub async fn get_block_records(
214 &self,
215 start: u32,
216 end: u32,
217 ) -> Result<Vec<BlockRecord>, ChiaQueryError> {
218 self.router.get_block_records(start, end).await
219 }
220
221 pub async fn get_block_spends(
222 &self,
223 header_hash: &str,
224 ) -> Result<Vec<CoinSpend>, ChiaQueryError> {
225 self.router.get_block_spends(header_hash).await
226 }
227
228 pub async fn get_block_spends_with_conditions(
229 &self,
230 header_hash: &str,
231 ) -> Result<Vec<CoinSpendWithConditions>, ChiaQueryError> {
232 self.router
233 .get_block_spends_with_conditions(header_hash)
234 .await
235 }
236
237 pub async fn get_blocks(
238 &self,
239 start: u32,
240 end: u32,
241 exclude_header_hash: bool,
242 exclude_reorged: bool,
243 ) -> Result<Vec<FullBlock>, ChiaQueryError> {
244 self.router
245 .get_blocks(start, end, exclude_header_hash, exclude_reorged)
246 .await
247 }
248
249 pub async fn get_unfinished_block_headers(
250 &self,
251 ) -> Result<Vec<UnfinishedBlockHeader>, ChiaQueryError> {
252 self.router.get_unfinished_block_headers().await
253 }
254
255 pub async fn get_coin_record_by_name(
260 &self,
261 name: &str,
262 ) -> Result<CoinRecord, ChiaQueryError> {
263 self.router.get_coin_record_by_name(name).await
264 }
265
266 pub async fn get_coin_record_by_name_opt(
271 &self,
272 name: &str,
273 ) -> Result<Option<CoinRecord>, ChiaQueryError> {
274 self.router.get_coin_record_by_name_opt(name).await
275 }
276
277 pub async fn get_coin_spend_opt(
280 &self,
281 coin_id: &str,
282 ) -> Result<Option<CoinSpend>, ChiaQueryError> {
283 self.router.get_coin_spend_opt(coin_id).await
284 }
285
286 pub async fn peak_height_opt(&self) -> Result<Option<u32>, ChiaQueryError> {
288 self.router.peak_height_opt().await
289 }
290
291 pub async fn block_timestamp_opt(
293 &self,
294 height: u32,
295 ) -> Result<Option<u64>, ChiaQueryError> {
296 self.router.block_timestamp_opt(height).await
297 }
298
299 pub async fn get_coin_records_by_hint(
300 &self,
301 hint: &str,
302 start_height: Option<u32>,
303 end_height: Option<u32>,
304 include_spent_coins: bool,
305 ) -> Result<Vec<CoinRecord>, ChiaQueryError> {
306 self.router
307 .get_coin_records_by_hint(hint, start_height, end_height, include_spent_coins)
308 .await
309 }
310
311 pub async fn get_coin_records_by_hints(
312 &self,
313 hints: &[String],
314 start_height: Option<u32>,
315 end_height: Option<u32>,
316 include_spent_coins: bool,
317 ) -> Result<Vec<CoinRecord>, ChiaQueryError> {
318 self.router
319 .get_coin_records_by_hints(hints, start_height, end_height, include_spent_coins)
320 .await
321 }
322
323 pub async fn get_coin_records_by_names(
324 &self,
325 names: &[String],
326 start_height: Option<u32>,
327 end_height: Option<u32>,
328 include_spent_coins: bool,
329 ) -> Result<Vec<CoinRecord>, ChiaQueryError> {
330 self.router
331 .get_coin_records_by_names(names, start_height, end_height, include_spent_coins)
332 .await
333 }
334
335 pub async fn get_coin_records_by_parent_ids(
336 &self,
337 parent_ids: &[String],
338 start_height: Option<u32>,
339 end_height: Option<u32>,
340 include_spent_coins: bool,
341 ) -> Result<Vec<CoinRecord>, ChiaQueryError> {
342 self.router
343 .get_coin_records_by_parent_ids(
344 parent_ids,
345 start_height,
346 end_height,
347 include_spent_coins,
348 )
349 .await
350 }
351
352 pub async fn get_coin_records_by_puzzle_hash(
353 &self,
354 puzzle_hash: &str,
355 start_height: Option<u32>,
356 end_height: Option<u32>,
357 include_spent_coins: bool,
358 ) -> Result<Vec<CoinRecord>, ChiaQueryError> {
359 self.router
360 .get_coin_records_by_puzzle_hash(
361 puzzle_hash,
362 start_height,
363 end_height,
364 include_spent_coins,
365 )
366 .await
367 }
368
369 pub async fn get_coin_records_by_puzzle_hashes(
370 &self,
371 puzzle_hashes: &[String],
372 start_height: Option<u32>,
373 end_height: Option<u32>,
374 include_spent_coins: bool,
375 ) -> Result<Vec<CoinRecord>, ChiaQueryError> {
376 self.router
377 .get_coin_records_by_puzzle_hashes(
378 puzzle_hashes,
379 start_height,
380 end_height,
381 include_spent_coins,
382 )
383 .await
384 }
385
386 pub async fn get_memos_by_coin_name(&self, name: &str) -> Result<Value, ChiaQueryError> {
387 self.router.get_memos_by_coin_name(name).await
388 }
389
390 pub async fn get_puzzle_and_solution(
391 &self,
392 coin_id: &str,
393 height: Option<u32>,
394 ) -> Result<CoinSpend, ChiaQueryError> {
395 self.router.get_puzzle_and_solution(coin_id, height).await
396 }
397
398 pub async fn get_puzzle_and_solution_with_conditions(
399 &self,
400 coin_id: &str,
401 height: Option<u32>,
402 ) -> Result<CoinSpendWithConditions, ChiaQueryError> {
403 self.router
404 .get_puzzle_and_solution_with_conditions(coin_id, height)
405 .await
406 }
407
408 pub async fn push_tx(
409 &self,
410 spend_bundle: &SpendBundle,
411 ) -> Result<TxStatus, ChiaQueryError> {
412 self.router.push_tx(spend_bundle).await
413 }
414
415 pub async fn get_fee_estimate(
420 &self,
421 spend_bundle: Option<&SpendBundle>,
422 target_times: Option<&[u64]>,
423 spend_count: Option<u64>,
424 ) -> Result<FeeEstimate, ChiaQueryError> {
425 self.router
426 .get_fee_estimate(spend_bundle, target_times, spend_count)
427 .await
428 }
429
430 pub async fn get_aggsig_additional_data(&self) -> Result<String, ChiaQueryError> {
435 self.router.get_aggsig_additional_data().await
436 }
437
438 pub async fn get_network_info(&self) -> Result<NetworkInfo, ChiaQueryError> {
439 self.router.get_network_info().await
440 }
441
442 pub async fn get_blockchain_state(&self) -> Result<BlockchainState, ChiaQueryError> {
443 self.router.get_blockchain_state().await
444 }
445
446 pub async fn get_network_space(
447 &self,
448 newer_block_header_hash: &str,
449 older_block_header_hash: &str,
450 ) -> Result<u64, ChiaQueryError> {
451 self.router
452 .get_network_space(newer_block_header_hash, older_block_header_hash)
453 .await
454 }
455
456 pub async fn get_all_mempool_items(
461 &self,
462 ) -> Result<HashMap<String, MempoolItem>, ChiaQueryError> {
463 self.router.get_all_mempool_items().await
464 }
465
466 pub async fn get_all_mempool_tx_ids(&self) -> Result<Vec<String>, ChiaQueryError> {
467 self.router.get_all_mempool_tx_ids().await
468 }
469
470 pub async fn get_mempool_item_by_tx_id(
471 &self,
472 tx_id: &str,
473 ) -> Result<MempoolItem, ChiaQueryError> {
474 self.router.get_mempool_item_by_tx_id(tx_id).await
475 }
476
477 pub async fn get_mempool_items_by_coin_name(
478 &self,
479 coin_name: &str,
480 include_spent_coins: Option<bool>,
481 ) -> Result<Vec<MempoolItem>, ChiaQueryError> {
482 self.router
483 .get_mempool_items_by_coin_name(coin_name, include_spent_coins)
484 .await
485 }
486
487 pub async fn wait_for_confirmation(
513 &self,
514 coin_id: &str,
515 poll_interval: Duration,
516 timeout: Duration,
517 ) -> Result<CoinRecord, ChiaQueryError> {
518 let deadline = tokio::time::Instant::now() + timeout;
519
520 loop {
521 match self.get_coin_record_by_name(coin_id).await {
522 Ok(record) if record.confirmed_block_index > 0 => {
523 return Ok(record);
524 }
525 Ok(_) => {
526 }
529 Err(ChiaQueryError::PeerRejection(_))
530 | Err(ChiaQueryError::CoinsetApiError(_)) => {
531 }
533 Err(e) => {
534 log::debug!("wait_for_confirmation poll error: {e}");
536 }
537 }
538
539 if tokio::time::Instant::now() + poll_interval > deadline {
540 return Err(ChiaQueryError::PeerConnection(format!(
541 "coin {coin_id} not confirmed within {timeout:?}"
542 )));
543 }
544
545 tokio::time::sleep(poll_interval).await;
546 }
547 }
548 }
549} #[cfg(feature = "native")]
552pub use native_client::{ChiaQuery, ChiaQueryConfig, NetworkType};