data3 0.1.0

SDK for accessing ethereum data hosted by indexers.
Documentation
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,
}

// https://eth.blockscout.com/api-docs
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);

        // assert!(false);
    }
    #[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);

        // assert!(false);
    }

    #[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);

        // assert!(false);
    }

    #[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);

        // assert!(false);
    }
}