use alloy::network::Network;
use alloy::primitives::{Address, U256};
use alloy::providers::Provider;
use alloy::sol_types::{sol, SolCall};
use crate::error::{OstiumError, Result};
use crate::types::UnsignedTransaction;
sol! {
#[sol(rpc)]
contract USDC {
function balanceOf(address account) external view returns (uint256);
function allowance(address owner, address spender) external view returns (uint256);
function approve(address spender, uint256 amount) external returns (bool);
function transfer(address to, uint256 amount) external returns (bool);
function transferFrom(address from, address to, uint256 amount) external returns (bool);
function decimals() external view returns (uint8);
function symbol() external view returns (string);
function name() external view returns (string);
function totalSupply() external view returns (uint256);
}
}
pub struct UsdcContract<P: Provider<N>, N: Network = alloy::network::Ethereum> {
contract: USDC::USDCInstance<P, N>,
}
impl<P: Provider<N>, N: Network> UsdcContract<P, N> {
pub fn new(address: Address, provider: P) -> Self {
let contract = USDC::new(address, provider);
Self { contract }
}
pub async fn balance_of(&self, account: Address) -> Result<U256> {
let balance = self
.contract
.balanceOf(account)
.call()
.await
.map_err(|e| OstiumError::contract(format!("Failed to get balance: {}", e)))?;
Ok(balance)
}
pub async fn allowance(&self, owner: Address, spender: Address) -> Result<U256> {
let allowance = self
.contract
.allowance(owner, spender)
.call()
.await
.map_err(|e| OstiumError::contract(format!("Failed to get allowance: {}", e)))?;
Ok(allowance)
}
pub async fn approve(&self, spender: Address, amount: U256) -> Result<()> {
let _receipt = self
.contract
.approve(spender, amount)
.send()
.await
.map_err(|e| OstiumError::contract(format!("Failed to approve: {}", e)))?
.get_receipt()
.await
.map_err(|e| OstiumError::contract(format!("Failed to get receipt: {}", e)))?;
Ok(())
}
pub async fn transfer(&self, to: Address, amount: U256) -> Result<()> {
let _receipt = self
.contract
.transfer(to, amount)
.send()
.await
.map_err(|e| OstiumError::contract(format!("Failed to transfer: {}", e)))?
.get_receipt()
.await
.map_err(|e| OstiumError::contract(format!("Failed to get receipt: {}", e)))?;
Ok(())
}
pub async fn decimals(&self) -> Result<u8> {
let decimals = self
.contract
.decimals()
.call()
.await
.map_err(|e| OstiumError::contract(format!("Failed to get decimals: {}", e)))?;
Ok(decimals)
}
pub async fn symbol(&self) -> Result<String> {
let symbol = self
.contract
.symbol()
.call()
.await
.map_err(|e| OstiumError::contract(format!("Failed to get symbol: {}", e)))?;
Ok(symbol)
}
pub async fn name(&self) -> Result<String> {
let name = self
.contract
.name()
.call()
.await
.map_err(|e| OstiumError::contract(format!("Failed to get name: {}", e)))?;
Ok(name)
}
pub async fn approve_unsigned(
&self,
spender: Address,
amount: U256,
tx_params: crate::UnsignedTransactionParams,
) -> Result<UnsignedTransaction> {
let approve_call = USDC::approveCall { spender, amount };
self.build_unsigned_transaction_from_call(approve_call, tx_params)
.await
}
pub async fn transfer_unsigned(
&self,
to: Address,
amount: U256,
tx_params: crate::UnsignedTransactionParams,
) -> Result<UnsignedTransaction> {
let transfer_call = USDC::transferCall { to, amount };
self.build_unsigned_transaction_from_call(transfer_call, tx_params)
.await
}
async fn build_unsigned_transaction_from_call<C>(
&self,
call: C,
tx_params: crate::UnsignedTransactionParams,
) -> Result<UnsignedTransaction>
where
C: SolCall,
{
let to = *self.contract.address();
let data = call.abi_encode();
let chain_id = self
.contract
.provider()
.get_chain_id()
.await
.map_err(|e| OstiumError::contract(format!("Failed to get chain ID: {}", e)))?;
let gas_limit = if tx_params.include_gas_estimates {
Some(U256::from(65_000)) } else {
None
};
let gas_price = if tx_params.include_gas_estimates {
match self.contract.provider().get_gas_price().await {
Ok(price) => Some(U256::from(price)),
Err(e) => {
tracing::warn!("Failed to get gas price: {}", e);
None
}
}
} else {
None
};
let nonce = if tx_params.include_nonce {
match self
.contract
.provider()
.get_transaction_count(tx_params.from)
.await
{
Ok(count) => Some(count),
Err(e) => {
tracing::warn!("Failed to get nonce: {}", e);
None
}
}
} else {
None
};
Ok(UnsignedTransaction {
to,
data,
value: U256::ZERO, gas_limit,
gas_price,
chain_id,
nonce,
})
}
}