NFT Marketplace: Implementation, Royalties, and IPFS
Building a complete NFT marketplace with royalties, IPFS metadata integration, and lazy minting.
NFT Marketplace: Implementation, Royalties, and IPFS
Building a complete NFT marketplace with advanced features.
Marketplace Components
1. Listing System
RUSTenum 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
RUSTstruct 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
RUSTstruct 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.
RUSTstruct 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
RUSTenum 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
- Royalty Enforcement: Always pay creator royalties
- IPFS Integration: Store metadata on IPFS
- Lazy Minting: Reduce creator costs
- Signature Verification: Verify lazy minting signatures
- Fee Structure: Transparent marketplace fees
Code Examples
NFT Marketplace
Basic NFT marketplace with listings
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
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!
Starter Code:
fn calculate_fee(price: u64, fee_percent: u8) -> u64 {
// fee_percent is in basis points (1000 = 100%)
// e.g., 25 = 2.5%
}