trident-sdk 0.2.0

End-user SDK for Trident deposits and cross-chain spends
Documentation
use alloy_primitives::{Address, U256};
use alloy_provider::network::Network;
use alloy_provider::{PendingTransactionBuilder, Provider};
use trident_evm_aa::{MulticallBuilder, MulticallError};

use crate::evm::abi::{IERC20, TridentDeposits};

/// Errors produced by the EVM-direct deposit helpers in
/// [`deposit`] and [`deposit_multicall`].
#[derive(Debug, thiserror::Error)]
pub enum EvmDepositError {
    /// An underlying contract call (allowance read, approve, or deposit) failed.
    #[error("Contract call failed: {0}")]
    Contract(#[from] alloy_contract::Error),

    /// Awaiting inclusion of the approval transaction failed.
    #[error("Approval transaction pending error: {0}")]
    PendingApproval(#[from] alloy_provider::PendingTransactionError),

    /// Submitting the 7702 multicall transaction failed.
    #[error(transparent)]
    Multicall(#[from] MulticallError),
}

/// Ensures `signer_address` has at least `amount` allowance on `token` for
/// `deposit_contract`, then submits `TridentDeposits.deposit(token, amount)`
/// and returns the deposit tx's [`PendingTransactionBuilder`].
///
/// If the current allowance is insufficient, sends an `approve` transaction
/// and waits for its inclusion before submitting the deposit. On return the
/// deposit tx is submitted but not yet mined; the caller awaits it via
/// `.get_receipt()` / `.watch()`.
///
/// For atomic approve + deposit in a single 7702 transaction, use
/// [`deposit_multicall`] instead.
pub async fn deposit<P, N>(
    provider: P,
    deposit_contract: Address,
    signer_address: Address,
    token: Address,
    amount: U256,
) -> Result<PendingTransactionBuilder<N>, EvmDepositError>
where
    P: Provider<N>,
    N: Network,
{
    ensure_allowance(&provider, token, signer_address, deposit_contract, amount).await?;
    let deposits = TridentDeposits::new(deposit_contract, provider);
    Ok(deposits.deposit(token, amount).send().await?)
}

/// Builds and submits a 7702-delegated multicall transaction that, for each
/// `(token, amount)` entry, runs an `IERC20.approve(deposit_contract, amount)`
/// call followed by a `TridentDeposits.deposit(token, amount)` call.
///
/// Approvals are inlined so the batch is self-contained: all approves and
/// deposits land atomically in a single transaction. `signer_address` must be
/// a 7702-delegated EOA and must match the key the provider is configured to
/// sign with.
pub async fn deposit_multicall<P, N, I>(
    provider: P,
    deposit_contract: Address,
    signer_address: Address,
    deposits: I,
) -> Result<PendingTransactionBuilder<N>, EvmDepositError>
where
    P: Provider<N>,
    N: Network,
    I: IntoIterator<Item = (Address, U256)>,
{
    let mut builder = MulticallBuilder::new();
    for (token, amount) in deposits {
        let approve = IERC20::approveCall {
            spender: deposit_contract,
            value: amount,
        };
        builder = builder.call(token, &approve);
        let deposit = TridentDeposits::depositCall { token, amount };
        builder = builder.call(deposit_contract, &deposit);
    }
    Ok(builder.send(provider, signer_address).await?)
}

async fn ensure_allowance<P, N>(
    provider: P,
    token: Address,
    owner: Address,
    spender: Address,
    required: U256,
) -> Result<(), EvmDepositError>
where
    P: Provider<N>,
    N: Network,
{
    let erc20 = IERC20::new(token, provider);
    let current = erc20.allowance(owner, spender).call().await?;
    if current >= required {
        return Ok(());
    }
    let pending = erc20.approve(spender, required).send().await?;
    pending.get_receipt().await?;
    Ok(())
}