Generative NFT Collection Development
10,000 unique CryptoPunks, 8,888 Azuki, 8,000 Milady — all these collections are built on one principle: algorithmic combination of trait layers with set probabilities creates unique images. The technical part of development consists of two equally important parts: image generator and smart contract minting.
Image Generation: From Layers to Token
Trait Structure and Rarity
Collection is divided into layers (background, body, clothing, eyes, mouth, accessories). Each layer contains variants with set weights. For example, for background:
"background": [
{ "name": "Gold", "weight": 2 },
{ "name": "Blue", "weight": 25 },
{ "name": "Gray", "weight": 73 }
]
Generator randomly selects variant proportional to weights and combines PNG layers. Result: 2% of collection gets gold background, 73% — gray.
Key problem: naive rarity implementation breaks due to conflicting traits (e.g., skeleton character can't wear regular clothes). Implement conditional traits: compatibility matrix of layers excluding invalid combinations. With many constraints, algorithm can loop — need backtracking with max-attempts.
Tool: custom Node.js generator using sharp for PNG compositing. sharp is 3–5 times faster than canvas solutions — 10k images generate in 5–15 minutes. For animated collections (GIF/APNG) — 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" }
]
}
image field must point to IPFS or Arweave. Centralized server — death of collection if it shuts. Upload via Pinata or NFT.Storage, get CID, form baseURI like ipfs://QmXxx/.
Smart Contract: ERC-721 and Minting Patterns
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;
}
Use ERC-721A (Azuki's implementation) instead of standard ERC-721: batch minting 5 tokens in ERC-721A consumes ~50k gas vs ~250k in classic. Difference is noticeable at 10k collection on Ethereum mainnet.
Minting Mechanics
Public mint — open to all, often with per-wallet limit. Protection: require(mintedPerWallet[msg.sender] + quantity <= MAX_PER_WALLET). Problem with contracts: msg.sender — is contract, bypasses limit. Add require(msg.sender == tx.origin) — but breaks Safe/AA wallets. Compromise: check msg.sender == tx.origin only during mint period, lift after.
Whitelist mint — Merkle tree proof. List of addresses → Merkle tree root → root stored in contract. User provides proof (array of hashes), contract verifies via MerkleProof.verify() from OpenZeppelin. Proof generated off-chain via merkletreejs, published on frontend.
Dutch auction mint — price starts high and falls every N minutes to minimum. Calculate current price via startPrice - (elapsedTime / step) * priceDecrement. User pays current price, excess ETH returned in same transaction.
Reveal Mechanic
Premium collections don't reveal traits until sale ends (anti-snipe). Before reveal: tokenURI() returns one shared placeholder. After reveal: owner calls setBaseURI(ipfsCID) and all tokens instantly show final images.
More honest mechanic: Chainlink VRF for random seed. Contract requests random via requestRandomWords(), gets answer in fulfillRandomWords(), records seed. All tokenIds randomly shuffled relative to seed — can't guess traits even knowing mint order.
Royalties and Marketplaces
ERC-2981 — on-chain royalties standard. Marketplaces supporting standard (Blur with option enabled, OpenSea, Rarible) automatically read royaltyInfo(tokenId, salePrice) and withhold percentage. Added via ERC2981 mixin from OpenZeppelin.
For royalty enforcement: OperatorFilterRegistry (Blur/OpenSea approach) blocks transfers through marketplace contracts not honoring royalties. But this is controversial mechanic — limits liquidity. Solution: variable flag royaltiesEnforced, which owner can toggle.
Development Process
Asset Preparation (artist-dependent). Layers in PNG with transparency, rarity table, incompatibility matrix.
Generator and Metadata (2–4 days). Node.js generator, batch collection generation, JSON metadata, upload to IPFS via Pinata API.
Smart Contract (3–5 days). ERC-721A basis, minting mechanics (public + whitelist + dutch auction depending on requirements), tests in Foundry: supply limits, per-wallet limits, Merkle proof, refund on dutch auction.
Frontend Mint Site (3–5 days). React + wagmi + viem, MetaMask/WalletConnect connection, wallet connect, rarity tracker.
Deployment. Testnet (Sepolia) → mainnet. Contract verification on Etherscan.
Full cycle from ready assets to mainnet deployment — 1.5–2 weeks. Cost depends on minting mechanics and frontend requirements.







