import os import requests import json from dotenv import load_dotenv load_dotenv() class VertexWeb3Engine: def __init__(self, contract_address, network="ethereum"): self.address = contract_address.strip() self.network = network self.api_key = os.getenv("ETHERSCAN_API_KEY", "").strip() # Configuración V2 (Obligatoria en 2026) self.networks = { "ethereum": {"url": "https://api.etherscan.io/v2/api", "id": "1"}, "bsc": {"url": "https://api.bscscan.com/v2/api", "id": "56"}, "polygon": {"url": "https://api.polygonscan.com/v2/api", "id": "137"} } config = self.networks.get(network, self.networks["ethereum"]) self.base_url = config["url"] self.chain_id = config["id"] def get_contract_source(self): """Descarga el código fuente usando Etherscan API V2""" params = { "chainid": self.chain_id, # Requerido para V2 "module": "contract", "action": "getsourcecode", "address": self.address, "apikey": self.api_key } try: response = requests.get(self.base_url, params=params, timeout=15) data = response.json() # Verificamos status de Etherscan if data.get("status") == "1" and data.get("result"): result = data["result"][0] raw_source = result.get("SourceCode", "") if not raw_source: return {"success": False, "error": "Contract not verified"} # Parseo corregido para evitar el error de 'slice' source_code = self._parse_source_code(raw_source) return { "success": True, "source_code": source_code, "contract_info": { "name": result.get("ContractName", "Unknown"), "compiler": result.get("CompilerVersion", "Unknown") } } return {"success": False, "error": data.get("result", "API Error")} except Exception as e: return {"success": False, "error": str(e)} def _parse_source_code(self, raw_source): """Maneja formatos plano y multi-archivo (JSON)""" # Verificamos que sea un string antes de recortar if not isinstance(raw_source, str) or not raw_source.startswith("{"): return raw_source try: # FIX: Aseguramos que el recorte se haga solo si es un string de verdad if raw_source.startswith("{{") and raw_source.endswith("}}"): clean_json = raw_source[1:-1] # Quitamos solo una pareja de llaves else: clean_json = raw_source parsed = json.loads(clean_json) if "sources" in parsed: all_code = [] for filename, file_data in parsed["sources"].items(): if "content" in file_data: all_code.append(f"// FILE: {filename}\n{file_data['content']}") return "\n\n".join(all_code) return raw_source except: return raw_source def scan_basic_vulnerabilities(self, source_code): """Análisis de patrones de riesgo""" if not source_code or not isinstance(source_code, str): return [] red_flags = [] patterns = { "selfdestruct": "CRITICAL: Contract can be destroyed.", "mint(": "HIGH: Infinite minting risk.", "delegatecall": "HIGH: Proxy execution risk." } for pattern, risk in patterns.items(): if pattern in source_code.lower(): red_flags.append({"pattern": pattern, "description": risk}) return red_flags