blueprint_eigenlayer_extra/services/
rewards.rs1use crate::error::{EigenlayerExtraError, Result};
2use alloy_primitives::{Address, FixedBytes, U256};
3use blueprint_core::info;
4use blueprint_keystore::backends::Backend;
5use blueprint_keystore::backends::eigenlayer::EigenlayerBackend;
6use blueprint_keystore::crypto::k256::K256Ecdsa;
7use blueprint_runner::config::BlueprintEnvironment;
8use eigensdk::client_elcontracts::reader::ELChainReader;
9use eigensdk::utils::rewardsv2::core::rewards_coordinator::{
10 IRewardsCoordinator, RewardsCoordinator,
11};
12use std::str::FromStr;
13
14#[derive(Clone)]
19pub struct RewardsManager {
20 env: BlueprintEnvironment,
21}
22
23impl RewardsManager {
24 pub fn new(env: BlueprintEnvironment) -> Self {
26 Self { env }
27 }
28
29 fn get_operator_address(&self) -> Result<Address> {
35 let ecdsa_public = self
36 .env
37 .keystore()
38 .first_local::<K256Ecdsa>()
39 .map_err(EigenlayerExtraError::Keystore)?;
40
41 let ecdsa_secret = self
42 .env
43 .keystore()
44 .expose_ecdsa_secret(&ecdsa_public)
45 .map_err(EigenlayerExtraError::Keystore)?
46 .ok_or_else(|| {
47 EigenlayerExtraError::InvalidConfiguration("No ECDSA secret found".into())
48 })?;
49
50 ecdsa_secret
51 .alloy_address()
52 .map_err(|e| EigenlayerExtraError::InvalidConfiguration(e.to_string()))
53 }
54
55 pub async fn get_claimable_rewards(&self) -> Result<U256> {
66 let contract_addresses = self
67 .env
68 .protocol_settings
69 .eigenlayer()
70 .map_err(|e| EigenlayerExtraError::InvalidConfiguration(e.to_string()))?;
71
72 let el_chain_reader = ELChainReader::new(
73 Some(contract_addresses.allocation_manager_address),
74 contract_addresses.delegation_manager_address,
75 contract_addresses.rewards_coordinator_address,
76 contract_addresses.avs_directory_address,
77 Some(contract_addresses.permission_controller_address),
78 self.env.http_rpc_endpoint.to_string(),
79 );
80
81 let distribution_root = el_chain_reader
83 .get_current_claimable_distribution_root()
84 .await
85 .map_err(|e| EigenlayerExtraError::EigenSdk(e.to_string()))?;
86
87 info!(
88 "Current claimable distribution root: {} (activated at {})",
89 distribution_root.root, distribution_root.activatedAt
90 );
91
92 Ok(U256::from(distribution_root.activatedAt))
107 }
108
109 pub async fn calculate_earnings_per_strategy(
120 &self,
121 ) -> Result<alloc::vec::Vec<(Address, U256)>> {
122 let operator_address = self.get_operator_address()?;
123 let contract_addresses = self
124 .env
125 .protocol_settings
126 .eigenlayer()
127 .map_err(|e| EigenlayerExtraError::InvalidConfiguration(e.to_string()))?;
128
129 let el_chain_reader = ELChainReader::new(
130 Some(contract_addresses.allocation_manager_address),
131 contract_addresses.delegation_manager_address,
132 contract_addresses.rewards_coordinator_address,
133 contract_addresses.avs_directory_address,
134 Some(contract_addresses.permission_controller_address),
135 self.env.http_rpc_endpoint.to_string(),
136 );
137
138 let (strategies, shares) = el_chain_reader
140 .get_staker_shares(operator_address)
141 .await
142 .map_err(|e| EigenlayerExtraError::EigenSdk(e.to_string()))?;
143
144 let mut earnings = alloc::vec::Vec::with_capacity(strategies.len());
146 for (strategy, share) in strategies.into_iter().zip(shares.into_iter()) {
147 if !share.is_zero() {
148 earnings.push((strategy, share));
149 info!(
150 "Operator {} has {} shares in strategy {}",
151 operator_address, share, strategy
152 );
153 }
154 }
155
156 Ok(earnings)
157 }
158
159 #[allow(dead_code)]
175 pub async fn claim_rewards(
176 &self,
177 _root: FixedBytes<32>,
178 reward_claim: IRewardsCoordinator::RewardsMerkleClaim,
179 ) -> Result<FixedBytes<32>> {
180 let contract_addresses = self
181 .env
182 .protocol_settings
183 .eigenlayer()
184 .map_err(|e| EigenlayerExtraError::InvalidConfiguration(e.to_string()))?;
185
186 let operator_address = self.get_operator_address()?;
187 let ecdsa_public = self
188 .env
189 .keystore()
190 .first_local::<K256Ecdsa>()
191 .map_err(EigenlayerExtraError::Keystore)?;
192 let ecdsa_secret = self
193 .env
194 .keystore()
195 .expose_ecdsa_secret(&ecdsa_public)
196 .map_err(EigenlayerExtraError::Keystore)?
197 .ok_or_else(|| {
198 EigenlayerExtraError::InvalidConfiguration("No ECDSA secret found".into())
199 })?;
200
201 let private_key = alloy_primitives::hex::encode(ecdsa_secret.0.to_bytes());
202 let wallet = alloy_signer_local::PrivateKeySigner::from_str(&private_key)
203 .map_err(|e| EigenlayerExtraError::InvalidConfiguration(e.to_string()))?;
204
205 let provider = blueprint_evm_extra::util::get_wallet_provider_http(
206 self.env.http_rpc_endpoint.clone(),
207 alloy_network::EthereumWallet::from(wallet),
208 );
209
210 let rewards_coordinator =
211 RewardsCoordinator::new(contract_addresses.rewards_coordinator_address, provider);
212
213 let receipt = rewards_coordinator
215 .processClaim(reward_claim, operator_address)
216 .send()
217 .await
218 .map_err(|e| EigenlayerExtraError::Transaction(e.to_string()))?
219 .get_receipt()
220 .await
221 .map_err(|e| EigenlayerExtraError::Transaction(e.to_string()))?;
222
223 info!(
224 "Rewards claimed successfully: {:?}",
225 receipt.transaction_hash
226 );
227
228 Ok(receipt.transaction_hash)
229 }
230
231 #[allow(dead_code)]
248 pub async fn claim_rewards_batch(
249 &self,
250 reward_claims: Vec<IRewardsCoordinator::RewardsMerkleClaim>,
251 ) -> Result<FixedBytes<32>> {
252 if reward_claims.is_empty() {
253 return Err(EigenlayerExtraError::InvalidConfiguration(
254 "No reward claims provided".into(),
255 ));
256 }
257
258 let contract_addresses = self
259 .env
260 .protocol_settings
261 .eigenlayer()
262 .map_err(|e| EigenlayerExtraError::InvalidConfiguration(e.to_string()))?;
263
264 let operator_address = self.get_operator_address()?;
265 let ecdsa_public = self
266 .env
267 .keystore()
268 .first_local::<K256Ecdsa>()
269 .map_err(EigenlayerExtraError::Keystore)?;
270 let ecdsa_secret = self
271 .env
272 .keystore()
273 .expose_ecdsa_secret(&ecdsa_public)
274 .map_err(EigenlayerExtraError::Keystore)?
275 .ok_or_else(|| {
276 EigenlayerExtraError::InvalidConfiguration("No ECDSA secret found".into())
277 })?;
278
279 let private_key = alloy_primitives::hex::encode(ecdsa_secret.0.to_bytes());
280 let wallet = alloy_signer_local::PrivateKeySigner::from_str(&private_key)
281 .map_err(|e| EigenlayerExtraError::InvalidConfiguration(e.to_string()))?;
282
283 let provider = blueprint_evm_extra::util::get_wallet_provider_http(
284 self.env.http_rpc_endpoint.clone(),
285 alloy_network::EthereumWallet::from(wallet),
286 );
287
288 let rewards_coordinator =
289 RewardsCoordinator::new(contract_addresses.rewards_coordinator_address, provider);
290
291 let receipt = rewards_coordinator
293 .processClaims(reward_claims.clone(), operator_address)
294 .send()
295 .await
296 .map_err(|e| EigenlayerExtraError::Transaction(e.to_string()))?
297 .get_receipt()
298 .await
299 .map_err(|e| EigenlayerExtraError::Transaction(e.to_string()))?;
300
301 info!(
302 "Batch claimed {} rewards successfully: {:?}",
303 reward_claims.len(),
304 receipt.transaction_hash
305 );
306
307 Ok(receipt.transaction_hash)
308 }
309
310 pub async fn is_operator_registered(&self) -> Result<bool> {
317 let operator_address = self.get_operator_address()?;
318 let contract_addresses = self
319 .env
320 .protocol_settings
321 .eigenlayer()
322 .map_err(|e| EigenlayerExtraError::InvalidConfiguration(e.to_string()))?;
323
324 let el_chain_reader = ELChainReader::new(
325 Some(contract_addresses.allocation_manager_address),
326 contract_addresses.delegation_manager_address,
327 contract_addresses.rewards_coordinator_address,
328 contract_addresses.avs_directory_address,
329 Some(contract_addresses.permission_controller_address),
330 self.env.http_rpc_endpoint.to_string(),
331 );
332
333 el_chain_reader
334 .is_operator_registered(operator_address)
335 .await
336 .map_err(|e| EigenlayerExtraError::EigenSdk(e.to_string()))
337 }
338}
339
340#[cfg(test)]
341mod tests {
342
343 #[tokio::test]
344 #[ignore] async fn test_rewards_manager_creation() {
346 }
349}