Розробка системи автолістингу на DEX при досягненні капіталізації

Проєктуємо та розробляємо блокчейн-рішення повного циклу: від архітектури смарт-контрактів до запуску DeFi-протоколів, NFT-маркетплейсів та криптобірж. Аудит безпеки, токеноміка, інтеграція з наявною інфраструктурою.
Показано 1 з 1Усі 1306 послуг
Розробка системи автолістингу на DEX при досягненні капіталізації
Середній
~3-5 днів
Часті запитання

Напрямки блокчейн-розробки

Етапи блокчейн-розробки

Останні роботи

  • image_website-b2b-advance_0.webp
    Розробка сайту компанії B2B ADVANCE
    1288
  • image_web-applications_feedme_466_0.webp
    Розробка веб-додатків для компанії FEEDME
    1198
  • image_websites_belfingroup_462_0.webp
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    902
  • image_ecommerce_furnoro_435_0.webp
    Розробка інтернет магазину для компанії FURNORO
    1123
  • image_logo-advance_0.webp
    Розробка логотипу компанії B2B Advance
    589
  • image_crm_enviok_479_0.webp
    Розробка веб-додатків для компанії Enviok
    860

Розробка системи автоматичного листингу на DEX при досягненні ринкової капіталізації

Завдання: токен продається на presale, і при досягненні певної суми (hardcap або softcap) ліквідність автоматично листується на Uniswap або аналогічному DEX — без ручного втручання команди. Це усуває людський фактор та rug pull ризик: команда не може «затримати» додавання ліквідності або додати її на невигідних умовах.

Механіка: як це працює

Смарт-контракт presale утримує зібрані кошти (ETH або USDC) в escrow. При виконанні умови (досягнуто ціль) — автоматично викликає Uniswap Router для створення пулу та додавання ліквідності. LP токени (доказ ліквідності) відправляються на lock контракт або спалюються, запобігаючи виведенню ліквідності.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/access/Ownable2Step.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";

interface IUniswapV2Router02 {
    function addLiquidityETH(
        address token,
        uint amountTokenDesired,
        uint amountTokenMin,
        uint amountETHMin,
        address to,
        uint deadline
    ) external payable returns (uint amountToken, uint amountETH, uint liquidity);
    
    function factory() external pure returns (address);
}

interface IUniswapV2Factory {
    function createPair(address tokenA, address tokenB) external returns (address pair);
    function getPair(address tokenA, address tokenB) external view returns (address pair);
}

