use crate::{error::InternalError, stores::TokenStore, Entry, Error, Token, TokenId};
use alloy::{network::Network, primitives::Address, providers::Provider, sol};
use async_trait::async_trait;
use bigdecimal::BigDecimal;
sol!(
#[sol(rpc)]
Erc20Contract,
"abi/erc20.json"
);
#[async_trait]
pub trait Erc20ProviderExt<N>: Provider<N> + Sized
where
N: Network,
{
async fn retrieve_token(&self, address: Address) -> Result<Token, Error> {
let instance = Erc20Contract::Erc20ContractInstance::new(address, self);
let symbol = instance
.symbol()
.call()
.await
.map_err(|err| Error::new(address.into(), err))?;
let decimals = instance
.decimals()
.call()
.await
.map_err(|err| Error::new(address.into(), err))?;
let token = Token::new(address, symbol, decimals);
Ok(token)
}
async fn get_token<'a, Id, S>(&'a self, id: Id, store: &'a mut S) -> Result<&'a Token, Error>
where
S: TokenStore<'a> + Send,
Id: Into<TokenId> + Send,
{
let id: TokenId = id.into();
let chain_id = self
.get_chain_id()
.await
.map_err(|err| Error::new(id.clone(), err))?;
match store.entry(chain_id, id.clone()) {
Entry::Occupied(e) => Ok(e.into_mut()),
Entry::Vacant(e) => {
let token = match id.clone() {
TokenId::Address(address) => self.retrieve_token(address).await,
TokenId::Symbol(symbol) => {
Err(Error::new(id, InternalError::NotInStore(symbol)))
}
}?;
Ok(e.insert(token))
}
}
}
async fn balance_of(&self, token: Address, address: Address) -> Result<BigDecimal, Error> {
let instance = Erc20Contract::Erc20ContractInstance::new(token, self);
let result = instance
.balanceOf(address)
.call()
.await
.map_err(|err| Error::new(address.into(), err))?;
let token = self.retrieve_token(token).await?;
let balance = token.get_balance(result);
Ok(balance)
}
}
#[async_trait]
impl<P, N> Erc20ProviderExt<N> for P
where
P: Provider<N>,
N: Network,
{
}