Skip to main content

blokli_client/client/
queries.rs

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(&registered_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    /// Paginated pending Curvy note query.
97    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    /// Paginated committed Curvy note query.
108    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    /// Paginated committed Curvy nullifier query.
119    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    /// `AccountCount` GraphQL query.
254    pub fn count_accounts(selector: AccountSelector) -> cynic::Operation<QueryAccountCount, AccountVariables> {
255        QueryAccountCount::build(AccountVariables::from(selector))
256    }
257
258    /// `Accounts` GraphQL query.
259    pub fn query_accounts(selector: AccountSelector) -> cynic::Operation<QueryAccounts, AccountVariables> {
260        QueryAccounts::build(AccountVariables::from(selector))
261    }
262
263    /// `NativeBalance` GraphQL query.
264    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    /// `TokenBalance` GraphQL query.
272    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    /// `TransactionCount` GraphQL query.
283    pub fn query_transaction_count(address: &ChainAddress) -> cynic::Operation<QueryTxCount, TxCountVariables> {
284        QueryTxCount::build(TxCountVariables {
285            address: address.encode_hex(),
286        })
287    }
288
289    /// `SafeAllowance` GraphQL query.
290    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    /// `TicketRedemptionStats` GraphQL query.
298    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    /// `safeBy` GraphQL query.
323    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    /// `ModuleAddressPrediction` GraphQL query.
334    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    /// `ChannelCount` GraphQL query.
345    #[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    /// `ChannelStats` GraphQL query.
351    pub fn query_channel_stats(
352        selector: ChannelSelector,
353    ) -> cynic::Operation<QueryChannelStats, ChannelStatsVariables> {
354        QueryChannelStats::build(ChannelStatsVariables::from(selector))
355    }
356
357    /// `Channels` GraphQL query.
358    pub fn query_channels(selector: ChannelSelector) -> cynic::Operation<QueryChannels, ChannelsVariables> {
359        QueryChannels::build(ChannelsVariables::from(selector))
360    }
361
362    /// `SafesBalance` GraphQL query.
363    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    /// `ServiceCount` GraphQL query.
372    pub fn count_services(selector: ServiceSelector) -> cynic::Operation<QueryServiceCount, ServiceVariables> {
373        QueryServiceCount::build(ServiceVariables::from(selector))
374    }
375
376    /// `Services` GraphQL query.
377    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    /// `ServiceTypes` GraphQL query.
387    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    /// `ServiceRegistryConfig` GraphQL query.
394    pub fn query_service_registry_config() -> cynic::Operation<QueryServiceRegistryConfig, ()> {
395        QueryServiceRegistryConfig::build(())
396    }
397
398    /// `Transaction` GraphQL query.
399    pub fn query_transaction(id: TxId) -> cynic::Operation<QueryTransaction, TransactionsVariables> {
400        QueryTransaction::build(TransactionsVariables { id: id.into() })
401    }
402
403    /// `ChainInfo` GraphQL query.
404    pub fn query_chain_info() -> cynic::Operation<QueryChainInfo, ()> {
405        QueryChainInfo::build(())
406    }
407
408    /// `Version` GraphQL query.
409    pub fn query_version() -> cynic::Operation<QueryVersion, ()> {
410        QueryVersion::build(())
411    }
412
413    /// `Health` GraphQL query.
414    pub fn query_health() -> cynic::Operation<QueryHealth, ()> {
415        QueryHealth::build(())
416    }
417
418    /// `Compatibility` GraphQL query.
419    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}