Skip to main content

strike_sdk/chain/
vault.rs

1//! USDT approval and balance helpers for the Vault contract.
2
3use alloy::primitives::{Address, Bytes, U256};
4use alloy::providers::DynProvider;
5use alloy::rpc::types::TransactionRequest;
6use alloy::sol_types::SolCall;
7use std::sync::Arc;
8use tokio::sync::Mutex;
9use tracing::info;
10
11use crate::chain::send_tx;
12use crate::config::StrikeConfig;
13use crate::contracts::MockUSDT;
14use crate::error::{Result, StrikeError};
15use crate::nonce::NonceSender;
16
17/// Client for vault-related operations (USDT approval, balance checks).
18pub struct VaultClient<'a> {
19    provider: &'a DynProvider,
20    signer_addr: Option<Address>,
21    config: &'a StrikeConfig,
22    nonce_sender: Option<Arc<Mutex<NonceSender>>>,
23}
24
25impl<'a> VaultClient<'a> {
26    pub(crate) fn new(
27        provider: &'a DynProvider,
28        signer_addr: Option<Address>,
29        config: &'a StrikeConfig,
30        nonce_sender: Option<Arc<Mutex<NonceSender>>>,
31    ) -> Self {
32        Self {
33            provider,
34            signer_addr,
35            config,
36            nonce_sender,
37        }
38    }
39
40    fn require_wallet(&self) -> Result<Address> {
41        self.signer_addr.ok_or(StrikeError::NoWallet)
42    }
43
44    /// Approve the Vault contract to spend USDT on behalf of the signer.
45    ///
46    /// Idempotent: skips if already max-approved (allowance >= U256::MAX / 2).
47    pub async fn approve_usdt(&self) -> Result<()> {
48        let signer = self.require_wallet()?;
49        let usdt = MockUSDT::new(self.config.addresses.usdt, self.provider);
50        let vault = self.config.addresses.vault;
51
52        if let Ok(current) = usdt.allowance(signer, vault).call().await {
53            if current >= (U256::MAX >> 1) {
54                info!("vault already approved for USDT — skipping");
55                return Ok(());
56            }
57        }
58
59        info!("approving vault for max USDT spend...");
60
61        let calldata = MockUSDT::approveCall {
62            spender: vault,
63            value: U256::MAX,
64        }
65        .abi_encode();
66
67        let mut tx = TransactionRequest::default()
68            .to(self.config.addresses.usdt)
69            .input(Bytes::from(calldata).into());
70        tx.gas = Some(100_000);
71
72        let pending = send_tx(self.provider, &self.nonce_sender, tx).await?;
73        let receipt = pending
74            .get_receipt()
75            .await
76            .map_err(|e| StrikeError::Contract(e.to_string()))?;
77        info!(tx = %receipt.transaction_hash, "vault approved for USDT");
78        Ok(())
79    }
80
81    /// Get the USDT balance of an address.
82    pub async fn usdt_balance(&self, address: Address) -> Result<U256> {
83        let usdt = MockUSDT::new(self.config.addresses.usdt, self.provider);
84        usdt.balanceOf(address)
85            .call()
86            .await
87            .map_err(|e| StrikeError::Contract(e.to_string()))
88    }
89
90    /// Get the current USDT allowance for the Vault contract.
91    pub async fn usdt_allowance(&self, owner: Address) -> Result<U256> {
92        let usdt = MockUSDT::new(self.config.addresses.usdt, self.provider);
93        usdt.allowance(owner, self.config.addresses.vault)
94            .call()
95            .await
96            .map_err(|e| StrikeError::Contract(e.to_string()))
97    }
98}