Розробка IDO-платформи

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

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

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

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

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

Розробка IDO-платформи

IDO (Initial DEX Offering) — механізм первинного розміщення токенів через децентралізований обмін. Технічно це звучить простіше, ніж є насправді. Основна проблема будь-якого token launch — front-running та MEV: боти мониторять mempool і купують токени раніше реальних покупців, одразу дампять ціну. Хороша IDO-платформа — це в першу чергу система захисту від цього, а не просто "контракт з кнопкою купити".

Моделі IDO-платформ

Перед проектуванням потрібно вибрати фундаментальну модель:

Fixed price sale — найпростіша: ціна фіксована, whitelist учасників. Проблема: при недооцінці проекту токени выкупаються ботами в перший блок. Потребує строгого whitelist + commit-reveal або часових слотів.

Dutch auction — ціна стартує високою і знижується до моменту повної продажи. Дає fair price discovery. Проблема: складно пояснити користувачам, висока ризик манипуляцій на останніх хвилинах.

Overflow/refund model (IDO за образцом Binance Launchpad) — користувачі "вносять" будь-яку суму, iteroverного розподіл пропорційно вкладу. Переплата повертається. Чесно, але потребує складної логіки розрахунку аллокацій.

Liquidity Bootstrapping Pool (LBP) — Balancer-based механізм. Початкове співвідношення ваг у пулі (напр., 95/5 token/USDC) змінюється по часу до кінцевого значення (50/50). Ціна стартує високою і знижується. Хороший захист від ботів за рахунок high initial price.

Архітектура смарт-контрактів

Core: IDO Pool Contract

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

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts/access/AccessControl.sol";
import "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol";

contract IDOPool is ReentrancyGuard, AccessControl {
    using SafeERC20 for IERC20;
    
    bytes32 public constant ADMIN_ROLE = keccak256("ADMIN_ROLE");
    
    struct PoolConfig {
        IERC20 saleToken;
        IERC20 paymentToken;       // USDC, USDT або нативна монета
        uint256 tokenPrice;         // у paymentToken, 18 decimals
        uint256 hardCap;            // максимальний raise у paymentToken
        uint256 softCap;            // мінімальний raise для успіху
        uint256 minAllocation;      // мінімальна покупка на кошелек
        uint256 maxAllocation;      // максимальна покупка на кошелек
        uint64  startTime;
        uint64  endTime;
        uint64  claimTime;          // коли відкривається claim
        bytes32 whitelistMerkleRoot;
        bool    isPublic;           // false = тільки whitelist
    }
    
    struct UserInfo {
        uint256 contributed;        // paymentToken сума внесена
        uint256 tokenAllocation;    // saleToken отримає
        bool    claimed;
        bool    refunded;
    }
    
    PoolConfig public config;
    mapping(address => UserInfo) public userInfo;
    uint256 public totalRaised;
    PoolStatus public status;
    
    enum PoolStatus { PENDING, ACTIVE, FILLED, FAILED, FINALIZED }
    
    event Contributed(address indexed user, uint256 amount, uint256 tokenAllocation);
    event Claimed(address indexed user, uint256 amount);
    event Refunded(address indexed user, uint256 amount);
    
    function contribute(
        uint256 paymentAmount,
        bytes32[] calldata merkleProof
    ) external nonReentrant {
        require(status == PoolStatus.ACTIVE, "Pool not active");
        require(block.timestamp >= config.startTime, "Not started");
        require(block.timestamp <= config.endTime, "Ended");
        
        // whitelist перевірка через Merkle proof
        if (!config.isPublic) {
            bytes32 leaf = keccak256(abi.encodePacked(msg.sender));
            require(
                MerkleProof.verify(merkleProof, config.whitelistMerkleRoot, leaf),
                "Not whitelisted"
            );
        }
        
        UserInfo storage user = userInfo[msg.sender];
        uint256 newContribution = user.contributed + paymentAmount;
        
        require(newContribution >= config.minAllocation, "Below min allocation");
        require(newContribution <= config.maxAllocation, "Exceeds max allocation");
        require(totalRaised + paymentAmount <= config.hardCap, "Exceeds hard cap");
        
        config.paymentToken.safeTransferFrom(msg.sender, address(this), paymentAmount);
        
        uint256 tokenAmount = (paymentAmount * 1e18) / config.tokenPrice;
        user.contributed += paymentAmount;
        user.tokenAllocation += tokenAmount;
        totalRaised += paymentAmount;
        
        if (totalRaised >= config.hardCap) {
            status = PoolStatus.FILLED;
        }
        
        emit Contributed(msg.sender, paymentAmount, tokenAmount);
    }
    
    function claim() external nonReentrant {
        require(status == PoolStatus.FINALIZED, "Not finalized");
        require(block.timestamp >= config.claimTime, "Claim not open");
        
        UserInfo storage user = userInfo[msg.sender];
        require(user.tokenAllocation > 0, "Nothing to claim");
        require(!user.claimed, "Already claimed");
        
        user.claimed = true;
        config.saleToken.safeTransfer(msg.sender, user.tokenAllocation);
        
        emit Claimed(msg.sender, user.tokenAllocation);
    }
    
    function refund() external nonReentrant {
        require(status == PoolStatus.FAILED, "Pool not failed");
        
        UserInfo storage user = userInfo[msg.sender];
        require(user.contributed > 0, "Nothing to refund");
        require(!user.refunded, "Already refunded");
        
        user.refunded = true;
        uint256 refundAmount = user.contributed;
        config.paymentToken.safeTransfer(msg.sender, refundAmount);
        
        emit Refunded(msg.sender, refundAmount);
    }
    
    function finalize() external onlyRole(ADMIN_ROLE) {
        require(
            status == PoolStatus.ACTIVE || status == PoolStatus.FILLED,
            "Cannot finalize"
        );
        require(block.timestamp > config.endTime, "Not ended");
        
        if (totalRaised >= config.softCap) {
            status = PoolStatus.FINALIZED;
            // передача зібраних коштів проекту
            config.paymentToken.safeTransfer(projectWallet, totalRaised);
        } else {
            status = PoolStatus.FAILED;
            // повернення saleToken проекту
            uint256 unsoldTokens = config.saleToken.balanceOf(address(this));
            config.saleToken.safeTransfer(projectWallet, unsoldTokens);
        }
    }
}

