use crate::error::{EigenlayerExtraError, Result};
use alloy_primitives::{Address, FixedBytes, U256};
use blueprint_core::info;
use blueprint_keystore::backends::Backend;
use blueprint_keystore::backends::eigenlayer::EigenlayerBackend;
use blueprint_keystore::crypto::k256::K256Ecdsa;
use blueprint_runner::config::BlueprintEnvironment;
use eigensdk::client_elcontracts::reader::ELChainReader;
use eigensdk::utils::rewardsv2::core::rewards_coordinator::{
IRewardsCoordinator, RewardsCoordinator,
};
use std::str::FromStr;
#[derive(Clone)]
pub struct RewardsManager {
env: BlueprintEnvironment,
}
impl RewardsManager {
pub fn new(env: BlueprintEnvironment) -> Self {
Self { env }
}
fn get_operator_address(&self) -> Result<Address> {
let ecdsa_public = self
.env
.keystore()
.first_local::<K256Ecdsa>()
.map_err(EigenlayerExtraError::Keystore)?;
let ecdsa_secret = self
.env
.keystore()
.expose_ecdsa_secret(&ecdsa_public)
.map_err(EigenlayerExtraError::Keystore)?
.ok_or_else(|| {
EigenlayerExtraError::InvalidConfiguration("No ECDSA secret found".into())
})?;
ecdsa_secret
.alloy_address()
.map_err(|e| EigenlayerExtraError::InvalidConfiguration(e.to_string()))
}
pub async fn get_claimable_rewards(&self) -> Result<U256> {
let contract_addresses = self
.env
.protocol_settings
.eigenlayer()
.map_err(|e| EigenlayerExtraError::InvalidConfiguration(e.to_string()))?;
let el_chain_reader = ELChainReader::new(
Some(contract_addresses.allocation_manager_address),
contract_addresses.delegation_manager_address,
contract_addresses.rewards_coordinator_address,
contract_addresses.avs_directory_address,
Some(contract_addresses.permission_controller_address),
self.env.http_rpc_endpoint.to_string(),
);
let distribution_root = el_chain_reader
.get_current_claimable_distribution_root()
.await
.map_err(|e| EigenlayerExtraError::EigenSdk(e.to_string()))?;
info!(
"Current claimable distribution root: {} (activated at {})",
distribution_root.root, distribution_root.activatedAt
);
Ok(U256::from(distribution_root.activatedAt))
}
pub async fn calculate_earnings_per_strategy(
&self,
) -> Result<alloc::vec::Vec<(Address, U256)>> {
let operator_address = self.get_operator_address()?;
let contract_addresses = self
.env
.protocol_settings
.eigenlayer()
.map_err(|e| EigenlayerExtraError::InvalidConfiguration(e.to_string()))?;
let el_chain_reader = ELChainReader::new(
Some(contract_addresses.allocation_manager_address),
contract_addresses.delegation_manager_address,
contract_addresses.rewards_coordinator_address,
contract_addresses.avs_directory_address,
Some(contract_addresses.permission_controller_address),
self.env.http_rpc_endpoint.to_string(),
);
let (strategies, shares) = el_chain_reader
.get_staker_shares(operator_address)
.await
.map_err(|e| EigenlayerExtraError::EigenSdk(e.to_string()))?;
let mut earnings = alloc::vec::Vec::with_capacity(strategies.len());
for (strategy, share) in strategies.into_iter().zip(shares.into_iter()) {
if !share.is_zero() {
earnings.push((strategy, share));
info!(
"Operator {} has {} shares in strategy {}",
operator_address, share, strategy
);
}
}
Ok(earnings)
}
#[allow(dead_code)]
pub async fn claim_rewards(
&self,
_root: FixedBytes<32>,
reward_claim: IRewardsCoordinator::RewardsMerkleClaim,
) -> Result<FixedBytes<32>> {
let contract_addresses = self
.env
.protocol_settings
.eigenlayer()
.map_err(|e| EigenlayerExtraError::InvalidConfiguration(e.to_string()))?;
let operator_address = self.get_operator_address()?;
let ecdsa_public = self
.env
.keystore()
.first_local::<K256Ecdsa>()
.map_err(EigenlayerExtraError::Keystore)?;
let ecdsa_secret = self
.env
.keystore()
.expose_ecdsa_secret(&ecdsa_public)
.map_err(EigenlayerExtraError::Keystore)?
.ok_or_else(|| {
EigenlayerExtraError::InvalidConfiguration("No ECDSA secret found".into())
})?;
let private_key = alloy_primitives::hex::encode(ecdsa_secret.0.to_bytes());
let wallet = alloy_signer_local::PrivateKeySigner::from_str(&private_key)
.map_err(|e| EigenlayerExtraError::InvalidConfiguration(e.to_string()))?;
let provider = blueprint_evm_extra::util::get_wallet_provider_http(
self.env.http_rpc_endpoint.clone(),
alloy_network::EthereumWallet::from(wallet),
);
let rewards_coordinator =
RewardsCoordinator::new(contract_addresses.rewards_coordinator_address, provider);
let receipt = rewards_coordinator
.processClaim(reward_claim, operator_address)
.send()
.await
.map_err(|e| EigenlayerExtraError::Transaction(e.to_string()))?
.get_receipt()
.await
.map_err(|e| EigenlayerExtraError::Transaction(e.to_string()))?;
info!(
"Rewards claimed successfully: {:?}",
receipt.transaction_hash
);
Ok(receipt.transaction_hash)
}
#[allow(dead_code)]
pub async fn claim_rewards_batch(
&self,
reward_claims: Vec<IRewardsCoordinator::RewardsMerkleClaim>,
) -> Result<FixedBytes<32>> {
if reward_claims.is_empty() {
return Err(EigenlayerExtraError::InvalidConfiguration(
"No reward claims provided".into(),
));
}
let contract_addresses = self
.env
.protocol_settings
.eigenlayer()
.map_err(|e| EigenlayerExtraError::InvalidConfiguration(e.to_string()))?;
let operator_address = self.get_operator_address()?;
let ecdsa_public = self
.env
.keystore()
.first_local::<K256Ecdsa>()
.map_err(EigenlayerExtraError::Keystore)?;
let ecdsa_secret = self
.env
.keystore()
.expose_ecdsa_secret(&ecdsa_public)
.map_err(EigenlayerExtraError::Keystore)?
.ok_or_else(|| {
EigenlayerExtraError::InvalidConfiguration("No ECDSA secret found".into())
})?;
let private_key = alloy_primitives::hex::encode(ecdsa_secret.0.to_bytes());
let wallet = alloy_signer_local::PrivateKeySigner::from_str(&private_key)
.map_err(|e| EigenlayerExtraError::InvalidConfiguration(e.to_string()))?;
let provider = blueprint_evm_extra::util::get_wallet_provider_http(
self.env.http_rpc_endpoint.clone(),
alloy_network::EthereumWallet::from(wallet),
);
let rewards_coordinator =
RewardsCoordinator::new(contract_addresses.rewards_coordinator_address, provider);
let receipt = rewards_coordinator
.processClaims(reward_claims.clone(), operator_address)
.send()
.await
.map_err(|e| EigenlayerExtraError::Transaction(e.to_string()))?
.get_receipt()
.await
.map_err(|e| EigenlayerExtraError::Transaction(e.to_string()))?;
info!(
"Batch claimed {} rewards successfully: {:?}",
reward_claims.len(),
receipt.transaction_hash
);
Ok(receipt.transaction_hash)
}
pub async fn is_operator_registered(&self) -> Result<bool> {
let operator_address = self.get_operator_address()?;
let contract_addresses = self
.env
.protocol_settings
.eigenlayer()
.map_err(|e| EigenlayerExtraError::InvalidConfiguration(e.to_string()))?;
let el_chain_reader = ELChainReader::new(
Some(contract_addresses.allocation_manager_address),
contract_addresses.delegation_manager_address,
contract_addresses.rewards_coordinator_address,
contract_addresses.avs_directory_address,
Some(contract_addresses.permission_controller_address),
self.env.http_rpc_endpoint.to_string(),
);
el_chain_reader
.is_operator_registered(operator_address)
.await
.map_err(|e| EigenlayerExtraError::EigenSdk(e.to_string()))
}
}
#[cfg(test)]
mod tests {
#[tokio::test]
#[ignore] async fn test_rewards_manager_creation() {
}
}