10,000 unique CryptoPunks, 8,888 Azuki, 8,000 Milady—all these collections are built on the same principle: algorithmic combination of trait layers with given probabilities creates unique images. The technical side consists of two equally important parts: the image generator and the mint smart contract. Errors at any stage—from an incorrect compatibility matrix to a contract vulnerability—can cost thousands of dollars in gas and reputation.
This guide covers generative NFT collection development on Ethereum using ERC-721A for gas savings, conditional traits, Merkle tree whitelist, Dutch auction, Chainlink VRF, and IPFS metadata with ERC-2981 royalties. OpenZeppelin libraries ensure security. Batch mint gas optimization saves up to 80%. Our team has been developing NFT collections for over 4 years, releasing more than 10 projects on Ethereum, Polygon, and Solana. This experience guarantees quality at every stage: from generation to deployment. In this article, we will break down the technical aspects of creating a generative collection: from image generation to smart contract deployment. You will learn how to avoid common mistakes and save up to 80% on gas with batch mint.
Trait structure and rarity
The collection is divided into layers (background, body, clothing, eyes, mouth, accessories). Each layer contains variants with assigned weights. For example, for background:
"background": [ { "name": "Gold", "weight": 2 }, { "name": "Blue", "weight": 25 }, { "name": "Gray", "weight": 73 } ] The generator randomly selects a variant proportional to the weights and combines PNG layers. Result: 2% of the collection get a gold background, 73% get gray.
Key problem: with a naive implementation, rarity is broken due to conflicting traits (e.g., a skeleton character cannot wear normal clothes). We implement conditional traits: a layer compatibility matrix that excludes invalid combinations. With many constraints, the algorithm can loop—backtracking with max-attempts is needed.
More about conditional traits
The compatibility matrix is defined as a bitmask: for each layer, allowed identifiers of other layers are listed. The Node.js generator sequentially selects a variant for each layer, checking compatibility with already selected ones. If looping occurs, increase max-attempts or restart generation from another layer.Tool: our own Node.js generator using sharp for compositing PNG layers. sharp is 3–5 times faster than canvas-based solutions—10k images are generated in 5–15 minutes. For animated collections (GIF/APNG), we use ffmpeg via child process.
Metadata and standards
Each token requires JSON metadata in OpenSea metadata standard format:
{ "name": "Collection #1234", "description": "...", "image": "ipfs://Qm.../1234.png", "attributes": [ { "trait_type": "Background", "value": "Gold" }, { "trait_type": "Eyes", "value": "Laser" } ] } The image field must point to IPFS or Arweave. A centralized server kills the collection if it goes down. We upload via Pinata or NFT.Storage, obtain a CID, and form a baseURI like ipfs://QmXxx/. All metadata is automatically uploaded to IPFS.
Smart contract: ERC-721 and mint mechanics
Why use ERC-721A?
Basic structure on OpenZeppelin:
contract MyCollection is ERC721A, Ownable, ReentrancyGuard { uint256 public constant MAX_SUPPLY = 10000; uint256 public constant MAX_PER_WALLET = 5; string private _baseTokenURI; mapping(address => uint256) public mintedPerWallet; } We use ERC-721A (Azuki's implementation) instead of standard ERC-721: batch minting 5 tokens in ERC-721A consumes ~50k gas vs ~250k in the classic implementation. The difference is noticeable for a 10k collection on Ethereum mainnet. Gas savings reach 80% for mass minting, saving over $10,000 in gas costs at typical gas prices.
| Metric | ERC-721 (OpenZeppelin) | ERC-721A (Azuki) |
|---|---|---|
| Gas per mint of 1 token | ~90k | ~50k |
| Gas per mint of 5 tokens | ~250k | ~50k |
| Burn support | Yes | Yes |
| Audit | Many audits | Audited (Azuki) |
Mint mechanics
Public mint — open to all, often with a per-wallet limit. Protection: require(mintedPerWallet[msg.sender] + quantity <= MAX_PER_WALLET). Problem with contracts: msg.sender is a contract, bypasses the limit. Adding require(msg.sender == tx.origin)—but this breaks Safe/AA wallets. Compromise: check msg.sender == tx.origin only during mint period, removed afterward.
Whitelist mint — Merkle tree proof. List of addresses → Merkle root → root stored in contract. User provides proof (array of hashes), contract verifies via MerkleProof.verify() from OpenZeppelin. Proof is generated off-chain using merkletreejs, published on the frontend.
Dutch auction mint — price starts high and drops every N minutes to a minimum. Current price is calculated via startPrice - (elapsedTime / step) * priceDecrement. User pays current price, excess ETH is refunded in the same transaction.
| Mechanic | Access | Price | Gas cost | Implementation complexity |
|---|---|---|---|---|
| Public mint | All | Fixed | Low | Low |
| Whitelist mint | By list | Fixed or discount | Medium | Medium |
| Dutch auction | All | Dynamic (descending) | Medium | High |
How to protect the collection from snipers?
Reveal mechanic — premium collections do not reveal traits until sale ends (anti-snipe). Before reveal: tokenURI() returns a common placeholder. After reveal: owner calls setBaseURI(ipfsCID) and all tokens instantly show final images.
A fairer mechanic: Chainlink VRF for random seed. Contract requests random via requestRandomWords(), receives response in fulfillRandomWords(), records seed. All tokenIds are randomly shuffled relative to the seed—impossible to guess traits even knowing mint order.
Royalties and marketplaces
ERC-2981 — on-chain royalties standard. Marketplaces supporting the standard (Blur with option enabled, OpenSea, Rarible) automatically read royaltyInfo(tokenId, salePrice) and deduct the percentage. Added via ERC2981 mixin from OpenZeppelin.
For enforced royalties: OperatorFilterRegistry (Blur/OpenSea approach) blocks transfers through marketplace contracts that do not respect royalties. But this is controversial—it limits liquidity. Solution: a toggle flag royaltiesEnforced that owner can disable.
Development process: step by step
- Asset preparation — PNG layers with transparency, rarity table, incompatibility matrix. Depends on the artist.
- Generator and metadata (2–4 days, ~$2000-$4000) — Node.js generator, batch generation of collection, JSON metadata, upload to IPFS via Pinata API.
- Smart contract (3–5 days, ~$3000-$5000) — ERC-721A basis, mint mechanics (public + whitelist + Dutch auction as required), tests in Foundry: supply limits, per-wallet limits, Merkle proof, refund for Dutch auction.
- Mint site frontend (3–5 days, ~$2000-$4000) — React + wagmi + viem, MetaMask/WalletConnect integration, rarity tracker.
- Deployment — Testnet (Sepolia) → mainnet. Contract verification on Etherscan.
Choosing mint mechanics
For gas savings and simplicity — public mint with ERC-721A. If access control and premium scenarios are important — combine whitelist + Dutch auction. We help select the optimal option for your collection and target audience.
What's included in turnkey development
- Source code of the image generator with trait configuration
- Smart contracts with selected mint mechanics (tested on testnet)
- Metadata for all tokens (uploaded to IPFS/Arweave)
- Mint site frontend with wallet connection
- Documentation for collection management (reveal, contract verification)
- Support during mainnet deployment
Full cycle from ready assets to mainnet deployment takes 1.5–2 weeks. Cost is calculated individually based on mint mechanics and frontend requirements. Typical range: $7,000-$13,000. Get a consultation for your collection—we will help you choose optimal mint mechanics and estimate the budget. Contact us to discuss your project.







