Разработка контрактов для claim-системы токенов

Проектируем и разрабатываем блокчейн-решения полного цикла: от архитектуры смарт-контрактов до запуска DeFi-протоколов, NFT-маркетплейсов и криптобирж. Аудит безопасности, токеномика, интеграция с существующей инфраструктурой.
Показано 1 из 1Все 1305 услуг
Разработка контрактов для claim-системы токенов
Средний
~2-3 дня
Часто задаваемые вопросы

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

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

Последние работы

  • image_website-b2b-advance_0.webp
    Разработка сайта компании B2B ADVANCE
    1374
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1256
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    965
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1208
  • image_logo-advance_0.webp
    Разработка логотипа компании B2B Advance
    667
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    954

Разработка контракта для claim-системы токенов

Вы развернули airdrop, но газ на деплой списка из 10 000 адресов съел половину бюджета. Или ещё хуже — кто-то нашёл способ клеймить токены дважды с одним proof. Такие проблемы встречаются в production постоянно. Claim-контракт — стандартный инструмент, но его реализация требует аккуратности. Мы, команда с 5-летним опытом в блокчейне, предлагаем создание claim-контрактов под ключ с гарантией безопасности и оптимизацией газа. В этой статье разберём, почему Merkle tree — стандарт индустрии, и как избежать дорогих ошибок.

Почему Merkle tree предпочтительнее on-chain списка?

Параметр On-chain whitelist Merkle tree
Gas при деплое (10k адресов) ~0.5 ETH ~0.01 ETH
Gas при claim ~50k (SLOAD) ~30k (SLOAD + 2x SHA3)
Хранилище 10k storage slots 1 bytes32
Возможность обновления требует миграции контракта смена корня

Экономия газа на деплое до 90% — для крупных проектов это тысячи долларов в эквиваленте.

Как реализовать Merkle-based claim?

Построение дерева (off-chain)

import { StandardMerkleTree } from "@openzeppelin/merkle-tree";

// Листья: [address, amount]
const values = [
    ["0xAddress1...", ethers.parseEther("100")],
    ["0xAddress2...", ethers.parseEther("250")],
    // ...
];

const tree = StandardMerkleTree.of(values, ["address", "uint256"]);
console.log("Merkle Root:", tree.root);

// Сохраняем дерево для генерации proofs
fs.writeFileSync("tree.json", JSON.stringify(tree.dump()));

// Для конкретного адреса генерируем proof
for (const [i, v] of tree.entries()) {
    if (v[0] === "0xAddress1...") {
        const proof = tree.getProof(i);
        console.log("Proof:", proof);
    }
}

Контракт

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

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

contract MerkleClaim is Ownable {
    IERC20 public immutable token;
    bytes32 public immutable merkleRoot;
    uint256 public immutable claimDeadline;

    // Packed bitmap для газовой эффективности вместо mapping(address => bool)
    mapping(uint256 => uint256) private claimedBitMap;

    event Claimed(address indexed account, uint256 amount, uint256 index);

    constructor(
        address _token,
        bytes32 _merkleRoot,
        uint256 _claimWindowDays
    ) Ownable(msg.sender) {
        token = IERC20(_token);
        merkleRoot = _merkleRoot;
        claimDeadline = block.timestamp + (_claimWindowDays * 1 days);
    }

    function isClaimed(uint256 index) public view returns (bool) {
        uint256 claimedWordIndex = index / 256;
        uint256 claimedBitIndex = index % 256;
        uint256 claimedWord = claimedBitMap[claimedWordIndex];
        uint256 mask = (1 << claimedBitIndex);
        return claimedWord & mask == mask;
    }

    function _setClaimed(uint256 index) private {
        uint256 claimedWordIndex = index / 256;
        uint256 claimedBitIndex = index % 256;
        claimedBitMap[claimedWordIndex] = claimedBitMap[claimedWordIndex] | (1 << claimedBitIndex);
    }

    function claim(
        uint256 index,
        address account,
        uint256 amount,
        bytes32[] calldata merkleProof
    ) external {
        require(block.timestamp <= claimDeadline, "Claim period ended");
        require(!isClaimed(index), "Already claimed");

        bytes32 leaf = keccak256(bytes.concat(keccak256(abi.encode(index, account, amount))));
        require(MerkleProof.verify(merkleProof, merkleRoot, leaf), "Invalid proof");

        _setClaimed(index);
        token.transfer(account, amount);

        emit Claimed(account, amount, index);
    }

    // Возврат неклеймленных токенов после дедлайна
    function recoverUnclaimed() external onlyOwner {
        require(block.timestamp > claimDeadline, "Claim period active");
        uint256 balance = token.balanceOf(address(this));
        token.transfer(owner(), balance);
    }
}

