Spaces:
Sleeping
Sleeping
File size: 4,086 Bytes
2a0f014 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 | 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 |