contract AutoListingPresale is Ownable2Step, ReentrancyGuard {
    using SafeERC20 for IERC20;
    
    IERC20 public immutable token;
    IUniswapV2Router02 public immutable router;
    
    uint256 public immutable softCap;           // мінімум для успіху presale
    uint256 public immutable hardCap;           // максимум збору
    uint256 public immutable tokenPrice;        // wei за токен (18 decimals)
    uint256 public immutable listingPercent;    // % зібраного ETH для ліквідності
    uint256 public immutable listingTokenPercent; // % токенів для ліквідності
    uint256 public immutable saleEnd;
    
    uint256 public totalRaised;
    bool public finalized;
    bool public listed;
    address public liquidityPair;
    
    mapping(address => uint256) public contributions;
    
    event Contribution(address indexed contributor, uint256 ethAmount);
    event Finalized(bool success, uint256 totalRaised);
    event ListedOnDEX(address pair, uint256 ethLiquidity, uint256 tokenLiquidity);
    event Refunded(address indexed contributor, uint256 amount);
    
    constructor(
        address _token,
        address _router,
        uint256 _softCap,
        uint256 _hardCap,
        uint256 _tokenPrice,
        uint256 _listingPercent,
        uint256 _listingTokenPercent,
        uint256 _saleEndTimestamp,
        address _owner
    ) Ownable2Step() {
        token = IERC20(_token);
        router = IUniswapV2Router02(_router);
        softCap = _softCap;
        hardCap = _hardCap;
        tokenPrice = _tokenPrice;
        listingPercent = _listingPercent;
        listingTokenPercent = _listingTokenPercent;
        saleEnd = _saleEndTimestamp;
        _transferOwnership(_owner);
    }
    
    // Участь у presale
    receive() external payable {
        _contribute(msg.sender, msg.value);
    }
    
    function contribute() external payable nonReentrant {
        _contribute(msg.sender, msg.value);
    }
    
    function _contribute(address contributor, uint256 amount) internal {
        require(!finalized, "Presale finalized");
        require(block.timestamp < saleEnd, "Sale ended");
        require(totalRaised + amount <= hardCap, "Hard cap reached");
        require(amount > 0, "Zero contribution");
        
        contributions[contributor] += amount;
        totalRaised += amount;
        
        emit Contribution(contributor, amount);
        
        // Автоматичний листинг при hardcap
        if (totalRaised >= hardCap) {
            _finalize();
        }
    }
    
    // Завершення після закінчення продажу або при hardcap
    function finalize() external {
        require(block.timestamp >= saleEnd || totalRaised >= hardCap, "Too early");
        require(!finalized, "Already finalized");
        _finalize();
    }
    
    function _finalize() internal {
        finalized = true;
        bool success = totalRaised >= softCap;
        
        emit Finalized(success, totalRaised);
        
        if (success) {
            _listOnDEX();
        }
        // Якщо softCap не досягнуто — користувачі отримають рефанд
    }
    
    function _listOnDEX() internal {
        require(!listed, "Already listed");
        listed = true;
        
        // ETH для ліквідності
        uint256 ethForLiquidity = totalRaised * listingPercent / 100;
        
        // Токени для ліквідності
        uint256 totalTokens = token.balanceOf(address(this));
        uint256 tokensForLiquidity = totalTokens * listingTokenPercent / 100;
        
        // Апрув роутеру
        token.safeApprove(address(router), tokensForLiquidity);
        
        // Додаємо ліквідність — LP токени іють на address(0) = burn
        // Це робить ліквідність постійною та нерозривною
        (uint256 addedToken, uint256 addedETH, uint256 lpTokens) = router.addLiquidityETH{
            value: ethForLiquidity
        }(
            address(token),
            tokensForLiquidity,
            tokensForLiquidity * 95 / 100, // 5% слиппаж
            ethForLiquidity * 95 / 100,
            address(0xdead),               // LP токени спалюються = заблоковані назавжди
            block.timestamp + 600          // дедлайн 10 хвилин
        );
        
        // Отримуємо адресу пулу
        address factory = router.factory();
        liquidityPair = IUniswapV2Factory(factory).getPair(
            address(token),
            0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 // WETH
        );
        
        emit ListedOnDEX(liquidityPair, addedETH, addedToken);
        
        // Решта ETH (за вирахуванням ліквідності) — у казну
        uint256 remaining = address(this).balance;
        if (remaining > 0) {
            payable(owner()).transfer(remaining);
        }
    }
    
    // Рефанд при неудачі (softCap не досягнуто)
    function claimRefund() external nonReentrant {
        require(finalized, "Not finalized");
        require(totalRaised < softCap, "Presale successful, no refund");
        
        uint256 amount = contributions[msg.sender];
        require(amount > 0, "No contribution");
        
        contributions[msg.sender] = 0;
        payable(msg.sender).transfer(amount);
        
        emit Refunded(msg.sender, amount);
    }
    
    // Клейм токенів при успіху
    function claimTokens() external nonReentrant {
        require(finalized && listed, "Not listed yet");
        
        uint256 contribution = contributions[msg.sender];
        require(contribution > 0, "Nothing to claim");
        
        // Токени учасника (пропорційно внеску)
        uint256 participantTokens = token.balanceOf(address(this)) 
            * contribution / totalRaised;
        
        contributions[msg.sender] = 0;
        token.safeTransfer(msg.sender, participantTokens);
    }
}

Розрахунок листингової ціни

Листингова ціна на DEX визначається співвідношенням токенів та ETH у пулі при створенні. Це потрібно чітко коммуніцувати учасникам presale:

Ціна presale: 0.001 ETH за токен
Hardcap: 100 ETH
listingPercent: 70% (70 ETH у ліквідність)
listingTokenPercent: 20% (з alокації presale)

Припустимо, продано 100,000,000 токенів за 100 ETH
Токени для ліквідності: 20,000,000
ETH для ліквідності: 70 ETH

