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