NFT Marketplace: Implementation, Royalties, and IPFS

Building a complete NFT marketplace with royalties, IPFS metadata integration, and lazy minting.

Advanced⏱️ 60 min📚 Prerequisites: 2

NFT Marketplace: Implementation, Royalties, and IPFS

Building a complete NFT marketplace with advanced features.

Marketplace Components

1. Listing System

RUST
enum ListingStatus {
    Active,
    Sold,
    Cancelled,
}

struct Listing {
    id: u64,
    token_id: u64,
    collection: String,
    seller: String,
    price: u64,
    status: ListingStatus,
    expires_at: u64,
}

struct Marketplace {
    listings: HashMap<u64, Listing>,
    next_listing_id: u64,
    fee_percentage: u8, // e.g., 25 = 2.5%
}

impl Marketplace {
    fn list_nft(
        &mut self,
        token_id: u64,
        collection: &str,
        seller: &str,
        price: u64,
        expires_at: u64,
    ) -> Result<u64, String> {
        // Verify seller owns NFT
        // Create listing
        
        let listing_id = self.next_listing_id;
        self.next_listing_id += 1;
        
        let listing = Listing {
            id: listing_id,
            token_id,
            collection: collection.to_string(),
            seller: seller.to_string(),
            price,
            status: ListingStatus::Active,
            expires_at,
        };
        
        self.listings.insert(listing_id, listing);
        Ok(listing_id)
    }
    
    fn buy_nft(
        &mut self,
        listing_id: u64,
        buyer: &str,
    ) -> Result<(), String> {
        let listing = self.listings.get_mut(&listing_id)
            .ok_or("Listing not found")?;
        
        if !matches!(listing.status, ListingStatus::Active) {
            return Err(String::from("Listing not active"));
        }
        
        // Calculate fees
        let marketplace_fee = (listing.price * self.fee_percentage as u64) / 1000;
        let seller_payment = listing.price - marketplace_fee;
        
        // Transfer NFT to buyer
        // Transfer payment to seller
        // Transfer fee to marketplace
        
        listing.status = ListingStatus::Sold;
        
        Ok(())
    }
}

2. Royalty System

RUST
struct RoyaltyInfo {
    recipient: String,
    percentage: u8, // e.g., 100 = 10%
}

struct NFTCollection {
    royalty_info: Option<RoyaltyInfo>,
    royalty_enforced: bool,
}

impl Marketplace {
    fn calculate_royalties(
        &self,
        collection: &str,
        sale_price: u64,
    ) -> Option<(String, u64)> {
        // Get collection royalty info
        // Calculate royalty amount
        
        // Simplified: assume 10% royalty
        let royalty_percentage = 10;
        let royalty_amount = (sale_price * royalty_percentage as u64) / 100;
        
        Some((String::from("royalty_recipient"), royalty_amount))
    }
    
    fn buy_with_royalties(
        &mut self,
        listing_id: u64,
        buyer: &str,
    ) -> Result<(), String> {
        let listing = self.listings.get(&listing_id)
            .ok_or("Listing not found")?;
        
        let total_price = listing.price;
        
        // Calculate royalties
        let (royalty_recipient, royalty_amount) = self
            .calculate_royalties(&listing.collection, total_price)
            .ok_or("Royalty calculation failed")?;
        
        // Calculate marketplace fee
        let marketplace_fee = (total_price * self.fee_percentage as u64) / 1000;
        
        // Seller receives: price - marketplace_fee - royalty
        let seller_payment = total_price - marketplace_fee - royalty_amount;
        
        // Transfer payments
        // - Royalty to creator
        // - Marketplace fee to marketplace
        // - Remaining to seller
        
        Ok(())
    }
}

3. IPFS Metadata

RUST
struct NFTMetadata {
    name: String,
    description: String,
    image_ipfs_hash: String, // IPFS CID
    attributes: Vec<Attribute>,
    external_url: Option<String>,
}

struct Attribute {
    trait_type: String,
    value: String,
}

struct IPFSStorage {
    gateway: String, // e.g., "https://ipfs.io/ipfs/"
}

impl IPFSStorage {
    fn get_metadata_url(&self, ipfs_hash: &str) -> String {
        format!("{}{}", self.gateway, ipfs_hash)
    }
    
    fn validate_ipfs_hash(&self, hash: &str) -> bool {
        // Validate IPFS CID format
        hash.starts_with("Qm") || hash.starts_with("bafy")
    }
}

struct NFT {
    token_id: u64,
    owner: String,
    metadata_ipfs: String,
    metadata: Option<NFTMetadata>,
}