Листингова ціна: 70 / 20,000,000 = 0.0000035 ETH
Це вище ціни presale → учасники в прибутку з моменту листингу

Якщо листингова ціна нижче ціни presale — учасники presale одразу в убитку. Погано для репутації проекту. Розраховуйте параметри заздалегідь.

Листинг Uniswap V3

Uniswap V2 простіше для автолістингу (немає цінового діапазону). Uniswap V3 дозволяє зосереджену ліквідність, але вимагає вказання цінового діапазону:

import "@uniswap/v3-periphery/contracts/interfaces/INonfungiblePositionManager.sol";

function _listOnUniswapV3() internal {
    INonfungiblePositionManager posManager = INonfungiblePositionManager(0xC36442b4a4522E871399CD717aBDD847Ab11FE88);
    
    // Створюємо пул з початковою ціною
    uint160 sqrtPriceX96 = calculateSqrtPrice(ethForLiquidity, tokensForLiquidity);
    posManager.createAndInitializePoolIfNecessary(
        address(token),
        WETH_ADDRESS,
        3000, // 0.3% fee tier
        sqrtPriceX96
    );
    
    // Мінтим позицію з широким діапазоном (майже повний діапазон)
    INonfungiblePositionManager.MintParams memory params = INonfungiblePositionManager.MintParams({
        token0: address(token) < WETH_ADDRESS ? address(token) : WETH_ADDRESS,
        token1: address(token) < WETH_ADDRESS ? WETH_ADDRESS : address(token),
        fee: 3000,
        tickLower: -887220, // майже мінімум
        tickUpper: 887220,  // майже максимум
        amount0Desired: ...,
        amount1Desired: ...,
        amount0Min: 0,
        amount1Min: 0,
        recipient: address(0xdead), // спалюємо LP NFT
        deadline: block.timestamp + 600
    });
    
    posManager.mint{value: ethForLiquidity}(params);
}

Для V3 потрібен WETH wrap токенів ETH через WETH.deposit{value: amount}(). Складніше, але дозволяє більш ефективну ліквідність.

LP Lock альтернативи

Замість спалювання LP токенів (address(0xdead)) — відправка на Lock контракт з часовою блокуванням:

contract LPLocker {
    struct Lock {
        address token;      // адреса LP токена
        uint256 amount;
        uint256 unlockAt;
        address owner;
    }
    
    mapping(uint256 => Lock) public locks;
    uint256 public lockCount;
    
    function lock(address lpToken, uint256 amount, uint256 unlockTimestamp) 
        external returns (uint256 lockId) 
    {
        require(unlockTimestamp > block.timestamp, "Invalid unlock time");
        
        lockId = ++lockCount;
        IERC20(lpToken).safeTransferFrom(msg.sender, address(this), amount);
        
        locks[lockId] = Lock({
            token: lpToken,
            amount: amount,
            unlockAt: unlockTimestamp,
            owner: msg.sender
        });
    }
    
    function unlock(uint256 lockId) external {
        Lock storage lk = locks[lockId];
        require(msg.sender == lk.owner, "Not owner");
        require(block.timestamp >= lk.unlockAt, "Still locked");
        
        IERC20(lk.token).safeTransfer(lk.owner, lk.amount);
        delete locks[lockId];
    }
}

Публічний lock сервіс (UNCX, Team.Finance, PinkLock) зручніший з точки зору довіри — користувачі можуть перевірити lock у відомому інтерфейсі.

Безпека

Reentrancy: функції з ETH transfer + token transfer потенційно вразливі. nonReentrant на все публічні функції обов'язково.

Front-running листингу: MEV боти можуть front-run транзакцію листингу, купивши токени до додавання ліквідності. Якщо токен торгується до листингу (малоймовірно при правильній архітектурі) — використовуйте commit-reveal або flashbots bundle.

Slippage в addLiquidityETH: amountTokenMin та amountETHMin не повинні бути 0 — інакше sandwich-атака може додати непропорційну ліквідність. 3–5% слиппаж достатньо.

Час розробки: 2–3 тижні включаючи presale контракт, LP locker, тести на fork. Аудит обов'язковий — контракт утримує кошти учасників.