Розробка контрактів для airdrop

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

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

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

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

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

Розробка контракту для airdrop

Класична помилка — реалізувати airdrop через loop з on-chain відправкою кожному адресу. При 10k одержувачів це 10k транзакцій, десятки тисяч доларів газу та кілька годин роботи. Правильний підхід — Merkle distributor: один раз on-chain встановлюєте Merkle root, кожен одержувач сам клеймить свої токени, платячи за своє включення.

Merkle distributor: стандартна реалізація

Список адрес та сум → будуєте off-chain Merkle tree → публікуєте root on-chain → користувач надає proof свого leaf → контракт верифікує та переводить токени.

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

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

contract MerkleAirdrop {
    IERC20 public immutable token;
    bytes32 public immutable merkleRoot;
    
    mapping(uint256 => uint256) private claimedBitMap;
    
    constructor(address _token, bytes32 _merkleRoot) {
        token = IERC20(_token);
        merkleRoot = _merkleRoot;
    }
    
    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 claim(
        uint256 index,
        address account,
        uint256 amount,
        bytes32[] calldata merkleProof
    ) external {
        require(!isClaimed(index), "Already claimed");
        
        bytes32 leaf = keccak256(bytes.concat(
            keccak256(abi.encode(index, account, amount))
        )); // Double-hash проти second preimage attack
        
        require(
            MerkleProof.verify(merkleProof, merkleRoot, leaf),
            "Invalid proof"
        );
        
        _setClaimed(index);
        require(token.transfer(account, amount), "Transfer failed");
        emit Claimed(index, account, amount);
    }
}

Чому double-hash leaf. Без нього — second preimage attack: зловмисник може підмінити leaf даними, які совпадають з intermediate node у дереві. OpenZeppelin використовує keccak256(bytes.concat(keccak256(abi.encode(...)))) саме для цього.

Bit packing для claimed. Замість mapping(address => bool) використовуємо bit array: 256 статусів в одному uint256 slot. Економія SLOAD газу ощутима при великій кількості кламів.

Генерація Merkle tree off-chain

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

// Список [index, address, amount]
const values = [
  [0, "0xAddress1...", ethers.parseEther("100")],
  [1, "0xAddress2...", ethers.parseEther("250")],
  // ...тисячі записів
]

const tree = StandardMerkleTree.of(values, ["uint256", "address", "uint256"])

console.log("Merkle Root:", tree.root) // → деплоїти у контракт

// Proof для конкретної адреси
for (const [i, v] of tree.entries()) {
  if (v[1] === "0xAddress1...") {
    const proof = tree.getProof(i)
    // proof — масив bytes32, потрібен користувачу для claim
  }
}

// Зберігаємо все дерево для раздачи proofs через API
import fs from "fs"
fs.writeFileSync("tree.json", JSON.stringify(tree.dump()))

Proofs роздаєте через простий API: GET /proof?address=0x... → повертає { index, amount, proof[] }. Користувач вставляє ці дані в UI та викликає claim.

Додаткові міркування

Expiry. Додайте deadline після якого unclaimed токени повертаються власнику. Інакше токени locked назавжди.

uint256 public immutable claimDeadline;

function claim(...) external {
    require(block.timestamp <= claimDeadline, "Airdrop expired");
    // ...
}

function recoverExpired() external onlyOwner {
    require(block.timestamp > claimDeadline, "Not expired yet");
    token.transfer(owner(), token.balanceOf(address(this)));
}

Vesting airdrop. Якщо токени не повинні бути доступні одразу — лінійний vesting прямо у distributor контракті: claim → токени на vesting schedule → claim vested по мірі часу.

Газ для користувачів. На mainnet клейм коштує ~$2-10. Розгляньте деплой на L2 (Arbitrum, Base, Optimism) — клейм коштує центи. Або gasless claim через ERC-2771 meta-transactions (користувач підписує, relayer платить газ).

Процес роботи

Список адрес та сум → off-chain генерація дерева → деплой контракту з root → трансфер токенів на контракт → API для proofs → UI для claim. Стандартний scope: 1-2 тижні включаючи тестування та деплой.