impl NFT {
    fn fetch_metadata(&mut self, ipfs: &IPFSStorage) -> Result<(), String> {
        if !ipfs.validate_ipfs_hash(&self.metadata_ipfs) {
            return Err(String::from("Invalid IPFS hash"));
        }
        
        // In real implementation: fetch from IPFS
        // For now: create placeholder
        self.metadata = Some(NFTMetadata {
            name: format!("NFT #{}", self.token_id),
            description: String::from("An awesome NFT"),
            image_ipfs_hash: self.metadata_ipfs.clone(),
            attributes: vec![
                Attribute {
                    trait_type: String::from("Rarity"),
                    value: String::from("Common"),
                },
            ],
            external_url: None,
        });
        
        Ok(())
    }
}

4. Lazy Minting

Lazy minting allows creating NFTs without paying gas until first sale.

RUST
struct LazyMintListing {
    id: u64,
    creator: String,
    metadata_ipfs: String,
    price: u64,
    signature: Vec<u8>, // Creator's signature
    status: ListingStatus,
}

struct LazyMintingMarketplace {
    lazy_listings: HashMap<u64, LazyMintListing>,
    next_id: u64,
}

impl LazyMintingMarketplace {
    fn create_lazy_listing(
        &mut self,
        creator: &str,
        metadata_ipfs: String,
        price: u64,
        signature: Vec<u8>,
    ) -> u64 {
        let id = self.next_id;
        self.next_id += 1;
        
        let listing = LazyMintListing {
            id,
            creator: creator.to_string(),
            metadata_ipfs,
            price,
            signature,
            status: ListingStatus::Active,
        };
        
        self.lazy_listings.insert(id, listing);
        id
    }
    
    fn buy_lazy_minted(
        &mut self,
        listing_id: u64,
        buyer: &str,
    ) -> Result<u64, String> {
        let listing = self.lazy_listings.get_mut(&listing_id)
            .ok_or("Listing not found")?;
        
        // Verify signature
        if !self.verify_signature(listing) {
            return Err(String::from("Invalid signature"));
        }
        
        // Mint NFT (now that buyer pays gas)
        let token_id = self.mint_nft(
            buyer,
            &listing.metadata_ipfs,
            &listing.creator,
        )?;
        
        // Transfer payment to creator
        // No marketplace fee on lazy minting (or reduced)
        
        listing.status = ListingStatus::Sold;
        
        Ok(token_id)
    }
    
    fn verify_signature(&self, listing: &LazyMintListing) -> bool {
        // Verify creator's signature
        // Ensures listing is authorized
        true
    }
    
    fn mint_nft(
        &self,
        owner: &str,
        metadata_ipfs: &str,
        creator: &str,
    ) -> Result<u64, String> {
        // Mint NFT with metadata
        // Set creator for royalties
        Ok(1)
    }
}

Auction System

RUST
enum AuctionStatus {
    Active,
    Ended,
    Cancelled,
}

struct Auction {
    id: u64,
    token_id: u64,
    seller: String,
    starting_price: u64,
    reserve_price: u64,
    highest_bid: u64,
    highest_bidder: Option<String>,
    end_time: u64,
    status: AuctionStatus,
}

struct AuctionHouse {
    auctions: HashMap<u64, Auction>,
    bids: HashMap<(u64, String), u64>, // (auction_id, bidder) -> bid_amount
}

impl AuctionHouse {
    fn place_bid(
        &mut self,
        auction_id: u64,
        bidder: &str,
        amount: u64,
    ) -> Result<(), String> {
        let auction = self.auctions.get_mut(&auction_id)
            .ok_or("Auction not found")?;
        
        if !matches!(auction.status, AuctionStatus::Active) {
            return Err(String::from("Auction not active"));
        }
        
        if amount <= auction.highest_bid {
            return Err(String::from("Bid too low"));
        }
        
        // Refund previous highest bidder
        if let Some(prev_bidder) = &auction.highest_bidder {
            // Refund logic
        }
        
        // Set new highest bid
        auction.highest_bid = amount;
        auction.highest_bidder = Some(bidder.to_string());
        self.bids.insert((auction_id, bidder.to_string()), amount);
        
        Ok(())
    }
    
    fn end_auction(&mut self, auction_id: u64) -> Result<(), String> {
        let auction = self.auctions.get_mut(&auction_id)
            .ok_or("Auction not found")?;
        
        if auction.highest_bid < auction.reserve_price {
            auction.status = AuctionStatus::Cancelled;
            // Refund highest bidder
            return Ok(());
        }
        
        // Transfer NFT to highest bidder
        // Transfer payment to seller
        
        auction.status = AuctionStatus::Ended;
        Ok(())
    }
}

Real-World Examples

  • OpenSea: Largest NFT marketplace
  • LooksRare: Community-owned marketplace
  • Magic Eden: Solana NFT marketplace
  • Rarible: Creator-focused marketplace

