Duration: 75 minutes
Prerequisites: Node.js, basic TypeScript knowledge, understanding of decentralized storage and WalletClient
usage
Learning Goals:
UHRP (Universal Hash Resource Protocol) is a decentralized file storage system that uses content hashing for addressing and retrieval. The BSV SDK provides StorageUploader
and StorageDownloader
classes for seamless integration with UHRP storage networks.
https://nanostore.babbage.systems
Important Note: This tutorial uses https://nanostore.babbage.systems
, which is a working UHRP storage service. The examples demonstrate correct SDK usage patterns and will work with:
Performance Note: UHRP storage operations may take time to complete as they involve blockchain transactions and network propagation. Upload operations can take 10-30 seconds or more depending on network conditions.
Network Propagation: After uploading, files typically take 30-60 seconds to propagate across the UHRP network before they become available for download. This is normal behavior for decentralized storage systems and ensures content integrity verification.
import { StorageUploader, WalletClient } from '@bsv/sdk'
async function basicFileUpload() {
const wallet = new WalletClient('auto', 'localhost')
const uploader = new StorageUploader({
storageURL: 'https://nanostore.babbage.systems',
wallet
})
// Create sample file
const fileData = new TextEncoder().encode('Hello, UHRP storage!')
const file = {
data: Array.from(fileData),
type: 'text/plain'
}
try {
const result = await uploader.publishFile({
file,
retentionPeriod: 60 * 24 * 7 // 7 days in minutes
})
console.log('File uploaded successfully!')
console.log('UHRP URL:', result.uhrpURL)
console.log('Published:', result.published)
return result
} catch (error) {
console.error('Upload failed:', error)
throw error
}
}
basicFileUpload().catch(console.error)
import { StorageDownloader } from '@bsv/sdk'
async function basicFileDownload(uhrpUrl: string) {
const downloader = new StorageDownloader({
networkPreset: 'mainnet'
})
try {
console.log('Downloading file:', uhrpUrl)
const result = await downloader.download(uhrpUrl)
console.log('File downloaded successfully!')
console.log('MIME Type:', result.mimeType)
console.log('Content length:', result.data.length, 'bytes')
// Convert to string if text file
if (result.mimeType?.startsWith('text/')) {
const content = new TextDecoder().decode(new Uint8Array(result.data))
console.log('Content:', content)
}
return result
} catch (error) {
console.error('Download failed:', error)
throw error
}
}
// Example usage (replace with actual UHRP URL)
// basicFileDownload('uhrp://abc123...').catch(console.error)
```typescript import { StorageUploader, StorageDownloader, WalletClient } from ‘@bsv/sdk’
interface FileMetadata { uhrpUrl: string originalName: string mimeType: string size: number uploadDate: Date expiryDate: Date tags: string[] }
class UHRPFileManager { private uploader: StorageUploader private downloader: StorageDownloader private fileRegistry: Map<string, FileMetadata> = new Map()
constructor(storageURL: string, wallet?: WalletClient) { this.uploader = new StorageUploader({ storageURL, wallet: wallet || new WalletClient(‘auto’, ‘localhost’) })
this.downloader = new StorageDownloader({
networkPreset: 'mainnet'
}) }
async uploadFile(
fileData: Uint8Array,
fileName: string,
mimeType: string,
retentionDays: number = 30,
tags: string[] = []
): Promise
const retentionMinutes = retentionDays * 24 * 60
try {
const result = await this.uploader.publishFile({
file,
retentionPeriod: retentionMinutes
})
const metadata: FileMetadata = {
uhrpUrl: result.uhrpURL,
originalName: fileName,
mimeType,
size: fileData.length,
uploadDate: new Date(),
expiryDate: new Date(Date.now() + retentionDays * 24 * 60 * 60 * 1000),
tags
}
this.fileRegistry.set(result.uhrpURL, metadata)
console.log(`File "${fileName}" uploaded successfully`)
console.log('UHRP URL:', result.uhrpURL)
return metadata
} catch (error) {
console.error(`Failed to upload "${fileName}":`, error)
throw error
} }
async downloadFile(uhrpUrl: string): Promise<{ data: Uint8Array metadata: FileMetadata | null }> { try { const result = await this.downloader.download(uhrpUrl) const metadata = this.fileRegistry.get(uhrpUrl) || null
console.log('File downloaded:', uhrpUrl)
return {
data: new Uint8Array(result.data),
metadata
}
} catch (error) {
console.error('Download failed:', error)
throw error
} }
async getFileInfo(uhrpUrl: string): Promise
async renewFile(uhrpUrl: string, additionalDays: number): Promise
try {
const result = await this.uploader.renewFile(uhrpUrl, additionalMinutes)
// Update local metadata if exists
const metadata = this.fileRegistry.get(uhrpUrl)
if (metadata) {
metadata.expiryDate = new Date(Date.now() + additionalDays * 24 * 60 * 60 * 1000)
this.fileRegistry.set(uhrpUrl, metadata)
}
console.log(`File renewed for ${additionalDays} days`)
return result
} catch (error) {
console.error('Failed to renew file:', error)
throw error
} }
listFiles(tag?: string): FileMetadata[] { const files = Array.from(this.fileRegistry.values())
if (tag) {
return files.filter(file => file.tags.includes(tag))
}
return files }
getExpiringFiles(daysAhead: number = 7): FileMetadata[] { const cutoffDate = new Date(Date.now() + daysAhead * 24 * 60 * 60 * 1000)
return Array.from(this.fileRegistry.values())
.filter(file => file.expiryDate <= cutoffDate)
.sort((a, b) => a.expiryDate.getTime() - b.expiryDate.getTime()) } }
async function demonstrateFileManager() { const fileManager = new UHRPFileManager(‘https://nanostore.babbage.systems’)
console.log(‘=== UHRP File Manager Demo ===’)
// Upload different types of files const textData = new TextEncoder().encode(‘This is a text document for UHRP storage.’) const jsonData = new TextEncoder().encode(JSON.stringify({ message: ‘Hello from UHRP’, timestamp: new Date().toISOString(), data: [1, 2, 3, 4, 5] }))
try { // Upload text file const textFile = await fileManager.uploadFile( textData, ‘document.txt’, ‘text/plain’, 30, [‘document’, ‘text’] )
// Upload JSON file
const jsonFile = await fileManager.uploadFile(
jsonData,
'data.json',
'application/json',
60,
['data', 'json']
)
console.log('\n=== File Registry ===')
const allFiles = fileManager.listFiles()
allFiles.forEach(file => {
console.log(`${file.originalName}: ${file.uhrpUrl}`)
})
// Test download
console.log('\n=== Testing Download ===')
const downloadResult = await fileManager.downloadFile(textFile.uhrpUrl)
const content = new TextDecoder().decode(downloadResult.data)
console.log('Downloaded content:', content)
// Check expiring files
console.log('\n=== Expiring Files ===')
const expiringFiles = fileManager.getExpiringFiles(365) // Next year
expiringFiles.forEach(file => {
console.log(`${file.originalName} expires: ${file.expiryDate.toISOString()}`)
})
return { textFile, jsonFile, allFiles } } catch (error) {
console.error('Demo failed:', error) } }
demonstrateFileManager().catch(console.error)
```typescript import { StorageUploader, StorageDownloader, WalletClient } from ‘@bsv/sdk’
class BatchFileOperations { private uploader: StorageUploader private downloader: StorageDownloader
constructor(storageURL: string, wallet?: WalletClient) { this.uploader = new StorageUploader({ storageURL, wallet: wallet || new WalletClient(‘auto’, ‘localhost’) })
this.downloader = new StorageDownloader() }
async batchUpload(files: Array<{
data: Uint8Array
name: string
type: string
retention?: number
}>): Promise<Array<{
success: boolean
file: string
uhrpUrl?: string
error?: string
}» {
console.log(Starting batch upload of ${files.length} files...
)
const results = await Promise.allSettled(
files.map(async (file) => {
const fileObj = {
data: Array.from(file.data),
type: file.type
}
const result = await this.uploader.publishFile({
file: fileObj,
retentionPeriod: (file.retention || 30) * 24 * 60
})
return { file: file.name, result }
})
)
return results.map((result, index) => {
const fileName = files[index].name
if (result.status === 'fulfilled') {
return {
success: true,
file: fileName,
uhrpUrl: result.value.result.uhrpURL
}
} else {
return {
success: false,
file: fileName,
error: result.reason.message
}
}
}) }
async batchDownload(uhrpUrls: string[]): Promise<Array<{
success: boolean
url: string
data?: Uint8Array
error?: string
}» {
console.log(Starting batch download of ${uhrpUrls.length} files...
)
const results = await Promise.allSettled(
uhrpUrls.map(url => this.downloader.download(url))
)
return results.map((result, index) => {
const url = uhrpUrls[index]
if (result.status === 'fulfilled') {
return {
success: true,
url,
data: new Uint8Array(result.value.data)
}
} else {
return {
success: false,
url,
error: result.reason.message
}
}
}) } }
async function demonstrateBatchOperations() { const batchOps = new BatchFileOperations(‘https://nanostore.babbage.systems’)
// Prepare test files const testFiles = [ { data: new TextEncoder().encode(‘File 1 content’), name: ‘file1.txt’, type: ‘text/plain’ }, { data: new TextEncoder().encode(‘File 2 content’), name: ‘file2.txt’, type: ‘text/plain’ }, { data: new TextEncoder().encode(JSON.stringify({ test: ‘data’ })), name: ‘data.json’, type: ‘application/json’ } ]
console.log(‘=== Batch Upload Demo ===’) const uploadResults = await batchOps.batchUpload(testFiles)
uploadResults.forEach(result => {
if (result.success) {
console.log(✅ ${result.file}: ${result.uhrpUrl}
)
} else {
console.log(❌ ${result.file}: ${result.error}
)
}
})
// Extract successful URLs for download test const successfulUrls = uploadResults .filter(r => r.success && r.uhrpUrl) .map(r => r.uhrpUrl!)
if (successfulUrls.length > 0) { console.log(‘\n=== Batch Download Demo ===’) const downloadResults = await batchOps.batchDownload(successfulUrls)
downloadResults.forEach(result => {
if (result.success) {
console.log(`✅ Downloaded: ${result.url} (${result.data?.length} bytes)`)
} else {
console.log(`❌ Failed: ${result.url} - ${result.error}`)
}
}) }
return { uploadResults, downloadResults: [] } }
demonstrateBatchOperations().catch(console.error)
Problem: StorageUploader fails with “No wallet available over any communication substrate” Solution:
Problem: Upload operations fail with HTTP 401 errors Solution:
Problem: Download operations fail with invalid URL error Solution:
uhrp://
)Problem: StorageDownloader works but StorageUploader fails Solution: This is expected behavior without a wallet connection. StorageDownloader works independently, while StorageUploader requires wallet authentication.
Problem: UHRP storage service returns errors or is unreachable Solution:
In this tutorial, you learned how to:
✅ Upload files to UHRP storage with StorageUploader
✅ Download and verify files with StorageDownloader
✅ Build file management systems with metadata tracking
✅ Implement batch operations for multiple files
✅ Handle file expiration and renewal
UHRP provides a robust foundation for decentralized file storage with content addressing and integrity verification.
UHRP provides a robust foundation for decentralized file storage with content addressing and integrity verification.
The WalletClient
provides the authentication and payment capabilities needed for UHRP operations.
WalletClient
The WalletClient
handles authentication automatically when you create StorageUploader
and StorageDownloader
instances.
WalletClient
Enables UHRPWhen you use UHRP with WalletClient
: