Airdrop campaign system development

We design and develop full-cycle blockchain solutions: from smart contract architecture to launching DeFi protocols, NFT marketplaces and crypto exchanges. Security audits, tokenomics, integration with existing infrastructure.
Showing 1 of 1 servicesAll 1306 services
Airdrop campaign system development
Medium
~3-5 business days
FAQ
Blockchain Development Services
Blockchain Development Stages
Latest works
  • image_website-b2b-advance_0.png
    B2B ADVANCE company website development
    1215
  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1161
  • image_websites_belfingroup_462_0.webp
    Website development for BELFINGROUP
    852
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1043
  • image_logo-advance_0.png
    B2B Advance company logo design
    561
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    823

Airdrop Campaign System Development

Technically airdrop is token distribution. Practically—marketing tool that either creates long-term protocol participants or generates one-time dump pressure. Difference determined not by token amount distributed, but by who receives them and on what terms.

Merkle Distributor: Standard for Mass Distribution

Naive approach—call transfer for each address. With 100,000 recipients that's 100,000 transactions, huge gas, guaranteed failure. Right approach—Merkle Distributor, where recipients claim tokens themselves.

Off-chain: form list (address → amount), build Merkle tree, publish root to contract.

On-chain: user provides Merkle proof, contract verifies and issues tokens.

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

    // Bitfield for tracking claimed—gas savings 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");

        // Verify 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: storing claimed status in packed bitfield (256 statuses in one uint256 slot) saves ~80% gas on SSTORE/SLOAD vs mapping(address => bool).

Airdrop Types and Applications

Retroactive Airdrop

Distribution to existing protocol users—most effective type. Uniswap UNI, Arbitrum ARB, Optimism OP—all were retroactive for early users.

Eligibility criteria determined by on-chain analysis:

  • Transaction volume during period
  • Number of unique contracts interacted with
  • Age of first transaction
  • Position retention (not just-in-time farming)

Sybil filtering—main technical task. One person with 1000 addresses shouldn't get 1000x more.

Sybil cluster indicators:

  • Addresses get ETH from same funding source
  • Transactions with identical patterns (same time of day, same protocols)
  • Empty addresses between actions (gas station pattern)
  • Minimal transactions to meet minimum criteria

Sybil detection tools: Chainanalysis Sybil (paid), own SQL analysis via Dune Analytics or indexed node.

Task-based System

User completes tasks → gets allocation. Typical tasks:

  • Follow on Twitter, Discord, Telegram
  • Testnet transactions
  • Referral of new users
  • Governance voting participation

Problem: easily farmed by bots. Tasks should require on-chain activity hard to simulate at scale.

Integration with Galxe / Layer3—ready platforms for task-based campaigns. API for on-chain task verification. Downside: platform takes fee and users stay on platform, not your site.

Vested Airdrop

Received tokens don't claim immediately but vest. Linear vesting 6–12 months.

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 {
        // Verify allocation (if first 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: first 3 months nothing (cliff), then linear vest 9 months. Reduces immediate dump, creates long-term holders.

Scoring System

For complex campaigns with many actions—off-chain scoring system:

interface UserScore {
  address: string;
  totalPoints: number;
  breakdown: {
    earlyAdopter: number;        // first 1000 users
    volumeScore: number;         // based on trading volume
    loyaltyScore: number;        // time using protocol
    referrals: number;           // successful referrals
    governanceVotes: number;     // governance participation
  };
}

// Allocation = f(points) with diminishing returns for anti-whale
function calculateAllocation(points: number, totalPoints: number): bigint {
  // Square root for whale mitigation
  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 (used in quadratic voting): decreases gap between large and small participants. Whale with 10,000 points gets not 10x more than 1,000-point user, but only ~3.16x.

Gas Optimization for Mass Claiming

With millions of claimers each saved gas is user money:

EIP-2612 Permit—instead of separate approve transaction, if user needs to do something with tokens right after claim (like staking):

function claimAndStake(
    uint256 index,
    uint256 amount,
    bytes32[] calldata proof,
    uint256 deadline,
    uint8 v, bytes32 r, bytes32 s
) external {
    // Claim tokens
    claim(index, msg.sender, amount, proof);

    // Permit for approve without separate transaction
    IERC20Permit(token).permit(msg.sender, address(staking), amount, deadline, v, r, s);

    // Stake immediately
    staking.stakeFor(msg.sender, amount);
}

Batch claiming—if user has allocations in multiple rounds:

function claimMultiple(
    uint256[] calldata indices,
    uint256[] calldata amounts,
    bytes32[][] calldata proofs
) external {
    uint256 totalAmount;
    for (uint i = 0; i < indices.length; i++) {
        // verify each proof
        totalAmount += amounts[i];
    }
    // one transfer instead of N
    IERC20(token).safeTransfer(msg.sender, totalAmount);
}

Frontend for Airdrop

Eligibility checker—enter address → check via API (backend has list) or directly from Merkle tree (if published fully):

async function checkEligibility(address: string) {
  // Normalize address
  const normalizedAddress = ethers.getAddress(address);

  // Get data from API or published snapshot
  const allocation = await fetchAllocation(normalizedAddress);

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

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

  // Check not already claimed
  const alreadyClaimed = await distributor.isClaimed(allocation.index);

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

Snapshot publication: Merkle tree data should be publicly accessible (GitHub, IPFS) so users can independently verify their allocation. Non-public snapshot—red flag for community.

Expiry and Unclaimed Tokens

Always set expiry on claim period (usually 1 year). Unclaimed tokens returned to treasury or burned:

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);
}