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

Проектируем и разрабатываем блокчейн-решения полного цикла: от архитектуры смарт-контрактов до запуска DeFi-протоколов, NFT-маркетплейсов и криптобирж. Аудит безопасности, токеномика, интеграция с существующей инфраструктурой.
Показано 1 из 1 услугВсе 1306 услуг
Разработка системы голландского аукциона для токенов
Средняя
~3-5 рабочих дней
Часто задаваемые вопросы
Направления блокчейн-разработки
Этапы блокчейн-разработки
Последние работы
  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1221
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1163
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    855
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1056
  • image_logo-advance_0.png
    Разработка логотипа компании B2B Advance
    561
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    828

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

Классический токен-сейл с фиксированной ценой создаёт одну из двух проблем: либо цена слишком низкая и все токены уходят в первые секунды (gas war, несправедливое распределение в пользу MEV-ботов), либо слишком высокая — и сейл не заполняется. Голландский аукцион решает обе проблемы за счёт механизма ценообразования: цена начинается высокой и снижается до тех пор, пока спрос не встретит предложение. Цена сейла — это рыночный клиринговый уровень, а не произвольное число из whitepaper.

Именно так Paradigm и a16z проводили первые крупные токен-дистрибьюции в DeFi-пространстве. Gnosis Protocol использует вариацию этого механизма для batch аукционов.

Механика контракта

Линейное vs экспоненциальное снижение цены

Простейшая реализация — линейная функция:

price(t) = startPrice - (startPrice - endPrice) * (t - startTime) / duration

Проблема линейной модели: большую часть времени цена снижается медленно, потому что равномерна во всём диапазоне. Участники ждут минимума — аукцион не заполняется в середине, все пытаются купить в конце.

Экспоненциальное снижение более реалистично описывает ценовое поведение рынка:

function getCurrentPrice() public view returns (uint256) {
    if (block.timestamp <= startTime) return startPrice;
    if (block.timestamp >= endTime) return endPrice;
    
    uint256 elapsed = block.timestamp - startTime;
    uint256 duration = endTime - startTime;
    
    // Экспоненциальное снижение через 18-decimal fixed point
    // price = startPrice * e^(-k * t/T)
    // Аппроксимируем через integer arithmetic
    uint256 priceDelta = startPrice - endPrice;
    uint256 decayFactor = PRBMath.exp(-int256(decayRate * elapsed / duration));
    
    return endPrice + priceDelta * decayFactor / 1e18;
}

На практике большинство production Dutch Auction контрактов используют дискретные шаги снижения (step-down) вместо непрерывной функции — это проще для понимания участниками и дешевле в газе.

Commit-reveal для защиты от MEV

В стандартном Dutch Auction все видят текущую цену, и как только она становится "справедливой", все пытаются купить одновременно. MEV-боты front-run реальных покупателей, выставляя более высокий gas price. Результат: газовая война, но уже при клиринговой цене вместо стартовой — немного лучше, но не идеально.

Решение — commit-reveal в Dutch Auction: участники отправляют encrypted commitment (hash от суммы и salt) без раскрытия намерения. После завершения фазы commitment — reveal фаза. Клиринговая цена рассчитывается по совокупному спросу.

Это сложнее в реализации, но устраняет front-running полностью. GnosisDAO использовал подобную схему для своих аукционов.

Ключевые контрактные параметры

struct AuctionConfig {
    uint256 startPrice;       // Максимальная цена (например, 1 ETH за токен)
    uint256 endPrice;         // Минимальная цена (например, 0.1 ETH)
    uint256 startTime;        // Unix timestamp начала
    uint256 endTime;          // Unix timestamp конца
    uint256 totalTokens;      // Количество токенов на продажу
    uint256 minBidAmount;     // Минимальная покупка
    bool allowWhitelist;      // Ограничить до whitelist
    bytes32 merkleRoot;       // Merkle root для whitelist
}

Whitelist через Merkle Proof

Если аукцион ограничен для определённых адресов:

function bid(uint256 amount, bytes32[] calldata merkleProof) external payable {
    if (config.allowWhitelist) {
        bytes32 leaf = keccak256(abi.encodePacked(msg.sender));
        require(
            MerkleProof.verify(merkleProof, config.merkleRoot, leaf),
            "Not whitelisted"
        );
    }
    
    uint256 currentPrice = getCurrentPrice();
    uint256 tokenAmount = msg.value * 1e18 / currentPrice;
    require(tokenAmount >= config.minBidAmount, "Below minimum");
    require(tokensSold + tokenAmount <= config.totalTokens, "Exceeds supply");
    
    tokensSold += tokenAmount;
    bids[msg.sender] += tokenAmount;
    
    emit BidPlaced(msg.sender, tokenAmount, currentPrice, msg.value);
}

Возврат переплаты: клиринговая цена

В классическом Dutch Auction участники платят цену момента покупки. В Fair Dutch Auction (DutchX, Gnosis) — все платят одну клиринговую цену, даже те, кто купил раньше по более высокой. Разница возвращается.

Это справедливее, но сложнее в реализации: нужно дождаться конца аукциона, рассчитать клиринговую цену, и дать каждому участнику забрать refund через отдельный claim.

function claim() external {
    require(auctionEnded, "Auction not ended");
    uint256 userBid = bids[msg.sender];
    require(userBid > 0, "No bid");
    
    uint256 paid = payments[msg.sender];
    uint256 cost = userBid * clearingPrice / 1e18;
    uint256 refund = paid - cost;
    
    bids[msg.sender] = 0;
    payments[msg.sender] = 0;
    
    // Переводим токены
    token.transfer(msg.sender, userBid);
    
    // Возвращаем переплату
    if (refund > 0) {
        (bool success, ) = msg.sender.call{value: refund}("");
        require(success, "Refund failed");
    }
}

Типичные ошибки и уязвимости

Ошибки расчёта клиринговой цены. Если tokensSold не достиг totalTokens — аукцион закрылся по endPrice. Если достиг раньше — клиринговая цена это цена момента заполнения. Контракт должен корректно обрабатывать оба сценария, иначе либо пользователи не получат refund, либо контракт отдаст больше токенов, чем должен.

Округление в пользу контракта. При делении wei на цену возникают remainder. Всегда округляем количество токенов вниз, сохраняем dust как treasury или включаем в burn-механизм.

Reentrancy в claim(). ETH-refund перед или вместе с transfer токенов — классическая точка reentrancy. Обновляем bids[msg.sender] = 0 до любых внешних вызовов.

Отсутствие паузы. Если обнаружена ошибка во время аукциона — нужна экстренная остановка. Функция pause() с multisig-контролем, которая замораживает новые bids, но не блокирует claim для уже сделанных.

Сроки разработки: 3-5 рабочих дней для базового Dutch Auction, до 2 недель для Fair Dutch Auction с commit-reveal. Стоимость рассчитывается индивидуально.