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

Deterministic Addressing

Collections have predictable addresses for marketplace integration

Signature Validation

Cryptographic verification of collection authenticity

CDN Delivery

Fast, reliable metadata delivery worldwide

Referrer System

Track and manage collections by integration partners

Storage Service Setup

Basic Configuration

typescript
import { 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

typescript
async 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

typescript
async 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

typescript
class 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

typescript
class 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

Caching
  • Cache frequently accessed collection data
  • Use ETags for conditional requests
  • Implement client-side caching strategies
  • Consider Redis for server-side caching
Batch Operations
  • Group multiple operations when possible
  • Use bulk endpoints for efficiency
  • Implement proper queue management
  • Handle rate limiting gracefully

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

tsx
import { 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 } }

Next Steps

Examples

See complete implementation examples using the storage service

Learn More
Error Handling

Learn comprehensive error handling patterns

Learn More
Ask a question... ⌘I