diff --git a/src/components/ui/TokenAuthModal.jsx b/src/components/ui/TokenAuthModal.jsx
new file mode 100644
index 0000000..244979b
--- /dev/null
+++ b/src/components/ui/TokenAuthModal.jsx
@@ -0,0 +1,526 @@
+// ============================================
+// TOKEN AUTHENTICATION MODAL
+// ============================================
+// Модальное окно для авторизации через Web3 токены
+// Поддерживает покупку, проверку и управление токенами
+// ============================================
+
+const TokenAuthModal = ({
+ isOpen,
+ onClose,
+ onAuthenticated,
+ tokenAuthManager,
+ web3ContractManager
+}) => {
+ const [currentStep, setCurrentStep] = React.useState('connect'); // connect, purchase, authenticate, success
+ const [walletAddress, setWalletAddress] = React.useState('');
+ const [isConnecting, setIsConnecting] = React.useState(false);
+ const [isPurchasing, setIsPurchasing] = React.useState(false);
+ const [isAuthenticating, setIsAuthenticating] = React.useState(false);
+ const [selectedTokenType, setSelectedTokenType] = React.useState('monthly');
+ const [tokenPrices, setTokenPrices] = React.useState(null);
+ const [userTokens, setUserTokens] = React.useState([]);
+ const [activeToken, setActiveToken] = React.useState(null);
+ const [error, setError] = React.useState('');
+ const [success, setSuccess] = React.useState('');
+
+ // Состояния для разных шагов
+ const [purchaseAmount, setPurchaseAmount] = React.useState('');
+ const [tokenId, setTokenId] = React.useState('');
+
+ React.useEffect(() => {
+ if (isOpen) {
+ initializeModal();
+ }
+ }, [isOpen]);
+
+ // Инициализация модального окна
+ const initializeModal = async () => {
+ try {
+ setCurrentStep('connect');
+ setError('');
+ setSuccess('');
+
+ // Проверяем статус кошелька
+ if (tokenAuthManager && tokenAuthManager.walletAddress) {
+ setWalletAddress(tokenAuthManager.walletAddress);
+ await checkUserTokens();
+ setCurrentStep('authenticate');
+ }
+
+ } catch (error) {
+ console.error('Modal initialization failed:', error);
+ setError('Failed to initialize authentication');
+ }
+ };
+
+ // Подключение кошелька
+ const connectWallet = async () => {
+ try {
+ setIsConnecting(true);
+ setError('');
+
+ if (!tokenAuthManager) {
+ throw new Error('Token auth manager not available');
+ }
+
+ // Инициализируем Web3
+ await tokenAuthManager.initialize();
+
+ if (tokenAuthManager.walletAddress) {
+ setWalletAddress(tokenAuthManager.walletAddress);
+ await checkUserTokens();
+ setCurrentStep('authenticate');
+ } else {
+ throw new Error('Failed to connect wallet');
+ }
+
+ } catch (error) {
+ console.error('Wallet connection failed:', error);
+ setError(error.message || 'Failed to connect wallet');
+ } finally {
+ setIsConnecting(false);
+ }
+ };
+
+ // Проверка токенов пользователя
+ const checkUserTokens = async () => {
+ try {
+ if (!web3ContractManager || !walletAddress) return;
+
+ // Получаем активные токены пользователя
+ const activeTokens = await web3ContractManager.getActiveUserTokens(walletAddress);
+
+ if (activeTokens.length > 0) {
+ // Получаем информацию о первом активном токене
+ const tokenInfo = await web3ContractManager.getTokenInfo(activeTokens[0]);
+ setActiveToken(tokenInfo);
+ setUserTokens(activeTokens);
+ }
+
+ } catch (error) {
+ console.error('Failed to check user tokens:', error);
+ }
+ };
+
+ // Получение цен токенов
+ const loadTokenPrices = async () => {
+ try {
+ if (!web3ContractManager) return;
+
+ const prices = await web3ContractManager.getTokenPrices();
+ setTokenPrices(prices);
+
+ } catch (error) {
+ console.error('Failed to load token prices:', error);
+ }
+ };
+
+ // Покупка токена
+ const purchaseToken = async () => {
+ try {
+ setIsPurchasing(true);
+ setError('');
+
+ if (!web3ContractManager || !walletAddress) {
+ throw new Error('Web3 contract manager not available');
+ }
+
+ let result;
+ if (selectedTokenType === 'monthly') {
+ result = await web3ContractManager.purchaseMonthlyToken(tokenPrices.monthlyWei);
+ } else {
+ result = await web3ContractManager.purchaseYearlyToken(tokenPrices.yearlyWei);
+ }
+
+ // Получаем ID токена из события
+ const tokenId = result.events.TokenMinted.returnValues.tokenId;
+ setTokenId(tokenId);
+
+ setSuccess(`Token purchased successfully! Token ID: ${tokenId}`);
+ setCurrentStep('authenticate');
+
+ // Обновляем список токенов
+ await checkUserTokens();
+
+ } catch (error) {
+ console.error('Token purchase failed:', error);
+ setError(error.message || 'Failed to purchase token');
+ } finally {
+ setIsPurchasing(false);
+ }
+ };
+
+ // Авторизация через токен
+ const authenticateWithToken = async (tokenId) => {
+ try {
+ setIsAuthenticating(true);
+ setError('');
+
+ if (!tokenAuthManager) {
+ throw new Error('Token auth manager not available');
+ }
+
+ // Определяем тип токена
+ let tokenType = 'monthly';
+ if (activeToken) {
+ tokenType = activeToken.tokenType === 0 ? 'monthly' : 'yearly';
+ }
+
+ // Авторизуемся через токен
+ const session = await tokenAuthManager.authenticateWithToken(tokenId, tokenType);
+
+ setSuccess('Authentication successful!');
+ setCurrentStep('success');
+
+ // Вызываем callback
+ if (onAuthenticated) {
+ onAuthenticated(session);
+ }
+
+ } catch (error) {
+ console.error('Authentication failed:', error);
+ setError(error.message || 'Failed to authenticate');
+ } finally {
+ setIsAuthenticating(false);
+ }
+ };
+
+ // Переключение на шаг покупки
+ const goToPurchase = () => {
+ setCurrentStep('purchase');
+ loadTokenPrices();
+ };
+
+ // Переключение на шаг авторизации
+ const goToAuthenticate = () => {
+ setCurrentStep('authenticate');
+ };
+
+ // Закрытие модального окна
+ const handleClose = () => {
+ setCurrentStep('connect');
+ setError('');
+ setSuccess('');
+ setTokenId('');
+ setActiveToken(null);
+ onClose();
+ };
+
+ // Форматирование цены
+ const formatPrice = (price) => {
+ if (!price) return 'Loading...';
+ return `${parseFloat(price).toFixed(4)} ETH`;
+ };
+
+ // Форматирование времени истечения
+ const formatExpiry = (timestamp) => {
+ if (!timestamp) return 'Unknown';
+ const date = new Date(timestamp * 1000);
+ return date.toLocaleDateString() + ' ' + date.toLocaleTimeString();
+ };
+
+ // Получение названия типа токена
+ const getTokenTypeName = (type) => {
+ return type === 0 ? 'Monthly' : 'Yearly';
+ };
+
+ // Рендер шага подключения
+ const renderConnectStep = () => (
+
+
+
+
Connect Your Wallet
+
Connect your MetaMask or other Web3 wallet to continue
+
+
+
+
+ {error && (
+
+ {error}
+
+ )}
+
+ );
+
+ // Рендер шага покупки
+ const renderPurchaseStep = () => (
+
+
+
Purchase Access Token
+
Choose your subscription plan
+
+
+
+
setSelectedTokenType('monthly')}
+ >
+
+
+
Monthly Plan
+
+ {formatPrice(tokenPrices?.monthly)}
+
+
30 days access
+
+
+
+
setSelectedTokenType('yearly')}
+ >
+
+
+
Yearly Plan
+
+ {formatPrice(tokenPrices?.yearly)}
+
+
365 days access
+
+
+ Save 17%
+
+
+
+
+
+
+
+
+
+
+
+
+ {error && (
+
+ {error}
+
+ )}
+
+ );
+
+ // Рендер шага авторизации
+ const renderAuthenticateStep = () => (
+
+
+
Authenticate with Token
+
Use your access token to authenticate
+
+
+ {activeToken ? (
+
+
+
+ Active Token Found
+
+
+
Token ID: {activeToken.tokenId}
+
Type: {getTokenTypeName(activeToken.tokenType)}
+
Expires: {formatExpiry(activeToken.expiryDate)}
+
+
+ ) : (
+
+
+
+ No Active Token
+
+
+ You don't have an active access token. Please purchase one first.
+
+
+ )}
+
+ {tokenId && (
+
+
+
+ New Token Purchased
+
+
+ Token ID: {tokenId}
+
+
+ )}
+
+
+ {activeToken && (
+
+ )}
+
+ {tokenId && (
+
+ )}
+
+
+
+
+ {error && (
+
+ {error}
+
+ )}
+
+ {success && (
+
+ {success}
+
+ )}
+
+ );
+
+ // Рендер шага успеха
+ const renderSuccessStep = () => (
+
+
+
+
Authentication Successful!
+
You are now authenticated and can access the service
+
+
+
+
+ );
+
+ // Рендер основного контента
+ const renderContent = () => {
+ switch (currentStep) {
+ case 'connect':
+ return renderConnectStep();
+ case 'purchase':
+ return renderPurchaseStep();
+ case 'authenticate':
+ return renderAuthenticateStep();
+ case 'success':
+ return renderSuccessStep();
+ default:
+ return renderConnectStep();
+ }
+ };
+
+ if (!isOpen) return null;
+
+ return (
+
+
+ {/* Header */}
+
+
Token Authentication
+
+
+
+ {/* Content */}
+
+ {renderContent()}
+
+
+ {/* Footer */}
+
+
+
Secure authentication powered by Web3
+
Your wallet address: {walletAddress ? `${walletAddress.substring(0, 6)}...${walletAddress.substring(38)}` : 'Not connected'}
+
+
+
+
+ );
+};
+
+export default TokenAuthModal;
diff --git a/src/components/ui/TokenStatus.jsx b/src/components/ui/TokenStatus.jsx
new file mode 100644
index 0000000..c0e16d9
--- /dev/null
+++ b/src/components/ui/TokenStatus.jsx
@@ -0,0 +1,290 @@
+// ============================================
+// TOKEN STATUS COMPONENT
+// ============================================
+// Компонент для отображения статуса токена доступа
+// Показывает информацию о текущем токене и времени до истечения
+// ============================================
+
+const TokenStatus = ({
+ tokenAuthManager,
+ web3ContractManager,
+ onShowTokenModal
+}) => {
+ const [tokenInfo, setTokenInfo] = React.useState(null);
+ const [timeLeft, setTimeLeft] = React.useState('');
+ const [isExpired, setIsExpired] = React.useState(false);
+ const [isLoading, setIsLoading] = React.useState(true);
+ const [updateInterval, setUpdateInterval] = React.useState(null);
+
+ React.useEffect(() => {
+ if (tokenAuthManager) {
+ loadTokenStatus();
+ startUpdateTimer();
+ }
+
+ return () => {
+ if (updateInterval) {
+ clearInterval(updateInterval);
+ }
+ };
+ }, [tokenAuthManager]);
+
+ // Загрузка статуса токена
+ const loadTokenStatus = async () => {
+ try {
+ setIsLoading(true);
+
+ if (!tokenAuthManager || !tokenAuthManager.isAuthenticated()) {
+ setTokenInfo(null);
+ setTimeLeft('');
+ setIsExpired(false);
+ return;
+ }
+
+ const session = tokenAuthManager.getCurrentSession();
+ if (!session) {
+ setTokenInfo(null);
+ return;
+ }
+
+ // Получаем информацию о токене
+ const info = tokenAuthManager.getTokenInfo();
+ setTokenInfo(info);
+
+ // Проверяем, не истек ли токен
+ const now = Date.now();
+ const expiresAt = info.expiresAt;
+ const timeRemaining = expiresAt - now;
+
+ if (timeRemaining <= 0) {
+ setIsExpired(true);
+ setTimeLeft('Expired');
+ } else {
+ setIsExpired(false);
+ updateTimeLeft(timeRemaining);
+ }
+
+ } catch (error) {
+ console.error('Failed to load token status:', error);
+ setTokenInfo(null);
+ } finally {
+ setIsLoading(false);
+ }
+ };
+
+ // Запуск таймера обновления
+ const startUpdateTimer = () => {
+ const interval = setInterval(() => {
+ if (tokenInfo && !isExpired) {
+ const now = Date.now();
+ const expiresAt = tokenInfo.expiresAt;
+ const timeRemaining = expiresAt - now;
+
+ if (timeRemaining <= 0) {
+ setIsExpired(true);
+ setTimeLeft('Expired');
+ // Уведомляем о истечении токена
+ handleTokenExpired();
+ } else {
+ updateTimeLeft(timeRemaining);
+ }
+ }
+ }, 1000); // Обновляем каждую секунду
+
+ setUpdateInterval(interval);
+ };
+
+ // Обновление оставшегося времени
+ const updateTimeLeft = (timeRemaining) => {
+ const days = Math.floor(timeRemaining / (24 * 60 * 60 * 1000));
+ const hours = Math.floor((timeRemaining % (24 * 60 * 60 * 1000)) / (60 * 60 * 1000));
+ const minutes = Math.floor((timeRemaining % (60 * 60 * 1000)) / (60 * 1000));
+ const seconds = Math.floor((timeRemaining % (60 * 1000)) / 1000);
+
+ let timeString = '';
+
+ if (days > 0) {
+ timeString = `${days}d ${hours}h`;
+ } else if (hours > 0) {
+ timeString = `${hours}h ${minutes}m`;
+ } else if (minutes > 0) {
+ timeString = `${minutes}m ${seconds}s`;
+ } else {
+ timeString = `${seconds}s`;
+ }
+
+ setTimeLeft(timeString);
+ };
+
+ // Обработка истечения токена
+ const handleTokenExpired = () => {
+ // Показываем уведомление
+ showExpiredNotification();
+
+ // Можно также автоматически открыть модальное окно для покупки нового токена
+ // if (onShowTokenModal) {
+ // setTimeout(() => onShowTokenModal(), 2000);
+ // }
+ };
+
+ // Показ уведомления об истечении
+ const showExpiredNotification = () => {
+ // Создаем уведомление в браузере
+ if ('Notification' in window && Notification.permission === 'granted') {
+ new Notification('SecureBit Token Expired', {
+ body: 'Your access token has expired. Please purchase a new one to continue.',
+ icon: '/logo/icon-192x192.png',
+ tag: 'token-expired'
+ });
+ }
+
+ // Показываем toast уведомление
+ showToast('Token expired', 'Your access token has expired. Please purchase a new one.', 'warning');
+ };
+
+ // Показ toast уведомления
+ const showToast = (title, message, type = 'info') => {
+ // Создаем toast элемент
+ const toast = document.createElement('div');
+ toast.className = `fixed top-4 right-4 z-50 p-4 rounded-lg shadow-lg max-w-sm ${
+ type === 'warning' ? 'bg-yellow-500 text-white' :
+ type === 'error' ? 'bg-red-500 text-white' :
+ type === 'success' ? 'bg-green-500 text-white' :
+ 'bg-blue-500 text-white'
+ }`;
+
+ toast.innerHTML = `
+
+
+
+
+
+
${title}
+
${message}
+
+
+
+ `;
+
+ document.body.appendChild(toast);
+
+ // Автоматически удаляем через 5 секунд
+ setTimeout(() => {
+ if (toast.parentElement) {
+ toast.remove();
+ }
+ }, 5000);
+ };
+
+ // Получение названия типа токена
+ const getTokenTypeName = (type) => {
+ return type === 'monthly' ? 'Monthly' : 'Yearly';
+ };
+
+ // Получение иконки типа токена
+ const getTokenTypeIcon = (type) => {
+ return type === 'monthly' ? 'fa-calendar-alt' : 'fa-calendar';
+ };
+
+ // Получение цвета для типа токена
+ const getTokenTypeColor = (type) => {
+ return type === 'monthly' ? 'text-blue-500' : 'text-green-500';
+ };
+
+ // Получение цвета для статуса
+ const getStatusColor = () => {
+ if (isExpired) return 'text-red-500';
+ if (tokenInfo && tokenInfo.timeLeft < 24 * 60 * 60 * 1000) return 'text-yellow-500'; // Меньше дня
+ return 'text-green-500';
+ };
+
+ // Получение иконки статуса
+ const getStatusIcon = () => {
+ if (isExpired) return 'fa-times-circle';
+ if (tokenInfo && tokenInfo.timeLeft < 24 * 60 * 60 * 1000) return 'fa-exclamation-triangle';
+ return 'fa-check-circle';
+ };
+
+ // Если токен не загружен или не авторизован
+ if (isLoading) {
+ return (
+
+
+ Loading token...
+
+ );
+ }
+
+ if (!tokenInfo) {
+ return (
+
+ );
+ }
+
+ // Если токен истек
+ if (isExpired) {
+ return (
+
+ );
+ }
+
+ // Отображение активного токена
+ return (
+
+ {/* Статус токена */}
+
+
+
+
+ {getTokenTypeName(tokenInfo.tokenType)} Token
+
+
+ {timeLeft} left
+
+
+
+
+ {/* Информация о токене */}
+
+
+
+
+ ID: {tokenInfo.tokenId}
+
+
+ {getTokenTypeName(tokenInfo.tokenType)}
+
+
+
+
+ {/* Кнопка управления */}
+
+
+ );
+};
+
+export default TokenStatus;
diff --git a/src/token-auth/SecureBitAccessToken.sol b/src/token-auth/SecureBitAccessToken.sol
new file mode 100644
index 0000000..89ed025
--- /dev/null
+++ b/src/token-auth/SecureBitAccessToken.sol
@@ -0,0 +1,456 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.30;
+
+import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
+import "@openzeppelin/contracts/access/Ownable.sol";
+import "@openzeppelin/contracts/utils/Counters.sol";
+import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
+import "@openzeppelin/contracts/security/Pausable.sol";
+
+/**
+ * @title SecureBit Access Token
+ * @dev ERC-721 токен для доступа к SecureBit сервису
+ * Поддерживает месячные и годовые подписки
+ */
+contract SecureBitAccessToken is ERC721, Ownable, ReentrancyGuard, Pausable {
+ using Counters for Counters.Counter;
+
+ Counters.Counter private _tokenIds;
+
+ // Структура для хранения информации о токене
+ struct TokenInfo {
+ uint256 tokenId;
+ address owner;
+ uint256 expiryDate;
+ TokenType tokenType;
+ bool isActive;
+ uint256 createdAt;
+ string metadata;
+ }
+
+ // Типы токенов
+ enum TokenType { MONTHLY, YEARLY }
+
+ // Маппинг токенов
+ mapping(uint256 => TokenInfo) public tokens;
+ mapping(address => uint256[]) public userTokens;
+
+ // Цены токенов (в wei)
+ uint256 public monthlyPrice = 0.01 ether; // 0.01 ETH
+ uint256 public yearlyPrice = 0.1 ether; // 0.1 ETH
+
+ // События
+ event TokenMinted(uint256 indexed tokenId, address indexed owner, TokenType tokenType, uint256 expiryDate);
+ event TokenExpired(uint256 indexed tokenId, address indexed owner);
+ event TokenRenewed(uint256 indexed tokenId, uint256 newExpiryDate);
+ event PriceUpdated(TokenType tokenType, uint256 oldPrice, uint256 newPrice);
+ event TokenDeactivated(uint256 indexed tokenId, address indexed owner);
+ event TokenTransferred(uint256 indexed tokenId, address indexed from, address indexed to);
+
+ // Модификаторы
+ modifier tokenExists(uint256 tokenId) {
+ require(_exists(tokenId), "Token does not exist");
+ _;
+ }
+
+ modifier tokenActive(uint256 tokenId) {
+ require(tokens[tokenId].isActive, "Token is not active");
+ _;
+ }
+
+ modifier onlyTokenOwner(uint256 tokenId) {
+ require(ownerOf(tokenId) == msg.sender, "Not token owner");
+ _;
+ }
+
+ constructor() ERC721("SecureBit Access Token", "SBAT") Ownable(msg.sender) {
+ // Конструктор автоматически устанавливает владельца
+ }
+
+ /**
+ * @dev Покупка месячного токена
+ */
+ function purchaseMonthlyToken() external payable nonReentrant whenNotPaused {
+ require(msg.value >= monthlyPrice, "Insufficient payment for monthly token");
+
+ uint256 newTokenId = _mintToken(msg.sender, TokenType.MONTHLY);
+
+ // Возвращаем излишки
+ if (msg.value > monthlyPrice) {
+ payable(msg.sender).transfer(msg.value - monthlyPrice);
+ }
+
+ emit TokenMinted(newTokenId, msg.sender, TokenType.MONTHLY, tokens[newTokenId].expiryDate);
+ }
+
+ /**
+ * @dev Покупка годового токена
+ */
+ function purchaseYearlyToken() external payable nonReentrant whenNotPaused {
+ require(msg.value >= yearlyPrice, "Insufficient payment for yearly token");
+
+ uint256 newTokenId = _mintToken(msg.sender, TokenType.YEARLY);
+
+ // Возвращаем излишки
+ if (msg.value > yearlyPrice) {
+ payable(msg.sender).transfer(msg.value - yearlyPrice);
+ }
+
+ emit TokenMinted(newTokenId, msg.sender, TokenType.YEARLY, tokens[newTokenId].expiryDate);
+ }
+
+ /**
+ * @dev Покупка нескольких токенов одного типа
+ */
+ function purchaseMultipleTokens(TokenType tokenType, uint256 quantity) external payable nonReentrant whenNotPaused {
+ require(quantity > 0 && quantity <= 10, "Invalid quantity (1-10)");
+
+ uint256 totalPrice = tokenType == TokenType.MONTHLY ? monthlyPrice * quantity : yearlyPrice * quantity;
+ require(msg.value >= totalPrice, "Insufficient payment");
+
+ uint256[] memory newTokenIds = new uint256[](quantity);
+
+ for (uint256 i = 0; i < quantity; i++) {
+ newTokenIds[i] = _mintToken(msg.sender, tokenType);
+ emit TokenMinted(newTokenIds[i], msg.sender, tokenType, tokens[newTokenIds[i]].expiryDate);
+ }
+
+ // Возвращаем излишки
+ if (msg.value > totalPrice) {
+ payable(msg.sender).transfer(msg.value - totalPrice);
+ }
+ }
+
+ /**
+ * @dev Внутренняя функция создания токена
+ */
+ function _mintToken(address owner, TokenType tokenType) internal returns (uint256) {
+ _tokenIds.increment();
+ uint256 newTokenId = _tokenIds.current();
+
+ uint256 expiryDate;
+ if (tokenType == TokenType.MONTHLY) {
+ expiryDate = block.timestamp + 30 days;
+ } else {
+ expiryDate = block.timestamp + 365 days;
+ }
+
+ TokenInfo memory newToken = TokenInfo({
+ tokenId: newTokenId,
+ owner: owner,
+ expiryDate: expiryDate,
+ tokenType: tokenType,
+ isActive: true,
+ createdAt: block.timestamp,
+ metadata: ""
+ });
+
+ tokens[newTokenId] = newToken;
+ userTokens[owner].push(newTokenId);
+
+ _safeMint(owner, newTokenId);
+
+ return newTokenId;
+ }
+
+ /**
+ * @dev Проверка валидности токена
+ */
+ function isTokenValid(uint256 tokenId) external view returns (bool) {
+ if (!_exists(tokenId)) return false;
+
+ TokenInfo memory token = tokens[tokenId];
+ return token.isActive && block.timestamp < token.expiryDate;
+ }
+
+ /**
+ * @dev Получение информации о токене
+ */
+ function getTokenInfo(uint256 tokenId) external view tokenExists(tokenId) returns (TokenInfo memory) {
+ return tokens[tokenId];
+ }
+
+ /**
+ * @dev Получение всех токенов пользователя
+ */
+ function getUserTokens(address user) external view returns (uint256[] memory) {
+ return userTokens[user];
+ }
+
+ /**
+ * @dev Получение активных токенов пользователя
+ */
+ function getActiveUserTokens(address user) external view returns (uint256[] memory) {
+ uint256[] memory allTokens = userTokens[user];
+ uint256 activeCount = 0;
+
+ // Подсчитываем активные токены
+ for (uint256 i = 0; i < allTokens.length; i++) {
+ if (tokens[allTokens[i]].isActive && block.timestamp < tokens[allTokens[i]].expiryDate) {
+ activeCount++;
+ }
+ }
+
+ // Создаем массив активных токенов
+ uint256[] memory activeTokens = new uint256[](activeCount);
+ uint256 currentIndex = 0;
+
+ for (uint256 i = 0; i < allTokens.length; i++) {
+ if (tokens[allTokens[i]].isActive && block.timestamp < tokens[allTokens[i]].expiryDate) {
+ activeTokens[currentIndex] = allTokens[i];
+ currentIndex++;
+ }
+ }
+
+ return activeTokens;
+ }
+
+ /**
+ * @dev Проверка, есть ли у пользователя активный токен
+ */
+ function hasActiveToken(address user) external view returns (bool) {
+ uint256[] memory userTokenList = userTokens[user];
+
+ for (uint256 i = 0; i < userTokenList.length; i++) {
+ if (tokens[userTokenList[i]].isActive && block.timestamp < tokens[userTokenList[i]].expiryDate) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * @dev Деактивация токена (только владельцем)
+ */
+ function deactivateToken(uint256 tokenId) external onlyTokenOwner(tokenId) tokenActive(tokenId) {
+ tokens[tokenId].isActive = false;
+ emit TokenDeactivated(tokenId, msg.sender);
+ }
+
+ /**
+ * @dev Продление токена
+ */
+ function renewToken(uint256 tokenId) external payable nonReentrant onlyTokenOwner(tokenId) whenNotPaused {
+ TokenInfo memory token = tokens[tokenId];
+ require(token.isActive, "Token is not active");
+
+ uint256 renewalPrice;
+ uint256 additionalTime;
+
+ if (token.tokenType == TokenType.MONTHLY) {
+ renewalPrice = monthlyPrice;
+ additionalTime = 30 days;
+ } else {
+ renewalPrice = yearlyPrice;
+ additionalTime = 365 days;
+ }
+
+ require(msg.value >= renewalPrice, "Insufficient payment for renewal");
+
+ // Обновляем дату истечения
+ tokens[tokenId].expiryDate += additionalTime;
+
+ // Возвращаем излишки
+ if (msg.value > renewalPrice) {
+ payable(msg.sender).transfer(msg.value - renewalPrice);
+ }
+
+ emit TokenRenewed(tokenId, tokens[tokenId].expiryDate);
+ }
+
+ /**
+ * @dev Обновление цен (только владельцем)
+ */
+ function updatePrices(uint256 newMonthlyPrice, uint256 newYearlyPrice) external onlyOwner {
+ require(newMonthlyPrice > 0 && newYearlyPrice > 0, "Prices must be greater than 0");
+
+ uint256 oldMonthlyPrice = monthlyPrice;
+ uint256 oldYearlyPrice = yearlyPrice;
+
+ monthlyPrice = newMonthlyPrice;
+ yearlyPrice = newYearlyPrice;
+
+ emit PriceUpdated(TokenType.MONTHLY, oldMonthlyPrice, newMonthlyPrice);
+ emit PriceUpdated(TokenType.YEARLY, oldYearlyPrice, newYearlyPrice);
+ }
+
+ /**
+ * @dev Вывод средств (только владельцем)
+ */
+ function withdrawFunds() external onlyOwner {
+ uint256 balance = address(this).balance;
+ require(balance > 0, "No funds to withdraw");
+
+ payable(owner()).transfer(balance);
+ }
+
+ /**
+ * @dev Экстренная пауза контракта
+ */
+ function pause() external onlyOwner {
+ _pause();
+ }
+
+ /**
+ * @dev Снятие паузы
+ */
+ function unpause() external onlyOwner {
+ _unpause();
+ }
+
+ /**
+ * @dev Получение баланса контракта
+ */
+ function getContractBalance() external view returns (uint256) {
+ return address(this).balance;
+ }
+
+ /**
+ * @dev Получение статистики
+ */
+ function getStats() external view returns (uint256 totalTokens, uint256 activeTokens, uint256 monthlyTokens, uint256 yearlyTokens) {
+ totalTokens = _tokenIds.current();
+
+ for (uint256 i = 1; i <= totalTokens; i++) {
+ if (tokens[i].isActive && block.timestamp < tokens[i].expiryDate) {
+ activeTokens++;
+
+ if (tokens[i].tokenType == TokenType.MONTHLY) {
+ monthlyTokens++;
+ } else {
+ yearlyTokens++;
+ }
+ }
+ }
+ }
+
+ /**
+ * @dev Удаление токена из массива пользователя
+ */
+ function _removeTokenFromUser(address user, uint256 tokenId) internal {
+ uint256[] storage tokenList = userTokens[user];
+ for (uint256 i = 0; i < tokenList.length; i++) {
+ if (tokenList[i] == tokenId) {
+ tokenList[i] = tokenList[tokenList.length - 1];
+ tokenList.pop();
+ break;
+ }
+ }
+ }
+
+ /**
+ * @dev Переопределение функции _beforeTokenTransfer для обновления userTokens
+ */
+ function _beforeTokenTransfer(
+ address from,
+ address to,
+ uint256 firstTokenId,
+ uint256 batchSize
+ ) internal virtual override {
+ super._beforeTokenTransfer(from, to, firstTokenId, batchSize);
+
+ // При трансфере обновляем userTokens и владельца токена
+ if (from != address(0) && to != address(0)) {
+ _removeTokenFromUser(from, firstTokenId);
+ userTokens[to].push(firstTokenId);
+ tokens[firstTokenId].owner = to;
+
+ emit TokenTransferred(firstTokenId, from, to);
+ }
+ }
+
+ /**
+ * @dev Получение URI токена
+ */
+ function tokenURI(uint256 tokenId) public view virtual override tokenExists(tokenId) returns (string memory) {
+ TokenInfo memory token = tokens[tokenId];
+
+ // Создаем JSON метаданные
+ string memory json = string(abi.encodePacked(
+ '{"name": "SecureBit Access Token #', _toString(tokenId), '",',
+ '"description": "Access token for SecureBit service",',
+ '"attributes": [',
+ '{"trait_type": "Type", "value": "', _tokenTypeToString(token.tokenType), '"},',
+ '{"trait_type": "Expiry Date", "value": "', _toString(token.expiryDate), '"},',
+ '{"trait_type": "Status", "value": "', token.isActive ? "Active" : "Inactive", '"},',
+ '{"trait_type": "Created At", "value": "', _toString(token.createdAt), '"}',
+ ']}'
+ ));
+
+ return string(abi.encodePacked('data:application/json;base64,', _base64Encode(bytes(json))));
+ }
+
+ /**
+ * @dev Конвертация числа в строку
+ */
+ function _toString(uint256 value) internal pure returns (string memory) {
+ if (value == 0) return "0";
+
+ uint256 temp = value;
+ uint256 digits;
+
+ while (temp != 0) {
+ digits++;
+ temp /= 10;
+ }
+
+ bytes memory buffer = new bytes(digits);
+
+ while (value != 0) {
+ digits -= 1;
+ buffer[digits] = bytes1(uint8(48 + uint256(value % 10)));
+ value /= 10;
+ }
+
+ return string(buffer);
+ }
+
+ /**
+ * @dev Конвертация типа токена в строку
+ */
+ function _tokenTypeToString(TokenType tokenType) internal pure returns (string memory) {
+ if (tokenType == TokenType.MONTHLY) return "Monthly";
+ if (tokenType == TokenType.YEARLY) return "Yearly";
+ return "Unknown";
+ }
+
+ /**
+ * @dev Base64 кодирование (исправленная версия)
+ */
+ function _base64Encode(bytes memory data) internal pure returns (string memory) {
+ if (data.length == 0) return "";
+
+ string memory table = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+ uint256 len = data.length;
+ uint256 encodedLen = 4 * ((len + 2) / 3);
+
+ bytes memory result = new bytes(encodedLen);
+
+ uint256 i = 0;
+ uint256 j = 0;
+
+ while (i < len) {
+ uint256 a = i < len ? uint8(data[i++]) : 0;
+ uint256 b = i < len ? uint8(data[i++]) : 0;
+ uint256 c = i < len ? uint8(data[i++]) : 0;
+
+ uint256 triple = (a << 16) + (b << 8) + c;
+
+ result[j++] = bytes1(uint8(bytes(table)[(triple >> 18) & 63]));
+ result[j++] = bytes1(uint8(bytes(table)[(triple >> 12) & 63]));
+ result[j++] = bytes1(uint8(bytes(table)[(triple >> 6) & 63]));
+ result[j++] = bytes1(uint8(bytes(table)[triple & 63]));
+ }
+
+ // Обработка padding
+ uint256 paddingCount = (3 - (len % 3)) % 3;
+ if (paddingCount > 0) {
+ for (uint256 k = encodedLen - paddingCount; k < encodedLen; k++) {
+ result[k] = "=";
+ }
+ }
+
+ return string(result);
+ }
+}
\ No newline at end of file
diff --git a/src/token-auth/TokenAuthManager.js b/src/token-auth/TokenAuthManager.js
new file mode 100644
index 0000000..6514040
--- /dev/null
+++ b/src/token-auth/TokenAuthManager.js
@@ -0,0 +1,508 @@
+// ============================================
+// TOKEN AUTHENTICATION MANAGER
+// ============================================
+// Система авторизации через ERC-20/ERC-721 токены
+// Поддерживает MetaMask и другие Web3 кошельки
+// ============================================
+
+class TokenAuthManager {
+ constructor() {
+ this.currentSession = null;
+ this.walletAddress = null;
+ this.tokenContract = null;
+ this.isInitialized = false;
+ this.sessionTimeout = null;
+ this.heartbeatInterval = null;
+
+ // Константы
+ this.TOKEN_TYPES = {
+ MONTHLY: 'monthly',
+ YEARLY: 'yearly'
+ };
+
+ this.SESSION_TIMEOUT = 30 * 60 * 1000; // 30 минут
+ this.HEARTBEAT_INTERVAL = 5 * 60 * 1000; // 5 минут
+
+ // События
+ this.events = {
+ onLogin: null,
+ onLogout: null,
+ onTokenExpired: null,
+ onSessionExpired: null,
+ onWalletConnected: null,
+ onWalletDisconnected: null
+ };
+
+ this.initialize();
+ }
+
+ // Инициализация системы
+ async initialize() {
+ try {
+ // Проверяем поддержку Web3
+ if (typeof window.ethereum !== 'undefined') {
+ console.log('✅ Web3 detected');
+ await this.setupWeb3();
+ } else {
+ console.warn('⚠️ Web3 not detected, MetaMask required');
+ this.showWeb3RequiredMessage();
+ }
+
+ // Проверяем существующие сессии
+ await this.checkExistingSession();
+
+ this.isInitialized = true;
+ console.log('✅ TokenAuthManager initialized');
+
+ } catch (error) {
+ console.error('❌ TokenAuthManager initialization failed:', error);
+ throw error;
+ }
+ }
+
+ // Настройка Web3 соединения
+ async setupWeb3() {
+ try {
+ // Запрашиваем доступ к аккаунтам
+ const accounts = await window.ethereum.request({
+ method: 'eth_requestAccounts'
+ });
+
+ if (accounts.length > 0) {
+ this.walletAddress = accounts[0];
+ console.log('🔗 Wallet connected:', this.walletAddress);
+
+ // Подписываемся на изменения аккаунтов
+ window.ethereum.on('accountsChanged', (accounts) => {
+ this.handleAccountChange(accounts);
+ });
+
+ // Подписываемся на изменения сети
+ window.ethereum.on('chainChanged', (chainId) => {
+ this.handleChainChange(chainId);
+ });
+
+ this.triggerEvent('onWalletConnected', this.walletAddress);
+
+ } else {
+ throw new Error('No accounts found');
+ }
+
+ } catch (error) {
+ console.error('❌ Web3 setup failed:', error);
+ throw error;
+ }
+ }
+
+ // Проверка существующей сессии
+ async checkExistingSession() {
+ try {
+ const sessionData = localStorage.getItem('securebit_token_session');
+ if (sessionData) {
+ const session = JSON.parse(sessionData);
+
+ // Проверяем валидность сессии
+ if (this.isSessionValid(session)) {
+ this.currentSession = session;
+ console.log('✅ Existing session restored');
+
+ // Запускаем мониторинг сессии
+ this.startSessionMonitoring();
+
+ return true;
+ } else {
+ // Удаляем невалидную сессию
+ localStorage.removeItem('securebit_token_session');
+ console.log('🗑️ Invalid session removed');
+ }
+ }
+
+ return false;
+
+ } catch (error) {
+ console.error('❌ Session check failed:', error);
+ return false;
+ }
+ }
+
+ // Проверка валидности сессии
+ isSessionValid(session) {
+ if (!session || !session.tokenId || !session.expiresAt) {
+ return false;
+ }
+
+ const now = Date.now();
+ const expiresAt = new Date(session.expiresAt).getTime();
+
+ return now < expiresAt;
+ }
+
+ // Авторизация через токен
+ async authenticateWithToken(tokenId, tokenType) {
+ try {
+ if (!this.walletAddress) {
+ throw new Error('Wallet not connected');
+ }
+
+ console.log('🔐 Authenticating with token:', { tokenId, tokenType, wallet: this.walletAddress });
+
+ // Проверяем токен в смарт-контракте
+ const tokenValid = await this.validateTokenInContract(tokenId, tokenType);
+
+ if (!tokenValid) {
+ throw new Error('Invalid or expired token');
+ }
+
+ // Создаем новую сессию
+ const session = await this.createSession(tokenId, tokenType);
+
+ // Завершаем старые сессии на других устройствах
+ await this.terminateOtherSessions(tokenId);
+
+ // Сохраняем сессию
+ this.currentSession = session;
+ localStorage.setItem('securebit_token_session', JSON.stringify(session));
+
+ // Запускаем мониторинг
+ this.startSessionMonitoring();
+
+ console.log('✅ Authentication successful');
+ this.triggerEvent('onLogin', session);
+
+ return session;
+
+ } catch (error) {
+ console.error('❌ Authentication failed:', error);
+ throw error;
+ }
+ }
+
+ // Проверка токена в смарт-контракте
+ async validateTokenInContract(tokenId, tokenType) {
+ try {
+ // Здесь будет логика проверки токена через Web3
+ // Пока используем заглушку для тестирования
+ console.log('🔍 Validating token in contract:', { tokenId, tokenType });
+
+ // Имитация проверки токена
+ const isValid = await this.mockTokenValidation(tokenId, tokenType);
+
+ return isValid;
+
+ } catch (error) {
+ console.error('❌ Token validation failed:', error);
+ return false;
+ }
+ }
+
+ // Заглушка для тестирования валидации токена
+ async mockTokenValidation(tokenId, tokenType) {
+ // Имитируем задержку сети
+ await new Promise(resolve => setTimeout(resolve, 1000));
+
+ // Простая проверка для демонстрации
+ const tokenHash = this.hashString(tokenId + tokenType + this.walletAddress);
+ const isValid = tokenHash % 10 !== 0; // 90% токенов валидны
+
+ console.log('🔍 Mock token validation result:', { tokenId, tokenType, isValid });
+
+ return isValid;
+ }
+
+ // Создание новой сессии
+ async createSession(tokenId, tokenType) {
+ const now = Date.now();
+ const expiresAt = this.calculateTokenExpiry(tokenType);
+
+ const session = {
+ id: this.generateSessionId(),
+ tokenId: tokenId,
+ tokenType: tokenType,
+ walletAddress: this.walletAddress,
+ createdAt: now,
+ expiresAt: expiresAt,
+ lastActivity: now,
+ signature: await this.signSessionData(tokenId, tokenType)
+ };
+
+ console.log('📝 Session created:', session);
+ return session;
+ }
+
+ // Расчет времени истечения токена
+ calculateTokenExpiry(tokenType) {
+ const now = Date.now();
+
+ switch (tokenType) {
+ case this.TOKEN_TYPES.MONTHLY:
+ return now + (30 * 24 * 60 * 60 * 1000); // 30 дней
+ case this.TOKEN_TYPES.YEARLY:
+ return now + (365 * 24 * 60 * 60 * 1000); // 365 дней
+ default:
+ throw new Error('Invalid token type');
+ }
+ }
+
+ // Генерация ID сессии
+ generateSessionId() {
+ return 'session_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
+ }
+
+ // Подпись данных сессии
+ async signSessionData(tokenId, tokenType) {
+ try {
+ const message = `SecureBit Token Auth\nToken: ${tokenId}\nType: ${tokenType}\nWallet: ${this.walletAddress}\nTimestamp: ${Date.now()}`;
+
+ const signature = await window.ethereum.request({
+ method: 'personal_sign',
+ params: [message, this.walletAddress]
+ });
+
+ return signature;
+
+ } catch (error) {
+ console.error('❌ Session signing failed:', error);
+ throw error;
+ }
+ }
+
+ // Завершение сессий на других устройствах
+ async terminateOtherSessions(tokenId) {
+ try {
+ // Отправляем сигнал о завершении через WebRTC или WebSocket
+ // Пока используем заглушку
+ console.log('🔄 Terminating other sessions for token:', tokenId);
+
+ // Здесь будет логика уведомления других устройств
+ // о необходимости завершения сессии
+
+ } catch (error) {
+ console.error('❌ Session termination failed:', error);
+ }
+ }
+
+ // Запуск мониторинга сессии
+ startSessionMonitoring() {
+ if (this.sessionTimeout) {
+ clearTimeout(this.sessionTimeout);
+ }
+
+ if (this.heartbeatInterval) {
+ clearInterval(this.heartbeatInterval);
+ }
+
+ // Таймер истечения сессии
+ const timeUntilExpiry = this.currentSession.expiresAt - Date.now();
+ this.sessionTimeout = setTimeout(() => {
+ this.handleSessionExpired();
+ }, timeUntilExpiry);
+
+ // Периодическая проверка активности
+ this.heartbeatInterval = setInterval(() => {
+ this.updateSessionActivity();
+ }, this.HEARTBEAT_INTERVAL);
+
+ console.log('⏰ Session monitoring started');
+ }
+
+ // Обработка истечения сессии
+ handleSessionExpired() {
+ console.log('⏰ Session expired');
+
+ this.currentSession = null;
+ localStorage.removeItem('securebit_token_session');
+
+ this.triggerEvent('onSessionExpired');
+ this.triggerEvent('onLogout');
+
+ // Показываем уведомление пользователю
+ this.showSessionExpiredMessage();
+ }
+
+ // Обновление активности сессии
+ updateSessionActivity() {
+ if (this.currentSession) {
+ this.currentSession.lastActivity = Date.now();
+ localStorage.setItem('securebit_token_session', JSON.stringify(this.currentSession));
+ }
+ }
+
+ // Выход из системы
+ async logout() {
+ try {
+ console.log('🚪 Logging out');
+
+ if (this.currentSession) {
+ // Завершаем сессию
+ await this.terminateOtherSessions(this.currentSession.tokenId);
+
+ this.currentSession = null;
+ localStorage.removeItem('securebit_token_session');
+ }
+
+ // Очищаем таймеры
+ if (this.sessionTimeout) {
+ clearTimeout(this.sessionTimeout);
+ this.sessionTimeout = null;
+ }
+
+ if (this.heartbeatInterval) {
+ clearInterval(this.heartbeatInterval);
+ this.heartbeatInterval = null;
+ }
+
+ this.triggerEvent('onLogout');
+ console.log('✅ Logout successful');
+
+ } catch (error) {
+ console.error('❌ Logout failed:', error);
+ }
+ }
+
+ // Обработка смены аккаунта
+ async handleAccountChange(accounts) {
+ console.log('🔄 Account changed:', accounts);
+
+ if (accounts.length === 0) {
+ // Пользователь отключил кошелек
+ await this.logout();
+ this.walletAddress = null;
+ this.triggerEvent('onWalletDisconnected');
+ } else {
+ // Пользователь сменил аккаунт
+ const newAddress = accounts[0];
+ if (newAddress !== this.walletAddress) {
+ this.walletAddress = newAddress;
+ await this.logout(); // Завершаем старую сессию
+ this.triggerEvent('onWalletConnected', newAddress);
+ }
+ }
+ }
+
+ // Обработка смены сети
+ async handleChainChange(chainId) {
+ console.log('🔄 Chain changed:', chainId);
+
+ // Проверяем, поддерживается ли новая сеть
+ const supportedChains = ['0x1', '0x3', '0x5']; // Mainnet, Ropsten, Goerli
+
+ if (!supportedChains.includes(chainId)) {
+ console.warn('⚠️ Unsupported network:', chainId);
+ // Показываем предупреждение пользователю
+ this.showUnsupportedNetworkMessage(chainId);
+ }
+ }
+
+ // Проверка статуса авторизации
+ isAuthenticated() {
+ return this.currentSession !== null && this.isSessionValid(this.currentSession);
+ }
+
+ // Получение текущей сессии
+ getCurrentSession() {
+ return this.currentSession;
+ }
+
+ // Получение информации о токене
+ getTokenInfo() {
+ if (!this.currentSession) {
+ return null;
+ }
+
+ const now = Date.now();
+ const expiresAt = this.currentSession.expiresAt;
+ const timeLeft = expiresAt - now;
+
+ return {
+ tokenId: this.currentSession.tokenId,
+ tokenType: this.currentSession.tokenType,
+ expiresAt: expiresAt,
+ timeLeft: timeLeft,
+ isExpired: timeLeft <= 0,
+ formattedTimeLeft: this.formatTimeLeft(timeLeft)
+ };
+ }
+
+ // Форматирование оставшегося времени
+ formatTimeLeft(timeLeft) {
+ if (timeLeft <= 0) {
+ return 'Expired';
+ }
+
+ const days = Math.floor(timeLeft / (24 * 60 * 60 * 1000));
+ const hours = Math.floor((timeLeft % (24 * 60 * 60 * 1000)) / (60 * 60 * 1000));
+ const minutes = Math.floor((timeLeft % (60 * 60 * 1000)) / (60 * 1000));
+
+ if (days > 0) {
+ return `${days}d ${hours}h`;
+ } else if (hours > 0) {
+ return `${hours}h ${minutes}m`;
+ } else {
+ return `${minutes}m`;
+ }
+ }
+
+ // Установка обработчиков событий
+ on(event, callback) {
+ if (this.events.hasOwnProperty(event)) {
+ this.events[event] = callback;
+ }
+ }
+
+ // Вызов событий
+ triggerEvent(event, data) {
+ if (this.events[event] && typeof this.events[event] === 'function') {
+ this.events[event](data);
+ }
+ }
+
+ // Утилиты
+ hashString(str) {
+ let hash = 0;
+ for (let i = 0; i < str.length; i++) {
+ const char = str.charCodeAt(i);
+ hash = ((hash << 5) - hash) + char;
+ hash = hash & hash; // Convert to 32bit integer
+ }
+ return Math.abs(hash);
+ }
+
+ // Показ сообщений пользователю
+ showWeb3RequiredMessage() {
+ // Показываем сообщение о необходимости Web3
+ const message = 'Web3 wallet (MetaMask) required for authentication';
+ console.warn('⚠️', message);
+ // Здесь можно добавить UI уведомление
+ }
+
+ showSessionExpiredMessage() {
+ const message = 'Your session has expired. Please authenticate again.';
+ console.warn('⏰', message);
+ // Здесь можно добавить UI уведомление
+ }
+
+ showUnsupportedNetworkMessage(chainId) {
+ const message = `Unsupported network detected: ${chainId}. Please switch to a supported network.`;
+ console.warn('⚠️', message);
+ // Здесь можно добавить UI уведомление
+ }
+
+ // Очистка ресурсов
+ destroy() {
+ if (this.sessionTimeout) {
+ clearTimeout(this.sessionTimeout);
+ }
+
+ if (this.heartbeatInterval) {
+ clearInterval(this.heartbeatInterval);
+ }
+
+ this.currentSession = null;
+ this.walletAddress = null;
+ this.isInitialized = false;
+
+ console.log('🗑️ TokenAuthManager destroyed');
+ }
+}
+
+export { TokenAuthManager };
diff --git a/src/token-auth/Web3ContractManager.js b/src/token-auth/Web3ContractManager.js
new file mode 100644
index 0000000..dea116e
--- /dev/null
+++ b/src/token-auth/Web3ContractManager.js
@@ -0,0 +1,621 @@
+// ============================================
+// WEB3 CONTRACT MANAGER
+// ============================================
+// Управление смарт-контрактом токенов доступа
+// Интеграция с MetaMask и другими Web3 провайдерами
+// ============================================
+
+class Web3ContractManager {
+ constructor() {
+ this.contract = null;
+ this.web3 = null;
+ this.contractAddress = null;
+ this.contractABI = null;
+ this.isInitialized = false;
+
+ // Адреса контрактов для разных сетей
+ this.CONTRACT_ADDRESSES = {
+ // Mainnet
+ '0x1': '0x0000000000000000000000000000000000000000', // Заменить на реальный адрес
+ // Ropsten (тестовая сеть)
+ '0x3': '0x0000000000000000000000000000000000000000', // Заменить на реальный адрес
+ // Goerli (тестовая сеть)
+ '0x5': '0x0000000000000000000000000000000000000000', // Заменить на реальный адрес
+ // Sepolia (тестовая сеть)
+ '0xaa36a7': '0x0000000000000000000000000000000000000000' // Заменить на реальный адрес
+ };
+
+ // ABI контракта (упрощенная версия)
+ this.CONTRACT_ABI = [
+ // События
+ {
+ "anonymous": false,
+ "inputs": [
+ {"indexed": true, "name": "tokenId", "type": "uint256"},
+ {"indexed": true, "name": "owner", "type": "address"},
+ {"indexed": false, "name": "tokenType", "type": "uint8"},
+ {"indexed": false, "name": "expiryDate", "type": "uint256"}
+ ],
+ "name": "TokenMinted",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {"indexed": true, "name": "tokenId", "type": "uint256"},
+ {"indexed": true, "name": "owner", "type": "address"}
+ ],
+ "name": "TokenExpired",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {"indexed": true, "name": "tokenId", "type": "uint256"},
+ {"indexed": false, "name": "newExpiryDate", "type": "uint256"}
+ ],
+ "name": "TokenRenewed",
+ "type": "event"
+ },
+
+ // Функции чтения
+ {
+ "inputs": [{"name": "tokenId", "type": "uint256"}],
+ "name": "isTokenValid",
+ "outputs": [{"name": "", "type": "bool"}],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [{"name": "tokenId", "type": "uint256"}],
+ "name": "getTokenInfo",
+ "outputs": [
+ {"name": "tokenId", "type": "uint256"},
+ {"name": "owner", "type": "address"},
+ {"name": "expiryDate", "type": "uint256"},
+ {"name": "tokenType", "type": "uint8"},
+ {"name": "isActive", "type": "bool"},
+ {"name": "createdAt", "type": "uint256"},
+ {"name": "metadata", "type": "string"}
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [{"name": "user", "type": "address"}],
+ "name": "getUserTokens",
+ "outputs": [{"name": "", "type": "uint256[]"}],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [{"name": "user", "type": "address"}],
+ "name": "getActiveUserTokens",
+ "outputs": [{"name": "", "type": "uint256[]"}],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [{"name": "user", "type": "address"}],
+ "name": "hasActiveToken",
+ "outputs": [{"name": "", "type": "bool"}],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "monthlyPrice",
+ "outputs": [{"name": "", "type": "uint256"}],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "yearlyPrice",
+ "outputs": [{"name": "", "type": "uint256"}],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "getStats",
+ "outputs": [
+ {"name": "totalTokens", "type": "uint256"},
+ {"name": "activeTokens", "type": "uint256"},
+ {"name": "monthlyTokens", "type": "uint256"},
+ {"name": "yearlyTokens", "type": "uint256"}
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+
+ // Функции записи
+ {
+ "inputs": [],
+ "name": "purchaseMonthlyToken",
+ "outputs": [],
+ "stateMutability": "payable",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "purchaseYearlyToken",
+ "outputs": [],
+ "stateMutability": "payable",
+ "type": "function"
+ },
+ {
+ "inputs": [{"name": "tokenId", "type": "uint256"}],
+ "name": "deactivateToken",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [{"name": "tokenId", "type": "uint256"}],
+ "name": "renewToken",
+ "outputs": [],
+ "stateMutability": "payable",
+ "type": "function"
+ }
+ ];
+
+ this.initialize();
+ }
+
+ // Инициализация Web3 и контракта
+ async initialize() {
+ try {
+ // Проверяем поддержку Web3
+ if (typeof window.ethereum !== 'undefined') {
+ console.log('✅ Web3 detected');
+
+ // Создаем Web3 экземпляр
+ this.web3 = new Web3(window.ethereum);
+
+ // Получаем текущую сеть
+ const chainId = await this.getCurrentChainId();
+ console.log('🔗 Current chain ID:', chainId);
+
+ // Получаем адрес контракта для текущей сети
+ this.contractAddress = this.CONTRACT_ADDRESSES[chainId];
+
+ if (!this.contractAddress || this.contractAddress === '0x0000000000000000000000000000000000000000') {
+ console.warn('⚠️ Contract not deployed on current network:', chainId);
+ this.showContractNotDeployedMessage(chainId);
+ return;
+ }
+
+ // Создаем экземпляр контракта
+ this.contract = new this.web3.eth.Contract(
+ this.CONTRACT_ABI,
+ this.contractAddress
+ );
+
+ console.log('📋 Contract initialized:', this.contractAddress);
+ this.isInitialized = true;
+
+ } else {
+ throw new Error('Web3 not detected');
+ }
+
+ } catch (error) {
+ console.error('❌ Web3ContractManager initialization failed:', error);
+ throw error;
+ }
+ }
+
+ // Получение текущего Chain ID
+ async getCurrentChainId() {
+ try {
+ const chainId = await window.ethereum.request({ method: 'eth_chainId' });
+ return chainId;
+ } catch (error) {
+ console.error('❌ Failed to get chain ID:', error);
+ throw error;
+ }
+ }
+
+ // Проверка валидности токена
+ async isTokenValid(tokenId) {
+ try {
+ if (!this.isInitialized || !this.contract) {
+ throw new Error('Contract not initialized');
+ }
+
+ const result = await this.contract.methods.isTokenValid(tokenId).call();
+ console.log('🔍 Token validation result:', { tokenId, isValid: result });
+
+ return result;
+
+ } catch (error) {
+ console.error('❌ Token validation failed:', error);
+ return false;
+ }
+ }
+
+ // Получение информации о токене
+ async getTokenInfo(tokenId) {
+ try {
+ if (!this.isInitialized || !this.contract) {
+ throw new Error('Contract not initialized');
+ }
+
+ const result = await this.contract.methods.getTokenInfo(tokenId).call();
+
+ // Преобразуем результат в удобный формат
+ const tokenInfo = {
+ tokenId: result.tokenId,
+ owner: result.owner,
+ expiryDate: parseInt(result.expiryDate),
+ tokenType: parseInt(result.tokenType),
+ isActive: result.isActive,
+ createdAt: parseInt(result.createdAt),
+ metadata: result.metadata
+ };
+
+ console.log('📋 Token info retrieved:', tokenInfo);
+ return tokenInfo;
+
+ } catch (error) {
+ console.error('❌ Failed to get token info:', error);
+ throw error;
+ }
+ }
+
+ // Получение токенов пользователя
+ async getUserTokens(userAddress) {
+ try {
+ if (!this.isInitialized || !this.contract) {
+ throw new Error('Contract not initialized');
+ }
+
+ const result = await this.contract.methods.getUserTokens(userAddress).call();
+ const tokenIds = result.map(id => parseInt(id));
+
+ console.log('👤 User tokens retrieved:', { user: userAddress, tokens: tokenIds });
+ return tokenIds;
+
+ } catch (error) {
+ console.error('❌ Failed to get user tokens:', error);
+ throw error;
+ }
+ }
+
+ // Получение активных токенов пользователя
+ async getActiveUserTokens(userAddress) {
+ try {
+ if (!this.isInitialized || !this.contract) {
+ throw new Error('Contract not initialized');
+ }
+
+ const result = await this.contract.methods.getActiveUserTokens(userAddress).call();
+ const tokenIds = result.map(id => parseInt(id));
+
+ console.log('✅ Active user tokens retrieved:', { user: userAddress, activeTokens: tokenIds });
+ return tokenIds;
+
+ } catch (error) {
+ console.error('❌ Failed to get active user tokens:', error);
+ throw error;
+ }
+ }
+
+ // Проверка наличия активного токена у пользователя
+ async hasActiveToken(userAddress) {
+ try {
+ if (!this.isInitialized || !this.contract) {
+ throw new Error('Contract not initialized');
+ }
+
+ const result = await this.contract.methods.hasActiveToken(userAddress).call();
+ console.log('🔍 Active token check:', { user: userAddress, hasActive: result });
+
+ return result;
+
+ } catch (error) {
+ console.error('❌ Failed to check active token:', error);
+ return false;
+ }
+ }
+
+ // Получение цен токенов
+ async getTokenPrices() {
+ try {
+ if (!this.isInitialized || !this.contract) {
+ throw new Error('Contract not initialized');
+ }
+
+ const [monthlyPrice, yearlyPrice] = await Promise.all([
+ this.contract.methods.monthlyPrice().call(),
+ this.contract.methods.yearlyPrice().call()
+ ]);
+
+ const prices = {
+ monthly: this.web3.utils.fromWei(monthlyPrice, 'ether'),
+ yearly: this.web3.utils.fromWei(yearlyPrice, 'ether'),
+ monthlyWei: monthlyPrice,
+ yearlyWei: yearlyPrice
+ };
+
+ console.log('💰 Token prices retrieved:', prices);
+ return prices;
+
+ } catch (error) {
+ console.error('❌ Failed to get token prices:', error);
+ throw error;
+ }
+ }
+
+ // Получение статистики контракта
+ async getContractStats() {
+ try {
+ if (!this.isInitialized || !this.contract) {
+ throw new Error('Contract not initialized');
+ }
+
+ const result = await this.contract.methods.getStats().call();
+
+ const stats = {
+ totalTokens: parseInt(result.totalTokens),
+ activeTokens: parseInt(result.activeTokens),
+ monthlyTokens: parseInt(result.monthlyTokens),
+ yearlyTokens: parseInt(result.yearlyTokens)
+ };
+
+ console.log('📊 Contract stats retrieved:', stats);
+ return stats;
+
+ } catch (error) {
+ console.error('❌ Failed to get contract stats:', error);
+ throw error;
+ }
+ }
+
+ // Покупка месячного токена
+ async purchaseMonthlyToken(price) {
+ try {
+ if (!this.isInitialized || !this.contract) {
+ throw new Error('Contract not initialized');
+ }
+
+ const accounts = await this.web3.eth.getAccounts();
+ const userAddress = accounts[0];
+
+ console.log('🛒 Purchasing monthly token:', { user: userAddress, price: price });
+
+ const result = await this.contract.methods.purchaseMonthlyToken().send({
+ from: userAddress,
+ value: price
+ });
+
+ console.log('✅ Monthly token purchased:', result);
+ return result;
+
+ } catch (error) {
+ console.error('❌ Monthly token purchase failed:', error);
+ throw error;
+ }
+ }
+
+ // Покупка годового токена
+ async purchaseYearlyToken(price) {
+ try {
+ if (!this.isInitialized || !this.contract) {
+ throw new Error('Contract not initialized');
+ }
+
+ const accounts = await this.web3.eth.getAccounts();
+ const userAddress = accounts[0];
+
+ console.log('🛒 Purchasing yearly token:', { user: userAddress, price: price });
+
+ const result = await this.contract.methods.purchaseYearlyToken().send({
+ from: userAddress,
+ value: price
+ });
+
+ console.log('✅ Yearly token purchased:', result);
+ return result;
+
+ } catch (error) {
+ console.error('❌ Yearly token purchase failed:', error);
+ throw error;
+ }
+ }
+
+ // Деактивация токена
+ async deactivateToken(tokenId) {
+ try {
+ if (!this.isInitialized || !this.contract) {
+ throw new Error('Contract not initialized');
+ }
+
+ const accounts = await this.web3.eth.getAccounts();
+ const userAddress = accounts[0];
+
+ console.log('🚫 Deactivating token:', { tokenId, user: userAddress });
+
+ const result = await this.contract.methods.deactivateToken(tokenId).send({
+ from: userAddress
+ });
+
+ console.log('✅ Token deactivated:', result);
+ return result;
+
+ } catch (error) {
+ console.error('❌ Token deactivation failed:', error);
+ throw error;
+ }
+ }
+
+ // Продление токена
+ async renewToken(tokenId, price) {
+ try {
+ if (!this.isInitialized || !this.contract) {
+ throw new Error('Contract not initialized');
+ }
+
+ const accounts = await this.web3.eth.getAccounts();
+ const userAddress = accounts[0];
+
+ console.log('🔄 Renewing token:', { tokenId, user: userAddress, price: price });
+
+ const result = await this.contract.methods.renewToken(tokenId).send({
+ from: userAddress,
+ value: price
+ });
+
+ console.log('✅ Token renewed:', result);
+ return result;
+
+ } catch (error) {
+ console.error('❌ Token renewal failed:', error);
+ throw error;
+ }
+ }
+
+ // Получение событий о создании токенов
+ async getTokenMintedEvents(fromBlock = 0, toBlock = 'latest') {
+ try {
+ if (!this.isInitialized || !this.contract) {
+ throw new Error('Contract not initialized');
+ }
+
+ const events = await this.contract.getPastEvents('TokenMinted', {
+ fromBlock: fromBlock,
+ toBlock: toBlock
+ });
+
+ console.log('📝 Token minted events retrieved:', events.length);
+ return events;
+
+ } catch (error) {
+ console.error('❌ Failed to get token minted events:', error);
+ throw error;
+ }
+ }
+
+ // Получение событий о продлении токенов
+ async getTokenRenewedEvents(fromBlock = 0, toBlock = 'latest') {
+ try {
+ if (!this.isInitialized || !this.contract) {
+ throw new Error('Contract not initialized');
+ }
+
+ const events = await this.contract.getPastEvents('TokenRenewed', {
+ fromBlock: fromBlock,
+ toBlock: toBlock
+ });
+
+ console.log('🔄 Token renewed events retrieved:', events.length);
+ return events;
+
+ } catch (error) {
+ console.error('❌ Failed to get token renewed events:', error);
+ throw error;
+ }
+ }
+
+ // Проверка поддержки сети
+ isNetworkSupported(chainId) {
+ return this.CONTRACT_ADDRESSES.hasOwnProperty(chainId);
+ }
+
+ // Получение поддерживаемых сетей
+ getSupportedNetworks() {
+ return Object.keys(this.CONTRACT_ADDRESSES).map(chainId => ({
+ chainId: chainId,
+ name: this.getNetworkName(chainId),
+ contractAddress: this.CONTRACT_ADDRESSES[chainId]
+ }));
+ }
+
+ // Получение названия сети
+ getNetworkName(chainId) {
+ const networkNames = {
+ '0x1': 'Ethereum Mainnet',
+ '0x3': 'Ropsten Testnet',
+ '0x5': 'Goerli Testnet',
+ '0xaa36a7': 'Sepolia Testnet'
+ };
+
+ return networkNames[chainId] || 'Unknown Network';
+ }
+
+ // Переключение на поддерживаемую сеть
+ async switchToNetwork(chainId) {
+ try {
+ if (!this.isNetworkSupported(chainId)) {
+ throw new Error(`Network ${chainId} is not supported`);
+ }
+
+ await window.ethereum.request({
+ method: 'wallet_switchEthereumChain',
+ params: [{ chainId: chainId }]
+ });
+
+ console.log('🔄 Switched to network:', chainId);
+
+ } catch (error) {
+ console.error('❌ Failed to switch network:', error);
+ throw error;
+ }
+ }
+
+ // Добавление новой сети
+ async addNetwork(chainId, networkName, rpcUrl, blockExplorerUrl) {
+ try {
+ await window.ethereum.request({
+ method: 'wallet_addEthereumChain',
+ params: [{
+ chainId: chainId,
+ chainName: networkName,
+ rpcUrls: [rpcUrl],
+ blockExplorerUrls: [blockExplorerUrl],
+ nativeCurrency: {
+ name: 'Ether',
+ symbol: 'ETH',
+ decimals: 18
+ }
+ }]
+ });
+
+ console.log('➕ Network added:', { chainId, name: networkName });
+
+ } catch (error) {
+ console.error('❌ Failed to add network:', error);
+ throw error;
+ }
+ }
+
+ // Показ сообщений пользователю
+ showContractNotDeployedMessage(chainId) {
+ const message = `Smart contract not deployed on network ${chainId}. Please switch to a supported network or deploy the contract.`;
+ console.warn('⚠️', message);
+ // Здесь можно добавить UI уведомление
+ }
+
+ // Получение статуса инициализации
+ getInitializationStatus() {
+ return {
+ isInitialized: this.isInitialized,
+ web3: !!this.web3,
+ contract: !!this.contract,
+ contractAddress: this.contractAddress,
+ currentChainId: this.web3 ? this.web3.currentProvider.chainId : null
+ };
+ }
+
+ // Очистка ресурсов
+ destroy() {
+ this.contract = null;
+ this.web3 = null;
+ this.contractAddress = null;
+ this.isInitialized = false;
+
+ console.log('🗑️ Web3ContractManager destroyed');
+ }
+}
+
+export { Web3ContractManager };
diff --git a/src/token-auth/config.js b/src/token-auth/config.js
new file mode 100644
index 0000000..9ae259c
--- /dev/null
+++ b/src/token-auth/config.js
@@ -0,0 +1,276 @@
+// ============================================
+// TOKEN AUTHENTICATION CONFIGURATION
+// ============================================
+// Конфигурация модуля токен-авторизации
+// Настройки для разных сетей и окружений
+// ============================================
+
+export const TOKEN_AUTH_CONFIG = {
+ // Основные настройки
+ APP_NAME: 'SecureBit',
+ APP_VERSION: '4.01.441',
+
+ // Настройки Web3
+ WEB3: {
+ // Поддерживаемые сети
+ SUPPORTED_NETWORKS: {
+ // Mainnet
+ '0x1': {
+ name: 'Ethereum Mainnet',
+ chainId: '0x1',
+ rpcUrl: 'https://mainnet.infura.io/v3/YOUR_INFURA_KEY',
+ blockExplorer: 'https://etherscan.io',
+ currency: {
+ name: 'Ether',
+ symbol: 'ETH',
+ decimals: 18
+ },
+ isTestnet: false
+ },
+
+ // Goerli (тестовая сеть)
+ '0x5': {
+ name: 'Goerli Testnet',
+ chainId: '0x5',
+ rpcUrl: 'https://goerli.infura.io/v3/YOUR_INFURA_KEY',
+ blockExplorer: 'https://goerli.etherscan.io',
+ currency: {
+ name: 'Goerli Ether',
+ symbol: 'ETH',
+ decimals: 18
+ },
+ isTestnet: true
+ },
+
+ // Sepolia (тестовая сеть)
+ '0xaa36a7': {
+ name: 'Sepolia Testnet',
+ chainId: '0xaa36a7',
+ rpcUrl: 'https://sepolia.infura.io/v3/YOUR_INFURA_KEY',
+ blockExplorer: 'https://sepolia.etherscan.io',
+ currency: {
+ name: 'Sepolia Ether',
+ symbol: 'ETH',
+ decimals: 18
+ },
+ isTestnet: true
+ }
+ },
+
+ // Настройки по умолчанию
+ DEFAULT_NETWORK: '0x5', // Goerli для тестирования
+ AUTO_SWITCH_NETWORK: true,
+ REQUEST_PERMISSIONS: ['eth_accounts', 'eth_requestAccounts']
+ },
+
+ // Настройки смарт-контракта
+ CONTRACT: {
+ // Адреса контрактов для разных сетей
+ ADDRESSES: {
+ '0x1': '0x0000000000000000000000000000000000000000', // Заменить на реальный адрес
+ '0x5': '0x0000000000000000000000000000000000000000', // Заменить на реальный адрес
+ '0xaa36a7': '0x0000000000000000000000000000000000000000' // Заменить на реальный адрес
+ },
+
+ // Настройки токенов
+ TOKENS: {
+ MONTHLY: {
+ name: 'Monthly Access Token',
+ symbol: 'SBAT-M',
+ duration: 30 * 24 * 60 * 60 * 1000, // 30 дней в миллисекундах
+ price: {
+ wei: '10000000000000000', // 0.01 ETH
+ eth: 0.01
+ },
+ features: ['Basic access', '30 days validity', 'Renewable']
+ },
+
+ YEARLY: {
+ name: 'Yearly Access Token',
+ symbol: 'SBAT-Y',
+ duration: 365 * 24 * 60 * 60 * 1000, // 365 дней в миллисекундах
+ price: {
+ wei: '100000000000000000', // 0.1 ETH
+ eth: 0.1
+ },
+ features: ['Premium access', '365 days validity', 'Renewable', '17% discount']
+ }
+ },
+
+ // Настройки газа
+ GAS: {
+ ESTIMATE_MARGIN: 1.2, // 20% запас для газа
+ MAX_GAS_LIMIT: 500000,
+ DEFAULT_GAS_PRICE: '20000000000' // 20 Gwei
+ }
+ },
+
+ // Настройки сессий
+ SESSION: {
+ // Таймауты
+ TIMEOUTS: {
+ SESSION_EXPIRY: 30 * 60 * 1000, // 30 минут
+ HEARTBEAT_INTERVAL: 5 * 60 * 1000, // 5 минут
+ TOKEN_CHECK_INTERVAL: 60 * 1000, // 1 минута
+ WARNING_BEFORE_EXPIRY: 24 * 60 * 60 * 1000 // 1 день
+ },
+
+ // Настройки безопасности
+ SECURITY: {
+ MAX_SESSIONS_PER_TOKEN: 1,
+ AUTO_LOGOUT_ON_EXPIRY: true,
+ CLEAR_SESSION_ON_WALLET_CHANGE: true,
+ VALIDATE_SIGNATURE: true
+ },
+
+ // Настройки хранения
+ STORAGE: {
+ SESSION_KEY: 'securebit_token_session',
+ WALLET_KEY: 'securebit_wallet_address',
+ SETTINGS_KEY: 'securebit_token_settings'
+ }
+ },
+
+ // Настройки UI
+ UI: {
+ // Темизация
+ THEME: {
+ PRIMARY_COLOR: '#ff6b35',
+ SUCCESS_COLOR: '#10b981',
+ WARNING_COLOR: '#f59e0b',
+ ERROR_COLOR: '#ef4444',
+ INFO_COLOR: '#3b82f6'
+ },
+
+ // Анимации
+ ANIMATIONS: {
+ MODAL_OPEN_DURATION: 300,
+ TOAST_DURATION: 5000,
+ LOADING_SPINNER_DURATION: 1000
+ },
+
+ // Уведомления
+ NOTIFICATIONS: {
+ ENABLE_BROWSER_NOTIFICATIONS: true,
+ ENABLE_TOAST_NOTIFICATIONS: true,
+ ENABLE_SOUND_NOTIFICATIONS: false,
+ SHOW_EXPIRY_WARNINGS: true
+ }
+ },
+
+ // Настройки логирования
+ LOGGING: {
+ LEVEL: 'info', // debug, info, warn, error
+ ENABLE_CONSOLE: true,
+ ENABLE_REMOTE: false,
+ REMOTE_ENDPOINT: 'https://logs.securebit.chat/api/logs',
+
+ // Фильтры
+ FILTERS: {
+ INCLUDE_WEB3_EVENTS: true,
+ INCLUDE_CONTRACT_CALLS: true,
+ INCLUDE_USER_ACTIONS: true,
+ INCLUDE_ERRORS: true
+ }
+ },
+
+ // Настройки тестирования
+ TESTING: {
+ ENABLE_MOCK_MODE: false,
+ MOCK_TOKEN_VALIDATION: true,
+ MOCK_PURCHASE: false,
+ MOCK_NETWORK_DELAY: 1000
+ },
+
+ // Настройки производительности
+ PERFORMANCE: {
+ // Кэширование
+ CACHE: {
+ ENABLE_TOKEN_CACHE: true,
+ TOKEN_CACHE_TTL: 5 * 60 * 1000, // 5 минут
+ PRICE_CACHE_TTL: 60 * 1000, // 1 минута
+ STATS_CACHE_TTL: 10 * 60 * 1000 // 10 минут
+ },
+
+ // Оптимизации
+ OPTIMIZATIONS: {
+ LAZY_LOAD_COMPONENTS: true,
+ DEBOUNCE_INPUT_CHANGES: 300,
+ THROTTLE_API_CALLS: 1000,
+ BATCH_UPDATE_UI: true
+ }
+ },
+
+ // Настройки интеграции
+ INTEGRATION: {
+ // WebRTC интеграция
+ WEBRTC: {
+ ENABLE_SESSION_SYNC: true,
+ SESSION_SYNC_INTERVAL: 30 * 1000, // 30 секунд
+ AUTO_TERMINATE_OTHER_SESSIONS: true
+ },
+
+ // PWA интеграция
+ PWA: {
+ ENABLE_OFFLINE_MODE: false,
+ CACHE_TOKEN_DATA: true,
+ SYNC_ON_RECONNECT: true
+ }
+ }
+};
+
+// Функции для работы с конфигурацией
+export const ConfigUtils = {
+ // Получение конфигурации для конкретной сети
+ getNetworkConfig(chainId) {
+ return TOKEN_AUTH_CONFIG.WEB3.SUPPORTED_NETWORKS[chainId] || null;
+ },
+
+ // Получение адреса контракта для сети
+ getContractAddress(chainId) {
+ return TOKEN_AUTH_CONFIG.CONTRACT.ADDRESSES[chainId] || null;
+ },
+
+ // Получение конфигурации токена
+ getTokenConfig(tokenType) {
+ return TOKEN_AUTH_CONFIG.CONTRACT.TOKENS[tokenType.toUpperCase()] || null;
+ },
+
+ // Проверка поддержки сети
+ isNetworkSupported(chainId) {
+ return TOKEN_AUTH_CONFIG.WEB3.SUPPORTED_NETWORKS.hasOwnProperty(chainId);
+ },
+
+ // Получение сети по умолчанию
+ getDefaultNetwork() {
+ return TOKEN_AUTH_CONFIG.WEB3.DEFAULT_NETWORK;
+ },
+
+ // Получение всех поддерживаемых сетей
+ getSupportedNetworks() {
+ return Object.keys(TOKEN_AUTH_CONFIG.WEB3.SUPPORTED_NETWORKS);
+ },
+
+ // Получение настроек сессии
+ getSessionConfig() {
+ return TOKEN_AUTH_CONFIG.SESSION;
+ },
+
+ // Получение настроек UI
+ getUIConfig() {
+ return TOKEN_AUTH_CONFIG.UI;
+ },
+
+ // Проверка режима тестирования
+ isTestMode() {
+ return TOKEN_AUTH_CONFIG.TESTING.ENABLE_MOCK_MODE;
+ },
+
+ // Получение настроек логирования
+ getLoggingConfig() {
+ return TOKEN_AUTH_CONFIG.LOGGING;
+ }
+};
+
+// Экспорт конфигурации по умолчанию
+export default TOKEN_AUTH_CONFIG;