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