Розробка системи airdrop-кампаній

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

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

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

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

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

Розроблення системи airdrop-кампанії

Технічно airdrop — це розподіл токенів. На практиці — маркетинговий інструмент, який або створює довгостроків учасників протоколу, або генерує одноразовий тиск продажи. Різниця визначається не сумою розподіляємих токенів, а тим, хто їх отримує та на яких умовах.

Merkle Distributor: стандарт для масового розподілу

Наївний підхід — вызвати transfer на кожну адресу. При 100,000 одержувачів це 100,000 транзакцій, великий gas, гарантована відмова. Правильний підхід — Merkle Distributor, де одержувачі самі вилучають токени.

Off-chain: формуємо список (address → amount), будуємо Merkle tree, публікуємо root в контракт.

On-chain: користувач надає Merkle proof, контракт перевіряє та видає токени.

contract MerkleDistributor {
    address public immutable token;
    bytes32 public immutable merkleRoot;

    // Bitfield для відслідковування claimed — економія gas vs mapping(address => bool)
    mapping(uint256 => uint256) private claimedBitMap;

    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] |= (1 << claimedBitIndex);
    }

    function claim(
        uint256 index,
        address account,
        uint256 amount,
        bytes32[] calldata merkleProof
    ) external {
        require(!isClaimed(index), "Already claimed");

        // Перевірка proof
        bytes32 node = keccak256(abi.encodePacked(index, account, amount));
        require(
            MerkleProof.verify(merkleProof, merkleRoot, node),
            "Invalid proof"
        );

        _setClaimed(index);
        IERC20(token).safeTransfer(account, amount);
        emit Claimed(index, account, amount);
    }
}

Bitfield vs mapping: зберігання claimed status в packed bitfield (256 статусів в одному uint256 slot) економить ~80% gas на SSTORE/SLOAD порівняно з mapping(address => bool).

Типи airdrop та їхнє застосування

Retroactive Airdrop

Розподіл для існуючих користувачів протоколу — найефективніший тип. Uniswap UNI, Arbitrum ARB, Optimism OP — усі були retroactive для ранніх користувачів.

Критерії eligibility визначаються on-chain аналізом:

  • Обсяг транзакцій за період
  • Кількість унікальних контрактів, з якими взаємодіяв адрес
  • Давність першої транзакції
  • Утримання позицій (не just-in-time farming)

Sybil filtering — головне технічне завдання. Одна людина з 1000 адрес не повинна отримати в 1000 раз більше.

Індикатори Sybil-кластерів:

  • Адреси отримують ETH з одного джерела фінансування
  • Транзакції з однаковими паттернами (одна й та ж час доби, одні й ті ж протоколи)
  • Пусті адреси між діями (gas station pattern)
  • Мінімальні транзакції для виконання мінімальних критеріїв

Інструменти Sybil detection: Chainanalysis Sybil (платний), власний SQL аналіз через Dune Analytics або indexed node.

Task-based система

Користувач виконує завдання → отримує allocation. Типові завдання:

  • Follow в Twitter, Discord, Telegram
  • Testnet транзакції
  • Referral нових користувачів
  • Участь у governance голосуванні

Проблема: легко фармяться ботами. Завдання повинні потребувати on-chain активності, яку важко симулювати в масштабі.

Інтеграція з Galxe / Layer3 — готові платформи для task-based кампаній. API для верифікації on-chain задач. Мінус: платформа бере fee і користувачи залишаються на платформі, а не на вашому сайті.

Vested Airdrop

Отримані токени не вилучаються одразу, а вестируються. Linear vesting 6–12 місяців.

contract VestedAirdrop is MerkleDistributor {
    uint256 public immutable vestingStart;
    uint256 public immutable vestingDuration;

    mapping(address => uint256) public claimed;
    mapping(address => uint256) public totalAllocated;

    function claimVested(
        uint256 index,
        address account,
        uint256 totalAmount,
        bytes32[] calldata merkleProof
    ) external {
        // Перевірка allocation (якщо перший claim)
        if (totalAllocated[account] == 0) {
            _verifyAndSetAllocation(index, account, totalAmount, merkleProof);
        }

        uint256 vested = _vestedAmount(account);
        uint256 claimable = vested - claimed[account];
        require(claimable > 0, "Nothing to claim");

        claimed[account] += claimable;
        IERC20(token).safeTransfer(account, claimable);
        emit VestedClaimed(account, claimable);
    }

    function _vestedAmount(address account) internal view returns (uint256) {
        if (block.timestamp < vestingStart) return 0;
        uint256 elapsed = block.timestamp - vestingStart;
        if (elapsed >= vestingDuration) return totalAllocated[account];
        return totalAllocated[account] * elapsed / vestingDuration;
    }
}

