Story SDK MCP Server

Official
import requests from web3 import Web3 from typing import Optional from ens import ENS class AddressResolver: """ Utility class for resolving addresses from domain names and vice versa. Implements a chain of responsibility pattern for address resolution. Supports both ENS (.eth) and Space ID (.ip) domains. """ def __init__(self, web3: Web3, chain_id: int = 1514): """ Initialize the address resolver. Args: web3: Web3 instance for address validation and formatting chain_id: Chain ID for the blockchain (default: 1514 for Story Protocol) """ self.web3 = web3 self.chain_id = chain_id self.space_id_api_url = "https://nameapi.space.id" # Initialize ENS instance self.ens = ENS(web3.provider) def resolve_address(self, address_or_domain: str) -> str: """ Resolve a domain name to an address or return the address if it's already in address format. Chain of responsibility: check if it's an address, try ENS, then try Space ID. Args: address_or_domain: Either an Ethereum address (0x...) or a domain name (name.eth or name.ip) Returns: str: The resolved Ethereum address Raises: ValueError: If the address cannot be resolved """ # First handler: Check if it's already an address if self._is_ethereum_address(address_or_domain): return self.web3.to_checksum_address(address_or_domain) # Second handler: Try to resolve as ENS domain if address_or_domain.lower().endswith(".eth"): ens_address = self._resolve_ens_domain(address_or_domain) if ens_address: return self.web3.to_checksum_address(ens_address) # Third handler: Try to resolve as a Space ID domain space_id_address = self._resolve_domain_to_address(address_or_domain) if space_id_address: return self.web3.to_checksum_address(space_id_address) # If we get here, we couldn't resolve the address raise ValueError(f"Could not resolve address or domain: {address_or_domain}") def get_domain_for_address(self, address: str) -> Optional[str]: """ Get the primary domain name for an address. Tries both ENS and Space ID reverse resolution. Args: address: Ethereum address Returns: str: The domain name or None if not found """ try: # Ensure address is in correct format address = self.web3.to_checksum_address(address) # Try ENS reverse resolution first ens_name = self._get_ens_name(address) if ens_name: return ens_name # If no ENS name, try Space ID response = requests.get( f"{self.space_id_api_url}/getName?chainid={self.chain_id}&address={address}" ) if response.status_code != 200: return None data = response.json() if data.get("code") != 0: return None return data.get("name") except Exception: return None def _is_ethereum_address(self, value: str) -> bool: """Check if a string is a valid Ethereum address.""" if not isinstance(value, str): return False return ( value.startswith("0x") and len(value) == 42 and self.web3.is_address(value) ) def _resolve_ens_domain(self, domain: str) -> Optional[str]: """Resolve an ENS domain name to an address.""" try: address = self.ens.address(domain) return address if address else None except Exception: return None def _get_ens_name(self, address: str) -> Optional[str]: """Get the ENS name for an address using reverse resolution.""" try: name = self.ens.name(address) # Verify the forward resolution matches if name and self.ens.address(name) == address: return name return None except Exception: return None def _resolve_domain_to_address(self, domain: str) -> Optional[str]: """Resolve a Space ID domain name to an address using Space ID API.""" try: # Make request to Space ID API response = requests.get( f"{self.space_id_api_url}/getAddress?domain={domain}" ) if response.status_code != 200: return None data = response.json() if data.get("code") != 0: return None return data.get("address") except Exception: return None # Create a convenience function for easy importing def create_address_resolver(web3: Web3, chain_id: int = 1514) -> AddressResolver: """ Create an AddressResolver instance. Args: web3: Web3 instance chain_id: Chain ID for the blockchain (default: 1514 for Story Protocol) Returns: AddressResolver: An instance of the AddressResolver """ return AddressResolver(web3, chain_id)