Best Practices

  1. Royalty Enforcement: Always pay creator royalties
  2. IPFS Integration: Store metadata on IPFS
  3. Lazy Minting: Reduce creator costs
  4. Signature Verification: Verify lazy minting signatures
  5. Fee Structure: Transparent marketplace fees

Code Examples

NFT Marketplace

Basic NFT marketplace with listings

RUST
use std::collections::HashMap;

enum ListingStatus {
    Active,
    Sold,
}

struct Listing {
    id: u64,
    token_id: u64,
    seller: String,
    price: u64,
    status: ListingStatus,
}

struct Marketplace {
    listings: HashMap<u64, Listing>,
    next_id: u64,
    fee_percentage: u8,
}

impl Marketplace {
    fn new() -> Self {
        Marketplace {
            listings: HashMap::new(),
            next_id: 1,
            fee_percentage: 25, // 2.5%
        }
    }
    
    fn list_nft(
        &mut self,
        token_id: u64,
        seller: &str,
        price: u64,
    ) -> u64 {
        let id = self.next_id;
        self.next_id += 1;
        
        self.listings.insert(id, Listing {
            id,
            token_id,
            seller: seller.to_string(),
            price,
            status: ListingStatus::Active,
        });
        
        id
    }
    
    fn buy_nft(
        &mut self,
        listing_id: u64,
        buyer: &str,
    ) -> Result<u64, String> {
        let listing = self.listings.get_mut(&listing_id)
            .ok_or("Listing not found")?;
        
        if matches!(listing.status, ListingStatus::Sold) {
            return Err(String::from("Already sold"));
        }
        
        // Calculate fees
        let marketplace_fee = (listing.price * self.fee_percentage as u64) / 1000;
        let seller_payment = listing.price - marketplace_fee;
        
        println!("Buying NFT #{} for {}", listing.token_id, listing.price);
        println!("Marketplace fee: {}", marketplace_fee);
        println!("Seller receives: {}", seller_payment);
        
        listing.status = ListingStatus::Sold;
        
        Ok(listing.token_id)
    }
}

fn main() {
    let mut marketplace = Marketplace::new();
    
    // List NFT
    let listing_id = marketplace.list_nft(1, "alice", 1000);
    println!("NFT listed with ID: {}", listing_id);
    
    // Buy NFT
    match marketplace.buy_nft(listing_id, "bob") {
        Ok(token_id) => {
            println!("NFT #{} purchased successfully!", token_id);
        }
        Err(e) => println!("Purchase failed: {}", e),
    }
}

Explanation:

NFT marketplaces enable buying and selling NFTs. They charge a fee on each sale. Listings track the NFT, seller, price, and status. When bought, the NFT transfers to the buyer and payment goes to the seller (minus fees).

Royalty System

Calculate and distribute royalties

RUST
struct RoyaltyInfo {
    recipient: String,
    percentage: u8, // e.g., 100 = 10%
}

struct Sale {
    price: u64,
    marketplace_fee_percent: u8,
    royalty_info: Option<RoyaltyInfo>,
}

impl Sale {
    fn calculate_payments(&self) -> (u64, u64, u64) {
        // Marketplace fee
        let marketplace_fee = (self.price * self.marketplace_fee_percent as u64) / 1000;
        
        // Royalty
        let (royalty_amount, royalty_recipient) = if let Some(ref royalty) = self.royalty_info {
            let amount = (self.price * royalty.percentage as u64) / 1000;
            (amount, royalty.recipient.clone())
        } else {
            (0, String::new())
        };
        
        // Seller payment
        let seller_payment = self.price - marketplace_fee - royalty_amount;
        
        (marketplace_fee, royalty_amount, seller_payment)
    }
}

fn main() {
    let sale = Sale {
        price: 1000,
        marketplace_fee_percent: 25, // 2.5%
        royalty_info: Some(RoyaltyInfo {
            recipient: String::from("0xCreator"),
            percentage: 100, // 10%
        }),
    };
    
    let (marketplace_fee, royalty, seller_payment) = sale.calculate_payments();
    
    println!("Sale price: {}", sale.price);
    println!("Marketplace fee: {}", marketplace_fee);
    println!("Royalty to creator: {}", royalty);
    println!("Seller receives: {}", seller_payment);
    println!("Total: {}", marketplace_fee + royalty + seller_payment);
}

Explanation:

Royalties ensure creators get paid on secondary sales. The sale price is split between marketplace fee, creator royalty, and seller payment. This incentivizes creators and is a key feature of NFT marketplaces.

Exercises

Calculate Marketplace Fee

Calculate marketplace fee from sale price!

Easy

Starter Code:

RUST
fn calculate_fee(price: u64, fee_percent: u8) -> u64 {
    // fee_percent is in basis points (1000 = 100%)
    // e.g., 25 = 2.5%
}