blueprint_eigenlayer_extra/services/
lifecycle.rs

1use crate::error::{EigenlayerExtraError, Result};
2use alloy_primitives::{Address, FixedBytes};
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::writer::ELChainWriter;
9use eigensdk::types::operator::Operator;
10
11/// Manager for operator lifecycle operations
12///
13/// Provides high-level abstractions for operator registration, deregistration,
14/// and metadata updates on EigenLayer.
15#[derive(Clone)]
16pub struct OperatorLifecycleManager {
17    env: BlueprintEnvironment,
18}
19
20/// Operator metadata for updates
21#[derive(Debug, Clone)]
22pub struct OperatorMetadata {
23    /// URL pointing to operator metadata JSON
24    pub metadata_url: String,
25    /// Address that can approve delegations (use Address::ZERO to disable)
26    pub delegation_approver_address: Address,
27}
28
29impl OperatorLifecycleManager {
30    /// Create a new OperatorLifecycleManager
31    pub fn new(env: BlueprintEnvironment) -> Self {
32        Self { env }
33    }
34
35    /// Get the operator address and private key from keystore
36    ///
37    /// # Errors
38    ///
39    /// * Keystore errors if ECDSA key not found or cannot be exposed
40    fn get_operator_credentials(&self) -> Result<(Address, String)> {
41        let ecdsa_public = self
42            .env
43            .keystore()
44            .first_local::<K256Ecdsa>()
45            .map_err(EigenlayerExtraError::Keystore)?;
46
47        let ecdsa_secret = self
48            .env
49            .keystore()
50            .expose_ecdsa_secret(&ecdsa_public)
51            .map_err(EigenlayerExtraError::Keystore)?
52            .ok_or_else(|| {
53                EigenlayerExtraError::InvalidConfiguration("No ECDSA secret found".into())
54            })?;
55
56        let operator_address = ecdsa_secret
57            .alloy_address()
58            .map_err(|e| EigenlayerExtraError::InvalidConfiguration(e.to_string()))?;
59
60        let private_key = alloy_primitives::hex::encode(ecdsa_secret.0.to_bytes());
61
62        Ok((operator_address, private_key))
63    }
64
65    /// Deregister the operator from this AVS's operator sets
66    ///
67    /// Removes the operator from the operator sets configured for this AVS,
68    /// preventing them from receiving new work or participating in this AVS.
69    ///
70    /// # Errors
71    ///
72    /// * Transaction errors
73    /// * Configuration errors
74    /// * Operator not registered
75    pub async fn deregister_operator(&self) -> Result<FixedBytes<32>> {
76        let (operator_address, private_key) = self.get_operator_credentials()?;
77        let contract_addresses = self
78            .env
79            .protocol_settings
80            .eigenlayer()
81            .map_err(|e| EigenlayerExtraError::InvalidConfiguration(e.to_string()))?;
82
83        let el_writer = ELChainWriter::new(
84            contract_addresses.strategy_manager_address,
85            contract_addresses.rewards_coordinator_address,
86            Some(contract_addresses.permission_controller_address),
87            Some(contract_addresses.allocation_manager_address),
88            contract_addresses.registry_coordinator_address,
89            eigensdk::client_elcontracts::reader::ELChainReader::new(
90                Some(contract_addresses.allocation_manager_address),
91                contract_addresses.delegation_manager_address,
92                contract_addresses.rewards_coordinator_address,
93                contract_addresses.avs_directory_address,
94                Some(contract_addresses.permission_controller_address),
95                self.env.http_rpc_endpoint.to_string(),
96            ),
97            self.env.http_rpc_endpoint.to_string(),
98            private_key,
99        );
100
101        // Deregister from this AVS's operator sets
102        let tx_hash = el_writer
103            .deregister_from_operator_sets(
104                operator_address,
105                contract_addresses.service_manager_address,
106                contract_addresses.operator_sets.clone(),
107            )
108            .await
109            .map_err(|e| EigenlayerExtraError::EigenSdk(e.to_string()))?;
110
111        info!(
112            "Operator {} deregistered from AVS {} operator sets {:?}: {:?}",
113            operator_address,
114            contract_addresses.service_manager_address,
115            contract_addresses.operator_sets,
116            tx_hash
117        );
118
119        Ok(tx_hash)
120    }
121
122    /// Update operator metadata
123    ///
124    /// Updates the operator's metadata URL and delegation approver address.
125    ///
126    /// # Arguments
127    ///
128    /// * `metadata` - New metadata configuration
129    ///
130    /// # Errors
131    ///
132    /// * Transaction errors
133    /// * Configuration errors
134    pub async fn update_operator_metadata(
135        &self,
136        metadata: OperatorMetadata,
137    ) -> Result<FixedBytes<32>> {
138        let (operator_address, private_key) = self.get_operator_credentials()?;
139        let contract_addresses = self
140            .env
141            .protocol_settings
142            .eigenlayer()
143            .map_err(|e| EigenlayerExtraError::InvalidConfiguration(e.to_string()))?;
144
145        let el_writer = ELChainWriter::new(
146            contract_addresses.strategy_manager_address,
147            contract_addresses.rewards_coordinator_address,
148            Some(contract_addresses.permission_controller_address),
149            Some(contract_addresses.allocation_manager_address),
150            contract_addresses.registry_coordinator_address,
151            eigensdk::client_elcontracts::reader::ELChainReader::new(
152                Some(contract_addresses.allocation_manager_address),
153                contract_addresses.delegation_manager_address,
154                contract_addresses.rewards_coordinator_address,
155                contract_addresses.avs_directory_address,
156                Some(contract_addresses.permission_controller_address),
157                self.env.http_rpc_endpoint.to_string(),
158            ),
159            self.env.http_rpc_endpoint.to_string(),
160            private_key,
161        );
162
163        // Create updated operator details
164        let operator_details = Operator {
165            address: operator_address,
166            delegation_approver_address: metadata.delegation_approver_address,
167            metadata_url: metadata.metadata_url,
168            allocation_delay: Some(30), // Default allocation delay
169            _deprecated_earnings_receiver_address: None,
170            staker_opt_out_window_blocks: Some(50400),
171        };
172
173        let tx_hash = el_writer
174            .update_operator_details(operator_details)
175            .await
176            .map_err(|e| EigenlayerExtraError::EigenSdk(e.to_string()))?;
177
178        info!(
179            "Operator {} metadata updated successfully: {:?}",
180            operator_address, tx_hash
181        );
182
183        Ok(tx_hash)
184    }
185
186    /// Get operator status
187    ///
188    /// Returns whether the operator is currently registered.
189    ///
190    /// # Errors
191    ///
192    /// * Contract interaction errors
193    /// * Configuration errors
194    pub async fn get_operator_status(&self) -> Result<bool> {
195        let (operator_address, _) = self.get_operator_credentials()?;
196        let contract_addresses = self
197            .env
198            .protocol_settings
199            .eigenlayer()
200            .map_err(|e| EigenlayerExtraError::InvalidConfiguration(e.to_string()))?;
201
202        let el_chain_reader = eigensdk::client_elcontracts::reader::ELChainReader::new(
203            Some(contract_addresses.allocation_manager_address),
204            contract_addresses.delegation_manager_address,
205            contract_addresses.rewards_coordinator_address,
206            contract_addresses.avs_directory_address,
207            Some(contract_addresses.permission_controller_address),
208            self.env.http_rpc_endpoint.to_string(),
209        );
210
211        el_chain_reader
212            .is_operator_registered(operator_address)
213            .await
214            .map_err(|e| EigenlayerExtraError::EigenSdk(e.to_string()))
215    }
216}
217
218#[cfg(test)]
219mod tests {
220
221    #[tokio::test]
222    #[ignore] // Requires EigenLayer deployment
223    async fn test_lifecycle_manager_creation() {
224        // This test would require a full BlueprintEnvironment setup
225        // with EigenLayer contract addresses
226    }
227}