Skip to main content

Overview

Multi-copy storage ensures your data is replicated across independent storage providers, improving availability and reducing dependency on any single provider.

Default Multi-Copy

By default, uploads go to 2 providers (1 primary + 1 secondary):
import { Synapse } from '@filoz/synapse-sdk'

const synapse = await Synapse.create({ privateKey, rpcUrl })

// Automatically uploads to 2 providers
const result = await synapse.storage.upload(data)

console.log(`Stored on ${result.copies.length} providers`)
for (const copy of result.copies) {
  console.log(`  ${copy.role}: Provider ${copy.providerId}`)
  console.log(`    Data Set: ${copy.dataSetId}`)
  console.log(`    Piece ID: ${copy.pieceId}`)
}

Specify Copy Count

// Upload to 3 providers
const result = await synapse.storage.upload(data, {
  count: 3,
})

console.log(`Uploaded to ${result.copies.length} providers`)

Specify Exact Providers

// Upload to specific providers
const result = await synapse.storage.upload(data, {
  providerIds: [1n, 3n, 5n],
})

// Verify all specified providers succeeded
const expectedIds = new Set([1n, 3n, 5n])
const actualIds = new Set(result.copies.map(c => c.providerId))

if (expectedIds.size === actualIds.size) {
  console.log('✓ All specified providers succeeded')
} else {
  const failedIds = [...expectedIds].filter(id => !actualIds.has(id))
  console.warn(`Failed on providers: ${failedIds.join(', ')}`)
}

Provider Selection

Endorsed vs Approved

// Endorsed providers (curated, high-quality)
const providers = await synapse.spRegistry.getAllActiveProviders()
const endorsedIds = providers
  .filter(p => p.endorsed)
  .map(p => p.id)

console.log(`Found ${endorsedIds.length} endorsed providers`)

// Upload to endorsed providers
const result = await synapse.storage.upload(data, {
  providerIds: endorsedIds.slice(0, 3),
})

Auto-Select by Region

const providers = await synapse.spRegistry.getAllActiveProviders()

// Filter by region (from capabilities)
const usProviders = providers.filter(p => 
  p.pdp.capabilities?.region === 'us-west'
)

if (usProviders.length >= 2) {
  const result = await synapse.storage.upload(data, {
    providerIds: usProviders.slice(0, 2).map(p => p.id),
  })
}

Handle Partial Failures

const result = await synapse.storage.upload(data, {
  count: 4,
})

console.log(`Success: ${result.copies.length} copies`)

if (result.failures.length > 0) {
  console.warn(`Failed: ${result.failures.length} copies`)
  
  for (const failure of result.failures) {
    console.warn(`  Provider ${failure.providerId}:`)
    console.warn(`    Role: ${failure.role}`)
    console.warn(`    Error: ${failure.error}`)
    console.warn(`    Explicit: ${failure.explicit}`)
  }
}

// Check if minimum copies succeeded
const MIN_COPIES = 2
if (result.copies.length < MIN_COPIES) {
  throw new Error(`Only ${result.copies.length} copies succeeded, need ${MIN_COPIES}`)
}

Retry Strategy

Auto-Retry on Failure

const MIN_COPIES = 3
let result
let attempts = 0
const MAX_ATTEMPTS = 3

while (attempts < MAX_ATTEMPTS) {
  result = await synapse.storage.upload(data, {
    count: MIN_COPIES,
    // Exclude failed providers from previous attempt
    excludeProviderIds: result?.failures.map(f => f.providerId) || [],
  })
  
  if (result.copies.length >= MIN_COPIES) {
    console.log(`✓ Achieved ${result.copies.length} copies`)
    break
  }
  
  attempts++
  console.log(`Attempt ${attempts}: only ${result.copies.length} copies, retrying...`)
}

if (result.copies.length < MIN_COPIES) {
  throw new Error('Failed to achieve minimum copies')
}

Manual Retry Specific Providers

const result = await synapse.storage.upload(data, {
  providerIds: [1n, 2n, 3n],
})

// Retry only failed providers
if (result.failures.length > 0) {
  const failedIds = result.failures.map(f => f.providerId)
  
  console.log(`Retrying failed providers: ${failedIds.join(', ')}`)
  
  const retryResult = await synapse.storage.upload(data, {
    providerIds: failedIds,
  })
  
  // Combine results
  const allCopies = [...result.copies, ...retryResult.copies]
  console.log(`Total successful copies: ${allCopies.length}`)
}

