""" Solidity Web3 Site Builder - Простой конструктор сайтов с поддержкой смарт-контрактов """ import tkinter as tk from tkinter import ttk, filedialog, messagebox, scrolledtext import os import json import shutil import sys from datetime import datetime import webbrowser import tempfile import secrets from typing import Optional, Dict, Any # Импорты библиотек from solcx import compile_source, install_solc, set_solc_version, get_installed_solc_versions from web3 import Web3 from web3.exceptions import ContractLogicError, TransactionNotFound, TimeExhausted from web3.middleware import ExtraDataToPOAMiddleware # Исправлено для web3.py 7.10.0 from eth_account import Account from eth_account.signers.local import LocalAccount # ================ БАЗОВЫЕ КЛАССЫ ================ class Component: """Базовый класс для всех компонентов""" def __init__(self, component_id: str, comp_type: str, x: int, y: int, width: int, height: int): self.id = component_id self.type = comp_type self.x = x self.y = y self.width = width self.height = height def to_dict(self) -> Dict[str, Any]: """Сериализация в словарь""" pass @classmethod def from_dict(cls, data: Dict[str, Any]) -> 'Component': """Десериализация из словаря""" pass class BasicComponent(Component): """Базовый компонент сайта""" def __init__(self, component_id: str, comp_type: str, x: int, y: int, width: int, height: int, **kwargs): super().__init__(component_id, comp_type, x, y, width, height) self.properties = kwargs def to_dict(self) -> Dict[str, Any]: return { 'id': self.id, 'type': self.type, 'x': self.x, 'y': self.y, 'width': self.width, 'height': self.height, 'properties': self.properties } @classmethod def from_dict(cls, data: Dict[str, Any]) -> 'BasicComponent': return cls( data['id'], data['type'], data['x'], data['y'], data['width'], data['height'], **data.get('properties', {}) ) class SolidityComponent(Component): """Компонент для работы с Solidity""" DEFAULT_CONTRACT_CODE = """// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract SimpleStorage { uint256 private storedData; constructor(uint256 initialValue) { storedData = initialValue; } function set(uint256 x) public { storedData = x; } function get() public view returns (uint256) { return storedData; } function increment() public { storedData += 1; } function decrement() public { require(storedData > 0, "Value cannot be negative"); storedData -= 1; } }""" def __init__(self, component_id: str, name: str, x: int, y: int, width: int, height: int): super().__init__(component_id, "solidity", x, y, width, height) self.name = name self.contract_name = "SimpleStorage" self.source_code = self.DEFAULT_CONTRACT_CODE self.compiled = False self.deployed = False self.contract_address = "" self.abi = "" self.bytecode = "" self.last_result = "" self.network_url = "http://localhost:8545" self.account = "" self.chain_id = 1337 # Ganache по умолчанию def to_dict(self) -> Dict[str, Any]: """Сериализация""" data = { 'id': self.id, 'name': self.name, 'type': self.type, 'x': self.x, 'y': self.y, 'width': self.width, 'height': self.height, 'contract_name': self.contract_name, 'source_code': self.source_code, 'compiled': self.compiled, 'deployed': self.deployed, 'contract_address': self.contract_address, 'network_url': self.network_url, 'account': self.account, 'chain_id': self.chain_id } return data @classmethod def from_dict(cls, data: Dict[str, Any]) -> 'SolidityComponent': component = cls( data['id'], data.get('name', 'Контракт Solidity'), data['x'], data['y'], data['width'], data['height'] ) component.contract_name = data.get('contract_name', 'SimpleStorage') component.source_code = data.get('source_code', component.DEFAULT_CONTRACT_CODE) component.compiled = data.get('compiled', False) component.deployed = data.get('deployed', False) component.contract_address = data.get('contract_address', '') component.network_url = data.get('network_url', 'http://localhost:8545') component.account = data.get('account', '') component.chain_id = data.get('chain_id', 1337) return component # ================ СЕРВИСНЫЕ КЛАССЫ ================ class Web3Deployer: """Сервис для деплоя контрактов""" def __init__(self, network_url: str, chain_id: int = 1337): self.network_url = network_url self.chain_id = chain_id self.w3 = None self._connect() def _connect(self) -> bool: """Подключение к сети""" try: self.w3 = Web3(Web3.HTTPProvider(self.network_url)) # Добавляем PoA middleware для сетей типа Ganache if "localhost" in self.network_url or "127.0.0.1" in self.network_url: self.w3.middleware_onion.inject(ExtraDataToPOAMiddleware, layer=0) # Исправлено return self.w3.is_connected() except Exception as e: print(f"Ошибка подключения: {e}") return False def is_connected(self) -> bool: """Проверка подключения""" return self.w3 and self.w3.is_connected() def get_account_balance(self, address: str) -> Optional[int]: """Получение баланса аккаунта""" try: return self.w3.eth.get_balance(address) except: return None def deploy_contract(self, abi: list, bytecode: str, private_key: str, constructor_args: tuple = (), gas_limit: int = 3000000) -> Dict[str, Any]: """Реальный деплой контракта""" if not self.is_connected(): raise ConnectionError("Нет подключения к сети") try: # Создаем аккаунт из приватного ключа account: LocalAccount = Account.from_key(private_key) # Получаем nonce nonce = self.w3.eth.get_transaction_count(account.address) # Получаем актуальную цену газа gas_price = self.w3.eth.gas_price # Создаем объект контракта contract = self.w3.eth.contract(abi=abi, bytecode=bytecode) # Строим транзакцию для конструктора transaction = contract.constructor(*constructor_args).build_transaction({ 'chainId': self.chain_id, 'gas': gas_limit, 'gasPrice': gas_price, 'nonce': nonce, 'from': account.address }) # Подписываем транзакцию signed_txn = self.w3.eth.account.sign_transaction(transaction, private_key) # Отправляем транзакцию tx_hash = self.w3.eth.send_raw_transaction(signed_txn.rawTransaction) # Ждем подтверждения tx_receipt = self.w3.eth.wait_for_transaction_receipt(tx_hash, timeout=120) return { 'success': True, 'contract_address': tx_receipt.contractAddress, 'transaction_hash': tx_hash.hex(), 'block_number': tx_receipt.blockNumber, 'gas_used': tx_receipt.gasUsed, 'account': account.address } except TimeExhausted: raise TimeoutError("Транзакция не подтвердилась в течение 2 минут") except ContractLogicError as e: raise ValueError(f"Ошибка логики контракта: {str(e)}") except Exception as e: raise Exception(f"Ошибка деплоя: {str(e)}") def test_contract_function(self, contract_address: str, abi: list, function_name: str, args: tuple = ()) -> Any: """Тестирование функции контракта""" try: contract = self.w3.eth.contract(address=contract_address, abi=abi) # Определяем тип функции (view/pure или transactional) function = getattr(contract.functions, function_name) # Для view/pure функций if function_name in ['get', 'balanceOf', 'totalSupply']: result = function(*args).call() return {'success': True, 'result': result, 'type': 'call'} # Для остальных функций (нужен аккаунт для теста) return {'success': True, 'type': 'transaction', 'message': f'Функция {function_name} готова к вызову'} except Exception as e: return {'success': False, 'error': str(e)} class PrivateKeyDialog: """Диалог для безопасного ввода приватного ключа""" def __init__(self, parent, title: str = "Введите приватный ключ"): self.parent = parent self.title = title self.result = None def show(self) -> Optional[str]: """Показать диалог и вернуть приватный ключ""" dialog = tk.Toplevel(self.parent) dialog.title(self.title) dialog.geometry("500x200") dialog.resizable(False, False) dialog.transient(self.parent) dialog.grab_set() # Центрируем диалог dialog.update_idletasks() x = self.parent.winfo_x() + (self.parent.winfo_width() - dialog.winfo_width()) // 2 y = self.parent.winfo_y() + (self.parent.winfo_height() - dialog.winfo_height()) // 2 dialog.geometry(f"+{x}+{y}") # Заголовок tk.Label(dialog, text="Введите приватный ключ (начинается с 0x):", font=('Arial', 10)).pack(pady=(20, 10)) # Поле ввода с маскировкой key_var = tk.StringVar() entry = tk.Entry(dialog, textvariable=key_var, width=70, show="*", font=('Consolas', 10)) entry.pack(pady=10, padx=20) # Кнопка показать/скрыть ключ show_var = tk.BooleanVar(value=False) def toggle_show(): if show_var.get(): entry.config(show="") show_btn.config(text="👁️ Скрыть") else: entry.config(show="*") show_btn.config(text="👁️ Показать") show_btn = tk.Button(dialog, text="👁️ Показать", command=toggle_show) show_btn.pack(pady=5) # Фрейм для кнопок btn_frame = tk.Frame(dialog) btn_frame.pack(pady=20) def on_ok(): key = key_var.get().strip() if self._validate_private_key(key): self.result = key dialog.destroy() else: messagebox.showerror("Ошибка", "Неверный формат приватного ключа!\n" "Ключ должен начинаться с 0x и иметь длину 64 символа (без 0x).\n" "Пример: 0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef") def on_cancel(): self.result = None dialog.destroy() def on_generate(): """Генерация нового приватного ключа""" private_key = "0x" + secrets.token_hex(32) key_var.set(private_key) messagebox.showinfo("Сгенерирован ключ", f"Сгенерирован новый приватный ключ.\n" f"Сохраните его в безопасном месте!\n\n" f"Адрес кошелька: {Account.from_key(private_key).address}") tk.Button(btn_frame, text="Сгенерировать", bg="#FF9800", fg="white", command=on_generate, width=12).pack(side=tk.LEFT, padx=5) tk.Button(btn_frame, text="OK", bg="#4CAF50", fg="white", command=on_ok, width=10).pack(side=tk.LEFT, padx=5) tk.Button(btn_frame, text="Отмена", bg="#f44336", fg="white", command=on_cancel, width=10).pack(side=tk.LEFT, padx=5) # Бинд на Enter dialog.bind('', lambda e: on_ok()) entry.focus_set() dialog.wait_window() return self.result def _validate_private_key(self, key: str) -> bool: """Валидация приватного ключа""" if not key or not key.startswith('0x'): return False # Проверяем длину (0x + 64 hex символа) if len(key) != 66: return False # Проверяем hex формат try: int(key, 16) return True except: return False # ================ ГЛАВНОЕ ОКНО ================ class SolidityBuilder: def __init__(self, root: tk.Tk): self.root = root self.root.title("Solidity Web3 Builder") self.root.geometry("1300x800") self.root.minsize(800, 600) # Инициализация solc self.setup_solc() # Проект self.components = [] self.selected_component = None self.current_file = None # Цвета self.bg_color = "#1a1a2e" self.sidebar_color = "#16213e" self.text_color = "#ffffff" self.accent_color = "#4a69bd" # Инициализация атрибутов для свойств self.prop_vars = {} self.prop_widgets = {} self.setup_ui() self.add_default_components() # Бинд для закрытия окна self.root.protocol("WM_DELETE_WINDOW", self.on_closing) def setup_solc(self): """Настраиваем Solidity компилятор""" try: # Устанавливаем solc если не установлен installed = get_installed_solc_versions() if not installed: install_solc('0.8.0') else: set_solc_version(installed[0]) except Exception as e: print(f"Ошибка настройки solc: {e}") def on_closing(self): """Обработка закрытия окна""" if self.components and not self.current_file: response = messagebox.askyesnocancel( "Выход", "Сохранить проект перед выходом?" ) if response is None: # Отмена return elif response: # Да self.save_project() self.root.destroy() def setup_ui(self): """Настройка интерфейса""" # Главный контейнер main_container = tk.Frame(self.root, bg=self.bg_color) main_container.pack(fill=tk.BOTH, expand=True) # Левая панель - Компоненты self.create_component_panel(main_container) # Центральная область - Холст self.create_canvas_area(main_container) # Правая панель - Свойства self.create_properties_panel(main_container) # Меню self.create_menu() # Статус бар self.create_status_bar() def create_status_bar(self): """Создаем статус бар""" self.status_bar = tk.Label(self.root, text="Готово", bd=1, relief=tk.SUNKEN, anchor=tk.W) self.status_bar.pack(side=tk.BOTTOM, fill=tk.X) def update_status(self, message: str): """Обновление статус бара""" self.status_bar.config(text=message) self.root.update_idletasks() def create_menu(self): """Создаем меню""" menubar = tk.Menu(self.root) self.root.config(menu=menubar) # Файл file_menu = tk.Menu(menubar, tearoff=0) menubar.add_cascade(label="Файл", menu=file_menu) file_menu.add_command(label="Новый проект", command=self.new_project) file_menu.add_command(label="Открыть", command=self.open_project) file_menu.add_command(label="Сохранить", command=self.save_project) file_menu.add_command(label="Сохранить как", command=self.save_project_as) file_menu.add_separator() file_menu.add_command(label="Экспорт сайта", command=self.export_site) file_menu.add_separator() file_menu.add_command(label="Выход", command=self.root.quit) # Инструменты tools_menu = tk.Menu(menubar, tearoff=0) menubar.add_cascade(label="Инструменты", menu=tools_menu) tools_menu.add_command(label="Компилятор Solidity", command=self.open_solidity_compiler) tools_menu.add_command(label="Тест сети", command=self.test_network) tools_menu.add_command(label="Генератор контрактов", command=self.generate_contract) tools_menu.add_separator() tools_menu.add_command(label="Генератор кошелька", command=self.generate_wallet) # Помощь help_menu = tk.Menu(menubar, tearoff=0) menubar.add_cascade(label="Помощь", menu=help_menu) help_menu.add_command(label="Документация", command=self.show_docs) help_menu.add_command(label="Примеры контрактов", command=self.show_examples) help_menu.add_separator() help_menu.add_command(label="О программе", command=self.show_about) def create_component_panel(self, parent): """Панель компонентов""" sidebar = tk.Frame(parent, width=200, bg=self.sidebar_color) sidebar.pack(side=tk.LEFT, fill=tk.Y) sidebar.pack_propagate(False) tk.Label(sidebar, text="Компоненты", bg=self.sidebar_color, fg=self.text_color, font=('Arial', 12, 'bold')).pack(pady=20) # Базовые компоненты tk.Label(sidebar, text="Базовые:", bg=self.sidebar_color, fg=self.text_color, font=('Arial', 10)).pack(anchor='w', padx=10, pady=(10, 0)) basic_components = [ ("Заголовок", "header"), ("Текст", "paragraph"), ("Кнопка", "button"), ("Изображение", "image") ] for name, ctype in basic_components: btn = tk.Button(sidebar, text=name, bg=self.accent_color, fg=self.text_color, relief=tk.FLAT, font=('Arial', 10), command=lambda t=ctype: self.add_basic_component(t)) btn.pack(pady=5, padx=10, fill=tk.X) # Web3 компоненты tk.Label(sidebar, text="Web3:", bg=self.sidebar_color, fg=self.text_color, font=('Arial', 10)).pack(anchor='w', padx=10, pady=(20, 0)) # Только контракты Solidity btn = tk.Button(sidebar, text="Контракт Solidity", bg=self.accent_color, fg=self.text_color, relief=tk.FLAT, font=('Arial', 10), command=lambda: self.add_solidity_component()) btn.pack(pady=5, padx=10, fill=tk.X) # Кнопки управления tk.Frame(sidebar, height=20, bg=self.sidebar_color).pack() # Отступ export_btn = tk.Button(sidebar, text="🚀 Экспорт", bg="#4CAF50", fg="white", font=('Arial', 11, 'bold'), command=self.export_site) export_btn.pack(side=tk.BOTTOM, pady=20, padx=10, fill=tk.X) preview_btn = tk.Button(sidebar, text="👁️ Предпросмотр", bg="#2196F3", fg="white", font=('Arial', 10), command=self.preview_site) preview_btn.pack(side=tk.BOTTOM, pady=5, padx=10, fill=tk.X) def create_properties_panel(self, parent): """Панель свойств компонентов""" # Правая панель self.props_frame = tk.Frame(parent, width=300, bg=self.sidebar_color) self.props_frame.pack(side=tk.RIGHT, fill=tk.Y) self.props_frame.pack_propagate(False) # Заголовок панели свойств self.props_title = tk.Label(self.props_frame, text="Свойства", bg=self.sidebar_color, fg=self.text_color, font=('Arial', 12, 'bold')) self.props_title.pack(pady=20) # Контейнер для свойств self.props_content = tk.Frame(self.props_frame, bg=self.sidebar_color) self.props_content.pack(fill=tk.BOTH, expand=True, padx=10) # Инициализация словарей для свойств self.prop_vars = {} self.prop_widgets = {} def create_canvas_area(self, parent): """Область холста""" canvas_frame = tk.Frame(parent, bg=self.bg_color) canvas_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=10, pady=10) # Панель инструментов холста toolbar = tk.Frame(canvas_frame, bg=self.sidebar_color) toolbar.pack(fill=tk.X, pady=(0, 10)) tk.Label(toolbar, text="Холст", bg=self.sidebar_color, fg=self.text_color, font=('Arial', 11, 'bold')).pack(side=tk.LEFT, padx=10) # Холст self.canvas = tk.Canvas(canvas_frame, bg="#0f3460", highlightthickness=1, highlightbackground="#4a69bd", scrollregion=(0, 0, 2000, 2000)) # Скроллбары v_scrollbar = ttk.Scrollbar(canvas_frame, orient=tk.VERTICAL, command=self.canvas.yview) h_scrollbar = ttk.Scrollbar(canvas_frame, orient=tk.HORIZONTAL, command=self.canvas.xview) self.canvas.configure(xscrollcommand=h_scrollbar.set, yscrollcommand=v_scrollbar.set) # Размещение h_scrollbar.pack(side=tk.BOTTOM, fill=tk.X) v_scrollbar.pack(side=tk.RIGHT, fill=tk.Y) self.canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) # Сетка self.draw_grid() # Бинды для перетаскивания self.canvas.bind("", self.on_canvas_click) self.canvas.bind("", self.on_canvas_drag) self.canvas.bind("", self.on_canvas_release) self.canvas.bind("", self.on_canvas_right_click) self.drag_data = {"x": 0, "y": 0, "item": None} def draw_grid(self): """Рисуем сетку на холсте""" self.canvas.delete("grid") width = 2000 height = 2000 grid_size = 20 # Вертикальные линии for x in range(0, width, grid_size): self.canvas.create_line(x, 0, x, height, fill="#2a3b5c", tags="grid", width=1) # Горизонтальные линии for y in range(0, height, grid_size): self.canvas.create_line(0, y, width, y, fill="#2a3b5c", tags="grid", width=1) def add_default_components(self): """Добавляем компоненты по умолчанию""" # Заголовок header = BasicComponent( "header_1", "header", 100, 50, 800, 80, text="Solidity Web3 Сайт", color="#ffffff", bg_color="#0f3460", font_size=36 ) self.components.append(header) self.draw_basic_component(header) # Текст paragraph = BasicComponent( "para_1", "paragraph", 100, 150, 600, 100, text="Создайте свой сайт с поддержкой смарт-контрактов Solidity\nИспользуйте Ganache для локального тестирования", color="#cccccc", font_size=16 ) self.components.append(paragraph) self.draw_basic_component(paragraph) def add_basic_component(self, comp_type): """Добавляем базовый компонент""" comp_id = f"{comp_type}_{len(self.components) + 1}" defaults = { 'header': { 'text': 'Новый заголовок', 'color': '#ffffff', 'bg_color': '#0f3460', 'font_size': 24, 'width': 400, 'height': 60 }, 'paragraph': { 'text': 'Введите текст здесь...', 'color': '#cccccc', 'font_size': 14, 'width': 500, 'height': 80 }, 'button': { 'text': 'Кнопка', 'color': '#ffffff', 'bg_color': '#4CAF50', 'font_size': 14, 'width': 120, 'height': 40 }, 'image': { 'src': 'https://placehold.co/400x300', 'alt': 'Изображение', 'width': 400, 'height': 300 } } props = defaults.get(comp_type, defaults['header']).copy() width = props.pop('width') height = props.pop('height') component = BasicComponent( comp_id, comp_type, 100, 100 + len(self.components) * 50, width, height, **props ) self.components.append(component) self.draw_basic_component(component) self.select_component(component) def add_solidity_component(self): """Добавляем компонент Solidity""" comp_id = f"solidity_{len(self.components) + 1}" component = SolidityComponent( comp_id, "Контракт Solidity", 100, 100 + len(self.components) * 50, 600, 400 ) self.components.append(component) self.draw_solidity_component(component) self.select_component(component) def draw_basic_component(self, component): """Рисуем базовый компонент на холсте""" x, y = component.x, component.y width, height = component.width, component.height # Рисуем прямоугольник rect = self.canvas.create_rectangle( x, y, x + width, y + height, fill=component.properties.get('bg_color', '#2d4059'), outline='#4a69bd', width=2, tags=('component', component.id) ) # Добавляем текст в зависимости от типа if component.type == 'header': self.canvas.create_text( x + width/2, y + height/2, text=component.properties.get('text', ''), fill=component.properties.get('color', '#ffffff'), font=('Arial', component.properties.get('font_size', 24)), tags=('text', component.id) ) elif component.type == 'paragraph': self.canvas.create_text( x + width/2, y + height/2, text=component.properties.get('text', ''), fill=component.properties.get('color', '#cccccc'), font=('Arial', component.properties.get('font_size', 14)), width=width - 20, tags=('text', component.id) ) elif component.type == 'button': self.canvas.create_text( x + width/2, y + height/2, text=component.properties.get('text', 'Кнопка'), fill=component.properties.get('color', '#ffffff'), font=('Arial', component.properties.get('font_size', 14)), tags=('text', component.id) ) elif component.type == 'image': self.canvas.create_text( x + width/2, y + height/2, text='🖼️ Изображение', fill='#ffffff', font=('Arial', 14), tags=('text', component.id) ) # Метка с типом label_y = y + height + 15 self.canvas.create_text( x + width/2, label_y, text=component.type.title(), fill='#cccccc', font=('Arial', 9), tags=('label', component.id) ) def draw_solidity_component(self, component): """Рисуем компонент Solidity на холсте""" x, y = component.x, component.y width, height = component.width, component.height # Рисуем специальный прямоугольник для контракта rect = self.canvas.create_rectangle( x, y, x + width, y + height, fill='#1c2541', outline='#5bc0be', width=3, tags=('component', 'solidity', component.id) ) # Иконка Solidity self.canvas.create_text( x + 30, y + 30, text="🛠️", font=('Arial', 24), tags=('icon', component.id) ) # Название контракта self.canvas.create_text( x + width/2, y + 40, text=f"Контракт: {component.contract_name}", fill='#5bc0be', font=('Arial', 16, 'bold'), tags=('title', component.id) ) # Статус status = "Не скомпилирован" status_color = "#ff6b6b" if component.compiled: status = "✓ Скомпилирован" status_color = "#4ecdc4" if component.deployed: status = f"✓ Деплоен" status_color = "#4CAF50" self.canvas.create_text( x + width/2, y + 80, text=status, fill=status_color, font=('Arial', 12), tags=('status', component.id) ) # Адрес контракта (если деплоен) if component.deployed and component.contract_address: self.canvas.create_text( x + width/2, y + 110, text=f"Адрес: {component.contract_address[:12]}...{component.contract_address[-10:]}", fill='#aaaaaa', font=('Arial', 9), tags=('address', component.id) ) # Метка label_y = y + height + 15 self.canvas.create_text( x + width/2, label_y, text="Solidity Контракт", fill='#5bc0be', font=('Arial', 10, 'bold'), tags=('label', component.id) ) def on_canvas_click(self, event): """Обработка клика на холсте""" x, y = self.canvas.canvasx(event.x), self.canvas.canvasy(event.y) items = self.canvas.find_overlapping(x-5, y-5, x+5, y+5) for item in items: tags = self.canvas.gettags(item) for tag in tags: if tag in [comp.id for comp in self.components]: self.select_component_by_id(tag) self.drag_data["item"] = tag self.drag_data["x"] = x self.drag_data["y"] = y return self.clear_selection() def on_canvas_drag(self, event): """Обработка перетаскивания""" if self.drag_data["item"]: x = self.canvas.canvasx(event.x) y = self.canvas.canvasy(event.y) dx = x - self.drag_data["x"] dy = y - self.drag_data["y"] if dx != 0 or dy != 0: items = self.canvas.find_withtag(self.drag_data["item"]) for item in items: self.canvas.move(item, dx, dy) self.drag_data["x"] = x self.drag_data["y"] = y def on_canvas_release(self, event): """Обработка отпускания кнопки мыши""" if self.drag_data["item"]: component = self.get_component_by_id(self.drag_data["item"]) if component: # Обновляем позицию компонента items = self.canvas.find_withtag(component.id) for item in items: if self.canvas.type(item) == 'rectangle': coords = self.canvas.coords(item) component.x = coords[0] component.y = coords[1] break self.drag_data["item"] = None def on_canvas_right_click(self, event): """Правый клик - контекстное меню""" x, y = self.canvas.canvasx(event.x), self.canvas.canvasy(event.y) items = self.canvas.find_overlapping(x-5, y-5, x+5, y+5) for item in items: tags = self.canvas.gettags(item) for tag in tags: if tag in [comp.id for comp in self.components]: self.select_component_by_id(tag) menu = tk.Menu(self.root, tearoff=0) menu.add_command(label="Удалить", command=self.delete_selected) menu.add_separator() menu.add_command(label="Дублировать", command=self.duplicate_selected) menu.add_command(label="Переименовать", command=self.rename_selected) if self.selected_component and self.selected_component.type == "solidity": menu.add_separator() menu.add_command(label="Скомпилировать", command=self.compile_selected_contract) menu.add_command(label="Деплоить", command=self.deploy_selected_contract) menu.add_command(label="Тестировать", command=lambda: self.test_contract_function("get")) try: menu.tk_popup(event.x_root, event.y_root) finally: menu.grab_release() return self.clear_selection() def select_component(self, component): """Выделяем компонент""" # Сначала снимаем выделение со всех компонентов for comp in self.components: items = self.canvas.find_withtag(comp.id) for item in items: if self.canvas.type(item) == 'rectangle': if comp.type == "solidity": self.canvas.itemconfig(item, outline='#5bc0be', width=3) else: self.canvas.itemconfig(item, outline='#4a69bd', width=2) # Выделяем выбранный компонент items = self.canvas.find_withtag(component.id) for item in items: if self.canvas.type(item) == 'rectangle': self.canvas.itemconfig(item, outline='#FFD700', width=4) # Золотая рамка для выделения self.selected_component = component self.update_properties_panel() def clear_selection(self): """Снимаем выделение""" if self.selected_component: component = self.selected_component items = self.canvas.find_withtag(component.id) for item in items: if self.canvas.type(item) == 'rectangle': if component.type == "solidity": self.canvas.itemconfig(item, outline='#5bc0be', width=3) else: self.canvas.itemconfig(item, outline='#4a69bd', width=2) self.selected_component = None self.update_properties_panel() def select_component_by_id(self, component_id): """Выделяем компонент по ID""" component = self.get_component_by_id(component_id) if component: self.select_component(component) def get_component_by_id(self, component_id): """Получаем компонент по ID""" for comp in self.components: if comp.id == component_id: return comp return None def delete_selected(self): """Удаляем выбранный компонент""" if not self.selected_component: return items = self.canvas.find_withtag(self.selected_component.id) for item in items: self.canvas.delete(item) self.components.remove(self.selected_component) self.selected_component = None self.update_properties_panel() def duplicate_selected(self): """Дублируем выбранный компонент""" if not self.selected_component: return comp = self.selected_component if comp.type == "solidity": new_comp = SolidityComponent( f"solidity_{len(self.components) + 1}", f"{comp.name} (копия)", comp.x + 50, comp.y + 50, comp.width, comp.height ) new_comp.contract_name = comp.contract_name new_comp.source_code = comp.source_code new_comp.network_url = comp.network_url new_comp.account = comp.account new_comp.chain_id = comp.chain_id new_comp.compiled = comp.compiled new_comp.deployed = comp.deployed new_comp.contract_address = comp.contract_address new_comp.abi = comp.abi new_comp.bytecode = comp.bytecode else: new_comp = BasicComponent( f"{comp.type}_{len(self.components) + 1}", comp.type, comp.x + 50, comp.y + 50, comp.width, comp.height, **comp.properties ) self.components.append(new_comp) if comp.type == "solidity": self.draw_solidity_component(new_comp) else: self.draw_basic_component(new_comp) self.select_component(new_comp) def rename_selected(self): """Переименовываем компонент""" if not self.selected_component: return if self.selected_component.type == "solidity": new_name = tk.simpledialog.askstring( "Переименовать контракт", "Введите новое имя контракта:", initialvalue=self.selected_component.contract_name ) if new_name: self.selected_component.contract_name = new_name self.redraw_component(self.selected_component) self.update_properties_panel() def update_properties_panel(self): """Обновляем панель свойств""" # Очищаем панель for widget in self.props_content.winfo_children(): widget.destroy() self.prop_vars.clear() self.prop_widgets.clear() if not self.selected_component: self.props_title.config(text="Свойства") tk.Label(self.props_content, text="Выберите компонент", bg=self.sidebar_color, fg='#aaaaaa').pack(pady=50) return # Настраиваем заголовок comp_type = self.selected_component.type if comp_type == "solidity": self.props_title.config(text=f"Контракт: {self.selected_component.contract_name}") else: self.props_title.config(text=f"Свойства: {comp_type}") # Создаем свойства в зависимости от типа компонента if comp_type == "solidity": self.create_solidity_properties() else: self.create_basic_properties() def create_basic_properties(self): """Создаем свойства для базового компонента""" row = 0 # Позиция и размер self.create_property_field("Позиция X", "x", self.selected_component.x, row) row += 1 self.create_property_field("Позиция Y", "y", self.selected_component.y, row) row += 1 self.create_property_field("Ширина", "width", self.selected_component.width, row) row += 1 self.create_property_field("Высота", "height", self.selected_component.height, row) row += 1 # Свойства компонента for prop_name, prop_value in self.selected_component.properties.items(): if isinstance(prop_value, (str, int, float)): self.create_property_field(prop_name.capitalize(), prop_name, prop_value, row) row += 1 # Кнопка обновления tk.Button(self.props_content, text="Применить", bg=self.accent_color, fg=self.text_color, command=self.apply_properties).pack(pady=20) def create_solidity_properties(self): """Создаем свойства для Solidity компонента""" component = self.selected_component # Заголовок tk.Label(self.props_content, text="Контракт Solidity", bg=self.sidebar_color, fg='#5bc0be', font=('Arial', 11, 'bold')).pack(anchor='w', pady=(0, 10)) # Имя контракта self.create_property_field("Имя контракта", "contract_name", component.contract_name, 0) # Кнопка редактирования кода tk.Button(self.props_content, text="✏️ Редактировать код", bg=self.accent_color, fg=self.text_color, command=self.edit_solidity_code).pack(fill=tk.X, pady=10) # Состояние status_frame = tk.Frame(self.props_content, bg=self.sidebar_color) status_frame.pack(fill=tk.X, pady=10) status_text = "Не скомпилирован" if component.compiled: status_text = "✓ Скомпилирован" if component.deployed: status_text = f"✓ Деплоен" tk.Label(status_frame, text="Состояние:", bg=self.sidebar_color, fg=self.text_color).pack(side=tk.LEFT) tk.Label(status_frame, text=status_text, bg=self.sidebar_color, fg='#4CAF50' if component.deployed else '#4ecdc4' if component.compiled else '#ff6b6b').pack(side=tk.RIGHT) # Кнопки управления контрактом btn_frame = tk.Frame(self.props_content, bg=self.sidebar_color) btn_frame.pack(fill=tk.X, pady=10) tk.Button(btn_frame, text="🛠️ Скомпилировать", bg="#2196F3", fg="white", command=self.compile_selected_contract).pack(side=tk.LEFT, padx=2, fill=tk.X, expand=True) tk.Button(btn_frame, text="🚀 Деплоить", bg="#4CAF50", fg="white", command=self.deploy_selected_contract).pack(side=tk.LEFT, padx=2, fill=tk.X, expand=True) # Настройки сети tk.Label(self.props_content, text="Настройки сети:", bg=self.sidebar_color, fg=self.text_color, font=('Arial', 10, 'bold')).pack(anchor='w', pady=(20, 5)) self.create_property_field("URL сети", "network_url", component.network_url, 5) self.create_property_field("Chain ID", "chain_id", component.chain_id, 6) # Адрес контракта (только для чтения) if component.contract_address: addr_frame = tk.Frame(self.props_content, bg=self.sidebar_color) addr_frame.pack(fill=tk.X, pady=5) tk.Label(addr_frame, text="Адрес:", bg=self.sidebar_color, fg=self.text_color, width=10).pack(side=tk.LEFT) addr_label = tk.Label(addr_frame, text=component.contract_address, bg=self.sidebar_color, fg='#4CAF50', font=('Consolas', 9)) addr_label.pack(side=tk.LEFT, fill=tk.X, expand=True) # Кнопка копирования tk.Button(addr_frame, text="📋", bg="#2196F3", fg="white", font=('Arial', 9), command=lambda: self.copy_to_clipboard(component.contract_address)).pack(side=tk.RIGHT) # Тестирование функций if component.compiled and component.deployed: tk.Label(self.props_content, text="Тестирование:", bg=self.sidebar_color, fg=self.text_color, font=('Arial', 10, 'bold')).pack(anchor='w', pady=(20, 5)) test_frame = tk.Frame(self.props_content, bg=self.sidebar_color) test_frame.pack(fill=tk.X, pady=5) tk.Button(test_frame, text="get()", bg="#9C27B0", fg="white", command=lambda: self.test_contract_function("get")).pack(side=tk.LEFT, padx=2) tk.Button(test_frame, text="set(100)", bg="#9C27B0", fg="white", command=lambda: self.test_contract_function("set", 100)).pack(side=tk.LEFT, padx=2) tk.Button(test_frame, text="increment()", bg="#9C27B0", fg="white", command=lambda: self.test_contract_function("increment")).pack(side=tk.LEFT, padx=2) def create_property_field(self, label, prop_name, value, row): """Создаем поле свойства""" frame = tk.Frame(self.props_content, bg=self.sidebar_color) frame.pack(fill=tk.X, pady=2) tk.Label(frame, text=label, bg=self.sidebar_color, fg=self.text_color, width=15, anchor='w').pack(side=tk.LEFT) var = tk.StringVar(value=str(value)) entry = tk.Entry(frame, textvariable=var, bg="#2a3b5c", fg=self.text_color, insertbackground=self.text_color) entry.pack(side=tk.LEFT, fill=tk.X, expand=True) self.prop_vars[prop_name] = var def apply_properties(self): """Применяем измененные свойства""" if not self.selected_component: return component = self.selected_component # Обновляем позицию и размер for prop in ['x', 'y', 'width', 'height']: if prop in self.prop_vars: try: value = int(float(self.prop_vars[prop].get())) setattr(component, prop, value) except: pass # Обновляем свойства компонента if hasattr(component, 'properties'): for prop_name, var in self.prop_vars.items(): if prop_name not in ['x', 'y', 'width', 'height']: component.properties[prop_name] = var.get() elif hasattr(component, 'contract_name'): # Для Solidity компонента for prop_name, var in self.prop_vars.items(): if hasattr(component, prop_name): try: if prop_name == 'chain_id': setattr(component, prop_name, int(var.get())) else: setattr(component, prop_name, var.get()) except: pass # Перерисовываем компонент self.redraw_component(component) def redraw_component(self, component): """Перерисовываем компонент""" items = self.canvas.find_withtag(component.id) for item in items: self.canvas.delete(item) if component.type == "solidity": self.draw_solidity_component(component) else: self.draw_basic_component(component) # === Solidity функции === def edit_solidity_code(self): """Редактирование кода Solidity""" if not self.selected_component or self.selected_component.type != "solidity": return component = self.selected_component dialog = tk.Toplevel(self.root) dialog.title(f"Редактор Solidity: {component.contract_name}") dialog.geometry("800x600") # Редактор кода editor_frame = tk.Frame(dialog) editor_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10) tk.Label(editor_frame, text="Код контракта Solidity:", font=('Arial', 11, 'bold')).pack(anchor='w') editor = scrolledtext.ScrolledText(editor_frame, wrap=tk.WORD, font=('Consolas', 10)) editor.pack(fill=tk.BOTH, expand=True, pady=5) editor.insert(1.0, component.source_code) def save_code(): component.source_code = editor.get(1.0, tk.END) component.compiled = False # Сбрасываем статус компиляции dialog.destroy() self.redraw_component(component) # Кнопки btn_frame = tk.Frame(dialog) btn_frame.pack(fill=tk.X, padx=10, pady=10) tk.Button(btn_frame, text="Сохранить", bg="#4CAF50", fg="white", command=save_code).pack(side=tk.RIGHT, padx=5) tk.Button(btn_frame, text="Отмена", bg="#f44336", fg="white", command=dialog.destroy).pack(side=tk.RIGHT, padx=5) tk.Button(btn_frame, text="Примеры", bg="#2196F3", fg="white", command=lambda: self.insert_example_code(editor)).pack(side=tk.LEFT) def compile_selected_contract(self): """Компилируем выбранный контракт""" if not self.selected_component or self.selected_component.type != "solidity": messagebox.showwarning("Ошибка", "Выберите компонент Solidity контракта") return component = self.selected_component try: self.update_status("Компиляция контракта...") # Компилируем контракт compiled_sol = compile_source( component.source_code, output_values=['abi', 'bin'], solc_version='0.8.0' ) # Ищем нужный контракт for contract_id, contract_interface in compiled_sol.items(): if component.contract_name in contract_id: component.abi = contract_interface['abi'] component.bytecode = contract_interface['bin'] component.compiled = True break else: # Если не нашли по имени, берем первый contract_id, contract_interface = compiled_sol.popitem() component.abi = contract_interface['abi'] component.bytecode = contract_interface['bin'] component.compiled = True # Обновляем отображение self.redraw_component(component) self.update_properties_panel() messagebox.showinfo("Успех", f"Контракт '{component.contract_name}' успешно скомпилирован!") self.update_status("Контракт скомпилирован") except Exception as e: messagebox.showerror("Ошибка компиляции", f"Ошибка: {str(e)}") self.update_status("Ошибка компиляции") def deploy_selected_contract(self): """Реальный деплой выбранного контракта""" if not self.selected_component or self.selected_component.type != "solidity": return component = self.selected_component if not component.compiled: messagebox.showwarning("Ошибка", "Сначала скомпилируйте контракт") return # Запрашиваем приватный ключ key_dialog = PrivateKeyDialog(self.root, "Приватный ключ для деплоя") private_key = key_dialog.show() if not private_key: return # Пользователь отменил # Диалог для параметров конструктора dialog = tk.Toplevel(self.root) dialog.title("Деплой контракта") dialog.geometry("500x400") tk.Label(dialog, text="Параметры деплоя", font=('Arial', 12, 'bold')).pack(pady=10) # Параметры конструктора tk.Label(dialog, text="Начальное значение для SimpleStorage:").pack(anchor='w', padx=20, pady=(10, 0)) initial_value = tk.Entry(dialog) initial_value.pack(fill=tk.X, padx=20, pady=5) initial_value.insert(0, "100") # Лимит газа tk.Label(dialog, text="Лимит газа (gas):").pack(anchor='w', padx=20, pady=(10, 0)) gas_limit = tk.Entry(dialog) gas_limit.pack(fill=tk.X, padx=20, pady=5) gas_limit.insert(0, "3000000") # Информация о сети info_frame = tk.Frame(dialog) info_frame.pack(fill=tk.X, padx=20, pady=10) tk.Label(info_frame, text=f"Сеть: {component.network_url}", anchor='w').pack(fill=tk.X) tk.Label(info_frame, text=f"Chain ID: {component.chain_id}", anchor='w').pack(fill=tk.X) # Получаем адрес из приватного ключа try: account = Account.from_key(private_key) tk.Label(info_frame, text=f"Кошелек: {account.address[:20]}...", anchor='w').pack(fill=tk.X) except: pass # Статус status_label = tk.Label(dialog, text="", fg="#666666") status_label.pack(pady=5) def deploy(): try: status_label.config(text="Подключение к сети...", fg="#2196F3") dialog.update() # Создаем деплоер deployer = Web3Deployer(component.network_url, component.chain_id) if not deployer.is_connected(): status_label.config(text="❌ Не удалось подключиться к сети", fg="#f44336") return status_label.config(text="✓ Подключено. Отправка транзакции...", fg="#4CAF50") dialog.update() # Получаем параметры constructor_args = (int(initial_value.get()),) gas_limit_val = int(gas_limit.get()) # Деплоим контракт result = deployer.deploy_contract( abi=component.abi, bytecode=component.bytecode, private_key=private_key, constructor_args=constructor_args, gas_limit=gas_limit_val ) # Обновляем компонент component.contract_address = result['contract_address'] component.deployed = True component.account = result['account'] status_label.config(text="✓ Контракт успешно деплоен!", fg="#4CAF50") dialog.update() # Показываем результат messagebox.showinfo("Успех", f"Контракт успешно деплоен!\n\n" f"Адрес контракта: {result['contract_address']}\n" f"Хэш транзакции: {result['transaction_hash']}\n" f"Блок: {result['block_number']}\n" f"Газ использовано: {result['gas_used']}\n" f"Аккаунт: {result['account']}") # Обновляем интерфейс self.redraw_component(component) self.update_properties_panel() self.update_status("Контракт деплоен") # Закрываем диалог через 2 секунды dialog.after(2000, dialog.destroy) except TimeoutError as e: status_label.config(text=f"❌ {str(e)}", fg="#f44336") except ValueError as e: status_label.config(text=f"❌ {str(e)}", fg="#f44336") except Exception as e: status_label.config(text=f"❌ Ошибка: {str(e)}", fg="#f44336") # Кнопки btn_frame = tk.Frame(dialog) btn_frame.pack(pady=20) tk.Button(btn_frame, text="Деплоить", bg="#4CAF50", fg="white", command=deploy, width=15).pack(side=tk.LEFT, padx=10) tk.Button(btn_frame, text="Отмена", bg="#f44336", fg="white", command=dialog.destroy, width=15).pack(side=tk.LEFT, padx=10) def test_contract_function(self, function_name: str, *args): """Тестирование функции контракта""" if not self.selected_component or self.selected_component.type != "solidity": return component = self.selected_component if not component.deployed or not component.contract_address: messagebox.showwarning("Ошибка", "Контракт не деплоен") return try: # Создаем деплоер для тестирования deployer = Web3Deployer(component.network_url, component.chain_id) if not deployer.is_connected(): messagebox.showerror("Ошибка", "Нет подключения к сети") return # Тестируем функцию result = deployer.test_contract_function( contract_address=component.contract_address, abi=component.abi, function_name=function_name, args=args ) if result['success']: if result['type'] == 'call': messagebox.showinfo("Результат", f"Функция {function_name} вызвана успешно\n" f"Результат: {result['result']}\n" f"Контракт: {component.contract_address}") else: messagebox.showinfo("Информация", f"Функция {function_name} требует транзакцию\n" f"{result['message']}\n\n" f"Для вызова этой функции нужен приватный ключ.") else: messagebox.showerror("Ошибка", f"Ошибка вызова функции: {result['error']}") except Exception as e: messagebox.showerror("Ошибка", f"Ошибка тестирования: {str(e)}") def copy_to_clipboard(self, text: str): """Копирование текста в буфер обмена""" self.root.clipboard_clear() self.root.clipboard_append(text) self.update_status("Скопировано в буфер обмена") def generate_wallet(self): """Генерация нового кошелька""" dialog = tk.Toplevel(self.root) dialog.title("Генератор кошелька") dialog.geometry("600x300") dialog.resizable(False, False) tk.Label(dialog, text="Новый кошелек Ethereum", font=('Arial', 14, 'bold')).pack(pady=20) # Генерируем ключи private_key = "0x" + secrets.token_hex(32) account = Account.from_key(private_key) # Приватный ключ tk.Label(dialog, text="Приватный ключ:", font=('Arial', 10, 'bold')).pack(anchor='w', padx=20, pady=(10, 0)) priv_frame = tk.Frame(dialog) priv_frame.pack(fill=tk.X, padx=20, pady=5) priv_var = tk.StringVar(value=private_key) priv_entry = tk.Entry(priv_frame, textvariable=priv_var, font=('Consolas', 9), state='readonly', width=70) priv_entry.pack(side=tk.LEFT, fill=tk.X, expand=True) tk.Button(priv_frame, text="📋", bg="#2196F3", fg="white", command=lambda: self.copy_to_clipboard(private_key)).pack(side=tk.RIGHT) # Адрес tk.Label(dialog, text="Адрес кошелька:", font=('Arial', 10, 'bold')).pack(anchor='w', padx=20, pady=(10, 0)) addr_frame = tk.Frame(dialog) addr_frame.pack(fill=tk.X, padx=20, pady=5) addr_var = tk.StringVar(value=account.address) addr_entry = tk.Entry(addr_frame, textvariable=addr_var, font=('Consolas', 9), state='readonly', width=70) addr_entry.pack(side=tk.LEFT, fill=tk.X, expand=True) tk.Button(addr_frame, text="📋", bg="#2196F3", fg="white", command=lambda: self.copy_to_clipboard(account.address)).pack(side=tk.RIGHT) # Предупреждение warning_frame = tk.Frame(dialog, bg="#FFF3CD", bd=1, relief=tk.SUNKEN) warning_frame.pack(fill=tk.X, padx=20, pady=20) tk.Label(warning_frame, text="⚠️ ВНИМАНИЕ!", bg="#FFF3CD", fg="#856404", font=('Arial', 10, 'bold')).pack(anchor='w', padx=10, pady=(10, 0)) tk.Label(warning_frame, text="Сохраните приватный ключ в безопасном месте!", bg="#FFF3CD", fg="#856404", wraplength=550).pack(anchor='w', padx=10, pady=(0, 10)) tk.Label(warning_frame, text="Никогда не используйте этот ключ в основной сети (Mainnet)!", bg="#FFF3CD", fg="#856404", wraplength=550).pack(anchor='w', padx=10, pady=(0, 10)) # Кнопка закрытия tk.Button(dialog, text="Закрыть", bg="#4CAF50", fg="white", command=dialog.destroy, width=20).pack(pady=20) def test_network(self): """Тестируем подключение к сети""" # Используем сеть из выбранного компонента или по умолчанию network_url = "http://localhost:8545" chain_id = 1337 if self.selected_component and self.selected_component.type == "solidity": network_url = self.selected_component.network_url chain_id = self.selected_component.chain_id dialog = tk.Toplevel(self.root) dialog.title("Тест сети") dialog.geometry("500x500") tk.Label(dialog, text="Тестирование подключения к сети", font=('Arial', 12, 'bold')).pack(pady=20) # Ввод URL сети frame = tk.Frame(dialog) frame.pack(fill=tk.X, padx=20, pady=10) tk.Label(frame, text="URL сети:").pack(side=tk.LEFT) network_url_entry = tk.Entry(frame) network_url_entry.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=10) network_url_entry.insert(0, network_url) # Chain ID chain_frame = tk.Frame(dialog) chain_frame.pack(fill=tk.X, padx=20, pady=10) tk.Label(chain_frame, text="Chain ID:").pack(side=tk.LEFT) chain_id_entry = tk.Entry(chain_frame) chain_id_entry.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=10) chain_id_entry.insert(0, str(chain_id)) result_text = scrolledtext.ScrolledText(dialog, height=15, font=('Consolas', 9)) result_text.pack(fill=tk.BOTH, expand=True, padx=20, pady=10) def test_connection(): try: url = network_url_entry.get() chain = int(chain_id_entry.get()) result_text.delete(1.0, tk.END) result_text.insert(tk.END, f"Подключение к: {url}\n") result_text.insert(tk.END, f"Chain ID: {chain}\n") result_text.insert(tk.END, "="*50 + "\n\n") deployer = Web3Deployer(url, chain) if deployer.is_connected(): result_text.insert(tk.END, "✓ Подключение успешно\n\n") # Информация о сети w3 = deployer.w3 result_text.insert(tk.END, f"Версия сети: {w3.net.version}\n") result_text.insert(tk.END, f"Последний блок: {w3.eth.block_number}\n") result_text.insert(tk.END, f"Газ цена: {w3.eth.gas_price} wei\n") result_text.insert(tk.END, f"Сложность: {w3.eth.get_block('latest').get('difficulty', 'N/A')}\n") # Аккаунты (для локальных сетей) try: accounts = w3.eth.accounts if accounts: result_text.insert(tk.END, f"\nАккаунты в сети: {len(accounts)}\n") for i, acc in enumerate(accounts[:5]): balance = w3.eth.get_balance(acc) result_text.insert(tk.END, f" {i}: {acc[:20]}... - {w3.from_wei(balance, 'ether')} ETH\n") if len(accounts) > 5: result_text.insert(tk.END, f" ... и еще {len(accounts)-5}\n") except Exception as e: result_text.insert(tk.END, f"\nНе удалось получить аккаунты: {e}\n") result_text.insert(tk.END, "\n" + "="*50 + "\n") result_text.insert(tk.END, "✅ Сеть готова к работе!\n") result_text.insert(tk.END, "Рекомендации:\n") result_text.insert(tk.END, "1. Убедитесь что у аккаунта есть ETH для газа\n") result_text.insert(tk.END, "2. Проверьте Chain ID\n") result_text.insert(tk.END, "3. Для Ganache используйте Chain ID = 1337\n") else: result_text.insert(tk.END, "✗ Не удалось подключиться к сети\n\n") result_text.insert(tk.END, "Убедитесь что:\n") result_text.insert(tk.END, "1. Ganache запущен (для localhost:8545)\n") result_text.insert(tk.END, "2. Правильный URL сети\n") result_text.insert(tk.END, "3. Сеть доступна\n") result_text.insert(tk.END, "4. Chain ID указан верно\n") except ValueError: result_text.insert(tk.END, "Ошибка: Chain ID должен быть числом\n") except Exception as e: result_text.insert(tk.END, f"Ошибка: {str(e)}\n") tk.Button(dialog, text="Тестировать", bg="#2196F3", fg="white", command=test_connection).pack(pady=10) def insert_example_code(self, editor): """Вставляем пример кода""" examples = { "SimpleStorage": """// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract SimpleStorage { uint256 private storedData; constructor(uint256 initialValue) { storedData = initialValue; } function set(uint256 x) public { storedData = x; } function get() public view returns (uint256) { return storedData; } }""", "Token": """// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract MyToken { string public name = "MyToken"; string public symbol = "MTK"; uint8 public decimals = 18; uint256 public totalSupply; mapping(address => uint256) public balanceOf; mapping(address => mapping(address => uint256)) public allowance; event Transfer(address indexed from, address indexed to, uint256 value); event Approval(address indexed owner, address indexed spender, uint256 value); constructor(uint256 initialSupply) { totalSupply = initialSupply * 10 ** uint256(decimals); balanceOf[msg.sender] = totalSupply; } function transfer(address to, uint256 value) public returns (bool) { require(balanceOf[msg.sender] >= value, "Insufficient balance"); balanceOf[msg.sender] -= value; balanceOf[to] += value; emit Transfer(msg.sender, to, value); return true; } }""", "Voting": """// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract Voting { struct Proposal { string name; uint voteCount; } Proposal[] public proposals; mapping(address => bool) public hasVoted; constructor(string[] memory proposalNames) { for (uint i = 0; i < proposalNames.length; i++) { proposals.push(Proposal({ name: proposalNames[i], voteCount: 0 })); } } function vote(uint proposal) public { require(!hasVoted[msg.sender], "Already voted"); require(proposal < proposals.length, "Invalid proposal"); proposals[proposal].voteCount++; hasVoted[msg.sender] = true; } function winningProposal() public view returns (uint winningProposal_) { uint winningVoteCount = 0; for (uint p = 0; p < proposals.length; p++) { if (proposals[p].voteCount > winningVoteCount) { winningVoteCount = proposals[p].voteCount; winningProposal_ = p; } } } }""" } # Диалог выбора примера example_dialog = tk.Toplevel(self.root) example_dialog.title("Выберите пример контракта") example_dialog.geometry("300x200") for name, code in examples.items(): btn = tk.Button(example_dialog, text=name, width=20, command=lambda c=code, d=example_dialog: (editor.delete(1.0, tk.END), editor.insert(1.0, c), d.destroy())) btn.pack(pady=5) def open_solidity_compiler(self): """Открываем отдельный компилятор Solidity""" dialog = tk.Toplevel(self.root) dialog.title("Компилятор Solidity") dialog.geometry("900x700") # Редактор кода editor_frame = tk.Frame(dialog) editor_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10) tk.Label(editor_frame, text="Введите код Solidity:", font=('Arial', 11, 'bold')).pack(anchor='w') editor = scrolledtext.ScrolledText(editor_frame, wrap=tk.WORD, font=('Consolas', 10)) editor.pack(fill=tk.BOTH, expand=True, pady=5) editor.insert(1.0, """// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract TestContract { string public message = "Hello, Web3!"; function setMessage(string memory newMessage) public { message = newMessage; } function getMessage() public view returns (string memory) { return message; } }""") # Панель результатов result_frame = tk.Frame(dialog) result_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10) notebook = ttk.Notebook(result_frame) notebook.pack(fill=tk.BOTH, expand=True) # Вкладка ABI abi_frame = tk.Frame(notebook) notebook.add(abi_frame, text="ABI") abi_text = scrolledtext.ScrolledText(abi_frame, wrap=tk.WORD, font=('Consolas', 9)) abi_text.pack(fill=tk.BOTH, expand=True) # Вкладка Bytecode bytecode_frame = tk.Frame(notebook) notebook.add(bytecode_frame, text="Bytecode") bytecode_text = scrolledtext.ScrolledText(bytecode_frame, wrap=tk.WORD, font=('Consolas', 9)) bytecode_text.pack(fill=tk.BOTH, expand=True) # Вкладка Ошибки error_frame = tk.Frame(notebook) notebook.add(error_frame, text="Ошибки") error_text = scrolledtext.ScrolledText(error_frame, wrap=tk.WORD, font=('Consolas', 9)) error_text.pack(fill=tk.BOTH, expand=True) def compile_code(): try: source_code = editor.get(1.0, tk.END) compiled_sol = compile_source( source_code, output_values=['abi', 'bin'], solc_version='0.8.0' ) # Очищаем предыдущие результаты abi_text.delete(1.0, tk.END) bytecode_text.delete(1.0, tk.END) error_text.delete(1.0, tk.END) for contract_id, contract_interface in compiled_sol.items(): abi_text.insert(tk.END, f"// {contract_id}\n") abi_text.insert(tk.END, json.dumps(contract_interface['abi'], indent=2)) abi_text.insert(tk.END, "\n\n") bytecode_text.insert(tk.END, f"// {contract_id}\n") bytecode_text.insert(tk.END, contract_interface['bin']) bytecode_text.insert(tk.END, "\n\n") error_text.insert(tk.END, "✓ Компиляция успешна!\n") except Exception as e: error_text.delete(1.0, tk.END) error_text.insert(tk.END, f"Ошибка компиляции:\n{str(e)}") # Кнопки btn_frame = tk.Frame(dialog) btn_frame.pack(fill=tk.X, padx=10, pady=10) tk.Button(btn_frame, text="🛠️ Скомпилировать", bg="#2196F3", fg="white", command=compile_code).pack(side=tk.LEFT, padx=5) tk.Button(btn_frame, text="Сохранить как компонент", bg="#4CAF50", fg="white", command=lambda: self.save_from_compiler(editor.get(1.0, tk.END))).pack(side=tk.RIGHT, padx=5) def save_from_compiler(self, source_code): """Сохраняем код из компилятора как компонент""" comp_id = f"solidity_{len(self.components) + 1}" component = SolidityComponent( comp_id, "Новый контракт", 100, 100 + len(self.components) * 50, 600, 400 ) component.source_code = source_code component.contract_name = "NewContract" self.components.append(component) self.draw_solidity_component(component) self.select_component(component) messagebox.showinfo("Успех", "Контракт добавлен на холст") def generate_contract(self): """Генератор контрактов""" dialog = tk.Toplevel(self.root) dialog.title("Генератор контрактов") dialog.geometry("600x500") tk.Label(dialog, text="Выберите тип контракта:", font=('Arial', 12, 'bold')).pack(pady=20) # Типы контрактов contracts_frame = tk.Frame(dialog) contracts_frame.pack(fill=tk.BOTH, expand=True, padx=20) contracts = [ ("Simple Storage", "Простое хранилище данных"), ("ERC20 Token", "Стандартный токен ERC-20"), ("Voting", "Система голосования"), ("Auction", "Аукцион") ] for name, desc in contracts: frame = tk.Frame(contracts_frame, relief=tk.RAISED, borderwidth=1) frame.pack(fill=tk.X, pady=5) tk.Label(frame, text=name, font=('Arial', 11, 'bold')).pack(anchor='w', padx=10, pady=5) tk.Label(frame, text=desc, fg='#666666').pack(anchor='w', padx=10, pady=(0, 5)) btn = tk.Button(frame, text="Создать", bg=self.accent_color, fg=self.text_color, command=lambda n=name: self.create_contract_template(n, dialog)) btn.pack(side=tk.RIGHT, padx=10, pady=5) def create_contract_template(self, contract_type, dialog): """Создаем шаблон контракта""" templates = { "Simple Storage": """// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract SimpleStorage { uint256 private storedData; constructor(uint256 initialValue) { storedData = initialValue; } function set(uint256 x) public { storedData = x; } function get() public view returns (uint256) { return storedData; } }""", "ERC20 Token": """// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract MyToken { string public name; string public symbol; uint8 public decimals; uint256 public totalSupply; mapping(address => uint256) public balanceOf; mapping(address => mapping(address => uint256)) public allowance; event Transfer(address indexed from, address indexed to, uint256 value); event Approval(address indexed owner, address indexed spender, uint256 value); constructor( string memory tokenName, string memory tokenSymbol, uint8 decimalUnits, uint256 initialSupply ) { name = tokenName; symbol = tokenSymbol; decimals = decimalUnits; totalSupply = initialSupply * 10 ** uint256(decimals); balanceOf[msg.sender] = totalSupply; } function transfer(address to, uint256 value) public returns (bool) { require(balanceOf[msg.sender] >= value, "Insufficient balance"); balanceOf[msg.sender] -= value; balanceOf[to] += value; emit Transfer(msg.sender, to, value); return true; } }""", "Voting": """// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract Voting { struct Proposal { string name; uint voteCount; } Proposal[] public proposals; mapping(address => bool) public hasVoted; constructor(string[] memory proposalNames) { for (uint i = 0; i < proposalNames.length; i++) { proposals.push(Proposal({ name: proposalNames[i], voteCount: 0 })); } } function vote(uint proposal) public { require(!hasVoted[msg.sender], "Already voted"); require(proposal < proposals.length, "Invalid proposal"); proposals[proposal].voteCount++; hasVoted[msg.sender] = true; } }""", "Auction": """// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract SimpleAuction { address public beneficiary; uint public auctionEndTime; address public highestBidder; uint public highestBid; mapping(address => uint) public pendingReturns; event HighestBidIncreased(address bidder, uint amount); event AuctionEnded(address winner, uint amount); constructor(uint _biddingTime) { beneficiary = msg.sender; auctionEndTime = block.timestamp + _biddingTime; } function bid() public payable { require(block.timestamp <= auctionEndTime, "Auction already ended"); require(msg.value > highestBid, "There already is a higher bid"); if (highestBid != 0) { pendingReturns[highestBidder] += highestBid; } highestBidder = msg.sender; highestBid = msg.value; emit HighestBidIncreased(msg.sender, msg.value); } }""" } if contract_type in templates: comp_id = f"solidity_{len(self.components) + 1}" component = SolidityComponent( comp_id, contract_type, 100, 100 + len(self.components) * 50, 600, 400 ) component.source_code = templates[contract_type] component.contract_name = contract_type.replace(" ", "") self.components.append(component) self.draw_solidity_component(component) self.select_component(component) dialog.destroy() messagebox.showinfo("Успех", f"Шаблон '{contract_type}' создан") def new_project(self): """Создаем новый проект""" if self.components and not messagebox.askyesno("Новый проект", "Сохранить текущий проект?"): return self.components = [] self.current_file = None self.selected_component = None self.canvas.delete("all") self.draw_grid() self.update_properties_panel() self.add_default_components() def save_project(self): """Сохраняем проект""" if self.current_file: self._save_to_file(self.current_file) else: self.save_project_as() def save_project_as(self): """Сохраняем проект как""" filename = filedialog.asksaveasfilename( defaultextension=".solidityweb", filetypes=[("Solidity Web Projects", "*.solidityweb"), ("Все файлы", "*.*")] ) if filename: self._save_to_file(filename) self.current_file = filename def _save_to_file(self, filename): """Сохраняем проект в файл""" try: data = { 'components': [], 'version': '1.0', 'saved_at': datetime.now().isoformat() } for comp in self.components: data['components'].append(comp.to_dict()) with open(filename, 'w', encoding='utf-8') as f: json.dump(data, f, indent=2, ensure_ascii=False) messagebox.showinfo("Сохранено", f"Проект сохранен в {filename}") except Exception as e: messagebox.showerror("Ошибка", f"Не удалось сохранить: {str(e)}") def open_project(self): """Открываем проект""" filename = filedialog.askopenfilename( filetypes=[("Solidity Web Projects", "*.solidityweb"), ("Все файлы", "*.*")] ) if not filename: return try: with open(filename, 'r', encoding='utf-8') as f: data = json.load(f) self.components = [] self.canvas.delete("all") self.draw_grid() for comp_data in data['components']: if comp_data['type'] == 'solidity': comp = SolidityComponent.from_dict(comp_data) self.draw_solidity_component(comp) else: comp = BasicComponent.from_dict(comp_data) self.draw_basic_component(comp) self.components.append(comp) self.current_file = filename self.selected_component = None self.update_properties_panel() messagebox.showinfo("Открыто", f"Проект загружен из {filename}") except Exception as e: messagebox.showerror("Ошибка", f"Не удалось открыть: {str(e)}") def preview_site(self): """Предпросмотр сайта""" dialog = tk.Toplevel(self.root) dialog.title("Предпросмотр сайта") dialog.geometry("1000x700") # Генерируем HTML html_content = self._generate_html() # Показываем в текстовом редакторе editor = scrolledtext.ScrolledText(dialog, wrap=tk.WORD, font=('Consolas', 10)) editor.pack(fill=tk.BOTH, expand=True, padx=10, pady=10) editor.insert(1.0, html_content) # Кнопки btn_frame = tk.Frame(dialog) btn_frame.pack(fill=tk.X, padx=10, pady=10) tk.Button(btn_frame, text="Копировать HTML", bg="#2196F3", fg="white", command=lambda: self.copy_to_clipboard(html_content)).pack(side=tk.LEFT, padx=5) tk.Button(btn_frame, text="Открыть в браузере", bg="#4CAF50", fg="white", command=self._open_in_browser).pack(side=tk.RIGHT, padx=5) def export_site(self): """Экспортируем сайт""" if not self.components: messagebox.showwarning("Ошибка", "Проект пуст") return export_dir = filedialog.askdirectory(title="Выберите папку для экспорта") if not export_dir: return try: site_dir = os.path.join(export_dir, "solidity_website") # Проверяем существование директории if os.path.exists(site_dir): response = messagebox.askyesno( "Подтверждение", f"Папка {site_dir} уже существует. Перезаписать?" ) if not response: return shutil.rmtree(site_dir) os.makedirs(site_dir, exist_ok=True) # Генерируем файлы html_content = self._generate_html() css_content = self._generate_css() js_content = self._generate_js() # Сохраняем файлы with open(os.path.join(site_dir, "index.html"), "w", encoding="utf-8") as f: f.write(html_content) with open(os.path.join(site_dir, "style.css"), "w", encoding="utf-8") as f: f.write(css_content) with open(os.path.join(site_dir, "script.js"), "w", encoding="utf-8") as f: f.write(js_content) # Сохраняем контракты contracts_dir = os.path.join(site_dir, "contracts") os.makedirs(contracts_dir, exist_ok=True) for comp in self.components: if comp.type == "solidity": contract_file = os.path.join(contracts_dir, f"{comp.contract_name}.sol") with open(contract_file, "w", encoding="utf-8") as f: f.write(comp.source_code) # Создаем README readme_content = self._generate_readme() with open(os.path.join(site_dir, "README.md"), "w", encoding="utf-8") as f: f.write(readme_content) messagebox.showinfo("Экспорт", f"Сайт экспортирован в:\n{site_dir}") except Exception as e: messagebox.showerror("Ошибка", f"Не удалось экспортировать: {str(e)}") def _generate_html(self): """Генерируем HTML""" html = """ Solidity Web3 Сайт
""" for comp in sorted(self.components, key=lambda c: (c.y, c.x)): if comp.type == "header": html += f"""

