NFT Metadata Manager and DEX Order Book Challenges
Build an NFT metadata manager and a DEX order book using Rust collections and string processing to practice production patterns common in Solana apps.
Challenge Mission: Real-World Applications
Build two production-ready systems that showcase advanced collection and string handling: an NFT metadata manager for digital assets and a DEX order book for trading operations. These challenges mirror real Solana dApp requirements!
Challenge 1: NFT Metadata Manager
Build a comprehensive NFT metadata management system that can process, validate, and organize NFT collections.
Core Data Structures
use std::collections::{HashMap, BTreeMap, HashSet};
#[derive(Debug, Clone)]
struct Attribute {
trait_type: String,
value: String,
rarity_percentage: Option<f64>,
}
#[derive(Debug, Clone)]
struct NFTMetadata {
name: String,
description: String,
image_url: String,
external_url: Option<String>,
attributes: Vec<Attribute>,
collection_id: String,
mint_address: String,
creator: String,
created_at: u64,
}
#[derive(Debug, Clone)]
struct Collection {
id: String,
name: String,
symbol: String,
description: String,
image_url: String,
total_supply: u32,
floor_price: Option<u64>,
creator: String,
verified: bool,
}
#[derive(Debug)]
enum MetadataError {
CollectionNotFound(String),
NFTNotFound(String),
InvalidImageUrl(String),
DuplicateMint(String),
InvalidAttribute(String),
ParsingError(String),
}
#[derive(Debug)]
struct RarityAnalysis {
total_nfts: u32,
attribute_counts: HashMap<String, HashMap<String, u32>>,
rarity_scores: HashMap<String, f64>,
}
struct NFTMetadataManager {
collections: HashMap<String, Collection>,
nfts: HashMap<String, NFTMetadata>, // mint_address -> metadata
collection_nfts: HashMap<String, Vec<String>>, // collection_id -> mint_addresses
attribute_index: HashMap<String, HashMap<String, Vec<String>>>, // trait_type -> value -> mint_addresses
}Implementation Requirements
impl NFTMetadataManager {
fn new() -> Self {
// TODO: Initialize all data structures
todo!()
}
// === Collection Management ===
fn create_collection(&mut self, collection: Collection) -> Result<(), MetadataError> {
// TODO: Add collection with validation
// - Check for duplicate collection IDs
// - Validate required fields
todo!()
}
fn get_collection(&self, collection_id: &str) -> Result<&Collection, MetadataError> {
// TODO: Retrieve collection by ID
todo!()
}
fn update_collection_floor_price(&mut self, collection_id: &str, price: u64) -> Result<(), MetadataError> {
// TODO: Update floor price for collection
todo!()
}
// === NFT Management ===
fn add_nft(&mut self, nft: NFTMetadata) -> Result<(), MetadataError> {
// TODO: Add NFT with comprehensive validation
// - Validate mint address format
// - Check collection exists
// - Validate image URL format
// - Check for duplicate mint addresses
// - Update indices
todo!()
}
fn get_nft(&self, mint_address: &str) -> Result<&NFTMetadata, MetadataError> {
// TODO: Retrieve NFT by mint address
todo!()
}
fn update_nft_metadata(&mut self, mint_address: &str, new_metadata: NFTMetadata) -> Result<(), MetadataError> {
// TODO: Update existing NFT metadata
// - Rebuild indices if attributes changed
todo!()
}
fn delete_nft(&mut self, mint_address: &str) -> Result<NFTMetadata, MetadataError> {
// TODO: Remove NFT and clean up indices
todo!()
}
// === Search and Filter Operations ===
fn get_nfts_by_collection(&self, collection_id: &str) -> Result<Vec<&NFTMetadata>, MetadataError> {
// TODO: Get all NFTs in a collection
todo!()
}
fn search_nfts_by_name(&self, query: &str) -> Vec<&NFTMetadata> {
// TODO: Search NFTs by name (case-insensitive, partial match)
todo!()
}
fn filter_by_attributes(&self, filters: Vec<(&str, &str)>) -> Vec<&NFTMetadata> {
// TODO: Filter NFTs by multiple attributes
// Example: [("Background", "Blue"), ("Eyes", "Laser")]
todo!()
}
fn get_nfts_by_creator(&self, creator: &str) -> Vec<&NFTMetadata> {
// TODO: Get all NFTs by creator
todo!()
}
fn get_nfts_with_trait(&self, trait_type: &str, value: &str) -> Vec<&NFTMetadata> {
// TODO: Get NFTs with specific trait value
todo!()
}
// === Rarity Analysis ===
fn calculate_rarity_scores(&mut self, collection_id: &str) -> Result<RarityAnalysis, MetadataError> {
// TODO: Calculate rarity scores for all NFTs in collection
// - Count frequency of each attribute value
// - Calculate rarity percentage for each attribute
// - Compute overall rarity score for each NFT
todo!()
}
fn get_rarest_nfts(&self, collection_id: &str, limit: usize) -> Result<Vec<&NFTMetadata>, MetadataError> {
// TODO: Get the rarest NFTs in collection (requires rarity calculation first)
todo!()
}
fn get_attribute_distribution(&self, collection_id: &str, trait_type: &str) -> Result<HashMap<String, u32>, MetadataError> {
// TODO: Get distribution of values for a specific trait type
todo!()
}
// === Metadata Processing ===
fn parse_metadata_json(&self, json_str: &str) -> Result<NFTMetadata, MetadataError> {
// TODO: Parse NFT metadata from JSON string
// Handle various JSON formats and validate required fields
todo!()
}
fn validate_image_url(&self, url: &str) -> Result<(), MetadataError> {
// TODO: Validate image URL format and accessibility
// Check for common image extensions and valid URL format
todo!()
}
fn sanitize_attribute_value(&self, value: &str) -> String {
// TODO: Clean and normalize attribute values
// - Trim whitespace
// - Convert to consistent case
// - Remove special characters if needed
todo!()
}
// === Batch Operations ===
fn bulk_import_collection(&mut self, collection: Collection, metadata_list: Vec<NFTMetadata>) -> Result<(u32, Vec<String>), MetadataError> {
// TODO: Import entire collection with batch processing
// Return (successful_count, error_messages)
todo!()
}
fn export_collection_metadata(&self, collection_id: &str) -> Result<String, MetadataError> {
// TODO: Export collection metadata as JSON
todo!()
}
// === Statistics and Analytics ===
fn get_collection_stats(&self, collection_id: &str) -> Result<CollectionStats, MetadataError> {
// TODO: Generate comprehensive collection statistics
todo!()
}
fn find_incomplete_metadata(&self) -> Vec<&NFTMetadata> {
// TODO: Find NFTs with missing or incomplete metadata
// Check for missing descriptions, invalid URLs, empty attributes
todo!()
}
fn get_top_attributes(&self, collection_id: &str, limit: usize) -> Result<Vec<(String, u32)>, MetadataError> {
// TODO: Get most common trait types in collection
todo!()
}
}
#[derive(Debug)]
struct CollectionStats {
total_nfts: u32,
unique_attributes: u32,
most_common_trait: (String, String, u32), // trait_type, value, count
rarest_trait: (String, String, u32),
average_attribute_count: f64,
creators_count: u32,
}Challenge 2: DEX Order Book
Build a decentralized exchange order book system for managing trading pairs and orders.
Core Data Structures
use std::collections::{HashMap, BTreeMap, VecDeque};
#[derive(Debug, Clone, PartialEq)]
enum OrderSide {
Buy,
Sell,
}
#[derive(Debug, Clone, PartialEq)]
enum OrderStatus {
Open,
PartiallyFilled { filled_amount: u64 },
Filled,
Cancelled,
Expired,
}
#[derive(Debug, Clone)]
struct Order {
id: String,
user: String,
trading_pair: String,
side: OrderSide,
amount: u64,
price: u64, // Price in smallest unit (like lamports)
filled_amount: u64,
status: OrderStatus,
created_at: u64,
expires_at: Option<u64>,
}
#[derive(Debug, Clone)]
struct Trade {
id: String,
trading_pair: String,
buy_order_id: String,
sell_order_id: String,
amount: u64,
price: u64,
buyer: String,
seller: String,
timestamp: u64,
}
#[derive(Debug)]
struct TradingPair {
base_token: String,
quote_token: String,
// Price-sorted order books (price -> order_ids)
buy_orders: BTreeMap<u64, Vec<String>>, // Highest price first
sell_orders: BTreeMap<u64, Vec<String>>, // Lowest price first
last_price: Option<u64>,
volume_24h: u64,
high_24h: Option<u64>,
low_24h: Option<u64>,
}
#[derive(Debug)]
enum OrderBookError {
TradingPairNotFound(String),
OrderNotFound(String),
InsufficientBalance,
InvalidPrice,
InvalidAmount,
OrderExpired,
UnauthorizedOperation,
DuplicateOrder(String),
}
#[derive(Debug)]
struct MarketData {
price: u64,
volume_24h: u64,
price_change_24h: i64, // Can be negative
high_24h: u64,
low_24h: u64,
trades_count_24h: u32,
}
struct DEXOrderBook {
trading_pairs: HashMap<String, TradingPair>,
orders: HashMap<String, Order>,
user_orders: HashMap<String, Vec<String>>, // user -> order_ids
trade_history: Vec<Trade>,
user_balances: HashMap<String, HashMap<String, u64>>, // user -> token -> balance
current_timestamp: u64,
}Implementation Requirements
impl DEXOrderBook {
fn new() -> Self {
// TODO: Initialize all data structures
todo!()
}
// === Trading Pair Management ===
fn create_trading_pair(&mut self, base_token: String, quote_token: String) -> Result<String, OrderBookError> {
// TODO: Create new trading pair
// - Generate pair ID (e.g., "SOL/USDC")
// - Validate token symbols
// - Check for duplicates
todo!()
}
fn get_trading_pairs(&self) -> Vec<&String> {
// TODO: Get all available trading pairs
todo!()
}
fn get_market_data(&self, pair: &str) -> Result<MarketData, OrderBookError> {
// TODO: Calculate and return market data for trading pair
todo!()
}
// === Order Management ===
fn place_order(&mut self, user: String, pair: String, side: OrderSide, amount: u64, price: u64) -> Result<String, OrderBookError> {
// TODO: Place new order with comprehensive validation
// - Validate trading pair exists
// - Check user balance
// - Generate unique order ID
// - Add to order book
// - Try to match immediately
// - Return order ID
todo!()
}
fn cancel_order(&mut self, order_id: &str, user: &str) -> Result<Order, OrderBookError> {
// TODO: Cancel order with authorization check
// - Verify user owns the order
// - Remove from order book
// - Update status
// - Refund any reserved balance
todo!()
}
fn get_order(&self, order_id: &str) -> Result<&Order, OrderBookError> {
// TODO: Retrieve order by ID
todo!()
}
fn get_user_orders(&self, user: &str) -> Vec<&Order> {
// TODO: Get all orders for a user
todo!()
}
fn get_open_orders(&self, user: &str) -> Vec<&Order> {
// TODO: Get only open orders for a user
todo!()
}
// === Order Book Operations ===
fn get_order_book(&self, pair: &str, depth: usize) -> Result<(Vec<(u64, u64)>, Vec<(u64, u64)>), OrderBookError> {
// TODO: Get order book depth
// Returns (buy_orders, sell_orders) as (price, total_amount) pairs
// Sorted by best prices first
todo!()
}
fn get_best_bid(&self, pair: &str) -> Result<Option<u64>, OrderBookError> {
// TODO: Get highest buy price
todo!()
}
fn get_best_ask(&self, pair: &str) -> Result<Option<u64>, OrderBookError> {
// TODO: Get lowest sell price
todo!()
}
fn get_spread(&self, pair: &str) -> Result<Option<u64>, OrderBookError> {
// TODO: Calculate bid-ask spread
todo!()
}
// === Matching Engine ===
fn match_orders(&mut self, pair: &str) -> Result<Vec<Trade>, OrderBookError> {
// TODO: Implement order matching algorithm
// - Match buy orders with sell orders
// - Execute trades at appropriate prices
// - Update order statuses
// - Generate trade records
// - Update balances
todo!()
}
fn execute_trade(&mut self, buy_order_id: &str, sell_order_id: &str, amount: u64, price: u64) -> Result<Trade, OrderBookError> {
// TODO: Execute a single trade between two orders
// - Update order filled amounts
// - Update order statuses
// - Transfer balances
// - Create trade record
todo!()
}
// === Balance Management ===
fn deposit(&mut self, user: String, token: String, amount: u64) -> Result<(), OrderBookError> {
// TODO: Add tokens to user balance
todo!()
}
fn withdraw(&mut self, user: &str, token: &str, amount: u64) -> Result<(), OrderBookError> {
// TODO: Remove tokens from user balance
// - Check sufficient balance
// - Account for reserved amounts in open orders
todo!()
}
fn get_balance(&self, user: &str, token: &str) -> u64 {
// TODO: Get user's available balance for token
todo!()
}
fn get_reserved_balance(&self, user: &str, token: &str) -> u64 {
// TODO: Get user's reserved balance (in open orders)
todo!()
}
// === Market Analysis ===
fn get_price_history(&self, pair: &str, hours: u64) -> Result<Vec<(u64, u64)>, OrderBookError> {
// TODO: Get price history as (timestamp, price) pairs
todo!()
}
fn get_volume_analysis(&self, pair: &str) -> Result<VolumeAnalysis, OrderBookError> {
// TODO: Analyze trading volume patterns
todo!()
}
fn get_trade_history(&self, pair: &str, limit: usize) -> Result<Vec<&Trade>, OrderBookError> {
// TODO: Get recent trades for pair
todo!()
}
fn get_user_trade_history(&self, user: &str, limit: usize) -> Vec<&Trade> {
// TODO: Get user's trade history
todo!()
}
// === Order Book Maintenance ===
fn expire_orders(&mut self) -> u32 {
// TODO: Remove expired orders
// - Check all orders against current timestamp
// - Cancel expired orders
// - Refund balances
// - Return count of expired orders
todo!()
}
fn cleanup_filled_orders(&mut self, older_than_hours: u64) -> u32 {
// TODO: Archive old filled orders
todo!()
}
fn advance_time(&mut self, new_timestamp: u64) {
// TODO: Update current timestamp and expire orders
self.current_timestamp = new_timestamp;
self.expire_orders();
}
// === Analytics and Reporting ===
fn generate_market_summary(&self) -> MarketSummary {
// TODO: Generate comprehensive market summary
todo!()
}
fn get_top_traders(&self, limit: usize) -> Vec<(String, u64)> {
// TODO: Get users with highest trading volume
todo!()
}
fn calculate_liquidity(&self, pair: &str) -> Result<LiquidityMetrics, OrderBookError> {
// TODO: Calculate liquidity metrics for trading pair
todo!()
}
}
#[derive(Debug)]
struct VolumeAnalysis {
total_volume_24h: u64,
buy_volume_24h: u64,
sell_volume_24h: u64,
trades_count_24h: u32,
average_trade_size: u64,
largest_trade_24h: u64,
}
#[derive(Debug)]
struct MarketSummary {
total_trading_pairs: usize,
total_orders: usize,
total_trades_24h: usize,
total_volume_24h: u64,
most_active_pair: String,
total_users: usize,
}
#[derive(Debug)]
struct LiquidityMetrics {
bid_liquidity: u64, // Total buy order value
ask_liquidity: u64, // Total sell order value
spread_percentage: f64,
depth_10_percent: u64, // Liquidity within 10% of best price
}Testing Your Implementations
NFT Metadata Manager Test
fn test_nft_metadata_manager() {
let mut manager = NFTMetadataManager::new();
// Test 1: Create collection
let collection = Collection {
id: "solana_monkeys".to_string(),
name: "Solana Monkeys".to_string(),
symbol: "SMB".to_string(),
description: "A collection of unique monkey NFTs on Solana".to_string(),
image_url: "https://example.com/collection.png".to_string(),
total_supply: 10000,
floor_price: Some(1000000), // 1 SOL in lamports
creator: "creator_wallet_address".to_string(),
verified: true,
};
manager.create_collection(collection).expect("Failed to create collection");
// Test 2: Add NFTs
for i in 1..=5 {
let nft = NFTMetadata {
name: format!("Monkey #{}", i),
description: format!("A unique monkey #{}", i),
image_url: format!("https://example.com/monkey_{}.png", i),
external_url: Some("https://solanmonkeys.com".to_string()),
attributes: vec![
Attribute {
trait_type: "Background".to_string(),
value: if i % 2 == 0 { "Blue".to_string() } else { "Red".to_string() },
rarity_percentage: None,
},
Attribute {
trait_type: "Eyes".to_string(),
value: if i <= 2 { "Laser".to_string() } else { "Normal".to_string() },
rarity_percentage: None,
},
],
collection_id: "solana_monkeys".to_string(),
mint_address: format!("monkey_mint_{}", i),
creator: "creator_wallet_address".to_string(),
created_at: 1640000000 + i as u64,
};
manager.add_nft(nft).expect("Failed to add NFT");
}
// Test 3: Search and filter
let laser_eyes = manager.get_nfts_with_trait("Eyes", "Laser");
println!("NFTs with laser eyes: {}", laser_eyes.len());
let blue_background = manager.filter_by_attributes(vec![("Background", "Blue")]);
println!("NFTs with blue background: {}", blue_background.len());
// Test 4: Rarity analysis
let rarity_analysis = manager.calculate_rarity_scores("solana_monkeys")
.expect("Failed to calculate rarity");
println!("Rarity analysis: {:#?}", rarity_analysis);
// Test 5: Collection stats
let stats = manager.get_collection_stats("solana_monkeys")
.expect("Failed to get collection stats");
println!("Collection stats: {:#?}", stats);
}DEX Order Book Test
fn test_dex_order_book() {
let mut dex = DEXOrderBook::new();
// Test 1: Create trading pair
let pair_id = dex.create_trading_pair("SOL".to_string(), "USDC".to_string())
.expect("Failed to create trading pair");
println!("Created trading pair: {}", pair_id);
// Test 2: Add user balances
dex.deposit("alice".to_string(), "SOL".to_string(), 10_000_000_000) // 10 SOL
.expect("Failed to deposit SOL for Alice");
dex.deposit("bob".to_string(), "USDC".to_string(), 1000_000_000) // 1000 USDC
.expect("Failed to deposit USDC for Bob");
// Test 3: Place orders
let buy_order = dex.place_order(
"bob".to_string(),
pair_id.clone(),
OrderSide::Buy,
1_000_000_000, // 1 SOL
95_000_000, // 95 USDC
).expect("Failed to place buy order");
let sell_order = dex.place_order(
"alice".to_string(),
pair_id.clone(),
OrderSide::Sell,
1_000_000_000, // 1 SOL
100_000_000, // 100 USDC
).expect("Failed to place sell order");
println!("Placed buy order: {}", buy_order);
println!("Placed sell order: {}", sell_order);
// Test 4: Check order book
let (bids, asks) = dex.get_order_book(&pair_id, 10)
.expect("Failed to get order book");
println!("Order book bids: {:?}", bids);
println!("Order book asks: {:?}", asks);
// Test 5: Place matching order
let matching_buy = dex.place_order(
"bob".to_string(),
pair_id.clone(),
OrderSide::Buy,
500_000_000, // 0.5 SOL
100_000_000, // 100 USDC (matches Alice's ask)
).expect("Failed to place matching buy order");
// Test 6: Check if trade occurred
let trades = dex.get_trade_history(&pair_id, 10)
.expect("Failed to get trade history");
println!("Recent trades: {:#?}", trades);
// Test 7: Check updated balances
println!("Alice SOL balance: {}", dex.get_balance("alice", "SOL"));
println!("Alice USDC balance: {}", dex.get_balance("alice", "USDC"));
println!("Bob SOL balance: {}", dex.get_balance("bob", "SOL"));
println!("Bob USDC balance: {}", dex.get_balance("bob", "USDC"));
// Test 8: Market data
let market_data = dex.get_market_data(&pair_id)
.expect("Failed to get market data");
println!("Market data: {:#?}", market_data);
}Success Criteria
Your implementations should demonstrate:
NFT Metadata Manager
- Efficient searching and filtering using HashMap and BTreeMap indices
- Comprehensive rarity analysis with statistical calculations
- Robust metadata validation and parsing
- Batch operations for large collections
- String processing excellence for attribute normalization
DEX Order Book
- Real-time order matching with price-time priority
- Accurate balance management with reservation system
- Market data calculations including spreads and volume
- Order book depth with efficient price sorting
- Trade execution with proper balance updates
Both Systems
- Memory efficiency with optimal collection usage
- Error handling with comprehensive error types
- Thread safety considerations (bonus: make it thread-safe)
- Performance optimization for large datasets
- Clean API design with intuitive method names
Bonus Challenges
Bonus 1: Cross-Platform Integration
// Add JSON import/export for both systems
impl NFTMetadataManager {
fn import_opensea_collection(&mut self, json_data: &str) -> Result<String, MetadataError> {
// Parse OpenSea API format
todo!()
}
fn export_to_arweave_format(&self, collection_id: &str) -> Result<String, MetadataError> {
// Export in Arweave metadata standard
todo!()
}
}
impl DEXOrderBook {
fn import_jupiter_liquidity(&mut self, pair: &str, data: &str) -> Result<(), OrderBookError> {
// Import liquidity data from Jupiter aggregator format
todo!()
}
}Bonus 2: Advanced Analytics
// Add sophisticated analysis features
impl NFTMetadataManager {
fn predict_rarity_trends(&self, collection_id: &str) -> RarityTrends {
// Analyze rarity distribution patterns
todo!()
}
fn find_similar_nfts(&self, mint_address: &str, threshold: f64) -> Vec<&NFTMetadata> {
// Find NFTs with similar attribute combinations
todo!()
}
}
impl DEXOrderBook {
fn calculate_market_maker_metrics(&self, user: &str) -> MarketMakerMetrics {
// Analyze user's market making performance
todo!()
}
fn detect_arbitrage_opportunities(&self) -> Vec<ArbitrageOpportunity> {
// Find price discrepancies across pairs
todo!()
}
}Bonus 3: Performance Optimization
// Add caching and optimization layers
use std::collections::LRU;
struct OptimizedNFTManager {
manager: NFTMetadataManager,
search_cache: LRU<String, Vec<String>>, // query -> results
rarity_cache: LRU<String, RarityAnalysis>,
}
struct OptimizedDEX {
dex: DEXOrderBook,
price_cache: LRU<String, MarketData>,
order_book_cache: LRU<String, (Vec<(u64, u64)>, Vec<(u64, u64)>)>,
}Production-Ready Skills!
These challenges mirror real-world Solana applications. NFT metadata management is crucial for marketplace dApps, while DEX order books power trading platforms. Mastering these patterns prepares you for building production Solana applications!
Ready for the next phase? Tomorrow we'll cover Error Handling and Code Organization - the keys to building maintainable Solana programs!