Storage Service
Manage collection metadata and assets with the BaseMint storage service
Overview
The BaseMint Storage Service provides a reliable, scalable solution for storing and managing NFT collection metadata. It offers deterministic addressing, CDN-backed delivery, and seamless integration with the CreateKit ecosystem.
Storage Architecture
How Storage Works
Metadata Submission
Collection metadata and creator signatures are submitted to the storage service
Validation & Storage
The service validates signatures and stores metadata with a deterministic address
CDN Distribution
Metadata is distributed via CDN for fast, global access
Marketplace Integration
Collections are discoverable via predicted addresses before deployment
Key Features
Storage Service Setup
Basic Configuration
typescriptimport { BaseMintStorage } from '@b3dotfun/basemint' // Initialize storage service const storage = new BaseMintStorage({ baseUrl: 'https://api.basemint.fun', // Production URL // For testnet development: // baseUrl: 'https://testnet-api.basemint.fun' }) // Test connection try { const health = await storage.healthCheck() console.log('✅ Storage service connected:', health) } catch (error) { console.error('❌ Storage service unavailable:', error) }
Environment Configuration
typescript// Environment-specific configuration const getStorageConfig = (environment: 'development' | 'staging' | 'production') => { const configs = { development: { baseUrl: 'https://testnet-api.basemint.fun', chainId: 1993 // B3 Testnet }, staging: { baseUrl: 'https://staging-api.basemint.fun', chainId: 1993 }, production: { baseUrl: 'https://api.basemint.fun', chainId: 8333 // B3 Mainnet } } return configs[environment] } const config = getStorageConfig(process.env.NODE_ENV as any || 'development') const storage = new BaseMintStorage(config)
Submitting Collections
Basic Collection Submission
typescriptasync function submitCollection( collectionMetadata: any, creatorSignature: string, referrerId?: string ) { try { const response = await storage.submitCollection( collectionMetadata, creatorSignature, referrerId // Optional: track collections by referrer ) console.log('✅ Collection submitted successfully') console.log('Collection ID:', response.collectionId) console.log('Predicted address:', response.predictedAddress) console.log('Metadata URI:', response.metadataUri) return response } catch (error: any) { if (error.message.includes('Invalid signature')) { console.error('❌ Creator signature verification failed') } else if (error.message.includes('Collection exists')) { console.error('❌ Collection with this address already exists') } else if (error.message.includes('Referrer not found')) { console.error('❌ Invalid referrer ID') } else { console.error('❌ Submission failed:', error.message) } throw error } } // Example usage const response = await submitCollection( collectionMetadata, creatorSignature, "my-game-platform" // Your referrer ID )
Batch Collection Submission
typescriptasync function submitMultipleCollections( collections: Array<{ metadata: any signature: string referrerId?: string }> ) { const results = [] for (const collection of collections) { try { const response = await storage.submitCollection( collection.metadata, collection.signature, collection.referrerId ) results.push({ success: true, collectionName: collection.metadata.name, response }) console.log(`✅ Submitted: ${collection.metadata.name}`) } catch (error) { results.push({ success: false, collectionName: collection.metadata.name, error: error.message }) console.error(`❌ Failed: ${collection.metadata.name}`, error) } } return results }
Querying Collections
Basic Queries
typescript// Query all collections const allCollections = await storage.queryCollections() console.log(`Found ${allCollections.collections.length} collections`) // Query with pagination const paginatedResults = await storage.queryCollections({ limit: 20, offset: 0 }) // Query by referrer const gameCollections = await storage.queryCollections({ referrer: "my-game-platform" }) // Query by creator const creatorCollections = await storage.queryCollections({ creator: "0x1234567890123456789012345678901234567890" })
Advanced Filtering
typescript// Complex query with multiple filters const advancedQuery = await storage.queryCollections({ referrer: "my-game-platform", creator: "0x1234567890123456789012345678901234567890", tokenStandard: "ERC721", chainId: 1993, limit: 50, offset: 0, sortBy: "createdAt", sortOrder: "desc" }) console.log("Advanced query results:", { total: advancedQuery.total, count: advancedQuery.collections.length, hasMore: advancedQuery.hasMore }) // Filter by deployment status const undeployedCollections = advancedQuery.collections.filter( collection => !collection.isDeployed ) const deployedCollections = advancedQuery.collections.filter( collection => collection.isDeployed ) console.log(`Undeployed: ${undeployedCollections.length}`) console.log(`Deployed: ${deployedCollections.length}`)
Search Functionality
typescript// Search by name or symbol const searchResults = await storage.searchCollections({ query: "fantasy", limit: 10 }) // Search with filters const filteredSearch = await storage.searchCollections({ query: "game", referrer: "my-game-platform", tokenStandard: "ERC1155" }) console.log("Search results:", searchResults.collections.map(c => ({ name: c.name, symbol: c.symbol, description: c.description })))
Referrer Management
Registering as a Referrer
typescript// Register your platform as a referrer async function registerAsReferrer(referrerId: string, metadata?: any) { try { await storage.registerReferrer(referrerId, metadata) console.log(`✅ Registered as referrer: ${referrerId}`) } catch (error: any) { if (error.message.includes('already exists')) { console.log(`ℹ️ Referrer ${referrerId} already registered`) } else { console.error('❌ Registration failed:', error) throw error } } } // Register with metadata await registerAsReferrer("my-game-platform", { name: "My Game Platform", website: "https://mygame.com", contact: "dev@mygame.com", description: "A gaming platform for NFT collections" })
Managing Referrer Collections
typescript// Get all collections for your platform async function getReferrerDashboard(referrerId: string) { const collections = await storage.queryCollections({ referrer: referrerId }) const stats = { total: collections.total, deployed: collections.collections.filter(c => c.isDeployed).length, undeployed: collections.collections.filter(c => !c.isDeployed).length, erc721: collections.collections.filter(c => c.tokenStandard === 'ERC721').length, erc1155: collections.collections.filter(c => c.tokenStandard === 'ERC1155').length } console.log("Referrer dashboard:", stats) return { collections: collections.collections, stats } } const dashboard = await getReferrerDashboard("my-game-platform")
Collection Management
Retrieving Collection Data
typescript// Get collection by address async function getCollectionDetails(address: string) { try { const collection = await storage.getCollection(address) console.log("Collection details:", { name: collection.name, symbol: collection.symbol, creator: collection.creator, gameOwner: collection.gameOwner, isDeployed: collection.isDeployed, createdAt: collection.createdAt, metadataUri: collection.metadataUri }) return collection } catch (error: any) { if (error.message.includes('not found')) { console.error('❌ Collection not found') } else { console.error('❌ Error retrieving collection:', error) } throw error } } // Get multiple collections by addresses async function getMultipleCollections(addresses: string[]) { const collections = await Promise.allSettled( addresses.map(address => storage.getCollection(address)) ) const successful = collections .filter((result): result is PromiseFulfilledResult<any> => result.status === 'fulfilled') .map(result => result.value) const failed = collections .filter((result): result is PromiseRejectedResult => result.status === 'rejected') .map(result => result.reason) console.log(`✅ Retrieved ${successful.length} collections`) console.log(`❌ Failed to retrieve ${failed.length} collections`) return { successful, failed } }
Updating Collections
Warning
Collection updates are limited to specific fields and may require additional authentication.
typescript// Update collection metadata (limited fields) async function updateCollectionMetadata( address: string, updates: { description?: string image?: string external_url?: string animation_url?: string } ) { try { const updatedCollection = await storage.updateCollection(address, updates) console.log('✅ Collection updated successfully') return updatedCollection } catch (error: any) { if (error.message.includes('not authorized')) { console.error('❌ Not authorized to update this collection') } else if (error.message.includes('immutable field')) { console.error('❌ Attempted to update immutable field') } else { console.error('❌ Update failed:', error) } throw error } }
Deleting Collections
typescript// Delete a single collection (only undeployed) async function deleteCollection(address: string) { try { await storage.deleteCollection(address) console.log(`✅ Collection ${address} deleted`) } catch (error: any) { if (error.message.includes('deployed')) { console.error('❌ Cannot delete deployed collection') } else if (error.message.includes('not found')) { console.error('❌ Collection not found') } else { console.error('❌ Deletion failed:', error) } throw error } } // Bulk delete collections (referrers only) async function bulkDeleteCollections( identifiers: string[], // UUIDs or addresses referrerId: string ) { try { const result = await storage.bulkDeleteCollections(identifiers, referrerId) console.log(`✅ Deleted ${result.deleted.length} collections`) console.log(`❌ Failed to delete ${result.failed.length} collections`) return result } catch (error) { console.error('❌ Bulk deletion failed:', error) throw error } }
Metadata Management
Custom Metadata URIs
typescript// Generate metadata URI for a collection function generateMetadataUri(collectionId: string, baseUrl: string): string { return `${baseUrl}/metadata/${collectionId}` } // Get metadata directly async function getCollectionMetadata(collectionId: string) { try { const metadataUri = generateMetadataUri(collectionId, storage.baseUrl) const response = await fetch(metadataUri) if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`) } const metadata = await response.json() return metadata } catch (error) { console.error('❌ Failed to fetch metadata:', error) throw error } } // Validate metadata format function validateMetadata(metadata: any): { isValid: boolean; errors: string[] } { const errors: string[] = [] if (!metadata.name) errors.push('Missing name') if (!metadata.description) errors.push('Missing description') if (!metadata.image) errors.push('Missing image') // Check OpenSea compatibility if (metadata.attributes && !Array.isArray(metadata.attributes)) { errors.push('Attributes must be an array') } return { isValid: errors.length === 0, errors } }
Asset Management
typescript// Upload assets to storage service (if supported) async function uploadAsset( file: File | Buffer, filename: string, contentType: string ): Promise<string> { try { // This depends on your storage service implementation const formData = new FormData() formData.append('file', file, filename) formData.append('contentType', contentType) const response = await fetch(`${storage.baseUrl}/upload`, { method: 'POST', body: formData }) if (!response.ok) { throw new Error(`Upload failed: ${response.statusText}`) } const result = await response.json() return result.url } catch (error) { console.error('❌ Asset upload failed:', error) throw error } } // Optimize images for NFT standards function getOptimizedImageUrl( originalUrl: string, size: 'thumbnail' | 'medium' | 'large' = 'medium' ): string { const sizeMap = { thumbnail: '200x200', medium: '640x640', large: '1200x1200' } // Example CDN URL transformation return `${originalUrl}?size=${sizeMap[size]}&format=webp&quality=85` }
Error Handling
Comprehensive Error Handling
typescriptclass StorageError extends Error { constructor( message: string, public code: string, public statusCode?: number ) { super(message) this.name = 'StorageError' } } async function robustStorageOperation<T>( operation: () => Promise<T>, retries: number = 3, delayMs: number = 1000 ): Promise<T> { let lastError: Error for (let attempt = 1; attempt <= retries; attempt++) { try { return await operation() } catch (error: any) { lastError = error // Don't retry certain errors if (error.message.includes('Invalid signature') || error.message.includes('Collection exists')) { throw error } console.warn(`Attempt ${attempt} failed:`, error.message) if (attempt < retries) { await new Promise(resolve => setTimeout(resolve, delayMs * attempt)) } } } throw new StorageError( `Operation failed after ${retries} attempts: ${lastError.message}`, 'MAX_RETRIES_EXCEEDED' ) } // Usage const result = await robustStorageOperation(async () => { return await storage.submitCollection(metadata, signature) })
Service Health Monitoring
typescriptclass StorageHealthMonitor { private storage: BaseMintStorage private healthStatus: 'healthy' | 'degraded' | 'unhealthy' = 'healthy' private lastCheck: Date = new Date() constructor(storage: BaseMintStorage) { this.storage = storage } async checkHealth(): Promise<{ status: string; latency: number; timestamp: Date }> { const startTime = Date.now() try { await this.storage.healthCheck() const latency = Date.now() - startTime this.healthStatus = latency < 2000 ? 'healthy' : 'degraded' this.lastCheck = new Date() return { status: this.healthStatus, latency, timestamp: this.lastCheck } } catch (error) { this.healthStatus = 'unhealthy' this.lastCheck = new Date() throw new StorageError( 'Storage service health check failed', 'HEALTH_CHECK_FAILED' ) } } getStatus(): { status: string; lastCheck: Date } { return { status: this.healthStatus, lastCheck: this.lastCheck } } async waitForHealthy(timeoutMs: number = 30000): Promise<void> { const startTime = Date.now() while (Date.now() - startTime < timeoutMs) { try { await this.checkHealth() if (this.healthStatus === 'healthy') return } catch { // Continue waiting } await new Promise(resolve => setTimeout(resolve, 1000)) } throw new StorageError( 'Storage service did not become healthy within timeout', 'HEALTH_TIMEOUT' ) } } // Usage const healthMonitor = new StorageHealthMonitor(storage) await healthMonitor.waitForHealthy() console.log('✅ Storage service is healthy')
Best Practices
Performance Optimization
Security Considerations
typescript// Validate signatures before submission function validateSignatureLocally( collectionMetadata: any, signature: string, expectedSigner: string ): boolean { // Implement local signature validation // This provides an additional security layer try { const recoveredAddress = recoverSignerFromMetadata(collectionMetadata, signature) return recoveredAddress.toLowerCase() === expectedSigner.toLowerCase() } catch { return false } } // Sanitize metadata before submission function sanitizeMetadata(metadata: any): any { return { ...metadata, // Remove potentially dangerous fields name: sanitizeString(metadata.name), description: sanitizeString(metadata.description), // Validate URLs image: validateImageUrl(metadata.image), external_url: validateUrl(metadata.external_url) } } function sanitizeString(input: string): string { return input?.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '') || '' }
Integration Examples
React Integration
tsximport { useState, useEffect } from 'react' interface UseStorageResult { collections: any[] loading: boolean error: string | null refetch: () => Promise<void> } export function useStorageCollections( storage: BaseMintStorage, filters?: any ): UseStorageResult { const [collections, setCollections] = useState<any[]>([]) const [loading, setLoading] = useState(true) const [error, setError] = useState<string | null>(null) const fetchCollections = async () => { try { setLoading(true) setError(null) const result = await storage.queryCollections(filters) setCollections(result.collections) } catch (err: any) { setError(err.message) } finally { setLoading(false) } } useEffect(() => { fetchCollections() }, [storage, JSON.stringify(filters)]) return { collections, loading, error, refetch: fetchCollections } }