1use std::collections::HashSet;
2
3use cynic::QueryBuilder;
4use hex::ToHex;
5
6use super::{BlokliClient, GraphQlQueries, response_to_data};
7use crate::{
8 api::{internal::*, types::*, v1::graphql::services::ServicePage, *},
9 errors::{BlokliClientError, ErrorKind},
10};
11
12fn parse_chain_address_hex(value: &str) -> Result<ChainAddress> {
13 let bytes = hex::decode(value.trim_start_matches("0x")).map_err(|_| ErrorKind::ParseError)?;
14 bytes.try_into().map_err(|_| ErrorKind::ParseError.into())
15}
16
17impl BlokliClient {
18 async fn source_key_ids_for_safe(&self, safe_address: ChainAddress) -> Result<HashSet<i32>> {
19 let safe_response = self
20 .build_query(GraphQlQueries::query_safe_by(SafeSelectorInput::Address, &safe_address))?
21 .await?;
22
23 let safe: Option<Safe> = match response_to_data(safe_response)?.safe_by {
24 Some(safe_result) => {
25 let parsed_safes: Result<Vec<Safe>> = safe_result.into();
26 parsed_safes?.into_iter().next()
27 }
28 None => None,
29 };
30
31 let Some(safe) = safe else {
32 return Ok(HashSet::new());
33 };
34
35 let mut source_key_ids = HashSet::new();
36 for registered_node in safe.registered_nodes {
37 let node_address = parse_chain_address_hex(®istered_node)?;
38 let accounts_response = self
39 .build_query(GraphQlQueries::query_accounts(AccountSelector::Address(node_address)))?
40 .await?;
41 let accounts_result = response_to_data(accounts_response)?.accounts;
42 let accounts: Vec<Account> = {
43 let parsed_accounts: Result<Vec<Account>> = accounts_result.into();
44 parsed_accounts?
45 };
46 for account in accounts {
47 source_key_ids.insert(account.keyid);
48 }
49 }
50
51 Ok(source_key_ids)
52 }
53
54 async fn filter_channels_by_safe(
55 &self,
56 channels: ChannelsList,
57 safe_address: ChainAddress,
58 ) -> Result<ChannelsList> {
59 let source_key_ids = self.source_key_ids_for_safe(safe_address).await?;
60 let filtered_channels: Vec<Channel> = channels
61 .channels
62 .into_iter()
63 .filter(|channel| source_key_ids.contains(&channel.source))
64 .collect();
65
66 Ok(ChannelsList {
67 __typename: channels.__typename,
68 channels: filtered_channels,
69 })
70 }
71}
72
73#[cfg(feature = "curvy")]
74impl GraphQlQueries {
75 fn curvy_page_size(first: u32) -> Result<i32> {
76 let first = i32::try_from(first).map_err(|_| ErrorKind::InvalidInput("Curvy page size exceeds i32"))?;
77 if !(1..=1_000).contains(&first) {
78 return Err(ErrorKind::InvalidInput("Curvy page size must be between 1 and 1000").into());
79 }
80 Ok(first)
81 }
82
83 fn curvy_event_page_variables(
84 from_block: Option<u64>,
85 after: Option<CurvyEventCursor>,
86 first: u32,
87 ) -> Result<CurvyEventPageVariables> {
88 let first = Self::curvy_page_size(first)?;
89 Ok(CurvyEventPageVariables {
90 from_block: from_block.map(|block| Uint64(block.to_string())),
91 after,
92 first: Some(first),
93 })
94 }
95
96 pub fn query_curvy_pending_notes(
98 from_block: Option<u64>,
99 after: Option<CurvyEventCursor>,
100 first: u32,
101 ) -> Result<cynic::Operation<QueryCurvyPendingNotes, CurvyEventPageVariables>> {
102 Ok(QueryCurvyPendingNotes::build(Self::curvy_event_page_variables(
103 from_block, after, first,
104 )?))
105 }
106
107 pub fn query_curvy_committed_notes(
109 from_block: Option<u64>,
110 after: Option<CurvyEventCursor>,
111 first: u32,
112 ) -> Result<cynic::Operation<QueryCurvyCommittedNotes, CurvyEventPageVariables>> {
113 Ok(QueryCurvyCommittedNotes::build(Self::curvy_event_page_variables(
114 from_block, after, first,
115 )?))
116 }
117
118 pub fn query_curvy_committed_nullifiers(
120 from_block: Option<u64>,
121 after: Option<CurvyEventCursor>,
122 first: u32,
123 ) -> Result<cynic::Operation<QueryCurvyCommittedNullifiers, CurvyEventPageVariables>> {
124 Ok(QueryCurvyCommittedNullifiers::build(Self::curvy_event_page_variables(
125 from_block, after, first,
126 )?))
127 }
128
129 pub fn query_curvy_sync_checkpoint(
130 block_hash: Option<String>,
131 ) -> cynic::Operation<QueryCurvySyncCheckpoint, CurvyCheckpointVariables> {
132 QueryCurvySyncCheckpoint::build(CurvyCheckpointVariables {
133 block_hash: block_hash.map(Hex32),
134 })
135 }
136
137 fn curvy_sync_page_variables(
138 checkpoint: String,
139 from_index: Option<u64>,
140 first: u32,
141 ) -> Result<CurvySyncPageVariables> {
142 Ok(CurvySyncPageVariables {
143 checkpoint: Hex32(checkpoint),
144 from_index: from_index.map(|index| Uint64(index.to_string())),
145 first: Some(Self::curvy_page_size(first)?),
146 })
147 }
148
149 pub fn query_curvy_sync_notes(
150 checkpoint: String,
151 from_index: Option<u64>,
152 first: u32,
153 ) -> Result<cynic::Operation<QueryCurvySyncNotes, CurvySyncPageVariables>> {
154 Ok(QueryCurvySyncNotes::build(Self::curvy_sync_page_variables(
155 checkpoint, from_index, first,
156 )?))
157 }
158
159 pub fn query_curvy_sync_nullifiers(
160 checkpoint: String,
161 from_index: Option<u64>,
162 first: u32,
163 ) -> Result<cynic::Operation<QueryCurvySyncNullifiers, CurvySyncPageVariables>> {
164 Ok(QueryCurvySyncNullifiers::build(Self::curvy_sync_page_variables(
165 checkpoint, from_index, first,
166 )?))
167 }
168
169 pub fn query_curvy_shard_roots(
170 checkpoint: String,
171 from_index: Option<u64>,
172 first: u32,
173 ) -> Result<cynic::Operation<QueryCurvyShardRoots, CurvySyncPageVariables>> {
174 Ok(QueryCurvyShardRoots::build(Self::curvy_sync_page_variables(
175 checkpoint, from_index, first,
176 )?))
177 }
178
179 pub fn query_curvy_aggregator_state() -> cynic::Operation<QueryCurvyAggregatorState, ()> {
180 QueryCurvyAggregatorState::build(())
181 }
182
183 pub fn query_curvy_note_status(note_id: String) -> cynic::Operation<QueryCurvyNoteStatus, CurvyNoteIdVariables> {
184 QueryCurvyNoteStatus::build(CurvyNoteIdVariables {
185 note_id: Hex32(note_id),
186 })
187 }
188
189 pub fn query_curvy_valid_notes_root(
190 root: String,
191 ) -> cynic::Operation<QueryCurvyValidNotesRoot, CurvyRootVariables> {
192 QueryCurvyValidNotesRoot::build(CurvyRootVariables { root: Hex32(root) })
193 }
194
195 pub fn query_curvy_nullifier_spent(
196 nullifier: String,
197 ) -> cynic::Operation<QueryCurvyNullifierSpent, CurvyNullifierVariables> {
198 QueryCurvyNullifierSpent::build(CurvyNullifierVariables {
199 nullifier: Hex32(nullifier),
200 })
201 }
202
203 pub fn query_curvy_vault_fees() -> cynic::Operation<QueryCurvyVaultFees, ()> {
204 QueryCurvyVaultFees::build(())
205 }
206
207 pub fn query_curvy_aggregator_fees() -> cynic::Operation<QueryCurvyAggregatorFees, ()> {
208 QueryCurvyAggregatorFees::build(())
209 }
210
211 pub fn query_curvy_vault_token_count() -> cynic::Operation<QueryCurvyVaultTokenCount, ()> {
212 QueryCurvyVaultTokenCount::build(())
213 }
214
215 pub fn query_curvy_vault_token(
216 token_id: String,
217 ) -> cynic::Operation<QueryCurvyVaultToken, CurvyVaultTokenVariables> {
218 QueryCurvyVaultToken::build(CurvyVaultTokenVariables {
219 token_id: Uint256(token_id),
220 })
221 }
222
223 pub fn query_curvy_entry_portal_address(
224 owner_hash: String,
225 recovery: String,
226 ) -> cynic::Operation<QueryCurvyEntryPortalAddress, CurvyEntryPortalVariables> {
227 QueryCurvyEntryPortalAddress::build(CurvyEntryPortalVariables {
228 owner_hash: Uint256(owner_hash),
229 recovery,
230 })
231 }
232
233 pub fn query_curvy_exit_portal_address(
234 exit_address: String,
235 exit_chain_id: String,
236 recovery: String,
237 ) -> cynic::Operation<QueryCurvyExitPortalAddress, CurvyExitPortalVariables> {
238 QueryCurvyExitPortalAddress::build(CurvyExitPortalVariables {
239 exit_address,
240 exit_chain_id: Uint256(exit_chain_id),
241 recovery,
242 })
243 }
244
245 pub fn query_curvy_portal_registered(
246 portal_address: String,
247 ) -> cynic::Operation<QueryCurvyPortalRegistered, CurvyPortalVariables> {
248 QueryCurvyPortalRegistered::build(CurvyPortalVariables { portal_address })
249 }
250}
251
252impl GraphQlQueries {
253 pub fn count_accounts(selector: AccountSelector) -> cynic::Operation<QueryAccountCount, AccountVariables> {
255 QueryAccountCount::build(AccountVariables::from(selector))
256 }
257
258 pub fn query_accounts(selector: AccountSelector) -> cynic::Operation<QueryAccounts, AccountVariables> {
260 QueryAccounts::build(AccountVariables::from(selector))
261 }
262
263 pub fn query_native_balance(address: &ChainAddress) -> cynic::Operation<QueryNativeBalance, BalanceVariables> {
265 QueryNativeBalance::build(BalanceVariables {
266 address: address.encode_hex(),
267 token: None,
268 })
269 }
270
271 pub fn query_token_balance(
273 address: &ChainAddress,
274 token: Token,
275 ) -> cynic::Operation<QueryHoprBalance, BalanceVariables> {
276 QueryHoprBalance::build(BalanceVariables {
277 address: address.encode_hex(),
278 token: Some(token),
279 })
280 }
281
282 pub fn query_transaction_count(address: &ChainAddress) -> cynic::Operation<QueryTxCount, TxCountVariables> {
284 QueryTxCount::build(TxCountVariables {
285 address: address.encode_hex(),
286 })
287 }
288
289 pub fn query_safe_allowance(address: &ChainAddress) -> cynic::Operation<QuerySafeAllowance, BalanceVariables> {
291 QuerySafeAllowance::build(BalanceVariables {
292 address: address.encode_hex(),
293 token: None,
294 })
295 }
296
297 pub fn query_redeemed_stats(
299 selector: RedeemedStatsSelector,
300 ) -> cynic::Operation<QueryRedeemedStats, RedeemedStatsVariables> {
301 QueryRedeemedStats::build(RedeemedStatsVariables {
302 filter: match selector {
303 RedeemedStatsSelector::SafeAddress(safe) => RedeemedStatsFilter {
304 safe_address: Some(safe.encode_hex()),
305 node_address: None,
306 },
307 RedeemedStatsSelector::NodeAddress(node) => RedeemedStatsFilter {
308 safe_address: None,
309 node_address: Some(node.encode_hex()),
310 },
311 RedeemedStatsSelector::SafeAndNodeAddress {
312 safe_address,
313 node_address,
314 } => RedeemedStatsFilter {
315 safe_address: Some(safe_address.encode_hex()),
316 node_address: Some(node_address.encode_hex()),
317 },
318 },
319 })
320 }
321
322 pub fn query_safe_by(
324 selector: SafeSelectorInput,
325 address: &ChainAddress,
326 ) -> cynic::Operation<QuerySafeBy, SafeByVariables> {
327 QuerySafeBy::build(SafeByVariables {
328 selector,
329 address: address.encode_hex(),
330 })
331 }
332
333 pub fn query_module_address_prediction(
335 input: ModulePredictionInput,
336 ) -> cynic::Operation<QueryModuleAddress, ModuleAddressVariables> {
337 QueryModuleAddress::build(ModuleAddressVariables {
338 nonce: Uint64(input.nonce.to_string()),
339 owner: input.owner.encode_hex(),
340 safe_address: input.safe_address.encode_hex(),
341 })
342 }
343
344 #[deprecated(note = "Use query_channel_stats instead, which returns both count and total wxHOPR balance.")]
346 pub fn query_channel_count(selector: ChannelSelector) -> cynic::Operation<QueryChannelCount, ChannelsVariables> {
347 QueryChannelCount::build(ChannelsVariables::from(selector))
348 }
349
350 pub fn query_channel_stats(
352 selector: ChannelSelector,
353 ) -> cynic::Operation<QueryChannelStats, ChannelStatsVariables> {
354 QueryChannelStats::build(ChannelStatsVariables::from(selector))
355 }
356
357 pub fn query_channels(selector: ChannelSelector) -> cynic::Operation<QueryChannels, ChannelsVariables> {
359 QueryChannels::build(ChannelsVariables::from(selector))
360 }
361
362 pub fn query_safes_balance(
364 owner_address: Option<ChainAddress>,
365 ) -> cynic::Operation<QuerySafesBalance, SafesBalanceVariables> {
366 QuerySafesBalance::build(SafesBalanceVariables {
367 owner_address: owner_address.map(hex::encode),
368 })
369 }
370
371 pub fn count_services(selector: ServiceSelector) -> cynic::Operation<QueryServiceCount, ServiceVariables> {
373 QueryServiceCount::build(ServiceVariables::from(selector))
374 }
375
376 pub fn query_services(
378 selector: ServiceSelector,
379 after: Option<Uint64>,
380 watermark: Option<Uint64>,
381 live_only: bool,
382 ) -> cynic::Operation<QueryServices, ServicePageVariables> {
383 QueryServices::build(ServicePageVariables::new(selector, after, watermark, live_only))
384 }
385
386 pub fn query_service_types(
388 service_type: Option<ServiceTypeId>,
389 ) -> cynic::Operation<QueryServiceTypes, ServiceTypeVariables> {
390 QueryServiceTypes::build(ServiceTypeVariables::from(service_type))
391 }
392
393 pub fn query_service_registry_config() -> cynic::Operation<QueryServiceRegistryConfig, ()> {
395 QueryServiceRegistryConfig::build(())
396 }
397
398 pub fn query_transaction(id: TxId) -> cynic::Operation<QueryTransaction, TransactionsVariables> {
400 QueryTransaction::build(TransactionsVariables { id: id.into() })
401 }
402
403 pub fn query_chain_info() -> cynic::Operation<QueryChainInfo, ()> {
405 QueryChainInfo::build(())
406 }
407
408 pub fn query_version() -> cynic::Operation<QueryVersion, ()> {
410 QueryVersion::build(())
411 }
412
413 pub fn query_health() -> cynic::Operation<QueryHealth, ()> {
415 QueryHealth::build(())
416 }
417
418 pub fn query_compatibility() -> cynic::Operation<QueryCompatibility, ()> {
420 QueryCompatibility::build(())
421 }
422}
423
424#[async_trait::async_trait]
425impl BlokliQueryClient for BlokliClient {
426 #[cfg(feature = "curvy")]
427 #[tracing::instrument(level = "debug", skip(self))]
428 async fn query_curvy_pending_notes(
429 &self,
430 from_block: Option<u64>,
431 after: Option<CurvyEventCursor>,
432 first: u32,
433 ) -> Result<CurvyPendingNotes> {
434 let operation = GraphQlQueries::query_curvy_pending_notes(from_block, after, first)?;
435 let response = self.build_query(operation)?.await?;
436 response_to_data(response)?.curvy_pending_notes.into()
437 }
438
439 #[cfg(feature = "curvy")]
440 #[tracing::instrument(level = "debug", skip(self))]
441 async fn query_curvy_committed_notes(
442 &self,
443 from_block: Option<u64>,
444 after: Option<CurvyEventCursor>,
445 first: u32,
446 ) -> Result<CurvyCommittedNotes> {
447 let operation = GraphQlQueries::query_curvy_committed_notes(from_block, after, first)?;
448 let response = self.build_query(operation)?.await?;
449 response_to_data(response)?.curvy_committed_notes.into()
450 }
451
452 #[cfg(feature = "curvy")]
453 #[tracing::instrument(level = "debug", skip(self))]
454 async fn query_curvy_committed_nullifiers(
455 &self,
456 from_block: Option<u64>,
457 after: Option<CurvyEventCursor>,
458 first: u32,
459 ) -> Result<CurvyCommittedNullifiers> {
460 let operation = GraphQlQueries::query_curvy_committed_nullifiers(from_block, after, first)?;
461 let response = self.build_query(operation)?.await?;
462 response_to_data(response)?.curvy_committed_nullifiers.into()
463 }
464
465 #[cfg(feature = "curvy")]
466 #[tracing::instrument(level = "debug", skip(self))]
467 async fn query_curvy_sync_checkpoint(&self, block_hash: Option<String>) -> Result<CurvySyncCheckpoint> {
468 let response = self
469 .build_query(GraphQlQueries::query_curvy_sync_checkpoint(block_hash))?
470 .await?;
471 response_to_data(response)?.curvy_sync_checkpoint.into()
472 }
473
474 #[cfg(feature = "curvy")]
475 #[tracing::instrument(level = "debug", skip(self))]
476 async fn query_curvy_sync_notes(
477 &self,
478 checkpoint: String,
479 from_index: Option<u64>,
480 first: u32,
481 ) -> Result<CurvySyncNotePage> {
482 let operation = GraphQlQueries::query_curvy_sync_notes(checkpoint, from_index, first)?;
483 let response = self.build_query(operation)?.await?;
484 response_to_data(response)?.curvy_sync_notes.into()
485 }
486
487 #[cfg(feature = "curvy")]
488 #[tracing::instrument(level = "debug", skip(self))]
489 async fn query_curvy_sync_nullifiers(
490 &self,
491 checkpoint: String,
492 from_index: Option<u64>,
493 first: u32,
494 ) -> Result<CurvySyncNullifierPage> {
495 let operation = GraphQlQueries::query_curvy_sync_nullifiers(checkpoint, from_index, first)?;
496 let response = self.build_query(operation)?.await?;
497 response_to_data(response)?.curvy_sync_nullifiers.into()
498 }
499
500 #[cfg(feature = "curvy")]
501 #[tracing::instrument(level = "debug", skip(self))]
502 async fn query_curvy_shard_roots(
503 &self,
504 checkpoint: String,
505 from_index: Option<u64>,
506 first: u32,
507 ) -> Result<CurvyShardRootPage> {
508 let operation = GraphQlQueries::query_curvy_shard_roots(checkpoint, from_index, first)?;
509 let response = self.build_query(operation)?.await?;
510 response_to_data(response)?.curvy_shard_roots.into()
511 }
512
513 #[cfg(feature = "curvy")]
514 #[tracing::instrument(level = "debug", skip(self))]
515 async fn query_curvy_aggregator_state(&self) -> Result<CurvyAggregatorState> {
516 let response = self
517 .build_query(GraphQlQueries::query_curvy_aggregator_state())?
518 .await?;
519 response_to_data(response)?.curvy_aggregator_state.into()
520 }
521
522 #[cfg(feature = "curvy")]
523 #[tracing::instrument(level = "debug", skip(self))]
524 async fn query_curvy_note_status(&self, note_id: String) -> Result<CurvyNoteStatus> {
525 let response = self
526 .build_query(GraphQlQueries::query_curvy_note_status(note_id))?
527 .await?;
528 response_to_data(response)?.curvy_note_status.into()
529 }
530
531 #[cfg(feature = "curvy")]
532 #[tracing::instrument(level = "debug", skip(self))]
533 async fn query_curvy_valid_notes_root(&self, root: String) -> Result<bool> {
534 let response = self
535 .build_query(GraphQlQueries::query_curvy_valid_notes_root(root))?
536 .await?;
537 let value: Result<CurvyBooleanValue> = response_to_data(response)?.curvy_valid_notes_root.into();
538 Ok(value?.value)
539 }
540
541 #[cfg(feature = "curvy")]
542 #[tracing::instrument(level = "debug", skip(self))]
543 async fn query_curvy_nullifier_spent(&self, nullifier: String) -> Result<bool> {
544 let response = self
545 .build_query(GraphQlQueries::query_curvy_nullifier_spent(nullifier))?
546 .await?;
547 let value: Result<CurvyBooleanValue> = response_to_data(response)?.curvy_nullifier_spent.into();
548 Ok(value?.value)
549 }
550
551 #[cfg(feature = "curvy")]
552 #[tracing::instrument(level = "debug", skip(self))]
553 async fn query_curvy_vault_fees(&self) -> Result<CurvyVaultFees> {
554 let response = self.build_query(GraphQlQueries::query_curvy_vault_fees())?.await?;
555 response_to_data(response)?.curvy_vault_fees.into()
556 }
557
558 #[cfg(feature = "curvy")]
559 #[tracing::instrument(level = "debug", skip(self))]
560 async fn query_curvy_aggregator_fees(&self) -> Result<CurvyAggregatorFees> {
561 let response = self.build_query(GraphQlQueries::query_curvy_aggregator_fees())?.await?;
562 response_to_data(response)?.curvy_aggregator_fees.into()
563 }
564
565 #[cfg(feature = "curvy")]
566 #[tracing::instrument(level = "debug", skip(self))]
567 async fn query_curvy_vault_token_count(&self) -> Result<CurvyVaultTokenCount> {
568 let response = self
569 .build_query(GraphQlQueries::query_curvy_vault_token_count())?
570 .await?;
571 response_to_data(response)?.curvy_vault_token_count.into()
572 }
573
574 #[cfg(feature = "curvy")]
575 #[tracing::instrument(level = "debug", skip(self))]
576 async fn query_curvy_vault_token(&self, token_id: String) -> Result<CurvyVaultToken> {
577 let response = self
578 .build_query(GraphQlQueries::query_curvy_vault_token(token_id))?
579 .await?;
580 response_to_data(response)?.curvy_vault_token.into()
581 }
582
583 #[cfg(feature = "curvy")]
584 #[tracing::instrument(level = "debug", skip(self))]
585 async fn query_curvy_entry_portal_address(&self, owner_hash: String, recovery: String) -> Result<String> {
586 let response = self
587 .build_query(GraphQlQueries::query_curvy_entry_portal_address(owner_hash, recovery))?
588 .await?;
589 let value: Result<CurvyAddress> = response_to_data(response)?.curvy_entry_portal_address.into();
590 Ok(value?.address)
591 }
592
593 #[cfg(feature = "curvy")]
594 #[tracing::instrument(level = "debug", skip(self))]
595 async fn query_curvy_exit_portal_address(
596 &self,
597 exit_address: String,
598 exit_chain_id: String,
599 recovery: String,
600 ) -> Result<String> {
601 let response = self
602 .build_query(GraphQlQueries::query_curvy_exit_portal_address(
603 exit_address,
604 exit_chain_id,
605 recovery,
606 ))?
607 .await?;
608 let value: Result<CurvyAddress> = response_to_data(response)?.curvy_exit_portal_address.into();
609 Ok(value?.address)
610 }
611
612 #[cfg(feature = "curvy")]
613 #[tracing::instrument(level = "debug", skip(self))]
614 async fn query_curvy_portal_registered(&self, portal_address: String) -> Result<bool> {
615 let response = self
616 .build_query(GraphQlQueries::query_curvy_portal_registered(portal_address))?
617 .await?;
618 let value: Result<CurvyBooleanValue> = response_to_data(response)?.curvy_portal_registered.into();
619 Ok(value?.value)
620 }
621
622 #[tracing::instrument(level = "debug", skip(self), fields(?selector))]
623 async fn count_accounts(&self, selector: AccountSelector) -> Result<u32> {
624 let resp = self.build_query(GraphQlQueries::count_accounts(selector))?.await?;
625
626 response_to_data(resp)?.account_count.into()
627 }
628
629 #[tracing::instrument(level = "debug", skip(self), fields(?selector))]
630 async fn query_accounts(&self, selector: AccountSelector) -> Result<Vec<Account>> {
631 if matches!(selector, AccountSelector::Any) {
632 return Err(ErrorKind::InvalidInput("filter must be specified on account query").into());
633 }
634
635 let resp = self.build_query(GraphQlQueries::query_accounts(selector))?.await?;
636
637 response_to_data(resp)?.accounts.into()
638 }
639
640 #[tracing::instrument(level = "debug", skip(self), fields(address = hex::encode(address)))]
641 async fn query_native_balance(&self, address: &ChainAddress) -> Result<NativeBalance> {
642 let resp = self.build_query(GraphQlQueries::query_native_balance(address))?.await?;
643
644 response_to_data(resp)?.native_balance.into()
645 }
646
647 #[tracing::instrument(level = "debug", skip(self), fields(address = hex::encode(address)))]
648 async fn query_token_balance(&self, address: &ChainAddress, token: Token) -> Result<HoprBalance> {
649 let resp = self
650 .build_query(GraphQlQueries::query_token_balance(address, token))?
651 .await?;
652
653 response_to_data(resp)?.hopr_balance.into()
654 }
655
656 #[tracing::instrument(level = "debug", skip(self), fields(address = hex::encode(address)))]
657 async fn query_transaction_count(&self, address: &ChainAddress) -> Result<u64> {
658 let resp = self
659 .build_query(GraphQlQueries::query_transaction_count(address))?
660 .await?;
661
662 response_to_data(resp)?.transaction_count.into()
663 }
664
665 #[tracing::instrument(level = "debug", skip(self), fields(address = hex::encode(address)))]
666 async fn query_safe_allowance(&self, address: &ChainAddress) -> Result<SafeHoprAllowance> {
667 let resp = self.build_query(GraphQlQueries::query_safe_allowance(address))?.await?;
668
669 response_to_data(resp)?.safe_hopr_allowance.into()
670 }
671
672 #[tracing::instrument(level = "debug", skip(self), fields(?selector))]
673 async fn query_redeemed_stats(&self, selector: RedeemedStatsSelector) -> Result<RedeemedStats> {
674 let resp = self
675 .build_query(GraphQlQueries::query_redeemed_stats(selector))?
676 .await?;
677
678 response_to_data(resp)?.ticket_redemption_stats.into()
679 }
680
681 #[tracing::instrument(level = "debug", skip(self), fields(?selector))]
682 async fn query_safe(&self, selector: SafeSelector) -> Result<Vec<Safe>> {
683 let (gql_selector, addr) = match selector {
684 SafeSelector::SafeAddress(addr) => (SafeSelectorInput::Address, addr),
685 SafeSelector::Owner(addr) => (SafeSelectorInput::Owner, addr),
686 SafeSelector::ChainKey(addr) => (SafeSelectorInput::ChainKey, addr),
687 SafeSelector::RegisteredNode(addr) => (SafeSelectorInput::RegisteredNode, addr),
688 };
689
690 let res = self
691 .build_query(GraphQlQueries::query_safe_by(gql_selector, &addr))?
692 .await?;
693
694 match response_to_data(res)?.safe_by {
695 Some(result) => result.into(),
696 None => Ok(Vec::new()),
697 }
698 }
699
700 async fn query_module_address_prediction(&self, input: ModulePredictionInput) -> Result<ChainAddress> {
701 let resp = self
702 .build_query(GraphQlQueries::query_module_address_prediction(input))?
703 .await?;
704
705 response_to_data(resp)?.calculate_module_address.into()
706 }
707
708 #[allow(deprecated)]
709 #[tracing::instrument(level = "debug", skip(self), fields(?selector))]
710 async fn count_channels(&self, selector: ChannelSelector) -> Result<u32> {
711 if selector.safe_address.is_some() {
712 let channels = self.query_channels(selector).await?;
713 return u32::try_from(channels.channels.len()).map_err(|_| ErrorKind::ParseError.into());
714 }
715
716 let resp = self.build_query(GraphQlQueries::query_channel_count(selector))?.await?;
717
718 response_to_data(resp)?.channel_count.into()
719 }
720
721 #[tracing::instrument(level = "debug", skip(self), fields(?selector))]
722 async fn query_channel_stats(&self, selector: ChannelSelector) -> Result<ChannelStats> {
723 let resp = self.build_query(GraphQlQueries::query_channel_stats(selector))?.await?;
724
725 response_to_data(resp)?.channel_stats.into()
726 }
727
728 #[tracing::instrument(level = "debug", skip(self), fields(?selector))]
729 async fn query_channels(&self, selector: ChannelSelector) -> Result<ChannelsList> {
730 if selector.filter.is_none() && selector.safe_address.is_none() {
731 return Err(ErrorKind::InvalidInput("at least one filter must be specified on channel query").into());
732 }
733
734 let safe_address = selector.safe_address;
735 let resp = self.build_query(GraphQlQueries::query_channels(selector))?.await?;
736 let channels_result = response_to_data(resp)?.channels;
737 let channels: ChannelsList = {
738 let parsed_channels: Result<ChannelsList> = channels_result.into();
739 parsed_channels?
740 };
741
742 if let Some(safe_address) = safe_address {
743 return self.filter_channels_by_safe(channels, safe_address).await;
744 }
745
746 Ok(channels)
747 }
748
749 #[tracing::instrument(level = "debug", skip(self), fields(?selector))]
750 async fn count_services(&self, selector: ServiceSelector) -> Result<u32> {
751 let resp = self.build_query(GraphQlQueries::count_services(selector))?.await?;
752
753 response_to_data(resp)?.service_count.into()
754 }
755
756 #[tracing::instrument(level = "debug", skip(self), fields(?selector))]
757 async fn query_services(&self, selector: ServiceSelector) -> Result<Vec<ServiceEntry>> {
758 let mut services = Vec::new();
759 let mut after = None;
760 let mut watermark = None;
761
762 loop {
763 let resp = self
764 .build_query(GraphQlQueries::query_services(
765 selector,
766 after,
767 watermark.clone(),
768 false,
769 ))?
770 .await?;
771 let page = Result::<ServicePage>::from(response_to_data(resp)?.services)?;
772 services.extend(page.services);
773 watermark = Some(page.watermark);
774 after = page.next_cursor;
775 if after.is_none() {
776 return Ok(services);
777 }
778 }
779 }
780
781 #[tracing::instrument(level = "debug", skip(self), fields(?selector))]
782 async fn query_live_services(&self, selector: ServiceSelector) -> Result<Vec<ServiceEntry>> {
783 let mut services = Vec::new();
784 let mut after = None;
785 let mut watermark = None;
786 loop {
787 let resp = self
788 .build_query(GraphQlQueries::query_services(selector, after, watermark.clone(), true))?
789 .await?;
790 let page = Result::<ServicePage>::from(response_to_data(resp)?.services)?;
791 services.extend(page.services);
792 watermark = Some(page.watermark);
793 after = page.next_cursor;
794 if after.is_none() {
795 return Ok(services);
796 }
797 }
798 }
799
800 #[tracing::instrument(level = "debug", skip(self))]
801 async fn query_service_types(&self, service_type: Option<ServiceTypeId>) -> Result<Vec<ServiceTypeInfo>> {
802 let resp = self
803 .build_query(GraphQlQueries::query_service_types(service_type))?
804 .await?;
805
806 response_to_data(resp)?.service_types.into()
807 }
808
809 #[tracing::instrument(level = "debug", skip(self))]
810 async fn query_service_registry_config(&self) -> Result<ServiceRegistryConfig> {
811 let resp = self
812 .build_query(GraphQlQueries::query_service_registry_config())?
813 .await?;
814
815 response_to_data(resp)?.service_registry_config.into()
816 }
817
818 #[tracing::instrument(level = "debug", skip(self))]
819 async fn query_transaction_status(&self, tx_id: TxId) -> Result<Transaction> {
820 let resp = self.build_query(GraphQlQueries::query_transaction(tx_id))?.await?;
821
822 response_to_data(resp)?
823 .transaction
824 .ok_or::<BlokliClientError>(ErrorKind::NoData.into())?
825 .into()
826 }
827
828 #[tracing::instrument(level = "debug", skip(self))]
829 async fn query_chain_info(&self) -> Result<ChainInfo> {
830 let resp = self.build_query(GraphQlQueries::query_chain_info())?.await?;
831
832 response_to_data(resp)?.chain_info.into()
833 }
834
835 #[tracing::instrument(level = "debug", skip(self))]
836 async fn query_version(&self) -> Result<String> {
837 let resp = self.build_query(GraphQlQueries::query_version())?.await?;
838
839 response_to_data(resp).map(|data| data.version)
840 }
841
842 #[tracing::instrument(level = "debug", skip(self))]
843 async fn query_health(&self) -> Result<String> {
844 let resp = self.build_query(GraphQlQueries::query_health())?.await?;
845
846 response_to_data(resp).map(|data| data.health)
847 }
848
849 #[tracing::instrument(level = "debug", skip(self))]
850 async fn query_compatibility(&self) -> Result<Compatibility> {
851 let resp = self.build_query(GraphQlQueries::query_compatibility())?.await?;
852
853 response_to_data(resp).map(|data| data.compatibility)
854 }
855
856 #[tracing::instrument(level = "debug", skip(self), fields(?owner_address))]
857 async fn query_safes_balance(&self, owner_address: Option<ChainAddress>) -> Result<SafesBalance> {
858 let resp = self
859 .build_query(GraphQlQueries::query_safes_balance(owner_address))?
860 .await?;
861
862 response_to_data(resp)?.safes_balance.into()
863 }
864}
865
866#[cfg(all(test, feature = "curvy"))]
867mod tests {
868 use serde_json::json;
869
870 use super::GraphQlQueries;
871 use crate::api::types::CurvyEventCursor;
872
873 #[test]
874 fn curvy_pending_query_serializes_structured_exclusive_cursor() {
875 let operation =
876 GraphQlQueries::query_curvy_pending_notes(Some(10), Some(CurvyEventCursor::new(11, 2, 3, 4)), 1000)
877 .expect("valid Curvy page");
878 let serialized = serde_json::to_value(operation).expect("operation should serialize");
879
880 assert_eq!(
881 serialized["variables"],
882 json!({
883 "fromBlock": "10",
884 "after": {
885 "block": "11",
886 "transactionIndex": "2",
887 "logIndex": "3",
888 "eventItemIndex": "4",
889 "blockHash": null,
890 },
891 "first": 1000,
892 })
893 );
894 }
895
896 #[test]
897 fn curvy_queries_reject_invalid_page_sizes() {
898 assert!(GraphQlQueries::query_curvy_pending_notes(None, None, 0).is_err());
899 assert!(GraphQlQueries::query_curvy_pending_notes(None, None, 1001).is_err());
900 assert!(GraphQlQueries::query_curvy_sync_notes("checkpoint".to_owned(), None, u32::MAX).is_err());
901 }
902
903 #[test]
904 fn curvy_query_builders_serialize_arguments() {
905 let committed = serde_json::to_value(
906 GraphQlQueries::query_curvy_committed_notes(Some(7), None, 25).expect("valid committed-notes query"),
907 )
908 .expect("operation should serialize");
909 assert_eq!(
910 committed["variables"],
911 json!({ "fromBlock": "7", "after": null, "first": 25 })
912 );
913
914 let nullifiers = serde_json::to_value(
915 GraphQlQueries::query_curvy_committed_nullifiers(None, None, 30).expect("valid committed-nullifiers query"),
916 )
917 .expect("operation should serialize");
918 assert_eq!(
919 nullifiers["variables"],
920 json!({ "fromBlock": null, "after": null, "first": 30 })
921 );
922
923 let checkpoint = serde_json::to_value(GraphQlQueries::query_curvy_sync_checkpoint(Some("0x01".to_owned())))
924 .expect("operation should serialize");
925 assert_eq!(checkpoint["variables"], json!({ "blockHash": "0x01" }));
926
927 let sync_notes = serde_json::to_value(
928 GraphQlQueries::query_curvy_sync_notes("0x02".to_owned(), Some(3), 40).expect("valid sync-notes query"),
929 )
930 .expect("operation should serialize");
931 assert_eq!(
932 sync_notes["variables"],
933 json!({ "checkpoint": "0x02", "fromIndex": "3", "first": 40 })
934 );
935
936 let sync_nullifiers = serde_json::to_value(
937 GraphQlQueries::query_curvy_sync_nullifiers("0x03".to_owned(), Some(4), 50)
938 .expect("valid sync-nullifiers query"),
939 )
940 .expect("operation should serialize");
941 assert_eq!(
942 sync_nullifiers["variables"],
943 json!({ "checkpoint": "0x03", "fromIndex": "4", "first": 50 })
944 );
945
946 let shard_roots = serde_json::to_value(
947 GraphQlQueries::query_curvy_shard_roots("0x04".to_owned(), Some(5), 60).expect("valid shard-roots query"),
948 )
949 .expect("operation should serialize");
950 assert_eq!(
951 shard_roots["variables"],
952 json!({ "checkpoint": "0x04", "fromIndex": "5", "first": 60 })
953 );
954
955 let note_status = serde_json::to_value(GraphQlQueries::query_curvy_note_status("0x05".to_owned()))
956 .expect("operation should serialize");
957 assert_eq!(note_status["variables"], json!({ "noteId": "0x05" }));
958
959 let valid_root = serde_json::to_value(GraphQlQueries::query_curvy_valid_notes_root("0x06".to_owned()))
960 .expect("operation should serialize");
961 assert_eq!(valid_root["variables"], json!({ "root": "0x06" }));
962
963 let spent = serde_json::to_value(GraphQlQueries::query_curvy_nullifier_spent("0x07".to_owned()))
964 .expect("operation should serialize");
965 assert_eq!(spent["variables"], json!({ "nullifier": "0x07" }));
966
967 let token = serde_json::to_value(GraphQlQueries::query_curvy_vault_token("8".to_owned()))
968 .expect("operation should serialize");
969 assert_eq!(token["variables"], json!({ "tokenId": "8" }));
970
971 let entry = serde_json::to_value(GraphQlQueries::query_curvy_entry_portal_address(
972 "9".to_owned(),
973 "0x10".to_owned(),
974 ))
975 .expect("operation should serialize");
976 assert_eq!(entry["variables"], json!({ "ownerHash": "9", "recovery": "0x10" }));
977
978 let exit = serde_json::to_value(GraphQlQueries::query_curvy_exit_portal_address(
979 "0x11".to_owned(),
980 "12".to_owned(),
981 "0x13".to_owned(),
982 ))
983 .expect("operation should serialize");
984 assert_eq!(
985 exit["variables"],
986 json!({ "exitAddress": "0x11", "exitChainId": "12", "recovery": "0x13" })
987 );
988
989 let portal = serde_json::to_value(GraphQlQueries::query_curvy_portal_registered("0x14".to_owned()))
990 .expect("operation should serialize");
991 assert_eq!(portal["variables"], json!({ "portalAddress": "0x14" }));
992
993 for operation in [
994 serde_json::to_value(GraphQlQueries::query_curvy_aggregator_state()),
995 serde_json::to_value(GraphQlQueries::query_curvy_vault_fees()),
996 serde_json::to_value(GraphQlQueries::query_curvy_aggregator_fees()),
997 serde_json::to_value(GraphQlQueries::query_curvy_vault_token_count()),
998 ] {
999 assert_eq!(
1000 operation.expect("operation should serialize")["variables"],
1001 serde_json::Value::Null
1002 );
1003 }
1004 }
1005}