inspect_contract_code
Analyze verified smart contract source code and metadata on blockchain networks to understand contract functionality and structure.
Instructions
Inspects a verified contract's source code or metadata.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| chain_id | Yes | The ID of the blockchain. | |
| address | Yes | The address of the smart contract. | |
| file_name | No | The name of the source file to inspect. If omitted, returns contract metadata and the list of source files. |
Implementation Reference
- The main handler function implementing the core logic of the 'inspect_contract_code' tool. It handles metadata retrieval or specific file fetching, progress reporting, validation, and response construction using shared helpers.@log_tool_invocation async def inspect_contract_code( chain_id: Annotated[str, Field(description="The ID of the blockchain.")], address: Annotated[str, Field(description="The address of the smart contract.")], file_name: Annotated[ str | None, Field( description=( "The name of the source file to inspect. " "If omitted, returns contract metadata and the list of source files." ), ), ] = None, *, ctx: Context, ) -> ToolResponse[ContractMetadata | ContractSourceFile]: """Inspects a verified contract's source code or metadata.""" if file_name is None: start_msg = f"Starting to fetch contract metadata for {address} on chain {chain_id}..." else: start_msg = f"Starting to fetch source code for '{file_name}' of contract {address} on chain {chain_id}..." await report_and_log_progress( ctx, progress=0.0, total=2.0, message=start_msg, ) processed = await _fetch_and_process_contract(chain_id, address, ctx) if file_name is None: metadata = ContractMetadata.model_validate(processed.metadata) instructions = None notes = None if metadata.constructor_args_truncated: notes = ["Constructor arguments were truncated to limit context size."] if processed.source_files: instructions = [ ( "To retrieve a specific file's contents, call this tool again with the " "'file_name' argument using one of the values from 'source_code_tree_structure'." ) ] return build_tool_response(data=metadata, instructions=instructions, notes=notes) if file_name not in processed.source_files: available = ", ".join(processed.source_files.keys()) raise ValueError( f"File '{file_name}' not found in the source code for this contract. Available files: {available}" ) return build_tool_response(data=ContractSourceFile(file_content=processed.source_files[file_name]))
- Key helper function that fetches contract data from Blockscout API or cache, processes source files and metadata (including truncation), and returns a CachedContract object used by the handler.async def _fetch_and_process_contract(chain_id: str, address: str, ctx: Context) -> CachedContract: """Fetch contract data from cache or Blockscout API.""" normalized_address = address.lower() cache_key = f"{chain_id}:{normalized_address}" if cached := await contract_cache.get(cache_key): return cached base_url = await get_blockscout_base_url(chain_id) await report_and_log_progress( ctx, progress=1.0, total=2.0, message="Resolved Blockscout instance URL.", ) api_path = f"/api/v2/smart-contracts/{normalized_address}" raw_data = await make_blockscout_request(base_url=base_url, api_path=api_path) await report_and_log_progress( ctx, progress=2.0, total=2.0, message="Successfully fetched contract data.", ) raw_data.setdefault("name", normalized_address) for key in [ "language", "compiler_version", "verified_at", "optimization_enabled", "optimization_runs", "evm_version", "license_type", "proxy_type", "is_fully_verified", "decoded_constructor_args", ]: raw_data.setdefault(key, None) source_files: dict[str, str] = {} if raw_data.get("source_code"): if raw_data.get("additional_sources"): main_file_path = _determine_file_path(raw_data) source_files[main_file_path] = raw_data.get("source_code") for item in raw_data.get("additional_sources", []): item_path = item.get("file_path") if item_path: source_files[item_path] = item.get("source_code") else: file_path = _determine_file_path(raw_data) source_files[file_path] = raw_data.get("source_code") # Create a copy to avoid mutating the original raw_data metadata_copy = raw_data.copy() # Process constructor args on the copy instead of the original from blockscout_mcp_server.tools.common import _truncate_constructor_args # Local import to avoid cycles processed_args, truncated_flag = _truncate_constructor_args(metadata_copy.get("constructor_args")) metadata_copy["constructor_args"] = processed_args metadata_copy["constructor_args_truncated"] = truncated_flag if metadata_copy["decoded_constructor_args"]: processed_decoded, decoded_truncated = _truncate_constructor_args(metadata_copy["decoded_constructor_args"]) metadata_copy["decoded_constructor_args"] = processed_decoded if decoded_truncated: metadata_copy["constructor_args_truncated"] = True metadata_copy["source_code_tree_structure"] = list(source_files.keys()) for field in [ "abi", "deployed_bytecode", "creation_bytecode", "source_code", "additional_sources", "file_path", ]: metadata_copy.pop(field, None) cached_contract = CachedContract(metadata=metadata_copy, source_files=source_files) await contract_cache.set(cache_key, cached_contract) return cached_contract
- Pydantic models ContractMetadata (for metadata response) and ContractSourceFile (for file content response) defining the structured output schema for the tool. Used in ToolResponse[ContractMetadata | ContractSourceFile].class ContractMetadata(BaseModel): """Detailed metadata for a verified smart contract.""" # Allow extra fields to preserve language-specific and contract-specific metadata # from Blockscout API that varies by verification status and contract type model_config = ConfigDict(extra="allow") name: str = Field(description="The name of the contract.") language: str | None = Field(description="The programming language of the contract (e.g., Solidity, Vyper).") compiler_version: str | None = Field(description="The compiler version used.") verified_at: str | None = Field(description="The timestamp when the contract was verified.") source_code_tree_structure: list[str] = Field(description="A list of all source file paths for the contract.") optimization_enabled: bool | None = Field(description="Flag indicating if compiler optimization was enabled.") optimization_runs: int | None = Field(description="The number of optimization runs.") evm_version: str | None = Field(description="The EVM version target.") license_type: str | None = Field(description="The license of the contract code (e.g., MIT, none).") proxy_type: str | None = Field( description="The type of proxy if the contract is a proxy (e.g., basic_implementation)." ) is_fully_verified: bool | None = Field(description="Flag indicating if the contract is fully verified.") constructor_args: str | None = Field(description="The raw constructor arguments, possibly truncated.") decoded_constructor_args: str | dict | list | None = Field( default=None, description="Decoded constructor arguments, if available." ) constructor_args_truncated: bool = Field( default=False, description="Indicates if constructor_args or decoded_constructor_args was truncated." ) # --- Model for inspect_contract_code File Payload --- class ContractSourceFile(BaseModel): """Container for a single contract source file.""" file_content: str = Field(description="The raw source code of the file.")
- blockscout_mcp_server/server.py:178-180 (registration)Registration of the 'inspect_contract_code' tool with the FastMCP server instance, including annotations for title, read-only hint, etc.structured_output=False, annotations=create_tool_annotations("Inspect Contract Code"), )(inspect_contract_code)