Использование bitmap вместо mapping(address => bool) — важная оптимизация. Один storage slot (32 байта) хранит 256 флагов. Для 10,000 участников нужно ~40 слотов вместо 10,000. Первый claim в слоте стоит 20,000 gas (SSTORE cold), последующие — 5,000 (SSTORE warm). Экономия ощутимая.

Как работает vesting claim?

Для команды и инвесторов claim обычно работает совместно с vesting. Клифф + линейная разблокировка — стандартная схема:

struct VestingSchedule {
    uint256 totalAmount;
    uint256 cliffEnd;       // timestamp конца клиффа
    uint256 vestingEnd;     // timestamp полной разблокировки
    uint256 claimed;        // уже клеймлено
}

mapping(address => VestingSchedule) public schedules;

function claimVested() external {
    VestingSchedule storage schedule = schedules[msg.sender];
    require(block.timestamp >= schedule.cliffEnd, "Cliff not reached");

    uint256 vested = _calculateVested(schedule);
    uint256 claimable = vested - schedule.claimed;
    require(claimable > 0, "Nothing to claim");

    schedule.claimed += claimable;
    token.transfer(msg.sender, claimable);
}

function _calculateVested(VestingSchedule memory s) private view returns (uint256) {
    if (block.timestamp >= s.vestingEnd) return s.totalAmount;
    if (block.timestamp < s.cliffEnd) return 0;

    uint256 vestingDuration = s.vestingEnd - s.cliffEnd;
    uint256 elapsed = block.timestamp - s.cliffEnd;
    return (s.totalAmount * elapsed) / vestingDuration;
}

Какие типичные уязвимости встречаются?

Уязвимость Последствия Решение
Double-claim без bitmap Потеря токенов при повторном claim с другим amount Bitmap с уникальным index
Griefing через claim от имени Принудительная отправка токенов на нежелательный адрес Проверка msg.sender == account
Отсутствие recoverUnclaimed Токены навсегда заперты в контракте Добавление функции возврата с таймлоком
Frontrunning proof Кража токенов через подмену account Включение account в leaf

Double-claim без bitmap

Если вместо bitmap использовать mapping(address => bool), и в списке один адрес встречается с разными amount — proof валиден для каждого варианта, флаг claimed[address] = true ставится один раз, но второй claim с другим amount тоже пройдёт. Bitmap с index как ключом исключает это: index уникален.

Griefing через claim от имени

Если claim(account, ...) вызывает не сам account — можно принудительно отправить токены на адрес, не прошедший KYC или контракт без receive(). Для протоколов с compliance requirements лучше ограничить: require(msg.sender == account).

Нет recoverUnclaimed

Токены на контракте навсегда, если deadline не обработан. Обязательно добавлять recovery функцию.

Frontrunning proof

Proof публичный, любой может его увидеть в mempool и отправить с account = свой адрес. Защита: в leaf включать account (уже реализовано выше) — proof работает только для конкретного адреса.

Multi-round claims

Для airdrops с несколькими раундами (например, retroactive + ongoing rewards) используют несколько merkle roots — по одному на раунд, или мутируемый root с timelock на обновление:

bytes32[] public merkleRoots;  // индекс = номер раунда
mapping(uint256 => mapping(uint256 => uint256)) private claimedBitMaps; // round => bitmap

function addRound(bytes32 root) external onlyOwner {
    merkleRoots.push(root);
}

Что входит в работу?

  • Исходный код смарт-контракта (Solidity 0.8.20) с полным покрытием тестами (Foundry, включая fuzzing).
  • Скрипты деплоя и верификации на Etherscan.
  • Документация по интеграции (ABI, интерфейсы, примеры вызовов).
  • Поддержка после деплоя: консультации, помощь в обновлении Merkle root.
  • Optional: внешний аудит безопасности (от 2 недель).

