Collection (ERC-721 Drop)
The 721 Drop flow launches a collection in clean, predictable phases (optional allowlist/presale → public mint).
It’s built for fair access, fixed supply, and simple on-chain parameters—no reveal step is used in Panthart’s flow.
Pre-flight checklist (what the UI validates)
- Name & Symbol: required and human-readable.
- Max Supply: integer > 0 (hard cap).
- Base URI: a final, stable URL (e.g.
ipfs://<CID>), no trailing slash.
The contract computestokenURI = baseURI + "/" + tokenId + ".json". - Allowlist (optional): a plain list of wallet addresses to include in the Merkle tree.
Addresses are checksummed/normalized and deduplicated by the UI.🧩Leaf format is
keccak256(abi.encodePacked(address)). There is no per-address quantity allowance in this contract—just in/out membership. - Per-wallet limit: integer > 0 (lifetime cap, enforced for both presale and public).
- Per-transaction limit: integer > 0.
- Prices: positive, in the chain’s native coin (payable
msg.value == quantity * price). - Schedule:
- Public sale start must be in the future at initialization.
- If using presale: start < end, and public start > presale end.
- Presale price ≤ public price.
- Royalties (ERC-2981): recipient + BPS ≤ 1000 (10%). Immutable after deploy.
- Platform fee: exact
feeAmountin native coin is forwarded at initialization. - Owner wallet: the connected wallet becomes the collection owner (via factory injection) and is required for admin actions (withdraw).
What the contract does (tl;dr)
- Cloneable (EIP-1167); owner injected by the NFTFactory at creation.
- Single base URI (no trailing slash); token IDs are 1..maxSupply.
- Presale (optional) via Merkle whitelist and windowed timebox.
Presale has its own maxSupply cap and price. - Public sale with per-tx and per-wallet limits.
- Limits:
maxPerWalletandmaxPerTxcome from the public sale config and apply during presale too. - Royalties: ERC-2981 default royalty set once at init.
- Events: emits mints, initialization, and withdrawals.
- Withdraw: owner can withdraw proceeds (native coin) any time.
Token URI layout
Use stable, content-addressed metadata. With baseURI = ipfs://bafy...:
tokenURI(1) -> ipfs://bafy…/1.json tokenURI(2) -> ipfs://bafy…/2.json
Each *.json should include standard fields (name, description, image, optional animation_url, attributes, etc.).
Base URI must not end with a slash. The contract inserts ”/” and “.json” for you.
Allowlist notes (incl. Comrades holders)
- Upload a newline/CSV list of addresses. The UI builds the Merkle root as
keccak256(address)leaves. - There is no per-address allowance column; any such column is ignored by this contract.
- Automatic eligibility: holders of ≥ 100 Comrades (Non-Fungible Comrades collection) are automatically included in the allowlist snapshot.
Practically, the UI unions:- your uploaded addresses and
- a snapshot of wallets holding ≥ 100 Comrades
…then generates the Merkle root from that union.
Step-by-step
1) Prepare your metadata & allowlist
- Upload all artwork metadata to IPFS (or another permanent store). Confirm the JSONs and media resolve.
- Take a snapshot of Comrades holders with ≥ 100 and merge with your uploaded allowlist.
- Verify the final list has unique, valid addresses.
2) Configure sale parameters
- Max supply: hard cap for the whole collection.
- Royalties: set recipient + BPS (≤ 10%). This is immutable.
- Presale (optional): set
start,end,price, presale maxSupply, and Merkle root from the union list. - Public sale: set
start(must be in the future),price,maxPerWallet,maxPerTx.
If presale is used: presaleStart < presaleEnd <= publicStart.
Presale price must be ≤ public price.
3) Deploy via Factory (initialize clone)
- The UI calls the NFTFactory to clone the 721 Drop implementation and pass your configs.
- You’ll approve a single transaction that also forwards the platform fee to the fee recipient.
- On success, you’ll see your new collection address and the DropInitialized event.
4) Point the Base URI (final)
- Provide
baseURIwithout a trailing slash (e.g.ipfs://bafy...). - Confirm a couple of tokenURIs resolve:
…/1.json,…/2.json, etc.
This contract exposes no setters for base URI, prices, or schedules after initialization. Treat them as final.
5) Mint windows go live
- Presale: eligible addresses call
presaleMint(quantity, proof)during the window.- Enforced:
quantity > 0,quantity ≤ maxPerTx, wallet lifetime ≤maxPerWallet, presale supply cap, andmsg.value == quantity * presalePrice.
- Enforced:
- Public: anyone can call
mint(quantity), with the same per-tx/per-wallet enforcement and public price.
6) Withdraw proceeds
- As owner, use the UI’s Withdraw to send the contract balance to your payout wallet.
Reference (functions & events)
Minting
presaleMint(uint256 quantity, bytes32[] proof)mint(uint256 quantity)
Views
totalSupply() → uint256(minted so far)tokenURI(uint256 tokenId) → stringsupportsInterface(bytes4) → bool(ERC-721 + ERC-2981)
Admin
withdrawProceeds(address payable to)(owner only)
Events
DropInitialized(owner, name, symbol)DropMinted(minter, tokenId, uri)ProceedsWithdrawn(to, amount)
Gotchas & tips
- Trailing slash: don’t add one to
baseURI. - Quantities in allowlist: not supported; it’s a simple membership Merkle tree.
- Caps apply everywhere:
maxPerWalletandmaxPerTxare enforced in both presale and public. - Native coin only: pricing is in the chain’s native coin (e.g., ETH/ETN on its EVM). No ERC-20 payment in this contract.
- ID start: token IDs start at 1 and increment sequentially.
- Presale cap:
presaleMinted + quantity ≤ presale.maxSupply. When presale ends, remaining supply is available to public untilmaxSupply.
Example allowlist (newline)
0xAbc0000000000000000000000000000000000000 0xDef0000000000000000000000000000000000000 0x1230000000000000000000000000000000000000
The UI will union this with the Comrades ≥100 snapshot before generating the Merkle root.
That’s the whole dance for the 721 Drop. To compare variants next, check Single (ERC-721 1/1) or 1155 Drop (Single-Token) pages to choose the right mint flavor for your project’s shape.