1pub mod coinset;
19pub mod peer;
20pub mod router;
21pub mod types;
22
23use std::collections::HashMap;
24use std::path::PathBuf;
25use std::time::Duration;
26
27use serde_json::Value;
28
29pub use types::*;
30
31#[derive(Debug, Clone, Copy, PartialEq, Eq)]
36pub enum NetworkType {
37 Mainnet,
38 Testnet11,
39}
40
41impl NetworkType {
42 pub fn network_id(self) -> &'static str {
43 match self {
44 Self::Mainnet => "mainnet",
45 Self::Testnet11 => "testnet11",
46 }
47 }
48
49 fn default_cert_path(self) -> PathBuf {
50 let base = dirs_home().join(".chia");
51 match self {
52 Self::Mainnet => base.join("mainnet/config/ssl/wallet/wallet_node.crt"),
53 Self::Testnet11 => base.join("testnet11/config/ssl/wallet/wallet_node.crt"),
54 }
55 }
56
57 fn default_key_path(self) -> PathBuf {
58 let base = dirs_home().join(".chia");
59 match self {
60 Self::Mainnet => base.join("mainnet/config/ssl/wallet/wallet_node.key"),
61 Self::Testnet11 => base.join("testnet11/config/ssl/wallet/wallet_node.key"),
62 }
63 }
64}
65
66fn dirs_home() -> PathBuf {
67 #[cfg(target_os = "windows")]
68 {
69 std::env::var("USERPROFILE")
70 .map(PathBuf::from)
71 .unwrap_or_else(|_| PathBuf::from("C:\\"))
72 }
73 #[cfg(not(target_os = "windows"))]
74 {
75 std::env::var("HOME")
76 .map(PathBuf::from)
77 .unwrap_or_else(|_| PathBuf::from("/"))
78 }
79}
80
81pub struct ChiaQueryConfig {
86 pub network: NetworkType,
87 pub max_peers: usize,
88 pub coinset_base_url: String,
89 pub coinset_fallback_enabled: bool,
90 pub cert_path: PathBuf,
91 pub key_path: PathBuf,
92 pub peer_connect_timeout: Duration,
93 pub peer_request_timeout: Duration,
94 pub coinset_request_timeout: Duration,
95}
96
97impl Default for ChiaQueryConfig {
98 fn default() -> Self {
99 let network = NetworkType::Mainnet;
100 Self {
101 network,
102 max_peers: 5,
103 coinset_base_url: "https://api.coinset.org".into(),
104 coinset_fallback_enabled: true,
105 cert_path: network.default_cert_path(),
106 key_path: network.default_key_path(),
107 peer_connect_timeout: Duration::from_secs(8),
108 peer_request_timeout: Duration::from_secs(30),
109 coinset_request_timeout: Duration::from_secs(30),
110 }
111 }
112}
113
114pub struct ChiaQuery {
119 router: router::QueryRouter,
120}
121
122impl ChiaQuery {
123 pub async fn new(cfg: ChiaQueryConfig) -> Result<Self, ChiaQueryError> {
131 let tls = peer::connect::create_tls(&cfg.cert_path, &cfg.key_path)?;
132
133 let peer_backend = peer::PeerBackend::new(
134 cfg.network,
135 tls,
136 cfg.max_peers,
137 cfg.peer_connect_timeout,
138 cfg.peer_request_timeout,
139 )
140 .await?;
141
142 let coinset_client =
143 coinset::CoinsetClient::new(&cfg.coinset_base_url, cfg.coinset_request_timeout)?;
144
145 Ok(Self {
146 router: router::QueryRouter {
147 peer: peer_backend,
148 coinset: coinset_client,
149 coinset_fallback_enabled: cfg.coinset_fallback_enabled,
150 },
151 })
152 }
153
154 pub async fn get_additions_and_removals(
159 &self,
160 header_hash: &str,
161 ) -> Result<AdditionsAndRemovals, ChiaQueryError> {
162 self.router.get_additions_and_removals(header_hash).await
163 }
164
165 pub async fn get_block(&self, header_hash: &str) -> Result<FullBlock, ChiaQueryError> {
166 self.router.get_block(header_hash).await
167 }
168
169 pub async fn get_block_by_height(&self, height: u32) -> Result<FullBlock, ChiaQueryError> {
171 self.router.get_block_by_height(height).await
172 }
173
174 pub async fn get_block_count_metrics(&self) -> Result<BlockCountMetrics, ChiaQueryError> {
175 self.router.get_block_count_metrics().await
176 }
177
178 pub async fn get_block_record(&self, header_hash: &str) -> Result<BlockRecord, ChiaQueryError> {
179 self.router.get_block_record(header_hash).await
180 }
181
182 pub async fn get_block_record_by_height(
183 &self,
184 height: u32,
185 ) -> Result<BlockRecord, ChiaQueryError> {
186 self.router.get_block_record_by_height(height).await
187 }
188
189 pub async fn get_block_records(
190 &self,
191 start: u32,
192 end: u32,
193 ) -> Result<Vec<BlockRecord>, ChiaQueryError> {
194 self.router.get_block_records(start, end).await
195 }
196
197 pub async fn get_block_spends(
198 &self,
199 header_hash: &str,
200 ) -> Result<Vec<CoinSpend>, ChiaQueryError> {
201 self.router.get_block_spends(header_hash).await
202 }
203
204 pub async fn get_block_spends_with_conditions(
205 &self,
206 header_hash: &str,
207 ) -> Result<Vec<CoinSpendWithConditions>, ChiaQueryError> {
208 self.router
209 .get_block_spends_with_conditions(header_hash)
210 .await
211 }
212
213 pub async fn get_blocks(
214 &self,
215 start: u32,
216 end: u32,
217 exclude_header_hash: bool,
218 exclude_reorged: bool,
219 ) -> Result<Vec<FullBlock>, ChiaQueryError> {
220 self.router
221 .get_blocks(start, end, exclude_header_hash, exclude_reorged)
222 .await
223 }
224
225 pub async fn get_unfinished_block_headers(
226 &self,
227 ) -> Result<Vec<UnfinishedBlockHeader>, ChiaQueryError> {
228 self.router.get_unfinished_block_headers().await
229 }
230
231 pub async fn get_coin_record_by_name(&self, name: &str) -> Result<CoinRecord, ChiaQueryError> {
236 self.router.get_coin_record_by_name(name).await
237 }
238
239 pub async fn get_coin_records_by_hint(
240 &self,
241 hint: &str,
242 start_height: Option<u32>,
243 end_height: Option<u32>,
244 include_spent_coins: bool,
245 ) -> Result<Vec<CoinRecord>, ChiaQueryError> {
246 self.router
247 .get_coin_records_by_hint(hint, start_height, end_height, include_spent_coins)
248 .await
249 }
250
251 pub async fn get_coin_records_by_hints(
252 &self,
253 hints: &[String],
254 start_height: Option<u32>,
255 end_height: Option<u32>,
256 include_spent_coins: bool,
257 ) -> Result<Vec<CoinRecord>, ChiaQueryError> {
258 self.router
259 .get_coin_records_by_hints(hints, start_height, end_height, include_spent_coins)
260 .await
261 }
262
263 pub async fn get_coin_records_by_names(
264 &self,
265 names: &[String],
266 start_height: Option<u32>,
267 end_height: Option<u32>,
268 include_spent_coins: bool,
269 ) -> Result<Vec<CoinRecord>, ChiaQueryError> {
270 self.router
271 .get_coin_records_by_names(names, start_height, end_height, include_spent_coins)
272 .await
273 }
274
275 pub async fn get_coin_records_by_parent_ids(
276 &self,
277 parent_ids: &[String],
278 start_height: Option<u32>,
279 end_height: Option<u32>,
280 include_spent_coins: bool,
281 ) -> Result<Vec<CoinRecord>, ChiaQueryError> {
282 self.router
283 .get_coin_records_by_parent_ids(
284 parent_ids,
285 start_height,
286 end_height,
287 include_spent_coins,
288 )
289 .await
290 }
291
292 pub async fn get_coin_records_by_puzzle_hash(
293 &self,
294 puzzle_hash: &str,
295 start_height: Option<u32>,
296 end_height: Option<u32>,
297 include_spent_coins: bool,
298 ) -> Result<Vec<CoinRecord>, ChiaQueryError> {
299 self.router
300 .get_coin_records_by_puzzle_hash(
301 puzzle_hash,
302 start_height,
303 end_height,
304 include_spent_coins,
305 )
306 .await
307 }
308
309 pub async fn get_coin_records_by_puzzle_hashes(
310 &self,
311 puzzle_hashes: &[String],
312 start_height: Option<u32>,
313 end_height: Option<u32>,
314 include_spent_coins: bool,
315 ) -> Result<Vec<CoinRecord>, ChiaQueryError> {
316 self.router
317 .get_coin_records_by_puzzle_hashes(
318 puzzle_hashes,
319 start_height,
320 end_height,
321 include_spent_coins,
322 )
323 .await
324 }
325
326 pub async fn get_memos_by_coin_name(&self, name: &str) -> Result<Value, ChiaQueryError> {
327 self.router.get_memos_by_coin_name(name).await
328 }
329
330 pub async fn get_puzzle_and_solution(
331 &self,
332 coin_id: &str,
333 height: Option<u32>,
334 ) -> Result<CoinSpend, ChiaQueryError> {
335 self.router.get_puzzle_and_solution(coin_id, height).await
336 }
337
338 pub async fn get_puzzle_and_solution_with_conditions(
339 &self,
340 coin_id: &str,
341 height: Option<u32>,
342 ) -> Result<CoinSpendWithConditions, ChiaQueryError> {
343 self.router
344 .get_puzzle_and_solution_with_conditions(coin_id, height)
345 .await
346 }
347
348 pub async fn push_tx(&self, spend_bundle: &SpendBundle) -> Result<TxStatus, ChiaQueryError> {
349 self.router.push_tx(spend_bundle).await
350 }
351
352 pub async fn get_fee_estimate(
357 &self,
358 spend_bundle: Option<&SpendBundle>,
359 target_times: Option<&[u64]>,
360 spend_count: Option<u64>,
361 ) -> Result<FeeEstimate, ChiaQueryError> {
362 self.router
363 .get_fee_estimate(spend_bundle, target_times, spend_count)
364 .await
365 }
366
367 pub async fn get_aggsig_additional_data(&self) -> Result<String, ChiaQueryError> {
372 self.router.get_aggsig_additional_data().await
373 }
374
375 pub async fn get_network_info(&self) -> Result<NetworkInfo, ChiaQueryError> {
376 self.router.get_network_info().await
377 }
378
379 pub async fn get_blockchain_state(&self) -> Result<BlockchainState, ChiaQueryError> {
380 self.router.get_blockchain_state().await
381 }
382
383 pub async fn get_network_space(
384 &self,
385 newer_block_header_hash: &str,
386 older_block_header_hash: &str,
387 ) -> Result<u64, ChiaQueryError> {
388 self.router
389 .get_network_space(newer_block_header_hash, older_block_header_hash)
390 .await
391 }
392
393 pub async fn get_all_mempool_items(
398 &self,
399 ) -> Result<HashMap<String, MempoolItem>, ChiaQueryError> {
400 self.router.get_all_mempool_items().await
401 }
402
403 pub async fn get_all_mempool_tx_ids(&self) -> Result<Vec<String>, ChiaQueryError> {
404 self.router.get_all_mempool_tx_ids().await
405 }
406
407 pub async fn get_mempool_item_by_tx_id(
408 &self,
409 tx_id: &str,
410 ) -> Result<MempoolItem, ChiaQueryError> {
411 self.router.get_mempool_item_by_tx_id(tx_id).await
412 }
413
414 pub async fn get_mempool_items_by_coin_name(
415 &self,
416 coin_name: &str,
417 include_spent_coins: Option<bool>,
418 ) -> Result<Vec<MempoolItem>, ChiaQueryError> {
419 self.router
420 .get_mempool_items_by_coin_name(coin_name, include_spent_coins)
421 .await
422 }
423
424 pub async fn wait_for_confirmation(
450 &self,
451 coin_id: &str,
452 poll_interval: Duration,
453 timeout: Duration,
454 ) -> Result<CoinRecord, ChiaQueryError> {
455 let deadline = tokio::time::Instant::now() + timeout;
456
457 loop {
458 match self.get_coin_record_by_name(coin_id).await {
459 Ok(record) if record.confirmed_block_index > 0 => {
460 return Ok(record);
461 }
462 Ok(_) => {
463 }
466 Err(ChiaQueryError::PeerRejection(_)) | Err(ChiaQueryError::CoinsetApiError(_)) => {
467 }
469 Err(e) => {
470 log::debug!("wait_for_confirmation poll error: {e}");
472 }
473 }
474
475 if tokio::time::Instant::now() + poll_interval > deadline {
476 return Err(ChiaQueryError::PeerConnection(format!(
477 "coin {coin_id} not confirmed within {timeout:?}"
478 )));
479 }
480
481 tokio::time::sleep(poll_interval).await;
482 }
483 }
484}