Процесс разработки claim-контракта

  1. Анализ требований — определяем список участников, суммы, vesting-расписание, необходимость multi-round.
  2. Проектирование архитектуры — выбираем Merkle tree, bitmap, vesting, дополнительные функции.
  3. Реализация — пишем смарт-контракт на Solidity 0.8.20 с полным покрытием тестами (Foundry, включая fuzzing).
  4. Аудит — проводим внутренний review + опционально внешний аудит (от 2 недель).
  5. Развертывание — деплой с оптимизированными параметрами (твердая гарантия gas efficiency).
  6. Документация и интеграция — предоставляем руководство по интеграции и поддержку.

Сроки: от 5 рабочих дней до 3 недель в зависимости от сложности. Стоимость рассчитывается индивидуально.

Мы реализовали уже более 30 claim-контрактов для проектов на Ethereum, Arbitrum и Polygon. Свяжитесь с нами для консультации — оценим проект за 2 дня. Закажите разработку под ключ и получите надёжный claim-контракт с защитой от всех типичных уязвимостей.

Разработка токенов: ERC-20, токеномика, вестинг

«ERC-20 — это просто» — фраза, после которой начинаются проблемы. Базовый transfer написать несложно. Но токен, у которого через шесть месяцев не происходит инфляционный коллапс, governance работает как задумано, а вестинг нельзя обойти через хитрую схему с делегированием — это уже проектирование.

ERC-20: что под капотом

Стандарт ERC-20 — девять функций. Сложность начинается с расширений:

ERC-20Permit (EIP-2612) — gasless approve через подпись. Пользователь подписывает permit(owner, spender, value, deadline, v, r, s) off-chain, spender вызывает permit() + transferFrom() в одной транзакции. Это убирает отдельный approve step. Но: подпись можно перехватить и использовать — нужен deadline и проверка nonce.

ERC-20Votes (EIP-5805) — snapshot балансов для governance. Checkpoint-система хранит историю балансов по номеру блока. getPastVotes(address, blockNumber) — баланс на момент создания proposal, а не текущий. Это предотвращает flash loan governance attack: нельзя занять токены и проголосовать ими в одной транзакции.

Rebasing токены (stETH, Ampleforth) — balanceOf меняется автоматически через изменение internal shares ratio. Высокая сложность интеграции: большинство DeFi протоколов не работают корректно с rebasing без wrapping в non-rebasing версию.

Fee-on-transfer токены — при каждом transfer снимается процент. Ломают AMM расчёты: пул получает меньше, чем ожидал. Uniswap v2/v3 не поддерживают fee-on-transfer нативно — нужны специальные pair/router.

Tokenomics: где математика превращается в экономику

Токеномика — это не таблица в Excel с суммой 100%. Это модель инцентивов, которая либо работает в долгосрочной перспективе, либо создаёт давление продаж которое убьёт проект.

Emission schedule и инфляция

Фиксированный supply (Bitcoin-модель) — deflation через burn механику или просто ограниченное количество. Подходит для store-of-value или utility токенов с ограниченным спросом на новые токены.

Инфляционная модель (Ethereum post-Merge, Curve) — новые токены выпускаются для стимулирования участников. Нужен баланс: emission должен быть ниже или равен value capture протоколом. Если протокол зарабатывает $100k/месяц, а эмиссия в рыночной стоимости $500k/месяц — постоянное давление продаж неизбежно.

Halving schedules (Bitcoin-style) — уменьшение emission со временем. Создаёт предсказуемость, но требует что утилити токена росла чтобы компенсировать падающие rewards для stakers/validators.

Supply distribution

Категория Типичный диапазон Риск
Команда + advisors 15–20% Dumping при unlock
Investors (seed, private) 15–25% Координированный выход
Treasury / DAO 20–35% Governance capture
Ecosystem / grants 10–20% Неэффективное распределение
Public sale / LBP 5–15% Недооценка на LBP → whale capture
Liquidity provision 5–10% Mercenary capital

Нет универсальной формулы. Есть принцип: никакой одной сущности не должно принадлежать >33% voting power при запуске. Иначе governance — фикция.

Vesting контракты: детали имеют значение

Linear vesting с cliff — стандарт для команды и инвесторов. cliff — период после TGE, в течение которого ничего не доступно. После cliff: линейный unlock до duration.

