P11: NFT Marketplace
P11: NFT Marketplace
Project Overview
| Attribute | Value |
|---|---|
| Main Language | Solidity + TypeScript |
| Alternative Languages | Vyper + Python, Rust (Solana) |
| Difficulty | Advanced |
| Coolness Level | Level 4: Hardcore Tech Flex |
| Business Potential | The โOpen Coreโ Infrastructure |
| Knowledge Area | NFTs / Marketplaces |
| Software or Tool | OpenSea Clone |
| Main Book | โMastering Ethereumโ by Andreas M. Antonopoulos & Gavin Wood |
Learning Objectives
By completing this project, you will:
- Master the ERC-721 token standard understanding how non-fungible tokens differ fundamentally from ERC-20 tokens and why unique token IDs enable digital ownership
- Implement marketplace listing mechanics including both escrow-based and approval-based patterns, understanding the security trade-offs of each approach
- Build multiple auction types including English (ascending), Dutch (descending), and sealed-bid auctions, understanding the game theory behind each
- Integrate the ERC-2981 royalty standard enabling automatic creator compensation on secondary sales, solving the attribution problem in digital art
- Handle offer mechanics and escrow patterns safely managing funds during the negotiation and settlement process
- Design gas-efficient marketplace contracts using packed structs, assembly optimization, and batch operations
- Build a complete frontend that interacts with IPFS for metadata and blockchain for ownership, creating a seamless user experience
Real World Outcome
When your NFT marketplace is complete, users will experience a fully functional decentralized trading platform. Here is exactly what they will see and interact with:
Web Interface - Home Page
+------------------------------------------------------------------+
| [Logo] NFT Marketplace [Connect Wallet] [0x7a2...3f4] |
+------------------------------------------------------------------+
| [Explore] [Create] [My NFTs] [Activity] |
+------------------------------------------------------------------+
| |
| FEATURED COLLECTIONS |
| +----------------+ +----------------+ +----------------+ |
| | [Bored Ape #42]| | [CryptoPunk #7]| | [Azuki #1337] | |
| | Floor: 2.5 ETH | | Floor: 15 ETH | | Floor: 3.2 ETH| |
| | 24h Vol: 45 ETH| | 24h Vol: 120ETH| | 24h Vol: 28ETH| |
| +----------------+ +----------------+ +----------------+ |
| |
| LIVE AUCTIONS TIME REMAINING |
| +--------------------------------------------------+ |
| | [NFT Image] "Genesis #001" Current: 5.2 ETH | 2h 34m |
| | [NFT Image] "Pixel Dragon #88" Current: 1.8 ETH | 45m 12s |
| | [NFT Image] "Meta House #5" Current: 12.1 ETH| 5h 02m |
| +--------------------------------------------------+ |
| |
| RECENT SALES |
| +--------------------------------------------------+ |
| | CryptoKitty #234 Sold for 0.5 ETH 2 min ago | |
| | Bored Ape #8821 Sold for 85 ETH 15 min ago| |
| | Doodle #4420 Sold for 3.2 ETH 32 min ago| |
| +--------------------------------------------------+ |
+------------------------------------------------------------------+
NFT Detail Page
+------------------------------------------------------------------+
| [Back to Collection] |
+------------------------------------------------------------------+
| +------------------+ |
| | | BORED APE YACHT CLUB #4821 |
| | [NFT Image] | Owned by 0x742d...f44e |
| | | |
| | | Created by: 0xBC4C...a891 (2.5% royalty) |
| +------------------+ |
| |
| +--------------------------------------------------+ |
| | CURRENT PRICE | |
| | 42.5 ETH ($127,500) | |
| | | |
| | [ BUY NOW ] [ MAKE OFFER ] | |
| +--------------------------------------------------+ |
| |
| PRICE HISTORY |
| [Chart showing price over time with sale points marked] |
| |
| OFFERS |
| +--------------------------------------------------+ |
| | 0x123...abc 38.0 ETH Expires: 2 days | [Accept] |
| | 0x456...def 35.5 ETH Expires: 5 days | [Accept] |
| | 0x789...ghi 30.0 ETH Expires: 7 days | [Accept] |
| +--------------------------------------------------+ |
| |
| ACTIVITY |
| +--------------------------------------------------+ |
| | Listed 42.5 ETH by 0x742...f44e 1 day ago |
| | Transfer -- 0xabc -> 0x742 3 days ago |
| | Sale 35.0 ETH 0xdef -> 0xabc 2 weeks ago |
| | Minted -- by 0xBC4...a891 6 months ago |
| +--------------------------------------------------+ |
| |
| PROPERTIES |
| +------------+ +------------+ +------------+ |
| | Background | | Fur | | Eyes | |
| | Aquamarine | | Robot | | Laser | |
| | 12% rare | | 2% rare | | 0.5% rare | |
| +------------+ +------------+ +------------+ |
+------------------------------------------------------------------+
Example Marketplace Interactions
Scenario 1: Listing an NFT for Fixed Price Sale
User clicks โSellโ on their NFT:
+--------------------------------------------------+
| LIST YOUR NFT FOR SALE |
+--------------------------------------------------+
| NFT: CryptoPunk #7234 |
| |
| Listing Type: (x) Fixed Price ( ) Auction |
| |
| Price: [____15.5____] ETH |
| |
| Duration: [7 days v] |
| |
| Marketplace Fee: 2.5% |
| Creator Royalty: 5.0% |
| You Receive: 92.5% = 14.3375 ETH |
| |
| [ APPROVE NFT ] โ [ CREATE LISTING ] |
+--------------------------------------------------+
Transaction Output (Listing):
Transaction Hash: 0x7a8b...4f2e
Status: Success
Block: 18,432,109
Gas Used: 124,532 (0.0031 ETH)
Event Logs:
Approval(owner: 0x742d...f44e, approved: 0xMarketplace, tokenId: 7234)
Listed(listingId: 0xa8f3...2e1b, seller: 0x742d...f44e, nft: 0xPunks, tokenId: 7234, price: 15500000000000000000)
Scenario 2: Buying an NFT
User clicks โBuy Nowโ on a listed NFT:
+--------------------------------------------------+
| CONFIRM PURCHASE |
+--------------------------------------------------+
| You are buying: |
| [NFT Thumbnail] Bored Ape #4821 |
| |
| Price: 42.50 ETH |
| + Marketplace Fee (2.5%): 1.0625 ETH |
| โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ |
| Total: 43.5625 ETH |
| |
| Your Balance: 52.34 ETH |
| |
| Distribution: |
| โ Seller (0x742d...f44e): 41.4375 ETH |
| โ Creator Royalty (2.5%): 1.0625 ETH |
| โ Marketplace Fee (2.5%): 1.0625 ETH |
| |
| [ CONFIRM PURCHASE ] |
+--------------------------------------------------+
Transaction Output (Purchase with Royalty Distribution):
Transaction Hash: 0x9c3d...8e7f
Status: Success
Block: 18,432,215
Gas Used: 187,234 (0.0047 ETH)
Event Logs:
Transfer(from: 0x742d...f44e, to: 0x5aAe...3ced, tokenId: 4821)
RoyaltyPaid(receiver: 0xBC4C...a891, amount: 1062500000000000000)
Sale(listingId: 0xa8f3...2e1b, buyer: 0x5aAe...3ced, price: 42500000000000000000)
Internal Transactions:
0x742d...f44e received 41.4375 ETH (seller proceeds)
0xBC4C...a891 received 1.0625 ETH (creator royalty)
0xFee...Treasury received 1.0625 ETH (marketplace fee)
Scenario 3: English Auction (Ascending Price)
+--------------------------------------------------+
| LIVE AUCTION |
+--------------------------------------------------+
| [Large NFT Image] |
| |
| "Celestial Genesis #001" |
| by ArtistDAO |
| |
| CURRENT BID: 12.5 ETH |
| by 0x789...ghi |
| |
| TIME REMAINING: 02:34:17 |
| [โโโโโโโโโโโโโโโโโโโโ] 65% |
| |
| Minimum Next Bid: 13.125 ETH (+5%) |
| |
| Your Bid: [________] ETH |
| |
| [ PLACE BID ] |
| |
| BID HISTORY: |
| 12.5 ETH 0x789...ghi 2 hours ago |
| 11.0 ETH 0x456...def 4 hours ago |
| 10.0 ETH 0x123...abc 6 hours ago |
| 8.5 ETH 0x789...ghi 8 hours ago (start) |
+--------------------------------------------------+
Transaction Output (Auction Bid):
Transaction Hash: 0x2b5e...9d1a
Status: Success
Block: 18,432,301
Event Logs:
BidPlaced(auctionId: 0x7f2a...3e8b, bidder: 0xnew...addr, amount: 13500000000000000000)
BidRefunded(previousBidder: 0x789...ghi, amount: 12500000000000000000)
Complete User Journey: Creator to Collector
CREATOR FLOW:
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ 1. CREATE COLLECTION โ
โ - Deploy ERC-721 contract (or use lazy minting) โ
โ - Set royalty percentage (e.g., 5%) โ
โ - Upload metadata to IPFS โ
โ โ
โ 2. MINT NFT โ
โ - Upload artwork to IPFS โ returns CID โ
โ - Create metadata JSON โ upload to IPFS โ
โ - Call mint(tokenURI) on contract โ
โ โ
โ 3. LIST ON MARKETPLACE โ
โ - Approve marketplace contract โ
โ - Create listing with price/auction parameters โ
โ โ
โ 4. RECEIVE PAYMENTS โ
โ - Primary sale: 100% - marketplace fee โ
โ - All secondary sales: Automatic royalty via ERC-2981 โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
COLLECTOR FLOW:
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ 1. BROWSE & DISCOVER โ
โ - Search by collection, traits, price range โ
โ - View rarity rankings and price history โ
โ โ
โ 2. ACQUIRE โ
โ - Buy Now: Instant purchase at listed price โ
โ - Make Offer: Propose lower price (seller can accept) โ
โ - Bid in Auction: Compete for unique pieces โ
โ โ
โ 3. MANAGE PORTFOLIO โ
โ - View all owned NFTs in "My NFTs" page โ
โ - Track floor prices and portfolio value โ
โ - List for resale when desired โ
โ โ
โ 4. RESELL โ
โ - List owned NFT on marketplace โ
โ - Creator automatically receives royalty on sale โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Example Royalty Distribution Flow
SECONDARY SALE: "CryptoArt #42" sells for 10 ETH
Original Creator: 0xCreator...001 (set 5% royalty)
Previous Owner: 0xSeller...002
New Owner: 0xBuyer...003
Marketplace: 0xMarket...004 (2.5% fee)
DISTRIBUTION:
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ โ
โ Buyer Pays: 10.00 ETH โ
โ โโโโโโโโโโโโ โ
โ Creator Royalty: 0.50 ETH (5.0%) โ 0xCreator...001 โ
โ Marketplace Fee: 0.25 ETH (2.5%) โ 0xMarket...004 โ
โ Seller Receives: 9.25 ETH (92.5%) โ 0xSeller...002 โ
โ โโโโโโโโโโโโ โ
โ Total: 10.00 ETH โ
โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
On-Chain Verification:
> cast call $NFT "royaltyInfo(uint256,uint256)" 42 10000000000000000000
> Returns: (0xCreator...001, 500000000000000000) // 0.5 ETH to creator
The Core Question Youโre Answering
How does digital ownership work when thereโs no physical object to possess?
This project answers one of the most profound questions in the digital age: What does it mean to โownโ something that can be infinitely copied? The traditional answer was: you canโt. Digital files have no scarcity.
NFTs invert this by shifting from copy-protection to origin-verification:
Traditional Digital Ownership: NFT Ownership:
"I have the only copy" "I have the verified original"
โ โ
Enforced by DRM Enforced by cryptography
Can be cracked Mathematically impossible to forge
Requires central authority Self-verifying on blockchain
The ERC-721 standard creates provable uniqueness. Each token ID maps to exactly one owner at any time:
// The fundamental truth of NFT ownership
mapping(uint256 => address) private _owners;
// tokenId 42 belongs to exactly one address
// This is enforced by consensus of thousands of nodes
// No one can claim otherwise without rewriting the blockchain
Understanding this project means understanding:
- Why ownership is a social construct that blockchains formalize mathematically
- How marketplaces coordinate the exchange of unique digital assets
- Why creator royalties represent a new economic model for digital art
- How the same principles apply beyond art to any unique digital asset (domains, tickets, credentials, real estate deeds)
Concepts You Must Understand First
1. ERC-721 Token Standard
The ERC-721 standard defines the interface for non-fungible tokens. Unlike ERC-20 where all tokens are identical, each ERC-721 token has a unique tokenId.
Core Interface:
interface IERC721 {
function balanceOf(address owner) external view returns (uint256);
function ownerOf(uint256 tokenId) external view returns (address);
function safeTransferFrom(address from, address to, uint256 tokenId) external;
function transferFrom(address from, address to, uint256 tokenId) external;
function approve(address to, uint256 tokenId) external;
function setApprovalForAll(address operator, bool approved) external;
function getApproved(uint256 tokenId) external view returns (address);
function isApprovedForAll(address owner, address operator) external view returns (bool);
}
Book Reference: โMastering Ethereumโ Chapter 10: Tokens - discusses token standards and NFT fundamentals.
Questions to Test Understanding:
- Why does
ownerOf()return an address, while ERC-20โsbalanceOf()returns a uint256? - Whatโs the difference between
approve()andsetApprovalForAll()? - Why does
safeTransferFrom()exist alongsidetransferFrom()? - How does an NFT collection create multiple tokens while remaining ERC-721 compliant?
2. The Approval Pattern
Before a marketplace can transfer your NFT, you must give it permission. There are two types of approval:
Single Token Approval: Operator Approval:
โโโโโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโ
โ approve(market, 42) โ โ setApprovalForAll( โ
โ โ โ market, true) โ
โ Only token #42 โ โ โ
โ can be transferred โ โ ALL your tokens in โ
โ by marketplace โ โ this collection can โ
โ โ โ be transferred โ
โโโโโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโ
Book Reference: โMastering Ethereumโ Chapter 10 covers approval mechanics in the context of token standards.
Questions to Test Understanding:
- Why would you ever use single-token approval instead of operator approval?
- What security risks does
setApprovalForAll(true)introduce? - How do malicious sites exploit approval patterns to drain wallets?
- How does revocation work, and why should users periodically audit their approvals?
3. Escrow vs. Approval-Based Marketplace Design
There are two fundamental patterns for marketplace contracts:
Escrow Pattern:
User lists NFT โ NFT transfers to marketplace contract โ stays there until sold
Pros: Simpler logic, clear ownership during listing
Cons: User loses possession, gas for transfer in/out
Approval Pattern:
User lists NFT โ NFT stays in user's wallet โ transfers directly to buyer on sale
Pros: User retains NFT until sold, more gas efficient
Cons: User can transfer NFT elsewhere (breaking listing), more complex checks
Book Reference: โDesigning Data-Intensive Applicationsโ Chapter 7 discusses transaction isolation levels and consistency guarantees that parallel these trade-offs.
Questions to Test Understanding:
- What happens in the approval pattern if the user transfers the NFT elsewhere after listing?
- Why do most production marketplaces (like OpenSea) use the approval pattern?
- How does the escrow pattern handle a seller who wants to cancel their listing?
- What reentrancy risks exist in each pattern?
4. ERC-2981 Royalty Standard
ERC-2981 defines a standard way for NFT contracts to signal royalty information:
interface IERC2981 {
function royaltyInfo(uint256 tokenId, uint256 salePrice)
external view returns (address receiver, uint256 royaltyAmount);
}
Key Insight: ERC-2981 is informational only. It tells marketplaces โthis creator wants X% royalty,โ but marketplaces must choose to honor it. Thereโs no on-chain enforcement.
Book Reference: โMastering Ethereumโ discussions of token standards and the composability of smart contracts.
Questions to Test Understanding:
- Why canโt ERC-2981 enforce royalties on-chain?
- How do platforms like OpenSea, Blur, and LooksRare differ in royalty enforcement?
- What are the economic arguments for and against mandatory royalties?
- How might future standards (like bound transfer hooks) enable enforced royalties?
5. Auction Theory and Game Theory
Different auction types optimize for different goals:
English Auction (Ascending):
Start low โ Bidders compete โ Highest bid wins
- Maximizes price discovery
- Creates FOMO and competition
- Most common for valuable NFTs
Dutch Auction (Descending):
Start high โ Price drops over time โ First bidder wins
- Faster conclusion
- Buyer sets their maximum willingness-to-pay
- Good for initial drops (avoids gas wars)
Sealed-Bid (Vickrey):
All bids hidden โ Highest bidder wins โ Pays second-highest price
- Incentivizes truthful bidding
- More complex to implement on-chain
Book Reference: โComputer Systems: A Programmerโs Perspectiveโ Chapter 12 discusses concurrency which relates to handling simultaneous bids.
Questions to Test Understanding:
- Why does a Dutch auction prevent gas wars during NFT drops?
- In a Vickrey auction, why is paying the second-highest price strategically important?
- How do you handle bid sniping (last-second bids) in English auctions?
- What prevents bid manipulation in on-chain auctions where all data is public?
6. IPFS and Metadata Storage
NFTs donโt store images on-chain (too expensive). Instead, they store a tokenURI pointing to metadata:
On-Chain: Off-Chain (IPFS):
โโโโโโโโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ tokenId: 42 โ โ { โ
โ owner: 0x742...f44e โ โ "name": "Bored Ape #42", โ
โ tokenURI: โโโโโ>โ "image": "ipfs://Qm...", โ
โ "ipfs://QmXyz..." โ โ "attributes": [...] โ
โโโโโโโโโโโโโโโโโโโโโโโโโโ โ } โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
IPFS Addressing: Content-addressed storage means the hash of the content IS the address. Change the file, change the address. This provides immutability.
Book Reference: โDesigning Data-Intensive Applicationsโ Chapter 3 covers storage engines and content-addressable storage concepts.
Questions to Test Understanding:
- Why do many NFT projects use IPFS instead of regular HTTP URLs?
- What happens to an NFT if the IPFS gateway goes down?
- How does pinning work, and who is responsible for keeping NFT metadata available?
- What are the trade-offs of storing metadata fully on-chain (like Loot)?
7. Reentrancy and Marketplace Security
Marketplaces handle ETH transfers, making them prime targets for reentrancy attacks:
Malicious Contract:
1. Calls buyNFT() with ETH
2. Marketplace sends NFT to attacker
3. Marketplace refunds excess ETH to attacker
4. Attacker's receive() function calls buyNFT() again
5. Repeat until marketplace drained
Book Reference: โSerious Cryptographyโ discusses protocol security, and โMastering Ethereumโ Chapter 9 covers smart contract security patterns.
Questions to Test Understanding:
- At what points in a marketplace transaction is reentrancy possible?
- How does the Checks-Effects-Interactions pattern prevent reentrancy?
- When is
transfer()vscall()for sending ETH more appropriate? - How do reentrancy guards (locks) work internally?
8. Gas Optimization for Marketplace Operations
Marketplace operations are gas-intensive. Understanding optimization is crucial:
// Unoptimized: 6 storage reads
struct Listing {
address seller; // 20 bytes, slot 0
bool active; // 1 byte, slot 1
uint256 price; // 32 bytes, slot 2
address nftContract; // 20 bytes, slot 3
uint256 tokenId; // 32 bytes, slot 4
}
// Optimized: Pack into fewer slots
struct Listing {
address seller; // 20 bytes โ
uint96 price; // 12 bytes โดโ slot 0 (32 bytes)
address nftContract; // 20 bytes โ
uint80 tokenId; // 10 bytes โ
bool active; // 1 byte โดโ slot 1 (31 bytes)
}
Book Reference: โComputer Systems: A Programmerโs Perspectiveโ Chapter 6 covers memory hierarchy and cache optimization principles that apply to EVM storage.
Questions to Test Understanding:
- Why does reading from the same storage slot cost less gas than reading from different slots?
- How do you balance uint96/uint80 constraints with real-world price/tokenId ranges?
- When does it make sense to use memory vs. storage vs. calldata?
- How do batch operations (listing multiple NFTs) save gas?
Questions to Guide Your Design
NFT Contract Design
- How will you handle the relationship between your marketplace and external NFT contracts?
- Should your marketplace work with any ERC-721, or only specific collections?
- How will you verify that an NFT contract implements ERC-721 before listing?
- How will you handle NFT contracts that donโt implement ERC-2981 for royalties?
Listing Mechanics
- Will you use escrow or approval pattern? What are the trade-offs in your specific case?
- How will you handle listing expiration? On-chain timer or off-chain with on-chain verification?
- What happens if a seller transfers their NFT elsewhere while itโs listed?
- How will you prevent users from listing the same NFT multiple times?
Pricing and Auctions
- Will you support multiple currencies (ETH, WETH, other ERC-20s)?
- For auctions, how will you handle the minimum bid increment?
- How will you prevent bid sniping in English auctions? (Extend time on late bids?)
- For Dutch auctions, how frequently should the price decrease?
Fees and Royalties
- How will you calculate and distribute multiple fee recipients (marketplace, creator, seller)?
- Will you honor ERC-2981 royalties, or allow sellers to bypass them?
- How will you handle royalties for NFTs that donโt implement ERC-2981?
- What happens if royalty info returns an invalid address (address(0))?
Security and Edge Cases
- How will you prevent reentrancy during purchase/settlement?
- What happens if a buyer sends more ETH than the listing price?
- How will you handle failed ETH transfers (seller is a contract that rejects ETH)?
- What emergency functions will you include (pause, withdraw)?
Thinking Exercise
Before writing any code, trace through this complete marketplace scenario on paper:
Scenario: Secondary Sale with Auction and Royalties
Setup:
- Creator Alice minted โGenesis Art #1โ with 5% royalty (ERC-2981)
- Alice sold it to Bob for 10 ETH (primary sale, no royalty)
- Bob lists it for English auction, starting at 5 ETH, 24-hour duration
Events:
- T+0h: Bob calls
createAuction(nftContract, tokenId, startPrice=5ETH, duration=24h) - T+2h: Carol bids 6 ETH
- T+8h: Dave bids 8 ETH
- T+23h: Eve bids 12 ETH
- T+23h59m: Dave bids 15 ETH (triggers time extension)
- T+24h29m: Eve bids 16 ETH
- T+24h44m: Auction ends, Eve wins
Trace on paper:
STEP 1: createAuction()
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ State Before: โ
โ _owners[1] = Bob โ
โ _approvals[1] = Marketplace (or isApprovedForAll = true) โ
โ auctions = empty โ
โ โ
โ Actions: โ
โ 1. Verify Bob owns token #1 โ
โ 2. Verify marketplace has approval โ
โ 3. Store auction: { seller: Bob, start: 5 ETH, end: T+24h } โ
โ 4. Emit AuctionCreated event โ
โ โ
โ State After: โ
โ auctions[auctionId] = { seller: Bob, ... } โ
โ NFT still with Bob (approval pattern) โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
STEP 2: Carol bids 6 ETH
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ State Before: โ
โ highestBidder = address(0) โ
โ highestBid = 0 โ
โ โ
โ Actions: โ
โ 1. Verify auction active โ
โ 2. Verify 6 ETH > 5 ETH (start price) โ
โ 3. Store Carol's bid โ
โ 4. Hold 6 ETH in escrow โ
โ โ
โ State After: โ
โ highestBidder = Carol โ
โ highestBid = 6 ETH โ
โ escrowedFunds[Carol] = 6 ETH โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
[Continue tracing each step...]
STEP 6: Eve bids 16 ETH (final)
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Actions: โ
โ 1. Verify 16 > 15 ETH (previous bid) โ
โ 2. Refund Dave's 15 ETH โ
โ 3. Store Eve's bid โ
โ 4. Check time remaining (15 min < 10 min extension threshold)โ
โ 5. Extend auction by 10 minutes โ
โ โ
โ State After: โ
โ highestBidder = Eve โ
โ highestBid = 16 ETH โ
โ auctionEndTime = T+24h44m โ
โ Dave's 15 ETH returned โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
STEP 7: Auction Settlement
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Preconditions: โ
โ - Current time > auction end time โ
โ - Auction not already settled โ
โ โ
โ Fee Calculation (16 ETH sale): โ
โ Creator Royalty (5%): 0.8 ETH โ Alice (0xCreator) โ
โ Marketplace Fee (2.5%): 0.4 ETH โ Treasury โ
โ Seller Proceeds: 14.8 ETH โ Bob โ
โ Total: 16.0 ETH โ โ
โ โ
โ Actions: โ
โ 1. Transfer NFT from Bob to Eve โ
โ 2. Send 0.8 ETH to Alice (creator) โ
โ 3. Send 0.4 ETH to Treasury โ
โ 4. Send 14.8 ETH to Bob (seller) โ
โ 5. Mark auction as settled โ
โ โ
โ Final State: โ
โ _owners[1] = Eve โ
โ Bob: +14.8 ETH โ
โ Alice: +0.8 ETH (royalty from secondary sale!) โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Questions to answer after tracing:
- What if Bob revokes marketplace approval between listing and settlement?
- What if Daveโs refund fails (his address is a contract that reverts)?
- How do you prevent Eve from calling settleAuction before time expires?
- What happens if the ERC-2981 royaltyInfo call returns address(0)?
- If thereโs a 10-minute extension rule, can an auction run forever with constant sniping?
The Interview Questions Theyโll Ask
ERC-721 Standard Deep Dive
- What is the difference between
transferFromandsafeTransferFrom?safeTransferFromchecks if the recipient is a contract, and if so, callsonERC721Received()to verify the contract can handle NFTs. Prevents tokens from being locked forever in incompatible contracts.
- Explain the purpose of
tokenURI()and how metadata is typically stored.- Returns a URI pointing to JSON metadata (name, description, image, attributes). Typically stored on IPFS for immutability and decentralization.
- How do you implement a custom ERC-721 collection with rarity traits?
- Trait data in metadata JSON, provenance hash for fairness, on-chain vs off-chain trait storage trade-offs.
- Whatโs the difference between
approve()andsetApprovalForAll()?approve()grants permission for a single tokenId.setApprovalForAll()grants operator status for all tokens owned by the sender in that collection.
- How does ERC-721A differ from standard ERC-721, and when would you use it?
- ERC-721A optimizes batch minting by not updating balances per-token. Saves gas for minting many sequential tokens.
Marketplace Design Patterns
- Explain the trade-offs between escrow and approval-based marketplace designs.
- Escrow: Simpler, NFT in custody, user loses possession, gas for transfer in/out
- Approval: User retains NFT, more efficient, but seller can break listing by transferring elsewhere
- How do you prevent the seller from transferring an NFT after listing it (in approval pattern)?
- Check
ownerOf()and approval status at purchase time. Accept that listings can become invalid.
- Check
- How would you implement a collection-wide floor price feature?
- Track lowest active listing per collection, update on list/delist/purchase, use heap or sorted set for efficiency.
- Describe how youโd implement lazy minting.
- Creator signs a message containing tokenURI and parameters. First buyer submits signature to mint-and-buy atomically. Creator never pays gas until sale.
- How do you handle offers on unlisted NFTs?
- Store offers mapping (nft, tokenId) => offers[]. Seller can accept by transferring to marketplace with offer reference. Or use signature-based offers.
Auction Mechanics
- How do you implement anti-sniping measures in an English auction?
- Time extension: if bid placed within last N minutes, extend auction by M minutes. Cap total extensions to prevent infinite auctions.
- Explain how a Dutch auction prevents gas wars during NFT drops.
- Price starts high and decreases. No competition for the same price point. First buyer at each price level wins without racing.
- How would you implement a sealed-bid auction on a public blockchain?
- Commit-reveal scheme: Bidders submit hash(bid, salt), then reveal bid+salt after commitment period. Prevents front-running.
- What happens if the highest bidderโs transaction fails during settlement?
- Store bids as escrowed ETH (sent with bid). Settlement uses stored funds, canโt fail on buyer side. Handle seller-side failures with pull-over-push pattern.
Security Considerations
- How do you prevent reentrancy in a marketplace contract?
- Checks-Effects-Interactions pattern: Update all state before external calls. Use reentrancy guards (mutex). Prefer pull-over-push for payments.
- What are the risks of using
transfer()vscall()for ETH transfers?transfer()has 2300 gas limit, can fail with receiving contracts.call()forwards all gas but requires reentrancy protection. Usecall()with guards.
- How would you implement emergency pause functionality?
Pausablepattern with onlyOwner/multisig. Pause listing, buying; allow withdrawals. Consider timelock for unpause.
- What attacks are possible if royaltyInfo returns malicious data?
- Returns 100% royalty to drain funds. Returns contract that reverts to DoS. Validate royalty <= max percentage, handle failed transfers gracefully.
Gas Optimization
- How can you optimize storage for listing data?
- Pack structs into fewer storage slots (address + uint96 = 32 bytes). Use uint96 for prices (up to ~79 billion ETH). Batch operations with bitmap for status.
- What are the gas implications of checking ERC-2981 support on every sale?
supportsInterface()costs gas. Cache results per contract. Use try/catch forroyaltyInfo()call. Consider off-chain registry.
Royalties and Economics
- Why canโt ERC-2981 enforce royalties on-chain?
- ERC-2981 is read-only (view function). Nothing prevents direct transfer bypassing marketplace. Enforcement requires transfer hooks (ERC-6150) or wrapper contracts.
- How has the royalty enforcement debate evolved? What are different approaches?
- OpenSea: Optional, then enforced via blocklist. Blur: Minimum 0.5%. Creator coins: Royalties as exit fee. Bound NFTs: Blacklist non-royalty marketplaces.
Hints in Layers
Hint 1: Starting Point
Begin with a minimal ERC-721 contract that you fully understand. Donโt use OpenZeppelin as a black box - study their implementation and write your own. Focus on _owners, _balances, and _tokenApprovals mappings. Get minting and transferring working before touching the marketplace.
Hint 2: Listing Structure
Your listing needs to capture: seller address, NFT contract address, token ID, price, listing type (fixed/auction), and status. Consider whether you need the listing ID to be deterministic (hash of nft+tokenId) or random. Deterministic makes lookups easier but prevents relisting the same NFT with different terms without canceling first.
Hint 3: The Purchase Flow
Think about purchase as a multi-step atomic operation: (1) verify listing active, (2) verify caller sent enough ETH, (3) calculate fee splits, (4) mark listing inactive, (5) transfer NFT, (6) distribute ETH. The order matters for security. What should happen before external calls vs. after?
Hint 4: Handling Royalties Gracefully
Not all NFTs implement ERC-2981. Use try/catch when calling royaltyInfo(). If it fails or returns address(0), proceed without royalty. If royalty percentage is unreasonable (>50%), cap it or reject. Document your policy clearly.
Hint 5: Auction State Machine
An auction moves through states: Created -> Active -> Ended -> Settled. Each state transition should be guarded. Only the highest bidder (or anyone after timeout) can trigger settlement. Store endTime rather than duration to avoid block.timestamp manipulation concerns across function calls.
Hint 6: Refunding Previous Bidders
When a new higher bid comes in, you must refund the previous bidder. Use pull-over-push: donโt send ETH in the bid function, store pending refunds. Provide a withdraw() function for users to claim. This prevents griefing where a malicious bidder blocks new bids.
Hint 7: Testing Strategy
Test the unhappy paths as thoroughly as happy paths. Test: listing NFT you donโt own, buying your own listing, bidding less than current bid, settling auction before end, double-settling, approval revoked between list and buy. Each should revert with clear error.
Hint 8: Frontend Integration
Your frontend needs to: (1) read listings from events or indexer, (2) fetch metadata from IPFS/HTTP, (3) prompt wallet approval before listing, (4) show transaction status during purchase. Consider using The Graph for indexing, wagmi/viem for wallet integration, and IPFS gateways with fallbacks.
Books That Will Help
| Book | Relevant Chapters | What Youโll Learn |
|---|---|---|
| โMastering Ethereumโ by Antonopoulos & Wood | Ch. 10: Tokens; Ch. 11: Oracles; Ch. 9: Smart Contract Security | ERC-721 fundamentals, how contracts interact, security patterns |
| โDesigning Data-Intensive Applicationsโ by Kleppmann | Ch. 7: Transactions; Ch. 8: Distributed Systems; Ch. 3: Storage | Atomicity of marketplace operations, consistency guarantees, indexing for queries |
| โComputer Systems: A Programmerโs Perspectiveโ by Bryant & OโHallaron | Ch. 6: Memory Hierarchy; Ch. 12: Concurrent Programming | Storage slot packing, understanding gas costs, handling concurrent bids |
| โSerious Cryptographyโ by Aumasson | Ch. 1: Encryption; Ch. 11: Authentication | Understanding signatures for lazy minting, commit-reveal for sealed bids |
Additional Resources
EIPs and Standards
- EIP-721: Non-Fungible Token Standard - The foundational NFT specification
- EIP-2981: NFT Royalty Standard - How royalties are communicated
- EIP-4907: Rental NFT Standard - Extends ERC-721 with rental functionality
- EIP-2309: Consecutive Transfer Extension - Efficient batch minting events
Production Marketplace Contracts
- OpenSea Seaport: GitHub - Advanced order-based marketplace protocol
- LooksRare: GitHub - Production marketplace implementation
- Blur: Study their aggregator and marketplace contracts on Etherscan
Development Tools
- Foundry: Book - Modern Solidity testing framework
- Hardhat: Docs - JavaScript-based development environment
- IPFS: Docs - Decentralized storage for NFT metadata
- The Graph: Docs - Indexing protocol for querying blockchain data
NFT Metadata Standards
- OpenSea Metadata Standards: Docs - How to structure metadata for marketplace compatibility
- ERC-721 Metadata JSON Schema: IPFS Example
Security Resources
- SWC Registry: Smart Contract Weakness Classification - Common vulnerabilities
- OpenZeppelin Defender: Docs - Transaction monitoring and security
- Damn Vulnerable DeFi: Website - Security challenges including NFT scenarios
Self-Assessment Checklist
Before moving to the next project, verify:
- I can explain the difference between ERC-20 and ERC-721 at a fundamental level
- I understand why
safeTransferFromexists and when itโs necessary - I can implement both escrow and approval-based marketplace patterns
- I understand the trade-offs of each pattern and can justify my choice
- My marketplace correctly handles ERC-2981 royalties (including contracts that donโt implement it)
- I can implement at least two auction types (English, Dutch, or sealed-bid)
- I understand how to prevent reentrancy in fund distribution
- My tests cover listing, buying, auction bidding, and edge cases
- I can explain why ERC-2981 canโt enforce royalties and the marketplace ecosystem implications
- I understand how NFT metadata works with IPFS and tokenURI
- I can optimize storage layout for gas efficiency
- Iโve considered emergency functions (pause, withdraw, upgrade path)
Whatโs Next?
With NFT marketplace mechanics mastered, you now understand how digital ownership and commerce work on-chain. Youโve seen how unique tokens enable marketplaces, and how royalties create new creator economics.
In Project 12: DAO Governance System, youโll build the organizational primitive of Web3: decentralized decision-making. Youโll implement proposal creation, voting mechanisms, delegation, and timelocks. This is where token ownership transforms into governance power, and where code becomes the constitution of digital organizations.