// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; // ── Адреса (TRON base58 → hex) ── // SunSwap V2 Router: TKzxdSv2FZKQrEqkKVgp5DcwEXBEKMg2Ax → 0x6e0617948fe030a7e4970f8389d4ad295f249b7e // USDT (TRC-20): TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t → 0xa614f803b6fd780986a42c78ec9c7f77e6ded13c // WTRX: TNUC9Qb1rRpS5CbWLmNMxXBjyFoydXjWFR → 0x891cdb91d149f23b1a45d9c5ca78a88d0cb44c18 // Fee Recipient: TYTfrem65362TFyQSARTheeYza1GQA37Ug → 0xf6b4D4E650Fc67982894f37ba97Ab2496781ddb6 interface ITRC20 { function balanceOf(address account) external view returns (uint256); function transfer(address to, uint256 amount) external returns (bool); function transferFrom(address from, address to, uint256 amount) external returns (bool); function approve(address spender, uint256 amount) external returns (bool); } /// @title FeeSwapRouter_TRX /// @notice Обёртка для SunSwap V2 на TRON. Берёт 0.7% комиссию /// с каждого свапа и бриджа, отправляет на fee wallet. /// Остальные 99.3% + calldata идут на SunSwap как есть. /// @dev Generic calldata forwarder — работает с любым роутером. /// На TRON нет OpenZeppelin, поэтому ReentrancyGuard и Ownable /// реализованы вручную. contract FeeSwapRouter_TRX { // ── Reentrancy Guard ── uint256 private constant NOT_ENTERED = 1; uint256 private constant ENTERED = 2; uint256 private _status = NOT_ENTERED; modifier nonReentrant() { require(_status != ENTERED, "ReentrancyGuard: reentrant call"); _status = ENTERED; _; _status = NOT_ENTERED; } // ── Ownable ── address private _owner; bool private _paused; modifier onlyOwner() { require(msg.sender == _owner, "Ownable: caller is not the owner"); _; } modifier whenNotPaused() { require(!_paused, "Pausable: paused"); _; } function owner() public view returns (address) { return _owner; } function paused() public view returns (bool) { return _paused; } // ── Захардкоженные адреса для TRON ── /// @notice SunSwap V2 Smart Router на TRON /// TRON: TKzxdSv2FZKQrEqkKVgp5DcwEXBEKMg2Ax address public constant SUNSWAP_ROUTER = 0x6E0617948FE030a7E4970f8389d4Ad295f249B7e; /// @notice Кошелёк, получающий 0.7% комиссию /// TRON: TYTfrem65362TFyQSARTheeYza1GQA37Ug address public constant FEE_RECIPIENT = 0xf6b4D4E650Fc67982894f37ba97Ab2496781ddb6; /// @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 BridgeWithFeeNative( address indexed user, uint256 totalIn, uint256 feeAmount ); event BridgeWithFeeToken( address indexed user, address indexed tokenIn, uint256 totalIn, uint256 feeAmount ); event EmergencyWithdrawNative(address indexed to, uint256 amount); event EmergencyWithdrawToken(address indexed token, address indexed to, uint256 amount); event Paused(address account); event Unpaused(address account); // ── Constructor ── constructor() { _owner = msg.sender; _paused = false; } // ═══════════════════════════════════════════════ // Swap: TRX → Token // Пользователь отправляет TRX. Контракт берёт 0.7%, // остальное + calldata пересылает на SunSwap Router. // ═══════════════════════════════════════════════ /// @notice Свап TRX → токен через SunSwap с 0.7% комиссией. /// @param routerCalldata Calldata для SunSwap Router function swapNativeWithFee( bytes calldata routerCalldata ) external payable nonReentrant whenNotPaused { require(msg.value > 0, "Zero value"); uint256 feeAmount = (msg.value * FEE_BPS) / BPS_DENOMINATOR; uint256 swapAmount = msg.value - feeAmount; // 0.7% → fee wallet if (feeAmount > 0) { (bool feeSent, ) = FEE_RECIPIENT.call{value: feeAmount}(""); require(feeSent, "Fee transfer failed"); } // 99.3% + calldata → SunSwap Router (bool success, ) = SUNSWAP_ROUTER.call{value: swapAmount}(routerCalldata); require(success, "Swap failed"); emit SwapWithFeeNative(msg.sender, msg.value, feeAmount, swapAmount, SUNSWAP_ROUTER); } // ═══════════════════════════════════════════════ // Swap: Token → TRX // Пользователь approve-ит токены на этот контракт. // Контракт берёт 0.7% комиссию в токенах, approve-ит // 99.3% на SunSwap, и пересылает calldata. // ═══════════════════════════════════════════════ /// @notice Свап токен → TRX через SunSwap с 0.7% комиссией. /// @param tokenIn Адрес входного TRC-20 токена (USDT и т.д.) /// @param amountIn Полная сумма токенов (включая 0.7% комиссию) /// @param routerCalldata Calldata для SunSwap Router function swapTokenWithFee( address tokenIn, uint256 amountIn, bytes calldata routerCalldata ) external nonReentrant whenNotPaused { require(amountIn > 0, "Zero amount"); require(tokenIn != address(0), "Zero address"); // Забираем токены у пользователя require( ITRC20(tokenIn).transferFrom(msg.sender, address(this), amountIn), "TransferFrom failed" ); uint256 feeAmount = (amountIn * FEE_BPS) / BPS_DENOMINATOR; uint256 swapAmount = amountIn - feeAmount; // 0.7% комиссию → fee wallet if (feeAmount > 0) { require( ITRC20(tokenIn).transfer(FEE_RECIPIENT, feeAmount), "Fee transfer failed" ); } // Approve 99.3% на SunSwap Router ITRC20(tokenIn).approve(SUNSWAP_ROUTER, swapAmount); // Calldata → SunSwap Router (bool success, ) = SUNSWAP_ROUTER.call(routerCalldata); require(success, "Swap failed"); emit SwapWithFeeToken(msg.sender, tokenIn, amountIn, feeAmount, swapAmount, SUNSWAP_ROUTER); } // ═══════════════════════════════════════════════ // Bridge Fee: TRX (native) // Берёт 0.7% с TRX перед бриджем. // Остаток возвращается пользователю для бриджа. // ═══════════════════════════════════════════════ /// @notice Взять 0.7% комиссию с TRX перед бриджем. /// Остаток возвращается msg.sender. function bridgeNativeFee() external payable nonReentrant whenNotPaused { require(msg.value > 0, "Zero value"); uint256 feeAmount = (msg.value * FEE_BPS) / BPS_DENOMINATOR; uint256 remaining = msg.value - feeAmount; // 0.7% → fee wallet if (feeAmount > 0) { (bool feeSent, ) = FEE_RECIPIENT.call{value: feeAmount}(""); require(feeSent, "Fee transfer failed"); } // Остаток → обратно пользователю if (remaining > 0) { (bool sent, ) = msg.sender.call{value: remaining}(""); require(sent, "Return transfer failed"); } emit BridgeWithFeeNative(msg.sender, msg.value, feeAmount); } /// @notice Взять 0.7% комиссию с TRC-20 токена перед бриджем. /// Остаток возвращается msg.sender. /// @param tokenIn Адрес TRC-20 токена /// @param amountIn Полная сумма (включая 0.7%) function bridgeTokenFee( address tokenIn, uint256 amountIn ) external nonReentrant whenNotPaused { require(amountIn > 0, "Zero amount"); require(tokenIn != address(0), "Zero address"); require( ITRC20(tokenIn).transferFrom(msg.sender, address(this), amountIn), "TransferFrom failed" ); uint256 feeAmount = (amountIn * FEE_BPS) / BPS_DENOMINATOR; uint256 remaining = amountIn - feeAmount; // 0.7% → fee wallet if (feeAmount > 0) { require( ITRC20(tokenIn).transfer(FEE_RECIPIENT, feeAmount), "Fee transfer failed" ); } // Остаток → обратно пользователю if (remaining > 0) { require( ITRC20(tokenIn).transfer(msg.sender, remaining), "Return transfer failed" ); } emit BridgeWithFeeToken(msg.sender, tokenIn, amountIn, feeAmount); } // ── Admin: Emergency ── function pause() external onlyOwner { _paused = true; emit Paused(msg.sender); } function unpause() external onlyOwner { _paused = false; emit Unpaused(msg.sender); } function emergencyWithdrawNative() external onlyOwner { uint256 balance = address(this).balance; require(balance > 0, "Zero balance"); (bool sent, ) = _owner.call{value: balance}(""); require(sent, "Transfer failed"); emit EmergencyWithdrawNative(_owner, balance); } function emergencyWithdrawToken(address token) external onlyOwner { require(token != address(0), "Zero address"); uint256 balance = ITRC20(token).balanceOf(address(this)); require(balance > 0, "Zero balance"); require(ITRC20(token).transfer(_owner, balance), "Transfer failed"); emit EmergencyWithdrawToken(token, _owner, balance); } /// @notice Принимаем TRX (рефанды, возвраты) receive() external payable {} }