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 router;
33
34pub use types::*;
35
36#[cfg(feature = "native")]
40mod native_client {
41 use std::collections::HashMap;
42 use std::path::PathBuf;
43 use std::time::Duration;
44
45 use serde_json::Value;
46
47 use crate::types::*;
48 use crate::{coinset, peer, router};
49
50 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
55 pub enum NetworkType {
56 Mainnet,
57 Testnet11,
58 }
59
60 impl NetworkType {
61 pub fn network_id(self) -> &'static str {
62 match self {
63 Self::Mainnet => "mainnet",
64 Self::Testnet11 => "testnet11",
65 }
66 }
67
68 fn default_cert_path(self) -> PathBuf {
69 let base = dirs_home().join(".chia");
70 match self {
71 Self::Mainnet => base.join("mainnet/config/ssl/wallet/wallet_node.crt"),
72 Self::Testnet11 => base.join("testnet11/config/ssl/wallet/wallet_node.crt"),
73 }
74 }
75
76 fn default_key_path(self) -> PathBuf {
77 let base = dirs_home().join(".chia");
78 match self {
79 Self::Mainnet => base.join("mainnet/config/ssl/wallet/wallet_node.key"),
80 Self::Testnet11 => base.join("testnet11/config/ssl/wallet/wallet_node.key"),
81 }
82 }
83 }
84
85 fn dirs_home() -> PathBuf {
86 #[cfg(target_os = "windows")]
87 {
88 std::env::var("USERPROFILE")
89 .map(PathBuf::from)
90 .unwrap_or_else(|_| PathBuf::from("C:\\"))
91 }
92 #[cfg(not(target_os = "windows"))]
93 {
94 std::env::var("HOME")
95 .map(PathBuf::from)
96 .unwrap_or_else(|_| PathBuf::from("/"))
97 }
98 }
99
100 pub struct ChiaQueryConfig {
105 pub network: NetworkType,
106 pub max_peers: usize,
107 pub coinset_base_url: String,
108 pub coinset_fallback_enabled: bool,
109 pub cert_path: PathBuf,
110 pub key_path: PathBuf,
111 pub peer_connect_timeout: Duration,
112 pub peer_request_timeout: Duration,
113 pub coinset_request_timeout: Duration,
114 }
115
116 impl Default for ChiaQueryConfig {
117 fn default() -> Self {
118 let network = NetworkType::Mainnet;
119 Self {
120 network,
121 max_peers: 5,
122 coinset_base_url: "https://api.coinset.org".into(),
123 coinset_fallback_enabled: true,
124 cert_path: network.default_cert_path(),
125 key_path: network.default_key_path(),
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 pub struct ChiaQuery {
138 router: router::QueryRouter,
139 }
140
141 impl ChiaQuery {
142 pub async fn new(cfg: ChiaQueryConfig) -> Result<Self, ChiaQueryError> {
150 let tls = peer::connect::create_tls(&cfg.cert_path, &cfg.key_path)?;
151
152 let peer_backend = peer::PeerBackend::new(
153 cfg.network,
154 tls,
155 cfg.max_peers,
156 cfg.peer_connect_timeout,
157 cfg.peer_request_timeout,
158 )
159 .await?;
160
161 let coinset_client =
162 coinset::CoinsetClient::new(&cfg.coinset_base_url, cfg.coinset_request_timeout)?;
163
164 Ok(Self {
165 router: router::QueryRouter {
166 peer: peer_backend,
167 coinset: coinset_client,
168 coinset_fallback_enabled: cfg.coinset_fallback_enabled,
169 },
170 })
171 }
172
173 pub async fn get_additions_and_removals(
178 &self,
179 header_hash: &str,
180 ) -> Result<AdditionsAndRemovals, ChiaQueryError> {
181 self.router.get_additions_and_removals(header_hash).await
182 }
183
184 pub async fn get_block(&self, header_hash: &str) -> Result<FullBlock, ChiaQueryError> {
185 self.router.get_block(header_hash).await
186 }
187
188 pub async fn get_block_by_height(&self, height: u32) -> Result<FullBlock, ChiaQueryError> {
190 self.router.get_block_by_height(height).await
191 }
192
193 pub async fn get_block_count_metrics(&self) -> Result<BlockCountMetrics, ChiaQueryError> {
194 self.router.get_block_count_metrics().await
195 }
196
197 pub async fn get_block_record(
198 &self,
199 header_hash: &str,
200 ) -> Result<BlockRecord, ChiaQueryError> {
201 self.router.get_block_record(header_hash).await
202 }
203
204 pub async fn get_block_record_by_height(
205 &self,
206 height: u32,
207 ) -> Result<BlockRecord, ChiaQueryError> {
208 self.router.get_block_record_by_height(height).await
209 }
210
211 pub async fn get_block_records(
212 &self,
213 start: u32,
214 end: u32,
215 ) -> Result<Vec<BlockRecord>, ChiaQueryError> {
216 self.router.get_block_records(start, end).await
217 }
218
219 pub async fn get_block_spends(
220 &self,
221 header_hash: &str,
222 ) -> Result<Vec<CoinSpend>, ChiaQueryError> {
223 self.router.get_block_spends(header_hash).await
224 }
225
226 pub async fn get_block_spends_with_conditions(
227 &self,
228 header_hash: &str,
229 ) -> Result<Vec<CoinSpendWithConditions>, ChiaQueryError> {
230 self.router
231 .get_block_spends_with_conditions(header_hash)
232 .await
233 }
234
235 pub async fn get_blocks(
236 &self,
237 start: u32,
238 end: u32,
239 exclude_header_hash: bool,
240 exclude_reorged: bool,
241 ) -> Result<Vec<FullBlock>, ChiaQueryError> {
242 self.router
243 .get_blocks(start, end, exclude_header_hash, exclude_reorged)
244 .await
245 }
246
247 pub async fn get_unfinished_block_headers(
248 &self,
249 ) -> Result<Vec<UnfinishedBlockHeader>, ChiaQueryError> {
250 self.router.get_unfinished_block_headers().await
251 }
252
253 pub async fn get_coin_record_by_name(
258 &self,
259 name: &str,
260 ) -> Result<CoinRecord, ChiaQueryError> {
261 self.router.get_coin_record_by_name(name).await
262 }
263
264 pub async fn get_coin_records_by_hint(
265 &self,
266 hint: &str,
267 start_height: Option<u32>,
268 end_height: Option<u32>,
269 include_spent_coins: bool,
270 ) -> Result<Vec<CoinRecord>, ChiaQueryError> {
271 self.router
272 .get_coin_records_by_hint(hint, start_height, end_height, include_spent_coins)
273 .await
274 }
275
276 pub async fn get_coin_records_by_hints(
277 &self,
278 hints: &[String],
279 start_height: Option<u32>,
280 end_height: Option<u32>,
281 include_spent_coins: bool,
282 ) -> Result<Vec<CoinRecord>, ChiaQueryError> {
283 self.router
284 .get_coin_records_by_hints(hints, start_height, end_height, include_spent_coins)
285 .await
286 }
287
288 pub async fn get_coin_records_by_names(
289 &self,
290 names: &[String],
291 start_height: Option<u32>,
292 end_height: Option<u32>,
293 include_spent_coins: bool,
294 ) -> Result<Vec<CoinRecord>, ChiaQueryError> {
295 self.router
296 .get_coin_records_by_names(names, start_height, end_height, include_spent_coins)
297 .await
298 }
299
300 pub async fn get_coin_records_by_parent_ids(
301 &self,
302 parent_ids: &[String],
303 start_height: Option<u32>,
304 end_height: Option<u32>,
305 include_spent_coins: bool,
306 ) -> Result<Vec<CoinRecord>, ChiaQueryError> {
307 self.router
308 .get_coin_records_by_parent_ids(
309 parent_ids,
310 start_height,
311 end_height,
312 include_spent_coins,
313 )
314 .await
315 }
316
317 pub async fn get_coin_records_by_puzzle_hash(
318 &self,
319 puzzle_hash: &str,
320 start_height: Option<u32>,
321 end_height: Option<u32>,
322 include_spent_coins: bool,
323 ) -> Result<Vec<CoinRecord>, ChiaQueryError> {
324 self.router
325 .get_coin_records_by_puzzle_hash(
326 puzzle_hash,
327 start_height,
328 end_height,
329 include_spent_coins,
330 )
331 .await
332 }
333
334 pub async fn get_coin_records_by_puzzle_hashes(
335 &self,
336 puzzle_hashes: &[String],
337 start_height: Option<u32>,
338 end_height: Option<u32>,
339 include_spent_coins: bool,
340 ) -> Result<Vec<CoinRecord>, ChiaQueryError> {
341 self.router
342 .get_coin_records_by_puzzle_hashes(
343 puzzle_hashes,
344 start_height,
345 end_height,
346 include_spent_coins,
347 )
348 .await
349 }
350
351 pub async fn get_memos_by_coin_name(&self, name: &str) -> Result<Value, ChiaQueryError> {
352 self.router.get_memos_by_coin_name(name).await
353 }
354
355 pub async fn get_puzzle_and_solution(
356 &self,
357 coin_id: &str,
358 height: Option<u32>,
359 ) -> Result<CoinSpend, ChiaQueryError> {
360 self.router.get_puzzle_and_solution(coin_id, height).await
361 }
362
363 pub async fn get_puzzle_and_solution_with_conditions(
364 &self,
365 coin_id: &str,
366 height: Option<u32>,
367 ) -> Result<CoinSpendWithConditions, ChiaQueryError> {
368 self.router
369 .get_puzzle_and_solution_with_conditions(coin_id, height)
370 .await
371 }
372
373 pub async fn push_tx(
374 &self,
375 spend_bundle: &SpendBundle,
376 ) -> Result<TxStatus, ChiaQueryError> {
377 self.router.push_tx(spend_bundle).await
378 }
379
380 pub async fn get_fee_estimate(
385 &self,
386 spend_bundle: Option<&SpendBundle>,
387 target_times: Option<&[u64]>,
388 spend_count: Option<u64>,
389 ) -> Result<FeeEstimate, ChiaQueryError> {
390 self.router
391 .get_fee_estimate(spend_bundle, target_times, spend_count)
392 .await
393 }
394
395 pub async fn get_aggsig_additional_data(&self) -> Result<String, ChiaQueryError> {
400 self.router.get_aggsig_additional_data().await
401 }
402
403 pub async fn get_network_info(&self) -> Result<NetworkInfo, ChiaQueryError> {
404 self.router.get_network_info().await
405 }
406
407 pub async fn get_blockchain_state(&self) -> Result<BlockchainState, ChiaQueryError> {
408 self.router.get_blockchain_state().await
409 }
410
411 pub async fn get_network_space(
412 &self,
413 newer_block_header_hash: &str,
414 older_block_header_hash: &str,
415 ) -> Result<u64, ChiaQueryError> {
416 self.router
417 .get_network_space(newer_block_header_hash, older_block_header_hash)
418 .await
419 }
420
421 pub async fn get_all_mempool_items(
426 &self,
427 ) -> Result<HashMap<String, MempoolItem>, ChiaQueryError> {
428 self.router.get_all_mempool_items().await
429 }
430
431 pub async fn get_all_mempool_tx_ids(&self) -> Result<Vec<String>, ChiaQueryError> {
432 self.router.get_all_mempool_tx_ids().await
433 }
434
435 pub async fn get_mempool_item_by_tx_id(
436 &self,
437 tx_id: &str,
438 ) -> Result<MempoolItem, ChiaQueryError> {
439 self.router.get_mempool_item_by_tx_id(tx_id).await
440 }
441
442 pub async fn get_mempool_items_by_coin_name(
443 &self,
444 coin_name: &str,
445 include_spent_coins: Option<bool>,
446 ) -> Result<Vec<MempoolItem>, ChiaQueryError> {
447 self.router
448 .get_mempool_items_by_coin_name(coin_name, include_spent_coins)
449 .await
450 }
451
452 pub async fn wait_for_confirmation(
478 &self,
479 coin_id: &str,
480 poll_interval: Duration,
481 timeout: Duration,
482 ) -> Result<CoinRecord, ChiaQueryError> {
483 let deadline = tokio::time::Instant::now() + timeout;
484
485 loop {
486 match self.get_coin_record_by_name(coin_id).await {
487 Ok(record) if record.confirmed_block_index > 0 => {
488 return Ok(record);
489 }
490 Ok(_) => {
491 }
494 Err(ChiaQueryError::PeerRejection(_))
495 | Err(ChiaQueryError::CoinsetApiError(_)) => {
496 }
498 Err(e) => {
499 log::debug!("wait_for_confirmation poll error: {e}");
501 }
502 }
503
504 if tokio::time::Instant::now() + poll_interval > deadline {
505 return Err(ChiaQueryError::PeerConnection(format!(
506 "coin {coin_id} not confirmed within {timeout:?}"
507 )));
508 }
509
510 tokio::time::sleep(poll_interval).await;
511 }
512 }
513 }
514} #[cfg(feature = "native")]
517pub use native_client::{ChiaQuery, ChiaQueryConfig, NetworkType};