{comp.properties.get('text', 'Заголовок')}

""" elif comp.type == "paragraph": html += f"""

{comp.properties.get('text', 'Текст')}

""" elif comp.type == "button": html += f"""
""" elif comp.type == "image": html += f"""
{comp.properties.get('alt', 'Изображение')}
""" elif comp.type == "solidity": status = "не скомпилирован" if comp.compiled: status = "скомпилирован" if comp.deployed: status = f"деплоен: {comp.contract_address}" html += f"""

🛠️ {comp.contract_name}

{status}
""" html += """
""" return html def _generate_css(self): """Генерируем CSS""" return """* { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: Arial, sans-serif; background: #1a1a2e; color: white; min-height: 100vh; padding: 20px; } .container { position: relative; width: 1200px; height: 800px; margin: 0 auto; background: #0f3460; border-radius: 10px; } .component { position: absolute; border-radius: 8px; padding: 15px; } .header { background: linear-gradient(90deg, #0f3460, #1a5f9c); display: flex; align-items: center; justify-content: center; } .header h1 { font-size: 2rem; text-align: center; } .paragraph { background: rgba(255, 255, 255, 0.1); display: flex; align-items: center; } .paragraph p { font-size: 1.1rem; line-height: 1.5; } .button { display: flex; align-items: center; justify-content: center; } .button button { background: #4CAF50; color: white; border: none; padding: 12px 24px; border-radius: 25px; font-size: 16px; cursor: pointer; width: 100%; height: 100%; } .image { display: flex; align-items: center; justify-content: center; overflow: hidden; } .image img { max-width: 100%; max-height: 100%; } .solidity { background: #1c2541; border: 2px solid #5bc0be; } .contract-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; } .contract-header h3 { color: #5bc0be; } .status { background: #2d4059; padding: 5px 10px; border-radius: 15px; font-size: 0.9rem; } .contract-actions { display: flex; gap: 10px; flex-wrap: wrap; } .contract-actions button { background: #4a69bd; color: white; border: none; padding: 8px 16px; border-radius: 5px; cursor: pointer; flex: 1; min-width: 100px; } .result { margin-top: 15px; padding: 10px; background: rgba(255, 255, 255, 0.1); border-radius: 5px; min-height: 40px; }""" def _generate_js(self): """Генерируем JavaScript""" return """let web3; let contract; let currentContractAddress; async function connectContract(address) { try { if (typeof window.ethereum !== 'undefined') { web3 = new Web3(window.ethereum); await window.ethereum.request({ method: 'eth_requestAccounts' }); // ABI контракта SimpleStorage const abi = [ { "inputs": [{"internalType": "uint256","name": "initialValue","type": "uint256"}], "stateMutability": "nonpayable", "type": "constructor" }, { "inputs": [], "name": "decrement", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [], "name": "get", "outputs": [{"internalType": "uint256","name": "","type": "uint256"}], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "increment", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [{"internalType": "uint256","name": "x","type": "uint256"}], "name": "set", "outputs": [], "stateMutability": "nonpayable", "type": "function" } ]; if (address && address.startsWith('0x')) { currentContractAddress = address; contract = new web3.eth.Contract(abi, address); alert('Контракт подключен: ' + address); } else { alert('Введите действительный адрес контракта'); } } else { alert('Установите MetaMask для работы с контрактами'); } } catch (error) { console.error('Ошибка подключения:', error); alert('Ошибка подключения к контракту'); } } async function callGet() { if (!contract) { alert('Сначала подключите контракт'); return; } try { const result = await contract.methods.get().call(); document.querySelector('.result').textContent = 'Результат get(): ' + result; } catch (error) { console.error('Ошибка вызова get:', error); alert('Ошибка вызова функции'); } } async function callSet(value) { if (!contract || !web3) { alert('Сначала подключите контракт'); return; } try { const accounts = await web3.eth.getAccounts(); await contract.methods.set(value).send({ from: accounts[0] }); document.querySelector('.result').textContent = 'Функция set(' + value + ') выполнена'; } catch (error) { console.error('Ошибка вызова set:', error); alert('Ошибка вызова функции'); } } // Автоподключение при загрузке window.addEventListener('DOMContentLoaded', () { if (typeof window.ethereum !== 'undefined') { web3 = new Web3(window.ethereum); } });""" def _generate_readme(self): """Генерация README файла""" contracts = [comp.contract_name for comp in self.components if comp.type == "solidity"] contracts_list = "\n".join([f"- {name}" for name in contracts]) return f"""# Solidity Web3 Сайт Сгенерировано с помощью Solidity Web3 Builder ## Содержимое проекта: 1. `index.html` - Главная страница 2. `style.css` - Стили 3. `script.js` - JavaScript с Web3 логикой 4. `contracts/` - Исходные коды контрактов ## Контракты в проекте: {contracts_list if contracts_list else "Нет контрактов"} ## Инструкция по запуску: 1. Установите MetaMask браузерное расширение 2. Подключитесь к тестовой сети (Goerli, Sepolia) или локальной сети (Ganache) 3. Откройте index.html в браузере 4. Подключите кошелек ## Требования для деплоя: 1. Установите Ganache для локального тестирования 2. Импортируйте приватный ключ в Ganache 3. Убедитесь что у аккаунта есть ETH для газа ## Безопасность: - Никогда не используйте приватные ключи от реальных кошельков - Тестируйте только на тестовых сетях - Проверяйте контракты на уязвимости перед использованием --- Сгенерировано: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')} """ def _open_in_browser(self): """Открываем в браузере""" try: # Создаем временный файл with tempfile.NamedTemporaryFile(mode='w', suffix='.html', delete=False) as f: f.write(self._generate_html()) temp_file = f.name # Открываем в браузере webbrowser.open(f'file://{temp_file}') except Exception as e: messagebox.showerror("Ошибка", f"Не удалось открыть в браузере: {str(e)}") def show_docs(self): """Показываем документацию""" dialog = tk.Toplevel(self.root) dialog.title("Документация") dialog.geometry("700x500") text = scrolledtext.ScrolledText(dialog, wrap=tk.WORD, font=('Arial', 10)) text.pack(fill=tk.BOTH, expand=True, padx=10, pady=10) docs = """ === Solidity Web3 Builder - Документация === 1. ОСНОВНЫЕ ВОЗМОЖНОСТИ: • Создание сайтов с поддержкой Web3 • Редактирование и компиляция Solidity контрактов • Визуальное размещение компонентов • Реальный деплой контрактов в сети • Экспорт готового сайта 2. РАБОТА С КОНТРАКТАМИ: • Редактирование кода в встроенном редакторе • Компиляция через py-solc-x • Реальный деплой с использованием приватных ключей • Тестирование функций контракта 3. БЕЗОПАСНОСТЬ: • Приватные ключи запрашиваются только при деплое • Ключи не сохраняются в проекте • Поддержка PoA middleware для Ganache • Генератор тестовых кошельков 4. ТРЕБОВАНИЯ ДЛЯ ДЕПЛОЯ: • Ganache (рекомендуется) для локального тестирования • Приватный ключ аккаунта с ETH для газа • Chain ID сети (1337 для Ganache) • URL сети (http://localhost:8545 для Ganache) 5. БЫСТРЫЙ СТАРТ: 1. Запустите Ganache на localhost:8545 2. Создайте компонент "Контракт Solidity" 3. Отредактируйте код контракта 4. Нажмите "Скомпилировать" 5. Нажмите "Деплоить" и введите приватный ключ 6. Протестируйте функции 7. Экспортируйте сайт === Важные замечания === • Используйте только тестовые приватные ключи! • Ganache Chain ID = 1337 • Для деплоя нужен ETH на аккаунте • Все транзакции реальные (в выбранной сети) """ text.insert(1.0, docs) text.config(state=tk.DISABLED) def show_examples(self): """Показываем примеры контрактов""" dialog = tk.Toplevel(self.root) dialog.title("Примеры контрактов") dialog.geometry("800x600") notebook = ttk.Notebook(dialog) notebook.pack(fill=tk.BOTH, expand=True) examples = { "Простое хранилище": """// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract SimpleStorage { uint256 private value; function set(uint256 newValue) public { value = newValue; } function get() public view returns (uint256) { return value; } }""", "Голосование": """// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract Voting { struct Proposal { string name; uint voteCount; } Proposal[] public proposals; mapping(address => bool) public hasVoted; constructor(string[] memory proposalNames) { for (uint i = 0; i < proposalNames.length; i++) { proposals.push(Proposal({ name: proposalNames[i], voteCount: 0 })); } } function vote(uint proposal) public { require(!hasVoted[msg.sender], "Already voted"); require(proposal < proposals.length, "Invalid proposal"); proposals[proposal].voteCount++; hasVoted[msg.sender] = true; } }""", "Краудфандинг": """// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract Crowdfunding { address public creator; uint public goal; uint public deadline; uint public totalFunds; mapping(address => uint) public contributions; event Funded(address donor, uint amount); event GoalReached(uint total); constructor(uint _goal, uint _duration) { creator = msg.sender; goal = _goal; deadline = block.timestamp + _duration; } function contribute() public payable { require(block.timestamp < deadline, "Campaign ended"); require(msg.value > 0, "Contribution must be positive"); contributions[msg.sender] += msg.value; totalFunds += msg.value; emit Funded(msg.sender, msg.value); if (totalFunds >= goal) { emit GoalReached(totalFunds); } } }""" } for title, code in examples.items(): frame = tk.Frame(notebook) notebook.add(frame, text=title) text = scrolledtext.ScrolledText(frame, wrap=tk.WORD, font=('Consolas', 10)) text.pack(fill=tk.BOTH, expand=True, padx=10, pady=10) text.insert(1.0, code) def show_about(self): """О программе""" dialog = tk.Toplevel(self.root) dialog.title("О программе") dialog.geometry("400x300") tk.Label(dialog, text="Solidity Web3 Builder", font=('Arial', 16, 'bold')).pack(pady=20) tk.Label(dialog, text="Версия 2.0", font=('Arial', 12)).pack(pady=10) tk.Label(dialog, text="Конструктор сайтов с реальной поддержкой\nсмарт-контрактов Solidity", justify=tk.CENTER).pack(pady=10) tk.Label(dialog, text="Возможности:", font=('Arial', 11, 'bold')).pack(pady=(20, 5)) features = [ "• Редактирование Solidity кода", "• Реальный деплой контрактов", "• Поддержка web3.py 7.x", "• Экспорт готовых Web3 сайтов" ] for feature in features: tk.Label(dialog, text=feature).pack() tk.Label(dialog, text="\nДля работы требуется:", font=('Arial', 10)).pack(pady=(10, 0)) tk.Label(dialog, text="Python 3.8+, web3, py-solc-x, eth-account", fg='#666666').pack() def main(): """Точка входа""" try: root = tk.Tk() app = SolidityBuilder(root) root.mainloop() except Exception as e: messagebox.showerror("Критическая ошибка", f"Программа завершилась с ошибкой:\n{str(e)}") sys.exit(1) if __name__ == "__main__": main()