// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; // ╔══════════════════════════════════════════════════════════════╗ // ║ FeeSwapRouter — ETHEREUM MAINNET ║ // ║ ║ // ║ Деплоить на: Ethereum Mainnet (chainId 1) ║ // ║ Нужен: ETH на газ (~$5-20) ║ // ║ DEX: Uniswap Universal Router V2.0 ║ // ║ Комиссия: 1% с каждого свапа → fee wallet ║ // ║ ║ // ║ КАК ЗАДЕПЛОИТЬ: ║ // ║ 1. Открой https://remix.ethereum.org ║ // ║ 2. Создай файл, вставь ВЕСЬ этот код ║ // ║ 3. Compiler → 0.8.20, Optimization ON (200 runs) ║ // ║ 4. Deploy → Injected Provider (MetaMask) ║ // ║ 5. В MetaMask выбери сеть ETHEREUM MAINNET ║ // ║ 6. Нажми Deploy, подтверди в MetaMask ║ // ║ 7. Сохрани адрес контракта после деплоя ║ // ╚══════════════════════════════════════════════════════════════╝ import "@openzeppelin/contracts/access/Ownable.sol"; import "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; import "@openzeppelin/contracts/utils/Pausable.sol"; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; /// @title FeeSwapRouter_ETH /// @notice Обёртка для Uniswap Universal Router. Берёт 1% комиссию с каждого /// свапа и отправляет на fee wallet. Остальные 99% + calldata идут /// на Universal Router как есть. /// @dev Работает с любой версией Uniswap (V2/V3/V4/Universal Router), /// потому что просто пересылает calldata без разбора. contract FeeSwapRouter_ETH is Ownable, ReentrancyGuard, Pausable { using SafeERC20 for IERC20; // ── Захардкоженные адреса для Ethereum ── /// @notice Uniswap Universal Router V2.0 на Ethereum Mainnet address public constant UNIVERSAL_ROUTER = 0x3fC91A3afd70395Cd496C647d5a6CC9D4B2b7FAD; /// @notice Uniswap Permit2 / Swap Proxy на Ethereum Mainnet address public constant SWAP_PROXY = 0x000000000022D473030F116dDEE9F6B43aC78BA3; /// @notice Кошелёк, получающий 1% комиссию address public constant FEE_RECIPIENT = 0xeDEb157eF86A4ecd1242762f339c2Bd5a0822718; /// @notice Комиссия 1% = 100 basis points (нельзя изменить) uint16 public constant FEE_BPS = 100; /// @notice Делитель для basis points uint16 private constant BPS_DENOMINATOR = 10_000; // ── Events ── event SwapWithFeeNative( address indexed user, uint256 totalIn, uint256 feeAmount, uint256 swapAmount, address indexed router ); event SwapWithFeeToken( address indexed user, address indexed tokenIn, uint256 totalIn, uint256 feeAmount, uint256 swapAmount, address indexed router ); event EmergencyWithdrawNative(address indexed to, uint256 amount); event EmergencyWithdrawToken(address indexed token, address indexed to, uint256 amount); // ── Errors ── error InsufficientValue(); error NativeTransferFailed(); error SwapFailed(); error ZeroAmount(); error ZeroAddress(); error WrongChain(); error UnauthorizedRouter(); // ── Constructor ── constructor() Ownable(msg.sender) { if (block.chainid != 1) revert WrongChain(); } // ═══════════════════════════════════════════════ // Swap: ETH → Token // Пользователь отправляет ETH. Контракт берёт 1%, // остальное + calldata пересылает на Universal Router. // ═══════════════════════════════════════════════ /// @notice Свап ETH → токен через Universal Router с 1% комиссией. /// @param routerCalldata Calldata сгенерированная Uniswap SDK /// (SwapRouter.swapCallParameters). Передаётся как есть. function swapNativeWithFee( bytes calldata routerCalldata ) external payable nonReentrant whenNotPaused { if (msg.value == 0) revert InsufficientValue(); uint256 feeAmount = (msg.value * FEE_BPS) / BPS_DENOMINATOR; uint256 swapAmount = msg.value - feeAmount; // 1% → fee wallet if (feeAmount > 0) { (bool feeSent, ) = FEE_RECIPIENT.call{value: feeAmount}(""); if (!feeSent) revert NativeTransferFailed(); } // 99% + calldata → Universal Router (bool success, ) = UNIVERSAL_ROUTER.call{value: swapAmount}(routerCalldata); if (!success) revert SwapFailed(); emit SwapWithFeeNative(msg.sender, msg.value, feeAmount, swapAmount, UNIVERSAL_ROUTER); } // ═══════════════════════════════════════════════ // Swap: Token → ETH // Пользователь approve-ит токены на этот контракт. // Контракт берёт 1% комиссию в токенах, approve-ит // 99% на Permit2/SwapProxy, и пересылает calldata // на Universal Router. // ═══════════════════════════════════════════════ /// @notice Свап токен → ETH через Universal Router с 1% комиссией. /// @param tokenIn Адрес входного токена (USDT, USDC, и т.д.) /// @param amountIn Полная сумма токенов (включая 1% комиссию) /// @param routerCalldata Calldata для Universal Router function swapTokenWithFee( address tokenIn, uint256 amountIn, bytes calldata routerCalldata ) external nonReentrant whenNotPaused { if (amountIn == 0) revert ZeroAmount(); if (tokenIn == address(0)) revert ZeroAddress(); // Забираем токены у пользователя IERC20(tokenIn).safeTransferFrom(msg.sender, address(this), amountIn); uint256 feeAmount = (amountIn * FEE_BPS) / BPS_DENOMINATOR; uint256 swapAmount = amountIn - feeAmount; // 1% комиссию → fee wallet if (feeAmount > 0) { IERC20(tokenIn).safeTransfer(FEE_RECIPIENT, feeAmount); } // Approve 99% на Permit2 (Uniswap Swap Proxy) IERC20(tokenIn).forceApprove(SWAP_PROXY, swapAmount); // Calldata → Universal Router (bool success, ) = UNIVERSAL_ROUTER.call(routerCalldata); if (!success) revert SwapFailed(); emit SwapWithFeeToken(msg.sender, tokenIn, amountIn, feeAmount, swapAmount, UNIVERSAL_ROUTER); } // ── Admin: Emergency ── function pause() external onlyOwner { _pause(); } function unpause() external onlyOwner { _unpause(); } function emergencyWithdrawNative() external onlyOwner { uint256 balance = address(this).balance; if (balance == 0) revert ZeroAmount(); (bool sent, ) = owner().call{value: balance}(""); if (!sent) revert NativeTransferFailed(); emit EmergencyWithdrawNative(owner(), balance); } function emergencyWithdrawToken(address token) external onlyOwner { if (token == address(0)) revert ZeroAddress(); uint256 balance = IERC20(token).balanceOf(address(this)); if (balance == 0) revert ZeroAmount(); IERC20(token).safeTransfer(owner(), balance); emit EmergencyWithdrawToken(token, owner(), balance); } /// @notice Принимаем ETH от Universal Router (рефанды, выходные средства) receive() external payable {} }