use alloy::primitives::Address;
use reqwest::Client;
use types::{AccountInfo, BlockScoutResponse, TokenBalance, Transaction};
use crate::{network::Network, serde_override::SerdeResponseParse};
pub mod types;
pub struct BlockScout {
base_url: String,
}
impl BlockScout {
pub fn new(base_url: String) -> Self {
BlockScout { base_url }
}
pub fn from_network(network: Network) -> Self {
let base_url = match network {
Network::EthereumMainnet => "https://eth.blockscout.com",
Network::OptimismMainnet => "https://optimism.blockscout.com",
Network::BaseMainnet => "https://base.blockscout.com",
Network::ArbitrumMainnet => "https://arbitrum.blockscout.com",
Network::CeloMainnet => "https://celo.blockscout.com",
_ => unimplemented!(),
};
BlockScout::new(base_url.to_string())
}
pub async fn address_info(&self, address: Address) -> crate::Result<AccountInfo> {
let url = format!("{}/api/v2/addresses/{}", self.base_url, address);
let client = reqwest::Client::new();
client
.get(&url)
.send()
.await?
.error_for_status()?
.serde_parse_custom()
.await
}
pub async fn address_transactions(
&self,
address: Address,
) -> crate::Result<BlockScoutResponse<Transaction>> {
let url = format!(
"{}/api/v2/addresses/{}/transactions",
self.base_url, address
);
let client = reqwest::Client::new();
client
.get(&url)
.send()
.await?
.error_for_status()?
.serde_parse_custom()
.await
}
pub async fn token_transfers(
&self,
address: Address,
) -> crate::Result<BlockScoutResponse<TokenBalance>> {
let url = format!(
"{}/api/v2/addresses/{}/token-transfers",
self.base_url, address
);
let client = Client::new();
client
.get(url)
.send()
.await?
.error_for_status()?
.serde_parse_custom()
.await
}
pub async fn token_balances(&self, address: Address) -> crate::Result<Vec<TokenBalance>> {
let url = format!(
"{}/api/v2/addresses/{}/token-balances",
self.base_url, address
);
let client = Client::new();
let response = client.get(url).send().await?.error_for_status()?;
serde_path_to_error::deserialize(&mut serde_json::Deserializer::from_str(
&response.text().await?,
))
.map_err(crate::Error::SerdePathToError)
}
}
#[cfg(test)]
mod tests {
use super::*;
use rstest::rstest;
#[rstest]
#[case(Network::EthereumMainnet)]
#[case(Network::OptimismMainnet)]
#[case(Network::ArbitrumMainnet)]
#[tokio::test]
async fn test_address_info(#[case] network: Network) {
let blockscout = BlockScout::from_network(network);
let result = blockscout
.address_info(
"0x742d35Cc6634C0532925a3b844Bc454e4438f44e"
.parse()
.unwrap(),
)
.await
.expect("Failed to fetch address info");
println!("{:#?}", result);
}
#[rstest]
#[case(Network::EthereumMainnet)]
#[case(Network::OptimismMainnet)]
#[case(Network::ArbitrumMainnet)]
#[tokio::test]
async fn test_address_transactions(#[case] network: Network) {
let blockscout = BlockScout::from_network(network);
let result = blockscout
.address_transactions(
"0x742d35Cc6634C0532925a3b844Bc454e4438f44e"
.parse()
.unwrap(),
)
.await
.expect("Failed to fetch address transactions");
println!("{:#?}", result);
}
#[rstest]
#[case(Network::EthereumMainnet)]
#[case(Network::OptimismMainnet)]
#[case(Network::ArbitrumMainnet)]
#[tokio::test]
async fn test_token_transfers(#[case] network: Network) {
let blockscout = BlockScout::from_network(network);
let result = blockscout
.token_transfers(
"0x742d35Cc6634C0532925a3b844Bc454e4438f44e"
.parse()
.unwrap(),
)
.await
.expect("Failed to fetch token transfers");
println!("{:#?}", result);
}
#[rstest]
#[case(Network::EthereumMainnet)]
#[case(Network::OptimismMainnet)]
#[case(Network::ArbitrumMainnet)]
#[tokio::test]
async fn test_token_balances(#[case] network: Network) {
let blockscout = BlockScout::from_network(network);
let result = blockscout
.token_balances(
"0x742d35Cc6634C0532925a3b844Bc454e4438f44e"
.parse()
.unwrap(),
)
.await
.expect("Failed to fetch token balances");
println!("{:#?}", result);
}
}