blueprint-runner 0.2.0-alpha.2

Runner for the Blueprint SDK
use crate::BlueprintConfig;
use crate::config::BlueprintEnvironment;
use crate::eigenlayer::error::EigenlayerError;
use crate::error::RunnerError;
use alloy_primitives::{Address, FixedBytes, U256, hex};
use alloy_signer::Signer;
use alloy_signer_local::PrivateKeySigner;
use blueprint_evm_extra::util::get_provider_http;
use blueprint_keystore::backends::Backend;
use blueprint_keystore::backends::eigenlayer::EigenlayerBackend;
use blueprint_keystore::crypto::k256::K256Ecdsa;
use eigensdk::client_elcontracts::{reader::ELChainReader, writer::ELChainWriter};
use eigensdk::types::operator::Operator;
use eigensdk::utils::rewardsv2::middleware::ecdsa_stake_registry::ECDSAStakeRegistry;
use eigensdk::utils::rewardsv2::middleware::ecdsa_stake_registry::ISignatureUtils::SignatureWithSaltAndExpiry;
use std::str::FromStr;

/// Eigenlayer protocol configuration for ECDSA-based contracts
#[derive(Clone, Copy)]
pub struct EigenlayerECDSAConfig {
    delegation_approver_address: Address,
}

impl EigenlayerECDSAConfig {
    /// Create a new `EigenlayerECDSAConfig`
    #[must_use]
    pub fn new(delegation_approver_address: Address) -> Self {
        Self {
            delegation_approver_address,
        }
    }
}

impl BlueprintConfig for EigenlayerECDSAConfig {
    async fn register(&self, env: &BlueprintEnvironment) -> Result<(), RunnerError> {
        register_ecdsa_impl(env, self.delegation_approver_address).await
    }

    async fn requires_registration(&self, env: &BlueprintEnvironment) -> Result<bool, RunnerError> {
        requires_registration_ecdsa_impl(env).await
    }
}

async fn requires_registration_ecdsa_impl(env: &BlueprintEnvironment) -> Result<bool, RunnerError> {
    let provider = get_provider_http(env.http_rpc_endpoint.clone());
    let contract_addresses = env.protocol_settings.eigenlayer()?;

    let ecdsa_public = env.keystore().first_local::<K256Ecdsa>()?;
    let ecdsa_secret = env
        .keystore()
        .expose_ecdsa_secret(&ecdsa_public)?
        .ok_or_else(|| EigenlayerError::Other("No ECDSA secret found".into()))?;
    let operator_address = ecdsa_secret
        .alloy_address()
        .map_err(|e| EigenlayerError::Crypto(e.into()))?;

    let stake_registry_address = contract_addresses.stake_registry_address;

    let ecdsa_stake_registry = ECDSAStakeRegistry::new(stake_registry_address, provider.clone());

    // Check if the operator has already registered for the service
    match ecdsa_stake_registry
        .operatorRegistered(operator_address)
        .call()
        .await
        .map_err(EigenlayerError::Contract)
    {
        Ok(is_registered) => Ok(!is_registered),
        Err(e) => Err(e.into()),
    }
}

