Skip to main content

Overview

Synapse SDK works in modern browsers with:
  • Wallet connectors (MetaMask, WalletConnect, etc.)
  • Web3 libraries (wagmi, viem)
  • File upload from browser
  • Progress tracking

Installation

npm install @filoz/synapse-sdk viem

Basic Setup

import { Synapse } from '@filoz/synapse-sdk'
import { createWalletClient, custom } from 'viem'
import { calibration } from '@filoz/synapse-core/chains'

// Connect to injected wallet (MetaMask, etc.)
const client = createWalletClient({
  chain: calibration,
  transport: custom(window.ethereum),
})

const [address] = await client.getAddresses()

const synapse = new Synapse({
  client: client.extend({ account: address }),
})

File Upload from Input

// HTML
<input type="file" id="fileInput" />
<button onclick="uploadFile()">Upload</button>
<div id="progress"></div>

// JavaScript
async function uploadFile() {
  const input = document.getElementById('fileInput')
  const file = input.files[0]
  
  if (!file) return
  
  const progressDiv = document.getElementById('progress')
  
  // Convert File to ReadableStream
  const stream = file.stream()
  
  const result = await synapse.storage.upload(stream, {
    pieceMetadata: {
      name: file.name,
      type: file.type,
      size: file.size.toString(),
    },
    callbacks: {
      onProgress: (bytes) => {
        const percent = (bytes / file.size) * 100
        progressDiv.textContent = `Uploading: ${percent.toFixed(1)}%`
      },
      onStored: (providerId, pieceCid) => {
        progressDiv.textContent = 'Stored! Committing on-chain...'
      },
      onPiecesConfirmed: () => {
        progressDiv.textContent = '✓ Upload complete!'
      },
    },
  })
  
  console.log('PieceCID:', result.pieceCid)
}

Download to Browser

async function downloadFile(pieceCid: string, fileName: string) {
  // Download data
  const data = await synapse.storage.download({ pieceCid })
  
  // Create blob and download link
  const blob = new Blob([data])
  const url = URL.createObjectURL(blob)
  
  const a = document.createElement('a')
  a.href = url
  a.download = fileName
  a.click()
  
  URL.revokeObjectURL(url)
}

// Usage
<button onclick="downloadFile('baga...', 'photo.jpg')">Download</button>

React Integration

Upload Component

import { useState } from 'react'
import { Synapse } from '@filoz/synapse-sdk'

function FileUpload({ synapse }: { synapse: Synapse }) {
  const [file, setFile] = useState<File | null>(null)
  const [progress, setProgress] = useState(0)
  const [pieceCid, setPieceCid] = useState<string | null>(null)
  const [uploading, setUploading] = useState(false)
  
  const handleUpload = async () => {
    if (!file) return
    
    setUploading(true)
    setProgress(0)
    
    try {
      const stream = file.stream()
      
      const result = await synapse.storage.upload(stream, {
        pieceMetadata: {
          name: file.name,
          type: file.type,
        },
        callbacks: {
          onProgress: (bytes) => {
            setProgress((bytes / file.size) * 100)
          },
        },
      })
      
      setPieceCid(result.pieceCid)
    } catch (error) {
      console.error('Upload failed:', error)
      alert('Upload failed')
    } finally {
      setUploading(false)
    }
  }
  
  return (
    <div>
      <input 
        type="file" 
        onChange={(e) => setFile(e.target.files?.[0] || null)}
        disabled={uploading}
      />
      
      <button 
        onClick={handleUpload} 
        disabled={!file || uploading}
      >
        {uploading ? 'Uploading...' : 'Upload'}
      </button>
      
      {uploading && (
        <div>
          <progress value={progress} max={100} />
          <span>{progress.toFixed(1)}%</span>
        </div>
      )}
      
      {pieceCid && (
        <div>✓ Uploaded: {pieceCid}</div>
      )}
    </div>
  )
}

Download Component

import { useState } from 'react'

function FileDownload({ synapse, pieceCid, fileName }: { 
  synapse: Synapse
  pieceCid: string
  fileName: string 
}) {
  const [downloading, setDownloading] = useState(false)
  
  const handleDownload = async () => {
    setDownloading(true)
    
    try {
      const data = await synapse.storage.download({ pieceCid })
      
      const blob = new Blob([data])
      const url = URL.createObjectURL(blob)
      
      const a = document.createElement('a')
      a.href = url
      a.download = fileName
      a.click()
      
      URL.revokeObjectURL(url)
    } catch (error) {
      console.error('Download failed:', error)
      alert('Download failed')
    } finally {
      setDownloading(false)
    }
  }
  
  return (
    <button onClick={handleDownload} disabled={downloading}>
      {downloading ? 'Downloading...' : 'Download'}
    </button>
  )
}

Using with Wagmi

import { useWalletClient } from 'wagmi'
import { useMemo } from 'react'
import { Synapse } from '@filoz/synapse-sdk'

function useSynapse() {
  const { data: walletClient } = useWalletClient()
  
  const synapse = useMemo(() => {
    if (!walletClient) return null
    
    return new Synapse({ client: walletClient })
  }, [walletClient])
  
  return synapse
}

