Files
cryptowallet/contracts/FeeSwapRouter_ETH.sol
ZOMBIIIIIII a81e29807c add project
2026-04-08 14:11:27 +03:00

187 lines
8.6 KiB
Solidity
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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 {}
}