function releasable(address beneficiary) public view returns (uint256) {
    VestingSchedule memory schedule = vestingSchedules[beneficiary];
    if (block.timestamp < schedule.cliff) return 0;

    uint256 elapsed = block.timestamp - schedule.cliff;
    uint256 vestingDuration = schedule.duration - (schedule.cliff - schedule.start);
    uint256 vested = schedule.totalAmount * elapsed / vestingDuration;

    return vested - schedule.released;
}

Типичные ошибки при реализации:

Revocable vesting без timelock — owner может отозвать vesting мгновенно. Если owner key скомпрометирован или команда недобросовестна — все unvested токены могут быть отозваны. Решение: revocation через multisig + governance vote.

Cliff не блокирует governance права — если используется ERC-20Votes, recipient может делегировать voting power с первого дня, даже если токены ещё не unlocked. Нужно явно разделить voting power и claim logic.

Отсутствие emergency pause — если обнаружена уязвимость в vesting контракте, нужна возможность приостановить claim. Pausable + timelock на unpause.

Liquidity Bootstrapping

Запуск ликвидности — критический момент. Три основных подхода:

Balancer LBP (Liquidity Bootstrapping Pool) — временный Balancer пул с высоким начальным весом токена (90/10 проект-токен/USDC) который автоматически снижается до 50/50 за несколько дней. Создаёт нисходящее ценовое давление, препятствуя ботам скупить всё по одной цене. После LBP ликвидность переносится в постоянный пул.

Fjord Foundry — специализированная платформа для LBP и fair launches. Меньше операционного overhead чем прямая интеграция с Balancer.

Uniswap v3 с ограниченным range — добавить ликвидность в узкий диапазон вокруг начальной цены. Высокая capital efficiency, но требует активного управления range.

TWAMM (Time-Weighted AMM) — механика для постепенной продажи/покупки больших объёмов без slippage. Paradigm предложил, реализован в FraxSwap.

Governance токены и voting механики

OpenZeppelin Governor — стандартная реализация on-chain governance. Модульная архитектура: GovernorVotes для counting, GovernorTimelockControl для timelock execution, GovernorSettings для изменяемых параметров.

Quorum — минимальный процент supply для валидности голосования. Слишком высокий quorum = apathy failure (не набирается голосов). Слишком низкий = whale capture. Compound установил quorum 400k COMP (4% supply) — на практике достигается редко без координации крупных holders.

Flash loan governance attack — атакующий занимает токены через flash loan, делегирует их себе, создаёт proposal или голосует, возвращает токены. ERC-20Votes с snapshot по номеру блока полностью блокирует это: нужно иметь токены на момент создания snapshot, который берётся в момент создания proposal.

Delegation — пользователи с малыми балансами часто не голосуют. Liquid delegation (как в Optimism) позволяет делегировать voting power конкретным addresses (delegates) без передачи ownership токенов.

Стек для токен-разработки

Контракты: Solidity 0.8.x, OpenZeppelin Contracts 5.x (ERC20, ERC20Permit, ERC20Votes, Governor, TimelockController, TokenVesting)

Аудит токеномики: Python модели с симуляцией emission/demand, cadCAD для complex systems modeling

Деплой и управление: Foundry scripts, Gnosis Safe для treasury, OpenZeppelin Defender для автоматизации

Аналитика: Dune Analytics для on-chain метрик, Token Terminal для protocol revenue

Процесс

Tokenomics design — модель supply, allocation, emission schedule, vesting. Стресс-тестирование сценариев (bear market, whale exit, governance capture attempt).

Контракт разработка — ERC-20 + extensions, vesting, governance. Foundry fuzz тесты на vesting calculations, governance thresholds.

Аудит — особое внимание на governance attack vectors, vesting bypass, permit replay attacks.

LBP / launch — выбор механики, настройка параметров, мониторинг первых 24 часов.

Post-launch — мониторинг supply distribution через Dune, governance participation metrics, treasury management.

Сроки

  • ERC-20 с permit и basic governance: 2–3 недели
  • Vesting контракт с revocation и cliff: 2–4 недели
  • Полный governance (Governor + Timelock + Token): 4–7 недель
  • Токен + LBP + governance + vesting: 8–14 недель