blueprint_client_eigenlayer/
client.rs

1use crate::error::Result;
2use alloy_primitives::{Address, Bytes, U256};
3use alloy_provider::{Provider, RootProvider};
4use blueprint_evm_extra::util::{get_provider_http, get_wallet_provider_http};
5use blueprint_runner::config::BlueprintEnvironment;
6use blueprint_std::collections::HashMap;
7use eigensdk::client_avsregistry::reader::AvsRegistryReader;
8use eigensdk::common::get_ws_provider;
9use eigensdk::utils::rewardsv2::middleware::registry_coordinator::RegistryCoordinator;
10use eigensdk::utils::rewardsv2::middleware::stake_registry::{IStakeRegistry, StakeRegistry};
11use eigensdk::utils::slashing::core::allocation_manager::{
12    AllocationManager, IAllocationManagerTypes,
13};
14use eigensdk::utils::slashing::core::delegation_manager::DelegationManager;
15use eigensdk::utils::slashing::middleware::operator_state_retriever::OperatorStateRetriever;
16use num_bigint::BigInt;
17
18/// Client that provides access to EigenLayer utility functions through the use of the [`BlueprintEnvironment`].
19#[derive(Clone)]
20pub struct EigenlayerClient {
21    pub config: BlueprintEnvironment,
22}
23
24impl EigenlayerClient {
25    /// Creates a new [`EigenlayerClient`].
26    #[must_use]
27    pub fn new(config: BlueprintEnvironment) -> Self {
28        Self { config }
29    }
30
31    /// Get the [`BlueprintEnvironment`] for this client
32    #[must_use]
33    pub fn config(&self) -> &BlueprintEnvironment {
34        &self.config
35    }
36
37    /// Get the provider for this client's http endpoint
38    ///
39    /// # Returns
40    /// - [`The HTTP provider`](RootProvider)
41    #[must_use]
42    pub fn get_provider_http(&self) -> RootProvider {
43        get_provider_http(self.config.http_rpc_endpoint.clone())
44    }
45
46    /// Get the provider for this client's http endpoint with the specified [`EthereumWallet`]
47    ///
48    /// # Returns
49    /// - [`The HTTP wallet provider`](RootProvider)
50    ///
51    /// [`EthereumWallet`]: alloy_network::EthereumWallet
52    #[must_use]
53    pub fn get_wallet_provider_http(&self, wallet: alloy_network::EthereumWallet) -> RootProvider {
54        get_wallet_provider_http(self.config.http_rpc_endpoint.clone(), wallet)
55    }
56
57    /// Get the provider for this client's websocket endpoint
58    ///
59    /// # Errors
60    ///
61    /// * Bad WS URL
62    ///
63    /// # Returns
64    /// - [`The WS provider`](RootProvider<BoxTransport>)
65    pub async fn get_provider_ws(&self) -> Result<RootProvider> {
66        get_ws_provider(self.config.ws_rpc_endpoint.as_str())
67            .await
68            .map_err(Into::into)
69    }
70
71    // TODO: Slashing contracts equivalent
72    // /// Get the slasher address from the `DelegationManager` contract
73    // ///
74    // /// # Returns
75    // /// - [`Address`] - The slasher address
76    // ///
77    // /// # Errors
78    // /// - [`Error::AlloyContract`] - If the call to the contract fails (i.e. the contract doesn't exist at the given address)
79    // pub async fn get_slasher_address(&self, eigen_pod_manager_address: Address) -> Result<Address> {
80    //     let provider = self.get_provider_http();
81    //     let eigen_pod_manager =
82    //         EigenPodManager::EigenPodManagerInstance::new(eigen_pod_manager_address, provider);
83    //     eigen_pod_manager
84    //         .slasher()
85    //         .call()
86    //         .await
87    //         .map(|a| a._0)
88    //         .map_err(Into::into)
89    // }
90
91    /// Provides a reader for the AVS registry.
92    ///
93    /// # Errors
94    ///
95    /// * The [`BlueprintEnvironment`] is not configured for Eigenlayer
96    /// * See [`AvsRegistryChainReader::new()`]
97    ///
98    /// [`AvsRegistryChainReader::new()`]: eigensdk::client_avsregistry::reader::AvsRegistryChainReader::new
99    pub async fn avs_registry_reader(
100        &self,
101    ) -> Result<eigensdk::client_avsregistry::reader::AvsRegistryChainReader> {
102        let http_rpc_endpoint = self.config.http_rpc_endpoint.clone();
103        let contract_addresses = self.config.protocol_settings.eigenlayer()?;
104        let registry_coordinator_address = contract_addresses.registry_coordinator_address;
105        let operator_state_retriever_address = contract_addresses.operator_state_retriever_address;
106        eigensdk::client_avsregistry::reader::AvsRegistryChainReader::new(
107            registry_coordinator_address,
108            operator_state_retriever_address,
109            http_rpc_endpoint.to_string(),
110        )
111        .await
112        .map_err(Into::into)
113    }
114
115    /// Provides a writer for the AVS registry.
116    ///
117    /// # Errors
118    ///
119    /// * The [`BlueprintEnvironment`] is not configured for Eigenlayer
120    /// * See [`AvsRegistryChainWriter::build_avs_registry_chain_writer()`]
121    ///
122    /// [`AvsRegistryChainWriter::build_avs_registry_chain_writer()`]: eigensdk::client_avsregistry::writer::AvsRegistryChainWriter::build_avs_registry_chain_writer
123    pub async fn avs_registry_writer(
124        &self,
125        private_key: String,
126    ) -> Result<eigensdk::client_avsregistry::writer::AvsRegistryChainWriter> {
127        let contract_addresses = self.config.protocol_settings.eigenlayer()?;
128        let registry_coordinator_address = contract_addresses.registry_coordinator_address;
129        let service_manager_address = contract_addresses.service_manager_address;
130
131        eigensdk::client_avsregistry::writer::AvsRegistryChainWriter::build_avs_registry_chain_writer(
132            self.config.http_rpc_endpoint.to_string(),
133            private_key,
134            registry_coordinator_address,
135            service_manager_address,
136        ).await
137        .map_err(Into::into)
138    }
139
140    /// Provides an operator info service.
141    ///
142    /// # Errors
143    ///
144    /// * The [`BlueprintEnvironment`] is not configured for Eigenlayer
145    /// * See [`OperatorInfoServiceInMemory::new()`]
146    ///
147    /// [`OperatorInfoServiceInMemory::new()`]: eigensdk::services_operatorsinfo::operatorsinfo_inmemory::OperatorInfoServiceInMemory::new
148    pub async fn operator_info_service_in_memory(
149        &self,
150    ) -> Result<(
151        eigensdk::services_operatorsinfo::operatorsinfo_inmemory::OperatorInfoServiceInMemory,
152        tokio::sync::mpsc::UnboundedReceiver<
153            eigensdk::services_operatorsinfo::operatorsinfo_inmemory::OperatorInfoServiceError,
154        >,
155    )> {
156        let avs_registry_reader = self.avs_registry_reader().await?;
157
158        eigensdk::services_operatorsinfo::operatorsinfo_inmemory::OperatorInfoServiceInMemory::new(
159            avs_registry_reader,
160            self.config.ws_rpc_endpoint.to_string(),
161        )
162        .await
163        .map_err(Into::into)
164    }
165
166    /// Provides an AVS registry service chain caller.
167    ///
168    /// # Errors
169    ///
170    /// * See [`Self::avs_registry_reader()`]
171    /// * See [`Self::operator_info_service_in_memory()`]
172    pub async fn avs_registry_service_chain_caller_in_memory(
173        &self,
174    ) -> Result<
175        eigensdk::services_avsregistry::chaincaller::AvsRegistryServiceChainCaller<
176            eigensdk::client_avsregistry::reader::AvsRegistryChainReader,
177            eigensdk::services_operatorsinfo::operatorsinfo_inmemory::OperatorInfoServiceInMemory,
178        >,
179    > {
180        let avs_registry_reader = self.avs_registry_reader().await?;
181        let (operator_info_service, _) = self.operator_info_service_in_memory().await?;
182
183        let cancellation_token = tokio_util::sync::CancellationToken::new();
184        let token_clone = cancellation_token.clone();
185        let provider = self.get_provider_http();
186        let current_block = provider.get_block_number().await?;
187        let operator_info_clone = operator_info_service.clone();
188
189        tokio::task::spawn(async move {
190            operator_info_clone
191                .start_service(&token_clone, 0, current_block)
192                .await
193        });
194
195        Ok(
196            eigensdk::services_avsregistry::chaincaller::AvsRegistryServiceChainCaller::new(
197                avs_registry_reader,
198                operator_info_service,
199            ),
200        )
201    }
202
203    /// Provides a BLS aggregation service.
204    ///
205    /// # Errors
206    ///
207    /// * See [`Self::avs_registry_service_chain_caller_in_memory()`]
208    pub async fn bls_aggregation_service_in_memory(&self) -> Result<eigensdk::services_blsaggregation::bls_agg::BlsAggregatorService<
209        eigensdk::services_avsregistry::chaincaller::AvsRegistryServiceChainCaller<
210            eigensdk::client_avsregistry::reader::AvsRegistryChainReader,
211            eigensdk::services_operatorsinfo::operatorsinfo_inmemory::OperatorInfoServiceInMemory
212        >
213    >>{
214        let avs_registry_service = self.avs_registry_service_chain_caller_in_memory().await?;
215        Ok(
216            eigensdk::services_blsaggregation::bls_agg::BlsAggregatorService::new(
217                avs_registry_service,
218            ),
219        )
220    }
221
222    /// Get Operator stake in Quorums at a given block.
223    ///
224    /// # Errors
225    ///
226    /// * See [`Self::avs_registry_reader()`]
227    /// * See [`AvsRegistryChainReader::get_operators_stake_in_quorums_at_block()`]
228    ///
229    /// [`AvsRegistryChainReader::get_operators_stake_in_quorums_at_block()`]: eigensdk::client_avsregistry::reader::AvsRegistryChainReader::get_operators_stake_in_quorums_at_block
230    pub async fn get_operator_stake_in_quorums_at_block(
231        &self,
232        block_number: u32,
233        quorum_numbers: Bytes,
234    ) -> Result<Vec<Vec<OperatorStateRetriever::Operator>>> {
235        self.avs_registry_reader()
236            .await?
237            .get_operators_stake_in_quorums_at_block(block_number.into(), quorum_numbers)
238            .await
239            .map_err(Into::into)
240    }
241
242    /// Get an Operator's stake in Quorums at current block.
243    ///
244    /// # Errors
245    ///
246    /// * See [`Self::avs_registry_reader()`]
247    /// * See [`AvsRegistryChainReader::get_operator_stake_in_quorums_of_operator_at_current_block()`]
248    ///
249    /// [`AvsRegistryChainReader::get_operator_stake_in_quorums_of_operator_at_current_block()`]: eigensdk::client_avsregistry::reader::AvsRegistryChainReader::get_operator_stake_in_quorums_of_operator_at_current_block
250    pub async fn get_operator_stake_in_quorums_at_current_block(
251        &self,
252        operator_id: alloy_primitives::FixedBytes<32>,
253    ) -> Result<HashMap<u8, BigInt>> {
254        self.avs_registry_reader()
255            .await?
256            .get_operator_stake_in_quorums_of_operator_at_current_block(operator_id)
257            .await
258            .map_err(Into::into)
259    }
260
261    /// Get an Operator by ID.
262    ///
263    /// # Errors
264    ///
265    /// * See [`Self::avs_registry_reader()`]
266    /// * See [`AvsRegistryReader::get_operator_from_id()`]
267    pub async fn get_operator_by_id(&self, operator_id: [u8; 32]) -> Result<Address> {
268        self.avs_registry_reader()
269            .await?
270            .get_operator_from_id(operator_id)
271            .await
272            .map_err(Into::into)
273    }
274
275    /// Get an Operator stake history.
276    ///
277    /// # Errors
278    ///
279    /// * The [`BlueprintEnvironment`] is not configured for Eigenlayer
280    pub async fn get_operator_stake_history(
281        &self,
282        operator_id: alloy_primitives::FixedBytes<32>,
283        quorum_number: u8,
284    ) -> Result<Vec<IStakeRegistry::StakeUpdate>> {
285        let contract_addresses = self.config.protocol_settings.eigenlayer()?;
286        let provider = self.get_provider_http();
287        let registry_coordinator = RegistryCoordinator::new(
288            contract_addresses.registry_coordinator_address,
289            provider.clone(),
290        );
291        let stake_registry_address = registry_coordinator.stakeRegistry().call().await?;
292        let instance =
293            StakeRegistry::StakeRegistryInstance::new(stake_registry_address, provider.clone());
294        let call_builder = instance.getStakeHistory(operator_id, quorum_number);
295        let response = call_builder.call().await?;
296        Ok(response)
297    }
298
299    /// Get an Operator stake update at a given index.
300    ///
301    /// # Errors
302    ///
303    /// * The [`BlueprintEnvironment`] is not configured for Eigenlayer
304    pub async fn get_operator_stake_update_at_index(
305        &self,
306        quorum_number: u8,
307        operator_id: alloy_primitives::FixedBytes<32>,
308        index: alloy_primitives::U256,
309    ) -> Result<IStakeRegistry::StakeUpdate> {
310        let contract_addresses = self.config.protocol_settings.eigenlayer()?;
311        let provider = self.get_provider_http();
312        let registry_coordinator = RegistryCoordinator::new(
313            contract_addresses.registry_coordinator_address,
314            provider.clone(),
315        );
316        let stake_registry_address = registry_coordinator.stakeRegistry().call().await?;
317        let instance =
318            StakeRegistry::StakeRegistryInstance::new(stake_registry_address, provider.clone());
319        let call_builder = instance.getStakeUpdateAtIndex(quorum_number, operator_id, index);
320        let response = call_builder.call().await?;
321        Ok(response)
322    }
323
324    /// Get an Operator's stake at a given block number.
325    ///
326    /// # Errors
327    ///
328    /// * The [`BlueprintEnvironment`] is not configured for Eigenlayer
329    pub async fn get_operator_stake_at_block_number(
330        &self,
331        operator_id: alloy_primitives::FixedBytes<32>,
332        quorum_number: u8,
333        block_number: u32,
334    ) -> Result<alloy_primitives::Uint<96, 2>> {
335        let contract_addresses = self.config.protocol_settings.eigenlayer()?;
336        let provider = self.get_provider_http();
337        let registry_coordinator = RegistryCoordinator::new(
338            contract_addresses.registry_coordinator_address,
339            provider.clone(),
340        );
341        let stake_registry_address = registry_coordinator.stakeRegistry().call().await?;
342        let instance =
343            StakeRegistry::StakeRegistryInstance::new(stake_registry_address, provider.clone());
344        let call_builder = instance.getStakeAtBlockNumber(operator_id, quorum_number, block_number);
345        let response = call_builder.call().await?;
346        Ok(response)
347    }
348
349    // TODO: Slashing contract equivalent
350    // /// Get an Operator's [`details`](OperatorDetails).
351    // pub async fn get_operator_details(
352    //     &self,
353    //     operator_addr: Address,
354    // ) -> Result<eigensdk::types::operator::Operator> {
355    //     let http_rpc_endpoint = self.config.http_rpc_endpoint.clone();
356    //     let contract_addresses = self.config.protocol_settings.eigenlayer()?;
357    //     let chain_reader = eigensdk::client_elcontracts::reader::ELChainReader::new(
358    //         Some(contract_addresses.allocation_manager_address),
359    //         contract_addresses.delegation_manager_address,
360    //         contract_addresses.rewards_coordinator_address,
361    //         contract_addresses.avs_directory_address,
362    //         Some(contract_addresses.permission_controller_address),
363    //         http_rpc_endpoint.clone(),
364    //     );
365    //     Ok(chain_reader.get_operator_details(operator_addr).await?)
366    // }
367
368    /// Get an Operator's latest stake update.
369    ///
370    /// # Errors
371    ///
372    /// * The [`BlueprintEnvironment`] is not configured for Eigenlayer
373    pub async fn get_latest_stake_update(
374        &self,
375        operator_id: alloy_primitives::FixedBytes<32>,
376        quorum_number: u8,
377    ) -> Result<IStakeRegistry::StakeUpdate> {
378        let contract_addresses = self.config.protocol_settings.eigenlayer()?;
379        let provider = self.get_provider_http();
380        let registry_coordinator = RegistryCoordinator::new(
381            contract_addresses.registry_coordinator_address,
382            provider.clone(),
383        );
384        let stake_registry_address = registry_coordinator.stakeRegistry().call().await?;
385        let instance =
386            StakeRegistry::StakeRegistryInstance::new(stake_registry_address, provider.clone());
387        let call_builder = instance.getLatestStakeUpdate(operator_id, quorum_number);
388        let response = call_builder.call().await?;
389        Ok(response)
390    }
391
392    /// Get an Operator's ID as [`FixedBytes`] from its [`Address`].
393    ///
394    /// # Errors
395    ///
396    /// * See [`Self::avs_registry_reader()`]
397    /// * See [`AvsRegistryChainReader::get_operator_id()`]
398    ///
399    /// [`FixedBytes`]: alloy_primitives::FixedBytes
400    /// [`AvsRegistryChainReader::get_operator_id()`]: eigensdk::client_avsregistry::reader::AvsRegistryChainReader::get_operator_id
401    pub async fn get_operator_id(
402        &self,
403        operator_addr: Address,
404    ) -> Result<alloy_primitives::FixedBytes<32>> {
405        self.avs_registry_reader()
406            .await?
407            .get_operator_id(operator_addr)
408            .await
409            .map_err(Into::into)
410    }
411
412    /// Get the total stake at a given block number from a given index.
413    ///
414    /// # Errors
415    ///
416    /// * The [`BlueprintEnvironment`] is not configured for Eigenlayer
417    pub async fn get_total_stake_at_block_number_from_index(
418        &self,
419        quorum_number: u8,
420        block_number: u32,
421        index: alloy_primitives::U256,
422    ) -> Result<alloy_primitives::Uint<96, 2>> {
423        let contract_addresses = self.config.protocol_settings.eigenlayer()?;
424        let provider = self.get_provider_http();
425        let registry_coordinator = RegistryCoordinator::new(
426            contract_addresses.registry_coordinator_address,
427            provider.clone(),
428        );
429        let stake_registry_address = registry_coordinator.stakeRegistry().call().await?;
430        let instance =
431            StakeRegistry::StakeRegistryInstance::new(stake_registry_address, provider.clone());
432        let call_builder =
433            instance.getTotalStakeAtBlockNumberFromIndex(quorum_number, block_number, index);
434        let response = call_builder.call().await?;
435        Ok(response)
436    }
437
438    /// Get the total stake history length of a given quorum.
439    ///
440    /// # Errors
441    ///
442    /// * The [`BlueprintEnvironment`] is not configured for Eigenlayer
443    pub async fn get_total_stake_history_length(
444        &self,
445        quorum_number: u8,
446    ) -> Result<alloy_primitives::U256> {
447        let contract_addresses = self.config.protocol_settings.eigenlayer()?;
448        let provider = self.get_provider_http();
449        let registry_coordinator = RegistryCoordinator::new(
450            contract_addresses.registry_coordinator_address,
451            provider.clone(),
452        );
453        let stake_registry_address = registry_coordinator.stakeRegistry().call().await?;
454        let instance =
455            StakeRegistry::StakeRegistryInstance::new(stake_registry_address, provider.clone());
456        let call_builder = instance.getTotalStakeHistoryLength(quorum_number);
457        let response = call_builder.call().await?;
458        Ok(response)
459    }
460
461    /// Provides the public keys of existing registered operators within the provided block range.
462    ///
463    /// # Errors
464    ///
465    /// * See [`Self::avs_registry_reader()`]
466    /// * See [`AvsRegistryChainReader::query_existing_registered_operator_pub_keys()`]
467    ///
468    /// [`AvsRegistryChainReader::query_existing_registered_operator_pub_keys()`]: eigensdk::client_avsregistry::reader::AvsRegistryChainReader::query_existing_registered_operator_pub_keys
469    pub async fn query_existing_registered_operator_pub_keys(
470        &self,
471        start_block: u64,
472        to_block: u64,
473    ) -> Result<(
474        Vec<Address>,
475        Vec<eigensdk::types::operator::OperatorPubKeys>,
476    )> {
477        self.avs_registry_reader()
478            .await?
479            .query_existing_registered_operator_pub_keys(
480                start_block,
481                to_block,
482                self.config.ws_rpc_endpoint.to_string(),
483            )
484            .await
485            .map_err(Into::into)
486    }
487
488    /// Get strategies in an operator set (quorum) for a given AVS.
489    ///
490    /// # Arguments
491    ///
492    /// * `avs_address` - The address of the AVS service manager
493    /// * `operator_set_id` - The ID of the operator set (quorum number)
494    ///
495    /// # Returns
496    ///
497    /// A vector of strategy addresses used in the specified operator set
498    ///
499    /// # Errors
500    ///
501    /// * [`Error::AlloyContractError`] - If the call to the contract fails
502    pub async fn get_strategies_in_operator_set(
503        &self,
504        avs_address: Address,
505        operator_set_id: u8,
506    ) -> Result<Vec<Address>> {
507        let contract_addresses = self.config.protocol_settings.eigenlayer()?;
508        let provider = self.get_provider_http();
509
510        let allocation_manager = AllocationManager::AllocationManagerInstance::new(
511            contract_addresses.allocation_manager_address,
512            provider,
513        );
514
515        let operator_set = AllocationManager::OperatorSet {
516            avs: avs_address,
517            id: u32::from(operator_set_id), // Convert u8 to u32
518        };
519
520        let result = allocation_manager
521            .getStrategiesInOperatorSet(operator_set)
522            .call()
523            .await?;
524
525        Ok(result)
526    }
527
528    /// Get strategy allocations for a specific operator and strategy.
529    ///
530    /// # Arguments
531    ///
532    /// * `operator_address` - The address of the operator
533    /// * `strategy_address` - The address of the strategy
534    ///
535    /// # Returns
536    ///
537    /// A tuple containing:
538    /// - A vector of operator sets the operator is part of
539    /// - A vector of allocations for each operator set
540    ///
541    /// # Errors
542    ///
543    /// * [`Error::AlloyContractError`] - If the call to the contract fails
544    pub async fn get_strategy_allocations(
545        &self,
546        operator_address: Address,
547        strategy_address: Address,
548    ) -> Result<(
549        Vec<AllocationManager::OperatorSet>,
550        Vec<IAllocationManagerTypes::Allocation>,
551    )> {
552        let contract_addresses = self.config.protocol_settings.eigenlayer()?;
553        let provider = self.get_provider_http();
554
555        let allocation_manager = AllocationManager::AllocationManagerInstance::new(
556            contract_addresses.allocation_manager_address,
557            provider,
558        );
559
560        let result = allocation_manager
561            .getStrategyAllocations(operator_address, strategy_address)
562            .call()
563            .await?;
564
565        Ok((result._0, result._1))
566    }
567
568    /// Get the maximum magnitude for a specific operator and strategy.
569    ///
570    /// # Arguments
571    ///
572    /// * `operator_address` - The address of the operator
573    /// * `strategy_address` - The address of the strategy
574    ///
575    /// # Returns
576    ///
577    /// The maximum magnitude
578    ///
579    /// # Errors
580    ///
581    /// * [`Error::AlloyContractError`] - If the call to the contract fails
582    pub async fn get_max_magnitude(
583        &self,
584        operator_address: Address,
585        strategy_address: Address,
586    ) -> Result<u64> {
587        let contract_addresses = self.config.protocol_settings.eigenlayer()?;
588        let provider = self.get_provider_http();
589
590        let allocation_manager = AllocationManager::AllocationManagerInstance::new(
591            contract_addresses.allocation_manager_address,
592            provider,
593        );
594
595        let result = allocation_manager
596            .getMaxMagnitude(operator_address, strategy_address)
597            .call()
598            .await?;
599
600        Ok(result)
601    }
602
603    /// Get slashable shares in queue for a specific operator and strategy.
604    ///
605    /// # Arguments
606    ///
607    /// * `operator_address` - The address of the operator
608    /// * `strategy_address` - The address of the strategy
609    ///
610    /// # Returns
611    ///
612    /// The amount of slashable shares in the queue
613    ///
614    /// # Errors
615    ///
616    /// * [`Error::AlloyContractError`] - If the call to the contract fails
617    pub async fn get_slashable_shares_in_queue(
618        &self,
619        operator_address: Address,
620        strategy_address: Address,
621    ) -> Result<U256> {
622        let contract_addresses = self.config.protocol_settings.eigenlayer()?;
623        let provider = self.get_provider_http();
624
625        let delegation_manager = DelegationManager::DelegationManagerInstance::new(
626            contract_addresses.delegation_manager_address,
627            provider,
628        );
629
630        let result = delegation_manager
631            .getSlashableSharesInQueue(operator_address, strategy_address)
632            .call()
633            .await?;
634
635        Ok(result)
636    }
637
638    /// Get all operators for a service at a specific block.
639    ///
640    /// # Arguments
641    ///
642    /// * `avs_address` - The address of the AVS service manager
643    /// * `block_number` - The block number to retrieve the operators at
644    /// * `quorum_numbers` - The quorum numbers to retrieve operators for
645    ///
646    /// # Returns
647    ///
648    /// A vector of vectors containing operators in each quorum
649    ///
650    /// # Errors
651    ///
652    /// * [`Error::AlloyContractError`] - If the call to the contract fails
653    pub async fn get_operators_for_service(
654        &self,
655        _avs_address: Address,
656        block_number: u32,
657        quorum_numbers: Vec<u8>,
658    ) -> Result<Vec<Vec<OperatorStateRetriever::Operator>>> {
659        let quorum_bytes = Bytes::from(quorum_numbers);
660        self.get_operator_stake_in_quorums_at_block(block_number, quorum_bytes)
661            .await
662    }
663
664    /// Get all slashable assets for an AVS.
665    ///
666    /// # Arguments
667    ///
668    /// * `avs_address` - The address of the AVS service manager
669    /// * `block_number` - The block number to retrieve the data at
670    /// * `quorum_numbers` - The quorum numbers (operator set IDs) to retrieve data for
671    ///
672    /// # Returns
673    ///
674    /// A hashmap where:
675    /// - Key: Operator address
676    /// - Value: A hashmap where:
677    ///   - Key: Strategy address
678    ///   - Value: Amount of slashable shares
679    ///
680    /// # Errors
681    ///
682    /// * [`Error::AlloyContractError`] - If any contract call fails
683    pub async fn get_slashable_assets_for_avs(
684        &self,
685        avs_address: Address,
686        block_number: u32,
687        quorum_numbers: Vec<u8>,
688    ) -> Result<HashMap<Address, HashMap<Address, U256>>> {
689        let mut result = HashMap::new();
690
691        let all_operator_info = self
692            .get_operators_for_service(avs_address, block_number, quorum_numbers.clone())
693            .await?;
694
695        for (operators, quorum_number) in all_operator_info.iter().zip(quorum_numbers) {
696            let strategies = self
697                .get_strategies_in_operator_set(avs_address, quorum_number)
698                .await?;
699
700            for operator in operators {
701                let operator_id = operator.operatorId;
702
703                let operator_address = self.get_operator_by_id(operator_id.into()).await?;
704
705                let operator_entry = result.entry(operator_address).or_insert_with(HashMap::new);
706
707                for strategy_address in &strategies {
708                    match self
709                        .get_slashable_shares_in_queue(operator_address, *strategy_address)
710                        .await
711                    {
712                        Ok(slashable_shares) => {
713                            operator_entry.insert(*strategy_address, slashable_shares);
714                        }
715                        Err(e) => {
716                            // Log the error but continue with other strategies
717                            blueprint_core::error!(
718                                "Error getting slashable shares for operator {operator_address}, strategy {strategy_address}: {e}",
719                            );
720                        }
721                    }
722                }
723            }
724        }
725
726        Ok(result)
727    }
728}