Cliff + linear: перші 3 місяці нічого (cliff), потім linear vest 9 місяців. Знижує одразу dump, створює довгостроків holders.

Система начисляння балів

Для складних кампаній з багатьма діями — off-chain система балів:

interface UserScore {
  address: string;
  totalPoints: number;
  breakdown: {
    earlyAdopter: number;        // перші 1000 користувачів
    volumeScore: number;         // на основі торгового обсягу
    loyaltyScore: number;        // тривалість використання
    referrals: number;           // успішні рефералі
    governanceVotes: number;     // участь у голосуванні
  };
}

// Allocation = f(points) з diminishing returns для anti-whale механізму
function calculateAllocation(points: number, totalPoints: number): bigint {
  // Квадратний корінь для зменшення whale домінування
  const sqrtScore = Math.sqrt(points);
  const totalSqrtScore = /* sum of sqrt scores for all users */ 0;
  const allocation = (TOTAL_AIRDROP_AMOUNT * BigInt(Math.floor(sqrtScore * 1e18)))
    / BigInt(Math.floor(totalSqrtScore * 1e18));
  return allocation;
}

Square root formula (використовується в quadratic voting): зменшує розрив між крупними та дрібними учасниками. Whale з 10,000 points отримає не в 10x більше ніж користувач з 1,000 points, а тільки ~в 3.16x.

Gas optimization для mass claiming

При мільйонах claimers кожна економлена gas — це гроші користувачів:

EIP-2612 Permit — замість окремої approve транзакції, якщо користувачу потрібно щось зробити з токенами одразу після claim (наприклад, застейкати):

function claimAndStake(
    uint256 index,
    uint256 amount,
    bytes32[] calldata proof,
    uint256 deadline,
    uint8 v, bytes32 r, bytes32 s
) external {
    // Claim токени
    claim(index, msg.sender, amount, proof);

    // Permit для approve без окремої транзакції
    IERC20Permit(token).permit(msg.sender, address(staking), amount, deadline, v, r, s);

    // Стейкаємо одразу
    staking.stakeFor(msg.sender, amount);
}

Batch claiming — якщо користувач має allocations у кількох раундах:

function claimMultiple(
    uint256[] calldata indices,
    uint256[] calldata amounts,
    bytes32[][] calldata proofs
) external {
    uint256 totalAmount;
    for (uint i = 0; i < indices.length; i++) {
        // верифікація кожного proof
        totalAmount += amounts[i];
    }
    // один transfer замість N
    IERC20(token).safeTransfer(msg.sender, totalAmount);
}

Frontend для airdrop

Eligibility checker — введення адреси → перевірка через API (backend має список) або прямо з Merkle tree (якщо опублікований повністю):

async function checkEligibility(address: string) {
  // Нормалізація адреси
  const normalizedAddress = ethers.getAddress(address);

  // Отримуємо дані з API або опублікованого snapshot
  const allocation = await fetchAllocation(normalizedAddress);

  if (!allocation) {
    return { eligible: false, amount: 0n, proof: [] };
  }

  const proof = getMerkleProof(merkleTree, allocation.index, normalizedAddress, allocation.amount);

  // Перевіряємо не клеймив ли вже
  const alreadyClaimed = await distributor.isClaimed(allocation.index);

  return {
    eligible: true,
    amount: allocation.amount,
    proof,
    alreadyClaimed
  };
}

Snapshot публікація: дані Merkle tree повинні бути публічно доступні (GitHub, IPFS) щоб користувачі могли незалежно верифікувати свою allocation. Непубличний snapshot — червоний прапор для community.

Expiry та unclaimed токени

Завжди встановлюйте expiry на період claim (зазвичай 1 рік). Unclaimed токени повертаються у казначейство або спалюються:

uint256 public constant EXPIRY = 365 days;
uint256 public immutable deployedAt;

function recoverUnclaimed() external onlyOwner {
    require(block.timestamp > deployedAt + EXPIRY, "Not expired");
    uint256 remaining = IERC20(token).balanceOf(address(this));
    IERC20(token).safeTransfer(treasury, remaining);
}