155 lines
5.9 KiB
Solidity
155 lines
5.9 KiB
Solidity
// SPDX-License-Identifier: MIT
|
||
pragma solidity ^0.8.20;
|
||
|
||
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_BSC
|
||
/// @notice Обёртка для PancakeSwap V3 Smart Router на BSC. Берёт 0.7% комиссию
|
||
/// с каждого свапа и отправляет на fee wallet. Остальные 99.3% + calldata
|
||
/// идут на Smart Router как есть.
|
||
/// @dev Работает с любой версией PancakeSwap (V2/V3/Smart Router),
|
||
/// потому что просто пересылает calldata без разбора.
|
||
contract FeeSwapRouter_BSC is Ownable, ReentrancyGuard, Pausable {
|
||
using SafeERC20 for IERC20;
|
||
|
||
// ── Захардкоженные адреса для BSC ──
|
||
|
||
/// @notice PancakeSwap V3 Smart Router на BSC
|
||
address public constant SMART_ROUTER = 0x13f4EA83D0bd40E75C8222255bc855a974568Dd4;
|
||
|
||
/// @notice Permit2 на BSC (общий для PancakeSwap и Uniswap)
|
||
address public constant PERMIT2 = 0x000000000022D473030F116dDEE9F6B43aC78BA3;
|
||
|
||
/// @notice Кошелёк, получающий 0.7% комиссию
|
||
address public constant FEE_RECIPIENT = 0xeDEb157eF86A4ecd1242762f339c2Bd5a0822718;
|
||
|
||
/// @notice Комиссия 0.7% = 70 basis points (нельзя изменить)
|
||
uint16 public constant FEE_BPS = 70;
|
||
|
||
/// @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();
|
||
|
||
// ── Constructor ──
|
||
|
||
constructor() Ownable(msg.sender) {
|
||
if (block.chainid != 56) revert WrongChain();
|
||
}
|
||
|
||
/// @notice Свап BNB → токен через PancakeSwap Smart Router с 1% комиссией.
|
||
/// @param routerCalldata Calldata сгенерированная PancakeSwap SDK.
|
||
/// Передаётся как есть.
|
||
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 → PancakeSwap Smart Router
|
||
(bool success, ) = SMART_ROUTER.call{value: swapAmount}(routerCalldata);
|
||
if (!success) revert SwapFailed();
|
||
|
||
emit SwapWithFeeNative(msg.sender, msg.value, feeAmount, swapAmount, SMART_ROUTER);
|
||
}
|
||
|
||
|
||
/// @notice Свап токен → BNB через PancakeSwap Smart Router с 1% комиссией.
|
||
/// @param tokenIn Адрес входного токена (USDT, DOGE, и т.д.)
|
||
/// @param amountIn Полная сумма токенов (включая 1% комиссию)
|
||
/// @param routerCalldata Calldata для Smart 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 (PancakeSwap Swap Proxy)
|
||
IERC20(tokenIn).forceApprove(PERMIT2, swapAmount);
|
||
|
||
// Calldata → Smart Router
|
||
(bool success, ) = SMART_ROUTER.call(routerCalldata);
|
||
if (!success) revert SwapFailed();
|
||
|
||
emit SwapWithFeeToken(msg.sender, tokenIn, amountIn, feeAmount, swapAmount, SMART_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 Принимаем BNB от PancakeSwap (рефанды, выходные средства)
|
||
receive() external payable {}
|
||
}
|