ts-sdk

Decentralized File Storage with UHRP

Duration: 75 minutes
Prerequisites: Node.js, basic TypeScript knowledge, understanding of decentralized storage and WalletClient usage
Learning Goals:

Introduction

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.

Prerequisites

For Upload Operations

For Download Operations Only

Service Availability

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.

Key Features

What You’ll Build

Setting Up UHRP Storage

Basic File Upload

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)

File Download and Verification

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)

Complete File Management System

File Manager Class

```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 file = { data: Array.from(fileData), type: mimeType }

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 { try { return await this.uploader.findFile(uhrpUrl) } catch (error) { console.error('Failed to get file info:', error) throw error } }

async renewFile(uhrpUrl: string, additionalDays: number): Promise { const additionalMinutes = additionalDays * 24 * 60

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)

Advanced Features

Batch Operations

```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)

Troubleshooting

Common Issues and Solutions

“No wallet available” Error

Problem: StorageUploader fails with “No wallet available over any communication substrate” Solution:

“401 Unauthorized” Error

Problem: Upload operations fail with HTTP 401 errors Solution:

“Invalid parameter UHRP url” Error

Problem: Download operations fail with invalid URL error Solution:

Download Works but Upload Fails

Problem: StorageDownloader works but StorageUploader fails Solution: This is expected behavior without a wallet connection. StorageDownloader works independently, while StorageUploader requires wallet authentication.

Service Unavailable

Problem: UHRP storage service returns errors or is unreachable Solution:

Best Practices

1. File Management

2. Error Handling

3. Performance

4. Security

Summary

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.

Next Steps

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.

Setting Up UHRP with WalletClient

The WalletClient handles authentication automatically when you create StorageUploader and StorageDownloader instances.

How WalletClient Enables UHRP

When you use UHRP with WalletClient: