erc8004 0.4.1

ERC-8004 Trustless Agents Rust SDK.
Documentation
//! Pre-configured network definitions with known contract addresses.
//!
//! ERC-8004 contracts are deployed via CREATE2 deterministic deployment,
//! so mainnet chains share the same addresses and testnet chains share
//! the same addresses.

use alloy::primitives::{Address, address};

/// Known contract addresses for a specific network deployment.
#[derive(Debug, Clone, Copy)]
pub struct NetworkAddresses {
    /// The Identity Registry (ERC-721) contract address.
    pub identity: Address,
    /// The Reputation Registry contract address.
    pub reputation: Address,
}

/// Pre-defined network configurations for ERC-8004 deployments.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum Network {
    /// Ethereum Mainnet (chain ID 1).
    EthereumMainnet,
    /// Ethereum Sepolia testnet (chain ID 11155111).
    EthereumSepolia,
    /// Base Mainnet (chain ID 8453).
    BaseMainnet,
    /// Base Sepolia testnet (chain ID 84532).
    BaseSepolia,
    /// Polygon Mainnet (chain ID 137).
    PolygonMainnet,
    /// Polygon Amoy testnet (chain ID 80002).
    PolygonAmoy,
    /// Arbitrum One Mainnet (chain ID 42161).
    ArbitrumMainnet,
    /// Arbitrum Sepolia testnet (chain ID 421614).
    ArbitrumSepolia,
    /// Celo Mainnet (chain ID 42220).
    CeloMainnet,
    /// Celo Alfajores testnet (chain ID 44787).
    CeloAlfajores,
    /// Gnosis Mainnet (chain ID 100).
    GnosisMainnet,
    /// Scroll Mainnet (chain ID 534352).
    ScrollMainnet,
    /// Scroll Sepolia testnet (chain ID 534351).
    ScrollSepolia,
    /// Taiko Mainnet — Alethia (chain ID 167000).
    TaikoMainnet,
    /// Monad Mainnet (chain ID 143).
    MonadMainnet,
    /// Monad Testnet (chain ID 10143).
    MonadTestnet,
    /// BNB Smart Chain Mainnet (chain ID 56).
    BscMainnet,
    /// BNB Smart Chain Testnet (chain ID 97).
    BscTestnet,
    /// Abstract Mainnet (chain ID 2741).
    AbstractMainnet,
    /// Abstract Testnet (chain ID 11124).
    AbstractTestnet,
    /// Avalanche C-Chain Mainnet (chain ID 43114).
    AvalancheMainnet,
    /// Avalanche Fuji Testnet (chain ID 43113).
    AvalancheTestnet,
    /// Linea Mainnet (chain ID 59144).
    LineaMainnet,
    /// Linea Sepolia testnet (chain ID 59141).
    LineaSepolia,
    /// Mantle Mainnet (chain ID 5000).
    MantleMainnet,
    /// Mantle Sepolia testnet (chain ID 5003).
    MantleSepolia,
    /// `MegaETH` Mainnet (chain ID 4326).
    MegaEthMainnet,
    /// `MegaETH` Testnet (chain ID 6342).
    MegaEthTestnet,
    /// Optimism Mainnet (chain ID 10).
    OptimismMainnet,
    /// Optimism Sepolia testnet (chain ID 11155420).
    OptimismSepolia,
}

/// Shared addresses for all mainnet deployments (CREATE2 deterministic).
const MAINNET_IDENTITY: Address = address!("8004A169FB4a3325136EB29fA0ceB6D2e539a432");
const MAINNET_REPUTATION: Address = address!("8004BAa17C55a88189AE136b182e5fdA19dE9b63");

/// Shared addresses for all testnet deployments (CREATE2 deterministic).
const TESTNET_IDENTITY: Address = address!("8004A818BFB912233c491871b3d84c89A494BD9e");
const TESTNET_REPUTATION: Address = address!("8004B663056A597Dffe9eCcC1965A193B7388713");

impl Network {
    /// Returns the known contract addresses for this network.
    #[must_use]
    pub const fn addresses(self) -> NetworkAddresses {
        match self {
            Self::EthereumMainnet
            | Self::BaseMainnet
            | Self::PolygonMainnet
            | Self::ArbitrumMainnet
            | Self::CeloMainnet
            | Self::GnosisMainnet
            | Self::ScrollMainnet
            | Self::TaikoMainnet
            | Self::MonadMainnet
            | Self::BscMainnet
            | Self::AbstractMainnet
            | Self::AvalancheMainnet
            | Self::LineaMainnet
            | Self::MantleMainnet
            | Self::MegaEthMainnet
            | Self::OptimismMainnet => NetworkAddresses {
                identity: MAINNET_IDENTITY,
                reputation: MAINNET_REPUTATION,
            },
            Self::EthereumSepolia
            | Self::BaseSepolia
            | Self::PolygonAmoy
            | Self::ArbitrumSepolia
            | Self::CeloAlfajores
            | Self::ScrollSepolia
            | Self::MonadTestnet
            | Self::BscTestnet
            | Self::AbstractTestnet
            | Self::AvalancheTestnet
            | Self::LineaSepolia
            | Self::MantleSepolia
            | Self::MegaEthTestnet
            | Self::OptimismSepolia => NetworkAddresses {
                identity: TESTNET_IDENTITY,
                reputation: TESTNET_REPUTATION,
            },
        }
    }

    /// Returns the EIP-155 chain ID for this network.
    #[must_use]
    pub const fn chain_id(self) -> u64 {
        match self {
            Self::EthereumMainnet => 1,
            Self::EthereumSepolia => 11_155_111,
            Self::BaseMainnet => 8453,
            Self::BaseSepolia => 84532,
            Self::PolygonMainnet => 137,
            Self::PolygonAmoy => 80002,
            Self::ArbitrumMainnet => 42161,
            Self::ArbitrumSepolia => 421_614,
            Self::CeloMainnet => 42220,
            Self::CeloAlfajores => 44787,
            Self::GnosisMainnet => 100,
            Self::ScrollMainnet => 534_352,
            Self::ScrollSepolia => 534_351,
            Self::TaikoMainnet => 167_000,
            Self::MonadMainnet => 143,
            Self::MonadTestnet => 10143,
            Self::BscMainnet => 56,
            Self::BscTestnet => 97,
            Self::AbstractMainnet => 2741,
            Self::AbstractTestnet => 11124,
            Self::AvalancheMainnet => 43114,
            Self::AvalancheTestnet => 43113,
            Self::LineaMainnet => 59144,
            Self::LineaSepolia => 59141,
            Self::MantleMainnet => 5000,
            Self::MantleSepolia => 5003,
            Self::MegaEthMainnet => 4326,
            Self::MegaEthTestnet => 6342,
            Self::OptimismMainnet => 10,
            Self::OptimismSepolia => 11_155_420,
        }
    }

    /// All known ERC-8004 network variants.
    pub const ALL: &[Self] = &[
        Self::EthereumMainnet,
        Self::EthereumSepolia,
        Self::BaseMainnet,
        Self::BaseSepolia,
        Self::PolygonMainnet,
        Self::PolygonAmoy,
        Self::ArbitrumMainnet,
        Self::ArbitrumSepolia,
        Self::CeloMainnet,
        Self::CeloAlfajores,
        Self::GnosisMainnet,
        Self::ScrollMainnet,
        Self::ScrollSepolia,
        Self::TaikoMainnet,
        Self::MonadMainnet,
        Self::MonadTestnet,
        Self::BscMainnet,
        Self::BscTestnet,
        Self::AbstractMainnet,
        Self::AbstractTestnet,
        Self::AvalancheMainnet,
        Self::AvalancheTestnet,
        Self::LineaMainnet,
        Self::LineaSepolia,
        Self::MantleMainnet,
        Self::MantleSepolia,
        Self::MegaEthMainnet,
        Self::MegaEthTestnet,
        Self::OptimismMainnet,
        Self::OptimismSepolia,
    ];

    /// Look up a [`Network`] by its EIP-155 chain ID.
    ///
    /// Returns [`None`] if the chain ID is not a known ERC-8004 deployment.
    #[must_use]
    pub fn from_chain_id(chain_id: u64) -> Option<Self> {
        Self::ALL.iter().find(|n| n.chain_id() == chain_id).copied()
    }

    /// Returns the `eip155:{chainId}` namespace prefix for agent registry identifiers.
    #[must_use]
    pub fn agent_registry_prefix(self) -> String {
        format!("eip155:{}:{}", self.chain_id(), self.addresses().identity)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_all_has_no_duplicate_chain_ids() {
        let mut ids: Vec<u64> = Network::ALL.iter().map(|n| n.chain_id()).collect();
        ids.sort_unstable();
        let before = ids.len();
        ids.dedup();
        assert_eq!(
            before,
            ids.len(),
            "Network::ALL contains duplicate chain IDs"
        );
    }

    #[test]
    fn test_chain_id_round_trip() {
        for &network in Network::ALL {
            let id = network.chain_id();
            assert_eq!(
                Network::from_chain_id(id),
                Some(network),
                "from_chain_id({id}) should return the original variant"
            );
        }
    }

    #[test]
    fn test_from_chain_id_unknown_returns_none() {
        assert_eq!(Network::from_chain_id(999_999_999), None);
    }

    #[test]
    fn test_create2_mainnet_addresses_are_consistent() {
        let reference = Network::EthereumMainnet.addresses();
        for &network in Network::ALL {
            let addrs = network.addresses();
            if addrs.identity != reference.identity {
                continue;
            }
            assert_eq!(
                addrs.reputation,
                reference.reputation,
                "chain {} has matching identity but mismatched reputation",
                network.chain_id()
            );
        }
    }

    #[test]
    fn test_testnet_addresses_differ_from_mainnet() {
        let mainnet = Network::EthereumMainnet.addresses();
        let testnet = Network::EthereumSepolia.addresses();
        assert_ne!(mainnet.identity, testnet.identity);
        assert_ne!(mainnet.reputation, testnet.reputation);
    }

    #[test]
    fn test_agent_registry_prefix_format() {
        let network = Network::EthereumMainnet;
        let prefix = network.agent_registry_prefix();
        let expected = format!(
            "eip155:{}:{}",
            network.chain_id(),
            network.addresses().identity
        );
        assert_eq!(prefix, expected);
    }
}