async fn register_ecdsa_impl(
    env: &BlueprintEnvironment,
    delegation_approver_address: Address,
) -> Result<(), RunnerError> {
    let contract_addresses = env.protocol_settings.eigenlayer()?;
    let registry_coordinator_address = contract_addresses.registry_coordinator_address;
    let allocation_manager_address = contract_addresses.allocation_manager_address;
    let delegation_manager_address = contract_addresses.delegation_manager_address;
    let strategy_manager_address = contract_addresses.strategy_manager_address;
    let avs_directory_address = contract_addresses.avs_directory_address;
    let service_manager_address = contract_addresses.service_manager_address;
    let stake_registry_address = contract_addresses.stake_registry_address;
    let rewards_coordinator_address = contract_addresses.rewards_coordinator_address;
    let permission_controller_address = contract_addresses.permission_controller_address;

    let ecdsa_public = env.keystore().first_local::<K256Ecdsa>()?;
    let ecdsa_secret = env
        .keystore()
        .expose_ecdsa_secret(&ecdsa_public)?
        .ok_or_else(|| EigenlayerError::Other("No ECDSA secret found".into()))?;
    let operator_address = ecdsa_secret
        .alloy_address()
        .map_err(|e| EigenlayerError::Crypto(e.into()))?;

    let operator_private_key = hex::encode(ecdsa_secret.0.to_bytes());
    blueprint_core::info!("Operator private key: {}", operator_private_key);
    let wallet = PrivateKeySigner::from_str(&operator_private_key)
        .map_err(|_| EigenlayerError::Other("Invalid private key".into()))?;

    let provider = get_provider_http(env.http_rpc_endpoint.clone());

    let el_chain_reader = ELChainReader::new(
        Some(allocation_manager_address),
        delegation_manager_address,
        rewards_coordinator_address,
        avs_directory_address,
        Some(permission_controller_address),
        env.http_rpc_endpoint.to_string(),
    );

    let el_writer = ELChainWriter::new(
        strategy_manager_address,
        rewards_coordinator_address,
        Some(permission_controller_address),
        Some(allocation_manager_address),
        registry_coordinator_address,
        el_chain_reader.clone(),
        env.http_rpc_endpoint.to_string(),
        operator_private_key.clone(),
    );

    // Get registration parameters from protocol settings
    let eigenlayer_settings = env
        .protocol_settings
        .eigenlayer()
        .map_err(|e| EigenlayerError::Other(e.to_string().into()))?;

    let operator_details = Operator {
        address: operator_address,
        delegation_approver_address,
        metadata_url: eigenlayer_settings.metadata_url.clone(),
        allocation_delay: Some(eigenlayer_settings.allocation_delay),
        _deprecated_earnings_receiver_address: None, // Deprecated in eigensdk-rs v2.0.0
        staker_opt_out_window_blocks: Some(eigenlayer_settings.staker_opt_out_window_blocks),
    };

    let tx_hash = el_writer
        .register_as_operator(operator_details)
        .await
        .map_err(EigenlayerError::ElContracts)?;

    blueprint_core::info!("Registered as operator for Eigenlayer {:?}", tx_hash);

    let digest_hash_salt: FixedBytes<32> = FixedBytes::from([0x02; 32]);
    let now = std::time::SystemTime::now();
    let sig_expiry = now.duration_since(std::time::UNIX_EPOCH).map_or_else(
        |_| U256::from(0),
        |duration| U256::from(duration.as_secs()) + U256::from(3600),
    );

    blueprint_core::info!(
        "Registration parameters: operator={:?}, manager={:?}, salt={:?}, expiry={:?}",
        operator_address,
        service_manager_address,
        digest_hash_salt,
        sig_expiry
    );

    let msg_to_sign = el_chain_reader
        .calculate_operator_avs_registration_digest_hash(
            operator_address,
            service_manager_address,
            digest_hash_salt,
            sig_expiry,
        )
        .await
        .map_err(|e| EigenlayerError::Other(e.into()))?;

    let operator_signature = wallet
        .sign_hash(&msg_to_sign)
        .await
        .map_err(EigenlayerError::SignatureError)?;
    let signing_key_address = wallet.address();

    let operator_signature_with_salt_and_expiry = SignatureWithSaltAndExpiry {
        signature: operator_signature.as_bytes().into(),
        salt: digest_hash_salt,
        expiry: sig_expiry,
    };

    // --- Register the operator to AVS ---

    let ecdsa_stake_registry = ECDSAStakeRegistry::new(stake_registry_address, provider.clone());

    let _register_response = ecdsa_stake_registry
        .registerOperatorWithSignature(
            operator_signature_with_salt_and_expiry.clone(),
            signing_key_address,
        )
        .send()
        .await
        .map_err(|e| EigenlayerError::Registration(format!("Failed to send registration tx: {e}")))?
        .get_receipt()
        .await
        .map_err(|e| {
            EigenlayerError::Registration(format!("Failed to get registration receipt: {e}"))
        })?;

    let is_registered = ecdsa_stake_registry
        .operatorRegistered(operator_address)
        .call()
        .await
        .map_err(|e| {
            EigenlayerError::Registration(format!("Failed to check registration: {}", e))
        })?;

    blueprint_core::info!("Operator Registration Status {:?}", is_registered);
    Ok(())
}