Merkle Tree Whitelist

Зберігати whitelist on-chain дорого — 1000 адресс = ~$30-50 в gas при деплое на Ethereum. Merkle tree вирішує це: зберігається тільки 32-байтний root, користувач надає proof при транзакції:

import { MerkleTree } from "merkletreejs";
import keccak256 from "keccak256";

function buildWhitelist(addresses: string[]): { root: string; proofs: Map<string, string[]> } {
    const leaves = addresses.map(addr => keccak256(addr));
    const tree = new MerkleTree(leaves, keccak256, { sortPairs: true });
    const root = tree.getHexRoot();
    
    const proofs = new Map<string, string[]>();
    for (const addr of addresses) {
        proofs.set(addr, tree.getHexProof(keccak256(addr)));
    }
    
    return { root, proofs };
}

IDO Factory

Для платформи с кількома одночасними IDO необхідний Factory pattern:

contract IDOFactory {
    address[] public pools;
    mapping(address => bool) public isPool;
    
    event PoolCreated(address indexed pool, address indexed projectToken);
    
    function createPool(IDOPool.PoolConfig calldata config) external returns (address pool) {
        pool = address(new IDOPool(config, msg.sender, address(this)));
        pools.push(pool);
        isPool[pool] = true;
        emit PoolCreated(pool, address(config.saleToken));
    }
}

Tier-система та staking

Професійні IDO-платформи (DAO Maker, Polkastarter, TrustPad) використовують tier-систему: користувачі стейкають платформенний токен і отримують гарантовану аллокацію пропорційно рівню:

contract TierSystem {
    IERC20 public platformToken;
    
    struct Tier {
        string name;
        uint256 minStake;        // мінімальний stake для tier
        uint256 allocationMultiplier; // у basis points (10000 = 100%)
        uint256 guaranteedAllocation; // гарантована сума в USD
    }
    
    Tier[] public tiers;
    mapping(address => uint256) public stakedAmount;
    mapping(address => uint256) public stakeTimestamp;
    
    uint256 public lockPeriod = 7 days; // lock перед IDO
    
    function getUserTier(address user) public view returns (uint256 tierIndex) {
        uint256 staked = stakedAmount[user];
        for (uint256 i = tiers.length; i > 0; i--) {
            if (staked >= tiers[i-1].minStake) return i-1;
        }
        return type(uint256).max; // немає tier
    }
}

Захист від ботів та MEV

Commit-reveal scheme: користувачи в фазі 1 відправляють hash(amount, nonce, address) без розкриття суми. У фазі 2 розкривають реальні дані. Боти не знають итогову суму до moment reveal.

FCFS з time slots: кожному tier назначено своєї часового вікна. Tier 1 купує з 12:00 до 12:05, Tier 2 — з 12:05 до 12:15. Боти першого рівня не можуть опередити стейкерів вищого tier.

Anti-snipe: перші N блоків після відкриття sales — 100% tax на sell для утримання снайперів. Це спірна мера, але часто застосовується.

Private mempool / Flashbots Protect: для EVM-сетей submission через Flashbots RPC виключає транзакції з публічного mempool и захищает від front-running.

Vesting при claim

Мгновенний cliff release всіх токенів при claim створює немедлений sell pressure. Правильна схема: TGE unlock 20%, решта по вестингу. Реалізується через інтеграцію з vesting-контрактом при finalize:

function finalize() external {
    // ...
    // створюємо vesting schedules для кожного учасника
    for (address participant in participants) {
        uint256 tgeAmount = userInfo[participant].tokenAllocation * TGE_PERCENT / 100;
        uint256 vestingAmount = userInfo[participant].tokenAllocation - tgeAmount;
        
        vestingContract.createSchedule(participant, tgeAmount, 0, 0, 1);
        vestingContract.createSchedule(participant, vestingAmount, claimTime, 0, vestingDuration);
    }
}

Інфраструктура платформи

Крім смарт-контрактів, IDO-платформа потребує:

Компонент Технологіі
Frontend dApp React + wagmi/viem, Web3Modal
KYC/AML Sumsub, Synaps або custom
Whitelist management API + Merkle tree generation
Real-time updates WebSocket + event listening
Admin panel Pool management, allocation calculator
Analytics The Graph subgraph для on-chain даних
Notifications Email + Telegram при відкритті pool

KYC інтеграція — обов'язкова тема для юрисдикцій з регулюванням (EU MiCA, US). Суть: KYC-провайдер верифіцює користувача, передає підпис/статус, який перевіряється перед whitelist реєстрацією або on-chain.