add project
This commit is contained in:
186
contracts/FeeSwapRouter_ETH.sol
Normal file
186
contracts/FeeSwapRouter_ETH.sol
Normal file
@@ -0,0 +1,186 @@
|
||||
// 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 {}
|
||||
}
|
||||
Reference in New Issue
Block a user