Anti-bot protection system for NFT minting

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
Anti-bot protection system for NFT minting
Medium
from 1 business day to 3 business days
FAQ
Blockchain Development Services
Blockchain Development Stages
Latest works
  • image_website-b2b-advance_0.png
    B2B ADVANCE company website development
    1214
  • 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
    1041
  • 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

Antibot Mint Protection System Development

Azuki January 2022: 8,700 NFTs in 3 minutes, 30 ETH gas at peak. Bots minted 2/3 of collection before real users. OpenSea week later: same addresses reselling at 10x. Classic situation without antibot protection. Next major launches — BAYC, CryptoPunks — learned from mistakes and implemented various mechanisms. Which one is right for your collection depends on audience size and desired distribution.

Protection Mechanisms and Trade-Offs

Merkle Whitelist: Most Common Protection

Merkle tree of whitelist addresses. Each address from list can prove membership by providing proof of O(log n) hashes. Contract stores only one root (32 bytes), not entire list.

bytes32 public merkleRoot;

function whitelistMint(uint256 quantity, bytes32[] calldata proof) external payable {
    bytes32 leaf = keccak256(abi.encodePacked(msg.sender));
    require(MerkleProof.verify(proof, merkleRoot, leaf), "Not whitelisted");
    require(!_whitelistClaimed[msg.sender], "Already claimed");
    _whitelistClaimed[msg.sender] = true;
    _mint(msg.sender, quantity);
}

Merkle tree generation — off-chain TypeScript script via merkletreejs. Root updated before mint via setMerkleRoot() (onlyOwner). Proofs users get via API or published on IPFS beforehand.

Vulnerability: if frontend compromised, attacker can request proof for any address from list via API. Protection: proofs issued only to wallet requesting them (signature-gated API), or entire list published beforehand (full transparency).

Commit-Reveal: Protection Against Frontrunning on Random Mint

Without commit-reveal: bot analyzes mempool, sees transaction with parameters, makes exact copy with higher gas — frontrunning. With commit-reveal: user first publishes keccak256(secret + address), then reveals secret after N blocks. During N blocks copying is pointless — secret unknown.

Two-stage process inconvenient for users. Use only where random distribution critical and users ready for two transactions.

Per-Address Limits: Necessary Minimum

Most basic protection — per-address limit:

mapping(address => uint256) public mintedByAddress;
uint256 public constant MAX_PER_ADDRESS = 3;

function mint(uint256 quantity) external {
    require(mintedByAddress[msg.sender] + quantity <= MAX_PER_ADDRESS, "Limit exceeded");
    mintedByAddress[msg.sender] += quantity;
    _mint(msg.sender, quantity);
}

Doesn't protect against Sybil — one bot creates thousands of addresses. But raises attack cost: need more wallets, gas for moving ETH between them. Combined with other methods — effective.

Deep Dive: Proof-of-Work on Mint

Rarest applied, but interesting mechanism. Idea: before minting must solve computational task — find nonce such that keccak256(address + nonce) < difficulty. This CPU/GPU work, bot does faster, but creates resource constraint.

uint256 public mintDifficulty = type(uint256).max / 1000; // 0.1% of hashes pass

function mint(uint256 nonce) external {
    bytes32 hash = keccak256(abi.encodePacked(msg.sender, nonce, block.number / 100));
    require(uint256(hash) < mintDifficulty, "Invalid proof of work");
    _mint(msg.sender, 1);
}

block.number / 100 — window of ~100 blocks (~20 minutes). Nonce valid only in this window, can't compute beforehand. Difficulty adjusted via mintDifficulty.

Problem: mobile users spend 10-30 seconds computing. Bots with GPU — 0.1 seconds. Asymmetry not in favor of regular users. Proof-of-work effective only combined with whitelist, where bots not in list initially.

Wait Time and Batch Limits

Additional anti-bot mechanic: maximum mint in first N blocks from start — 1 token. After N blocks — up to MAX_PER_ADDRESS. Bot hitting first second gets only 1 token. Users arriving after minute can take more.

uint256 public publicMintStartBlock;

function maxMintForBlock(uint256 _block) public view returns (uint256) {
    if (_block < publicMintStartBlock + 50) return 1;  // first ~10 min
    return MAX_PER_ADDRESS;
}

Mechanism Comparison

Mechanism Attack Cost UX for Users Implementation Difficulty
Per-address limit Low (Sybil) Excellent Minimal
Merkle whitelist High Good Medium
Commit-reveal High Poor (2 txs) High
Proof-of-work Medium Normal Medium
Batch limit by time Medium Excellent Low

Recommended Combinations

Small collection (<1000), closed community: Merkle whitelist + per-address limit 2-3.

Medium collection (1000-10000), open mint: Whitelist phase (Merkle) → public phase with batch limit by time + per-address limit.

Large collection (>10000), high demand: Whitelist phase + public phase with proof-of-work or raffle via VRF.

Development Process

Analysis (1 day). Determine mechanics: whitelist or not, how many phases, per-address limits.

Development (1-3 days). Contract with chosen mechanisms. Off-chain script for Merkle tree generation. API for issuing proofs.

Testing. Separately test each mechanism: whitelist proof verification, limits, timing mechanics. Foundry fuzz test: testMintLimit(address,uint256) — any combination shouldn't exceed limit.

Timeline Estimates

System with Merkle whitelist + per-address limit — 1-2 days. Full multi-phase system with proof-of-work and commit-reveal — 3-5 days.