trident-sdk 0.2.0

End-user SDK for Trident deposits and cross-chain spends
Documentation
use alloy_contract::Error as ContractError;
use alloy_primitives::{Address, U256};
use alloy_provider::network::AnyNetwork;
use alloy_provider::{DynProvider, PendingTransactionError, Provider};
use alloy_signer::Signer;
use async_trait::async_trait;

use crate::chain_handler::{
    ChainHandler, ChainHandlerBuildError, ChainHandlerBuilder, ChainHandlerError,
};
use crate::conversions::ProtoConversionError;
use crate::deposit::DepositContext;
use crate::proto::v1;
use crate::spend_attestation::SpendAttestationChainContext;
use crate::{Domain, TridentClientBuilder};

use super::signing;

/// Errors produced by EVM-specific SDK operations.
///
/// This error type is returned by on-chain EVM helpers such as
/// [`super::signing::sign_spend_intent`].
#[derive(Debug, thiserror::Error)]
pub enum EvmChainError {
    /// The underlying contract call or transaction submission failed.
    #[error("Contract call failed: {0}")]
    Contract(#[from] ContractError),

    /// Waiting on a pending transaction failed.
    #[error("Transaction pending error: {0}")]
    PendingTransaction(#[from] PendingTransactionError),

    /// Producing the spend-intent signature failed.
    #[error("Failed to sign spend intent: {0}")]
    Signer(alloy_signer::Error),

    /// Converting between protobuf and EVM-native types failed.
    #[error("Invalid proto conversion: {0}")]
    ProtoConversion(#[from] ProtoConversionError),
}

pub(crate) struct EvmChainHandler {
    provider: DynProvider<AnyNetwork>,
    signer: Box<dyn Signer + Send + Sync>,
    deposit_contract: Address,
    delivery_contract: Address,
}

impl EvmChainHandler {
    pub(crate) fn new(
        provider: DynProvider<AnyNetwork>,
        signer: Box<dyn Signer + Send + Sync>,
        deposit_contract: Address,
        delivery_contract: Address,
    ) -> Self {
        Self {
            provider,
            signer,
            deposit_contract,
            delivery_contract,
        }
    }
}

#[async_trait]
impl ChainHandler for EvmChainHandler {
    fn delivery_contract(&self) -> v1::MultiChainAddress {
        self.delivery_contract.into()
    }

    fn deposit_contract(&self) -> v1::MultiChainAddress {
        self.deposit_contract.into()
    }

    fn signer(&self) -> v1::MultiChainAddress {
        self.signer.address().into()
    }

    async fn sign_spend_intent(
        &self,
        intent: v1::SpendIntent,
    ) -> Result<Vec<u8>, ChainHandlerError> {
        signing::sign_spend_intent(self.signer.as_ref(), intent)
            .await
            .map_err(Into::into)
    }

    fn prepare_deposit(
        &self,
        token: v1::MultiChainAddress,
        amount: U256,
    ) -> Result<DepositContext, ChainHandlerError> {
        let token_address = Address::try_from(token).map_err(EvmChainError::from)?;
        Ok(DepositContext::Evm {
            provider: self.provider.clone(),
            signer_address: self.signer.address(),
            deposit_contract: self.deposit_contract,
            token: token_address,
            amount,
        })
    }

    fn prepare_spend_attestation(&self) -> SpendAttestationChainContext {
        SpendAttestationChainContext::Evm {
            provider: self.provider.clone(),
            delivery_contract: self.delivery_contract,
            signer_address: self.signer.address(),
        }
    }
}

/// Pending EVM chain registration. Materializes into an [`EvmChainHandler`]
/// once the delivery service has reported the deployment's deposit and
/// delivery contract addresses.
pub(crate) struct EvmChainHandlerBuilder {
    provider: DynProvider<AnyNetwork>,
    signer: Box<dyn Signer + Send + Sync>,
}

#[async_trait]
impl ChainHandlerBuilder for EvmChainHandlerBuilder {
    async fn build(
        self: Box<Self>,
        delivery_contract: v1::MultiChainAddress,
        deposit_contract: v1::MultiChainAddress,
    ) -> Result<Box<dyn ChainHandler>, ChainHandlerBuildError> {
        let delivery_contract = Address::try_from(delivery_contract)?;
        let deposit_contract = Address::try_from(deposit_contract)?;
        Ok(Box::new(EvmChainHandler::new(
            self.provider,
            self.signer,
            deposit_contract,
            delivery_contract,
        )))
    }
}

impl TridentClientBuilder {
    /// Registers an EVM chain `domain` with its provider and signer.
    ///
    /// The signer's address is used as `src_spender` for spends originating
    /// from this domain and as the sender for
    /// [`crate::TridentClient::deposit`].
    pub fn with_evm_chain(
        self,
        domain: Domain,
        provider: impl Provider<AnyNetwork> + 'static,
        signer: impl Signer + Send + Sync + 'static,
    ) -> Self {
        self.with_chain(
            domain,
            EvmChainHandlerBuilder {
                provider: provider.erased(),
                signer: Box::new(signer),
            },
        )
    }
}