Pre-Create Contexts

// Create contexts once, reuse for multiple uploads
const contexts = await synapse.storage.createContexts({
  count: 3,
  withCDN: true,
  callbacks: {
    onProviderSelected: (provider) => {
      console.log(`Using provider: ${provider.serviceProvider}`)
    },
  },
})

console.log(`Created ${contexts.length} contexts`)

// Use contexts for multiple uploads
const files = ['file1.txt', 'file2.txt', 'file3.txt']

for (const file of files) {
  const result = await synapse.storage.upload(
    fs.readFileSync(file),
    { contexts }
  )
  console.log(`${file}: ${result.copies.length} copies`)
}

Geo-Distributed Storage

const providers = await synapse.spRegistry.getAllActiveProviders()

// Distribute across regions
const regions = ['us-west', 'us-east', 'eu-west']
const selectedProviders: bigint[] = []

for (const region of regions) {
  const regionProvider = providers.find(p => 
    p.pdp.capabilities?.region === region && p.active
  )
  
  if (regionProvider) {
    selectedProviders.push(regionProvider.id)
  }
}

if (selectedProviders.length >= 2) {
  const result = await synapse.storage.upload(data, {
    providerIds: selectedProviders,
  })
  
  console.log('Geo-distributed storage:')
  for (const copy of result.copies) {
    const provider = providers.find(p => p.id === copy.providerId)
    console.log(`  ${provider?.pdp.capabilities?.region}: ${copy.dataSetId}`)
  }
}

Cost Optimization

// Compare costs for different copy counts
const fileSize = 1024 * 1024 * 100 // 100 MB

for (const count of [2, 3, 4]) {
  const preflight = await synapse.storage.preflightUpload({ 
    size: fileSize,
  })
  
  // Multiply by copy count
  const monthlyCost = preflight.estimatedCost.perMonth * BigInt(count)
  
  console.log(`${count} copies: ${formatUnits(monthlyCost)} USDFC/month`)
}

Verify All Copies

const result = await synapse.storage.upload(data)

// Verify each copy is retrievable
for (const copy of result.copies) {
  try {
    const context = await synapse.storage.createContext({
      providerId: copy.providerId,
    })
    
    const downloaded = await context.download({ 
      pieceCid: result.pieceCid 
    })
    
    console.log(`✓ Provider ${copy.providerId}: verified`)
  } catch (error) {
    console.error(`✗ Provider ${copy.providerId}: failed to verify`)
  }
}

Multi-Copy with Different Metadata

// Create contexts with different metadata per provider
const contexts = await Promise.all([
  synapse.storage.createContext({
    metadata: { tier: 'premium', region: 'us-west' },
    withCDN: true,
  }),
  synapse.storage.createContext({
    metadata: { tier: 'standard', region: 'us-east' },
    withCDN: false,
  }),
])

const result = await synapse.storage.upload(data, { contexts })

Monitor Copy Status

const result = await synapse.storage.upload(data, {
  count: 3,
  callbacks: {
    onCopyComplete: (providerId, pieceCid) => {
      console.log(`✓ Copy complete on provider ${providerId}`)
    },
    onCopyFailed: (providerId, pieceCid, error) => {
      console.error(`✗ Copy failed on provider ${providerId}: ${error.message}`)
    },
  },
})

Redundancy Levels

const REDUNDANCY_LEVELS = {
  minimal: 2,    // Development
  standard: 3,   // Production
  high: 5,       // Critical data
  maximum: 7,    // Maximum safety
}

const result = await synapse.storage.upload(data, {
  count: REDUNDANCY_LEVELS.high,
})

if (result.copies.length < REDUNDANCY_LEVELS.high) {
  console.warn('Did not achieve target redundancy level')
}

Best Practices

Use 2+ Copies

Always use at least 2 copies for production data

Check Failures

Always check result.failures array

Endorsed Providers

Prefer endorsed providers for reliability

Geo-Distribute

Distribute copies across regions when possible

Next Steps

Split Operations

Use split operations for batch uploads

Provider Registry

Learn about provider discovery

Build docs developers (and LLMs) love