// Usage in component
function MyComponent() {
  const synapse = useSynapse()
  
  if (!synapse) {
    return <div>Connect wallet to use storage</div>
  }
  
  return <FileUpload synapse={synapse} />
}

Progress Bar Component

function ProgressBar({ progress }: { progress: number }) {
  return (
    <div style={{ width: '100%', backgroundColor: '#f0f0f0' }}>
      <div
        style={{
          width: `${progress}%`,
          backgroundColor: '#4caf50',
          height: '20px',
          transition: 'width 0.3s ease',
        }}
      >
        {progress.toFixed(1)}%
      </div>
    </div>
  )
}

// Usage
<ProgressBar progress={uploadProgress} />

Check Balance Before Upload

import { formatUnits } from '@filoz/synapse-sdk'

async function checkBalanceAndUpload(file: File) {
  // Check wallet balance
  const balance = await synapse.payments.walletBalance('USDFC')
  
  if (balance < 1000000000000000000n) { // Less than 1 USDFC
    alert('Insufficient USDFC balance. Please deposit funds.')
    return
  }
  
  // Check preflight
  const preflight = await synapse.storage.preflightUpload({ 
    size: file.size 
  })
  
  if (!preflight.allowanceCheck.sufficient) {
    alert('Please approve the storage service first')
    return
  }
  
  // Proceed with upload
  const result = await synapse.storage.upload(file.stream())
  return result
}

Image Preview

function ImagePreview({ pieceCid }: { pieceCid: string }) {
  const [imageUrl, setImageUrl] = useState<string | null>(null)
  const [loading, setLoading] = useState(true)
  
  useEffect(() => {
    async function loadImage() {
      try {
        const data = await synapse.storage.download({ pieceCid })
        const blob = new Blob([data])
        const url = URL.createObjectURL(blob)
        setImageUrl(url)
      } catch (error) {
        console.error('Failed to load image:', error)
      } finally {
        setLoading(false)
      }
    }
    
    loadImage()
    
    return () => {
      if (imageUrl) {
        URL.revokeObjectURL(imageUrl)
      }
    }
  }, [pieceCid])
  
  if (loading) return <div>Loading...</div>
  
  return imageUrl ? <img src={imageUrl} alt="Preview" /> : <div>Failed to load</div>
}

Cancel Upload

function CancellableUpload({ synapse }: { synapse: Synapse }) {
  const [controller, setController] = useState<AbortController | null>(null)
  const [uploading, setUploading] = useState(false)
  
  const handleUpload = async (file: File) => {
    const newController = new AbortController()
    setController(newController)
    setUploading(true)
    
    try {
      const result = await synapse.storage.upload(file.stream(), {
        signal: newController.signal,
      })
      
      alert('Upload complete!')
    } catch (error: any) {
      if (error.name === 'AbortError') {
        alert('Upload cancelled')
      } else {
        alert('Upload failed')
      }
    } finally {
      setUploading(false)
      setController(null)
    }
  }
  
  const handleCancel = () => {
    controller?.abort()
  }
  
  return (
    <div>
      <input type="file" onChange={(e) => handleUpload(e.target.files![0])} />
      {uploading && (
        <button onClick={handleCancel}>Cancel Upload</button>
      )}
    </div>
  )
}

Multiple File Upload

function MultiFileUpload({ synapse }: { synapse: Synapse }) {
  const [files, setFiles] = useState<File[]>([])
  const [results, setResults] = useState<Record<string, string>>({})
  
  const handleUpload = async () => {
    const contexts = await synapse.storage.createContexts({ count: 2 })
    const [primary, secondary] = contexts
    
    // Store all files
    const stored = await Promise.all(
      files.map(file => primary.store(file.stream()))
    )
    
    // Pull and commit
    const pieces = stored.map(s => ({ pieceCid: s.pieceCid }))
    const extraData = await secondary.presignForCommit(pieces)
    
    await secondary.pull({ 
      pieces: pieces.map(p => p.pieceCid), 
      from: primary,
      extraData,
    })
    
    await primary.commit({ pieces })
    await secondary.commit({ pieces, extraData })
    
    // Map files to PieceCIDs
    const newResults: Record<string, string> = {}
    files.forEach((file, i) => {
      newResults[file.name] = stored[i].pieceCid
    })
    setResults(newResults)
  }
  
  return (
    <div>
      <input 
        type="file" 
        multiple 
        onChange={(e) => setFiles(Array.from(e.target.files || []))}
      />
      <button onClick={handleUpload}>Upload All</button>
      
      {Object.entries(results).map(([name, cid]) => (
        <div key={name}>
          {name}: {cid}
        </div>
      ))}
    </div>
  )
}

Best Practices

Use Streaming

Use File.stream() for memory-efficient uploads

Show Progress

Always show progress feedback to users

Check Balance

Verify balance before attempting uploads

Handle Errors

Gracefully handle network and wallet errors

Limitations

  • Browser file size limits vary by browser
  • Session keys improve UX by reducing wallet prompts
  • Test thoroughly with different wallets
  • Consider chunking very large files

Next Steps

Session Keys

Reduce wallet prompts with session keys

Upload/Download

Learn core upload/download patterns

Build docs developers (and LLMs) love