Skip to main content

trident_sdk/evm/
deposit.rs

1use alloy_primitives::{Address, U256};
2use alloy_provider::network::Network;
3use alloy_provider::{PendingTransactionBuilder, Provider};
4use trident_evm_aa::{MulticallBuilder, MulticallError};
5
6use crate::evm::abi::{IERC20, TridentDeposits};
7
8/// Errors produced by the EVM-direct deposit helpers in
9/// [`deposit`] and [`deposit_multicall`].
10#[derive(Debug, thiserror::Error)]
11pub enum EvmDepositError {
12    /// An underlying contract call (allowance read, approve, or deposit) failed.
13    #[error("Contract call failed: {0}")]
14    Contract(#[from] alloy_contract::Error),
15
16    /// Awaiting inclusion of the approval transaction failed.
17    #[error("Approval transaction pending error: {0}")]
18    PendingApproval(#[from] alloy_provider::PendingTransactionError),
19
20    /// Submitting the 7702 multicall transaction failed.
21    #[error(transparent)]
22    Multicall(#[from] MulticallError),
23}
24
25/// Ensures `signer_address` has at least `amount` allowance on `token` for
26/// `deposit_contract`, then submits `TridentDeposits.deposit(token, amount)`
27/// and returns the deposit tx's [`PendingTransactionBuilder`].
28///
29/// If the current allowance is insufficient, sends an `approve` transaction
30/// and waits for its inclusion before submitting the deposit. On return the
31/// deposit tx is submitted but not yet mined; the caller awaits it via
32/// `.get_receipt()` / `.watch()`.
33///
34/// For atomic approve + deposit in a single 7702 transaction, use
35/// [`deposit_multicall`] instead.
36pub async fn deposit<P, N>(
37    provider: P,
38    deposit_contract: Address,
39    signer_address: Address,
40    token: Address,
41    amount: U256,
42) -> Result<PendingTransactionBuilder<N>, EvmDepositError>
43where
44    P: Provider<N>,
45    N: Network,
46{
47    ensure_allowance(&provider, token, signer_address, deposit_contract, amount).await?;
48    let deposits = TridentDeposits::new(deposit_contract, provider);
49    Ok(deposits.deposit(token, amount).send().await?)
50}
51
52/// Builds and submits a 7702-delegated multicall transaction that, for each
53/// `(token, amount)` entry, runs an `IERC20.approve(deposit_contract, amount)`
54/// call followed by a `TridentDeposits.deposit(token, amount)` call.
55///
56/// Approvals are inlined so the batch is self-contained: all approves and
57/// deposits land atomically in a single transaction. `signer_address` must be
58/// a 7702-delegated EOA and must match the key the provider is configured to
59/// sign with.
60pub async fn deposit_multicall<P, N, I>(
61    provider: P,
62    deposit_contract: Address,
63    signer_address: Address,
64    deposits: I,
65) -> Result<PendingTransactionBuilder<N>, EvmDepositError>
66where
67    P: Provider<N>,
68    N: Network,
69    I: IntoIterator<Item = (Address, U256)>,
70{
71    let mut builder = MulticallBuilder::new();
72    for (token, amount) in deposits {
73        let approve = IERC20::approveCall {
74            spender: deposit_contract,
75            value: amount,
76        };
77        builder = builder.call(token, &approve);
78        let deposit = TridentDeposits::depositCall { token, amount };
79        builder = builder.call(deposit_contract, &deposit);
80    }
81    Ok(builder.send(provider, signer_address).await?)
82}
83
84async fn ensure_allowance<P, N>(
85    provider: P,
86    token: Address,
87    owner: Address,
88    spender: Address,
89    required: U256,
90) -> Result<(), EvmDepositError>
91where
92    P: Provider<N>,
93    N: Network,
94{
95    let erc20 = IERC20::new(token, provider);
96    let current = erc20.allowance(owner, spender).call().await?;
97    if current >= required {
98        return Ok(());
99    }
100    let pending = erc20.approve(spender, required).send().await?;
101    pending.get_receipt().await?;
102    Ok(())
103}