add project
This commit is contained in:
240
contracts/DEPLOY.md
Normal file
240
contracts/DEPLOY.md
Normal file
@@ -0,0 +1,240 @@
|
||||
# FeeSwapRouter — Инструкция по деплою
|
||||
|
||||
## Четыре сети — четыре подхода
|
||||
|
||||
| Сеть | Метод | DEX | Fee wallet | Комиссия | Нужен на газ |
|
||||
|------|-------|-----|------------|----------|-------------|
|
||||
| **Ethereum** (chainId 1) | Solidity контракт (Remix) | Uniswap Universal Router | `0xeDEb157eF86A4ecd1242762f339c2Bd5a0822718` | ~~1%~~ → **0.7%** | ETH (~$5-20) |
|
||||
| **BSC** (chainId 56) | Solidity контракт (Remix) | PancakeSwap V3 Smart Router | `0xeDEb157eF86A4ecd1242762f339c2Bd5a0822718` | **0.7%** | BNB (~$0.50-1.00) |
|
||||
| **TRON** | Solidity контракт (TronIDE) | SunSwap V2 Smart Router | `TYTfrem65362TFyQSARTheeYza1GQA37Ug` | **0.7%** | TRX (~50-150 TRX) |
|
||||
| **Solana** | Jupiter Referral Program | Jupiter Aggregator | `Co43MKwqMRMCvhscVVrtQWvma87NEV7ba4cfo8cksgzJ` | **0.7%** | SOL (~0.01 SOL) |
|
||||
|
||||
Все адреса и проценты захардкожены — никаких параметров при деплое вводить НЕ НУЖНО.
|
||||
|
||||
**Важно**: ETH контракт уже задеплоен с 1% комиссией. Если нужно 0.7%, нужно задеплоить новый контракт.
|
||||
|
||||
---
|
||||
|
||||
## 1. Деплой FeeSwapRouter_ETH.sol (Ethereum)
|
||||
|
||||
**Статус: ✅ Задеплоен (1%) — `0xbdC4A97C2814E496160638d87e1F1b14154e30b6`**
|
||||
**⚠️ Нужен передеплой с 0.7% если хочешь 0.7% on-chain**
|
||||
|
||||
### Что нужно
|
||||
- MetaMask с ETH на балансе (на газ уйдёт ~$5-20)
|
||||
- В MetaMask выбрана сеть **Ethereum Mainnet**
|
||||
|
||||
### Шаги
|
||||
|
||||
1. Открой https://remix.ethereum.org
|
||||
2. Слева в File Explorer нажми **+** → создай файл `FeeSwapRouter_ETH.sol`
|
||||
3. Вставь **ВЕСЬ** код из файла `contracts/FeeSwapRouter_ETH.sol`
|
||||
4. Слева выбери вкладку **Solidity Compiler** (иконка с буквой S)
|
||||
- Compiler: **0.8.20**
|
||||
- **Enable optimization**: включи, runs: **200**
|
||||
- Нажми **Compile FeeSwapRouter_ETH.sol**
|
||||
- Должна появиться зелёная галочка (без ошибок)
|
||||
5. Слева выбери вкладку **Deploy & Run Transactions** (иконка со стрелкой)
|
||||
- Environment: **Injected Provider - MetaMask**
|
||||
- Убедись что MetaMask на сети **Ethereum Mainnet**
|
||||
- В выпадающем списке контрактов выбери **FeeSwapRouter_ETH**
|
||||
- Нажми **Deploy**
|
||||
- Подтверди транзакцию в MetaMask
|
||||
6. После подтверждения — скопируй адрес контракта из консоли Remix
|
||||
7. Сохрани адрес в `.env`:
|
||||
```
|
||||
FEE_SWAP_ROUTER_ETH=0x...твой_адрес...
|
||||
```
|
||||
|
||||
### Верификация на Etherscan
|
||||
- Зайди на https://etherscan.io/verifyContract
|
||||
- Адрес контракта: вставь адрес из шага 6
|
||||
- Compiler: Solidity 0.8.20
|
||||
- Optimization: Yes, 200 runs
|
||||
- License: MIT
|
||||
- Код: в Remix правой кнопкой на файл → **Flatten**, вставь результат
|
||||
|
||||
---
|
||||
|
||||
## 2. Деплой FeeSwapRouter_BSC.sol (BSC)
|
||||
|
||||
### Что нужно
|
||||
- MetaMask с BNB на балансе (на газ уйдёт ~$0.50-1.00)
|
||||
- В MetaMask добавлена сеть **BNB Smart Chain**:
|
||||
- RPC: `https://bsc-dataseed.binance.org`
|
||||
- Chain ID: `56`
|
||||
- Symbol: `BNB`
|
||||
- Explorer: `https://bscscan.com`
|
||||
|
||||
### Шаги
|
||||
|
||||
1. Открой https://remix.ethereum.org
|
||||
2. Создай файл `FeeSwapRouter_BSC.sol`
|
||||
3. Вставь **ВЕСЬ** код из файла `contracts/FeeSwapRouter_BSC.sol`
|
||||
4. Compiler → **0.8.20**, optimization ON (200 runs), Compile
|
||||
5. Deploy → Injected Provider → MetaMask на сети **BNB Smart Chain**
|
||||
6. Выбери контракт **FeeSwapRouter_BSC**, нажми **Deploy**, подтверди в MetaMask
|
||||
7. Скопируй адрес, сохрани в `.env`:
|
||||
```
|
||||
FEE_SWAP_ROUTER_BSC=0x...твой_адрес...
|
||||
```
|
||||
|
||||
### Верификация на BscScan
|
||||
- Зайди на https://bscscan.com/verifyContract
|
||||
- Compiler: Solidity 0.8.20
|
||||
- Optimization: Yes, 200 runs
|
||||
- License: MIT
|
||||
- Код: в Remix → Flatten → вставь результат
|
||||
|
||||
---
|
||||
|
||||
## 3. Деплой FeeSwapRouter_TRX.sol (TRON)
|
||||
|
||||
**Статус: ✅ Задеплоен — `TX8E6X7X1FWYRYuYR2LTvS7zm1KchcVs5E`**
|
||||
|
||||
### Что нужно
|
||||
- **TronLink** кошелёк (расширение для Chrome, аналог MetaMask для TRON)
|
||||
- TRX на балансе (~50-150 TRX на газ)
|
||||
- В TronLink выбрана сеть **TRON Mainnet**
|
||||
|
||||
### Шаги
|
||||
|
||||
1. Открой https://www.tronide.io (это Remix для TRON)
|
||||
2. Слева в File Explorer нажми **+** → создай файл `FeeSwapRouter_TRX.sol`
|
||||
3. Вставь **ВЕСЬ** код из файла `contracts/FeeSwapRouter_TRX.sol`
|
||||
4. Compiler → **0.8.20**, optimization ON (200 runs), Compile
|
||||
- ⚠️ TronIDE может не поддерживать OpenZeppelin импорты — но этот контракт
|
||||
НЕ использует OpenZeppelin (ReentrancyGuard и Ownable реализованы вручную)
|
||||
5. Deploy → Injected Provider → **TronLink** (должен быть установлен)
|
||||
- Убедись что TronLink на сети **TRON Mainnet**
|
||||
- В выпадающем списке выбери **FeeSwapRouter_TRX**
|
||||
- Нажми **Deploy**, подтверди в TronLink
|
||||
6. Скопируй адрес контракта (будет в формате T...)
|
||||
7. Сохрани в `.env`:
|
||||
```
|
||||
FEE_SWAP_ROUTER_TRX=TX8E6X7X1FWYRYuYR2LTvS7zm1KchcVs5E
|
||||
```
|
||||
|
||||
### Верификация на TronScan
|
||||
- Зайди на https://tronscan.org/#/contracts/verify
|
||||
- Вставь адрес контракта (T...)
|
||||
- Compiler: 0.8.20
|
||||
- Optimization: ON
|
||||
- Код: скопируй весь контракт
|
||||
|
||||
### Адреса в контракте (TRON base58 → hex)
|
||||
|
||||
| Контракт | TRON адрес | Hex (в Solidity) |
|
||||
|----------|-----------|-----------------|
|
||||
| SunSwap V2 Router | `TKzxdSv2FZKQrEqkKVgp5DcwEXBEKMg2Ax` | `0x6e0617948fE030a7e4970f8389D4AD295F249b7e` |
|
||||
| USDT (TRC-20) | `TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t` | `0xa614f803b6fd780986a42c78ec9c7f77e6ded13c` |
|
||||
| WTRX | `TNUC9Qb1rRpS5CbWLmNMxXBjyFoydXjWFR` | `0x891cdb91d149f23b1a45d9c5ca78a88d0cb44c18` |
|
||||
|
||||
---
|
||||
|
||||
## 4. Настройка комиссии на Solana (Jupiter Referral)
|
||||
|
||||
На Solana НЕ нужен смарт-контракт. Jupiter Aggregator **нативно поддерживает** платформенную комиссию через Referral Program.
|
||||
|
||||
### Что нужно
|
||||
- Кошелёк Solana с ~0.01 SOL (на ренту и газ)
|
||||
- Fee wallet: `Co43MKwqMRMCvhscVVrtQWvma87NEV7ba4cfo8cksgzJ`
|
||||
|
||||
### Шаги
|
||||
|
||||
#### Шаг 1: Создай Referral Account
|
||||
|
||||
1. Зайди на https://referral.jup.ag
|
||||
2. Подключи кошелёк `Co43MKwqMRMCvhscVVrtQWvma87NEV7ba4cfo8cksgzJ`
|
||||
3. Нажми **Create Referral Account**
|
||||
4. Подтверди транзакцию (~0.003 SOL)
|
||||
5. Скопируй **Referral Account Public Key** — это будет `JUPITER_REFERRAL_ACCOUNT`
|
||||
|
||||
#### Шаг 2: Создай Token Fee Accounts
|
||||
|
||||
Для каждого токена, с которого берётся комиссия, нужен отдельный fee token account:
|
||||
|
||||
1. На странице https://referral.jup.ag перейди в **Claim Token Accounts**
|
||||
2. Нажми **Create Token Account** для каждого нужного токена:
|
||||
- **SOL** (wrapped): `So11111111111111111111111111111111111111112`
|
||||
- **USDT**: `Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB`
|
||||
- **USDC**: `EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v`
|
||||
3. Подтверди каждую транзакцию (~0.002 SOL каждая)
|
||||
|
||||
#### Шаг 3: Сохрани в `.env`
|
||||
|
||||
```
|
||||
JUPITER_REFERRAL_ACCOUNT=...твой_referral_account_pubkey...
|
||||
JUPITER_FEE_BPS=70
|
||||
```
|
||||
|
||||
#### Как это работает
|
||||
|
||||
Jupiter сам:
|
||||
1. Считает 0.7% от суммы свапа
|
||||
2. Отправляет комиссию на fee token account
|
||||
3. Остальные 99.3% идут пользователю
|
||||
|
||||
Комиссия накапливается на token fee accounts. Можно клеймить (забирать) через https://referral.jup.ag → **Claim**.
|
||||
|
||||
---
|
||||
|
||||
## 5. Bridge комиссия (0.7%)
|
||||
|
||||
Комиссия на бридж работает **off-chain** — перед мостом отправляется отдельная транзакция:
|
||||
|
||||
| Сеть | Как берётся fee | Fee wallet |
|
||||
|------|----------------|------------|
|
||||
| ETH → * | Отдельный ETH/ERC20 transfer перед бриджем | `0xeDEb157eF86A4ecd1242762f339c2Bd5a0822718` |
|
||||
| SOL → * | Отдельный SOL transfer перед бриджем | `Co43MKwqMRMCvhscVVrtQWvma87NEV7ba4cfo8cksgzJ` |
|
||||
| TRX → * | Отдельный TRC20 transfer перед бриджем | `TYTfrem65362TFyQSARTheeYza1GQA37Ug` |
|
||||
|
||||
Контракт **НЕ нужен** для bridge fee — логика встроена в код кошелька (`apps/web/src/lib/bridge/execute.ts`).
|
||||
|
||||
---
|
||||
|
||||
## После деплоя — интеграция с кошельком
|
||||
|
||||
После получения адресов контрактов:
|
||||
|
||||
1. Добавить адреса в `.env`:
|
||||
```
|
||||
FEE_SWAP_ROUTER_ETH=0xbdC4A97C2814E496160638d87e1F1b14154e30b6
|
||||
FEE_SWAP_ROUTER_BSC=0xbdC4A97C2814E496160638d87e1F1b14154e30b6
|
||||
FEE_SWAP_ROUTER_TRX=TX8E6X7X1FWYRYuYR2LTvS7zm1KchcVs5E
|
||||
JUPITER_REFERRAL_ACCOUNT=...referral_pubkey...
|
||||
JUPITER_FEE_BPS=70
|
||||
```
|
||||
2. ✅ BSC swap proxy — вызывает FeeSwapRouter_BSC
|
||||
3. ✅ TRX swap proxy — вызывает FeeSwapRouter_TRX
|
||||
4. Обновить SOL swap proxy — добавить `platformFeeBps=70` в Jupiter запросы
|
||||
5. Approve токены должны идти на адрес FeeSwapRouter (не на DEX роутер)
|
||||
|
||||
---
|
||||
|
||||
## Админ-функции (ETH, BSC, TRX контракты)
|
||||
|
||||
Кошелёк, с которого задеплоил — это **owner**. Доступные функции:
|
||||
|
||||
| Функция | Что делает |
|
||||
|---------|-----------|
|
||||
| `pause()` | Экстренная остановка всех свапов |
|
||||
| `unpause()` | Возобновить свапы |
|
||||
| `emergencyWithdrawNative()` | Вывести застрявший ETH/BNB/TRX |
|
||||
| `emergencyWithdrawToken(address)` | Вывести застрявшие токены |
|
||||
|
||||
Вызывать через:
|
||||
- **ETH**: Etherscan → Write Contract
|
||||
- **BSC**: BscScan → Write Contract
|
||||
- **TRX**: TronScan → Write Contract
|
||||
|
||||
**Важно**: комиссия (0.7%) и fee wallet захардкожены — их нельзя изменить. Это сделано специально для безопасности.
|
||||
|
||||
---
|
||||
|
||||
## Порядок деплоя (рекомендуемый)
|
||||
|
||||
1. ✅ ETH контракт (уже задеплоен, но с 1% — передеплоить если нужно 0.7%)
|
||||
2. BSC контракт → через Remix + MetaMask
|
||||
3. TRX контракт → через TronIDE + TronLink
|
||||
4. SOL referral → через https://referral.jup.ag
|
||||
113
contracts/FeeSetup_SOL.md
Normal file
113
contracts/FeeSetup_SOL.md
Normal file
@@ -0,0 +1,113 @@
|
||||
# Solana — Настройка комиссии через Jupiter Referral Program
|
||||
|
||||
На Solana **НЕ нужен** смарт-контракт. Jupiter Aggregator нативно поддерживает платформенную комиссию.
|
||||
|
||||
## Как работает
|
||||
|
||||
```
|
||||
User → Jupiter Swap → 99.3% пользователю + 0.7% на Referral Fee Account
|
||||
```
|
||||
|
||||
Jupiter сам:
|
||||
1. Считает 0.7% от суммы свапа
|
||||
2. Отправляет комиссию на твой fee token account
|
||||
3. Остальные 99.3% идут пользователю
|
||||
|
||||
Комиссия накапливается на token fee accounts. Забирать (клеймить) можно через https://referral.jup.ag → **Claim**.
|
||||
|
||||
---
|
||||
|
||||
## Настройка (одноразовая)
|
||||
|
||||
### Шаг 1: Создай Referral Account
|
||||
|
||||
1. Зайди на https://referral.jup.ag
|
||||
2. Подключи кошелёк `Co43MKwqMRMCvhscVVrtQWvma87NEV7ba4cfo8cksgzJ` (fee wallet для SOL)
|
||||
3. Нажми **Create Referral Account**
|
||||
4. Подтверди транзакцию (~0.003 SOL)
|
||||
5. Скопируй **Referral Account Public Key**
|
||||
|
||||
### Шаг 2: Создай Token Fee Accounts
|
||||
|
||||
Для каждого токена нужен отдельный fee token account:
|
||||
|
||||
1. На https://referral.jup.ag → **Claim Token Accounts**
|
||||
2. Нажми **Create Token Account** для каждого:
|
||||
- **SOL** (wrapped): `So11111111111111111111111111111111111111112`
|
||||
- **USDT**: `Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB`
|
||||
- **USDC**: `EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v`
|
||||
3. Подтверди каждую транзакцию (~0.002 SOL каждая)
|
||||
|
||||
### Шаг 3: Добавь в .env
|
||||
|
||||
```env
|
||||
JUPITER_REFERRAL_ACCOUNT=...твой_referral_account_pubkey...
|
||||
JUPITER_FEE_BPS=70
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Как поменять комиссию
|
||||
|
||||
Комиссия задаётся **одной переменной** в `.env`:
|
||||
|
||||
```env
|
||||
JUPITER_FEE_BPS=70 # 0.7% (текущая)
|
||||
```
|
||||
|
||||
Примеры значений:
|
||||
|
||||
| JUPITER_FEE_BPS | Комиссия |
|
||||
|-----------------|----------|
|
||||
| 10 | 0.1% |
|
||||
| 25 | 0.25% |
|
||||
| 50 | 0.5% |
|
||||
| **70** | **0.7%** |
|
||||
| 100 | 1.0% |
|
||||
| 200 | 2.0% |
|
||||
|
||||
**Чтобы поменять**: просто измени `JUPITER_FEE_BPS` в `.env` и перезапусти сервер. Передеплой не нужен.
|
||||
|
||||
**Максимум**: Jupiter позволяет до 255 BPS (2.55%).
|
||||
|
||||
---
|
||||
|
||||
## Где это в коде
|
||||
|
||||
Файл: `apps/api/src/routes/sol-swap-proxy.routes.ts`
|
||||
|
||||
**Quote** — передаёт `platformFeeBps` в Jupiter API:
|
||||
```
|
||||
GET /quote?...&platformFeeBps=70
|
||||
```
|
||||
Jupiter возвращает quote уже с учётом комиссии — пользователь видит реальный выход.
|
||||
|
||||
**Build** — передаёт `feeAccount` (Referral Account) в Jupiter Swap API:
|
||||
```json
|
||||
{
|
||||
"quoteResponse": {...},
|
||||
"userPublicKey": "...",
|
||||
"feeAccount": "...referral_account_pubkey..."
|
||||
}
|
||||
```
|
||||
Jupiter встраивает инструкцию перевода комиссии прямо в транзакцию свапа.
|
||||
|
||||
---
|
||||
|
||||
## Отличие от ETH/BSC/TRX
|
||||
|
||||
| | ETH / BSC / TRX | Solana |
|
||||
|---|---|---|
|
||||
| Контракт | Свой FeeSwapRouter | Не нужен |
|
||||
| Комиссия | Захардкожена в контракте | Настраивается через env |
|
||||
| Изменение % | Нужен передеплой контракта | Поменять env + перезапуск |
|
||||
| Где копится fee | На fee wallet напрямую | На Jupiter fee token accounts |
|
||||
| Как забрать fee | Уже на кошельке | Claim через referral.jup.ag |
|
||||
|
||||
---
|
||||
|
||||
## Стоимость
|
||||
|
||||
- Создание Referral Account: ~0.003 SOL (~$0.50)
|
||||
- Каждый Token Fee Account: ~0.002 SOL (~$0.30)
|
||||
- Итого на 3 токена (SOL, USDT, USDC): ~0.009 SOL (~$1.50)
|
||||
154
contracts/FeeSwapRouter_BSC.sol
Normal file
154
contracts/FeeSwapRouter_BSC.sol
Normal file
@@ -0,0 +1,154 @@
|
||||
// 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 {}
|
||||
}
|
||||
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 {}
|
||||
}
|
||||
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