Overview
The FilecoinWarmStorageService (FWSS) contract combines PDP verification with integrated payment rails for data set management. It handles:
Client authorization via EIP-712 signatures
Provider whitelist management (approved/endorsed)
Payment rail creation and management
Data set and piece lifecycle
Proof validation callbacks
Contract Split
FWSS is split into two contracts:
Main Contract : Write operations (state changes)
StateView Contract : Read operations (gas-efficient queries)
import { calibration } from '@filoz/synapse-core/chains'
console . log ( 'Main:' , calibration . contracts . fwss . address )
console . log ( 'View:' , calibration . contracts . fwssStateView . address )
Data Structures
Data Set
struct DataSet {
uint256 id;
address client;
address payer;
uint256 providerId;
address provider;
uint256 railId;
uint256 startEpoch;
uint256 terminatedAtEpoch;
MetadataEntry[] metadata;
}
Piece
struct Piece {
uint256 id;
uint256 dataSetId;
bytes32 pieceCid;
uint256 size;
uint256 scheduledRemovalEpoch;
MetadataEntry[] metadata;
}
Service Price
struct ServicePrice {
uint256 pricePerTiBPerMonthNoCDN;
uint256 pricePerTiBCdnEgress;
uint256 pricePerTiBCacheMissEgress;
address tokenAddress;
uint256 epochsPerMonth;
uint256 minimumPricePerMonth;
}
Read Operations
Get Service Price
import * as WarmStorage from '@filoz/synapse-core/warm-storage'
import { createPublicClient , http } from 'viem'
import { calibration } from '@filoz/synapse-core/chains'
const client = createPublicClient ({
chain: calibration ,
transport: http (),
})
const price = await WarmStorage . getServicePrice ( client )
console . log ( 'Price per TiB/month:' , price . pricePerTiBPerMonthNoCDN )
console . log ( 'CDN egress:' , price . pricePerTiBCdnEgress )
console . log ( 'Cache miss egress:' , price . pricePerTiBCacheMissEgress )
console . log ( 'Token:' , price . tokenAddress )
console . log ( 'Epochs/month:' , price . epochsPerMonth )
Get Approved Providers
const providerIds = await WarmStorage . getApprovedProviders ( client , {
offset: 0 n ,
limit: 100 n ,
})
console . log ( ` ${ providerIds . length } approved providers` )
Get Endorsed Providers
const endorsedIds = await WarmStorage . getEndorsedProviders ( client )
console . log ( ` ${ endorsedIds . length } endorsed providers` )
Get Data Set
const dataSet = await WarmStorage . getDataSet ( client , {
dataSetId: 123 n
})
if ( dataSet ) {
console . log ( 'Client:' , dataSet . client )
console . log ( 'Provider:' , dataSet . serviceProvider )
console . log ( 'Rail ID:' , dataSet . railId )
console . log ( 'Start:' , dataSet . startEpoch )
console . log ( 'Terminated:' , dataSet . terminatedAtEpoch )
console . log ( 'Metadata:' , dataSet . metadata )
}
Get Client Data Sets
const dataSets = await WarmStorage . getClientDataSetsWithDetails ( client , {
address: account . address ,
})
for ( const ds of dataSets ) {
console . log ( `Data Set ${ ds . id } :` )
console . log ( ` Provider: ${ ds . serviceProvider } ` )
console . log ( ` Active: ${ ds . terminatedAtEpoch === 0 n } ` )
console . log ( ` Managed by service: ${ ds . isManagedByService } ` )
}
Get Piece
const piece = await WarmStorage . getPiece ( client , {
pieceId: 456 n
})
if ( piece ) {
console . log ( 'PieceCID:' , piece . pieceCid )
console . log ( 'Data Set:' , piece . dataSetId )
console . log ( 'Size:' , piece . size )
console . log ( 'Scheduled removal:' , piece . scheduledRemovalEpoch )
}
Write Operations
Most write operations are called by storage providers (Curio) via PDPVerifier callbacks,
not directly by clients. Use the Synapse SDK for client operations.
Terminate Data Set
import { createWalletClient } from 'viem'
const walletClient = createWalletClient ({
chain: calibration ,
transport: http (),
account ,
})
const hash = await WarmStorage . terminateDataSet ( walletClient , {
dataSetId: 123 n ,
})
const receipt = await walletClient . waitForTransactionReceipt ({ hash })
console . log ( 'Data set terminated' )
Add Approved Provider (Owner Only)
const hash = await WarmStorage . addApprovedProvider ( walletClient , {
providerId: 5 n ,
})
Remove Approved Provider (Owner Only)
const hash = await WarmStorage . removeApprovedProvider ( walletClient , {
providerId: 5 n ,
})
Endorse Provider (Owner Only)
const hash = await WarmStorage . endorseProvider ( walletClient , {
providerId: 1 n ,
})
Events
DataSetCreated
event DataSetCreated (
uint256 indexed dataSetId ,
address indexed client ,
address indexed payer ,
uint256 providerId ,
uint256 railId
);
PiecesAdded
event PiecesAdded (
uint256 indexed dataSetId ,
uint256 [] pieceIds ,
bytes32 [] pieceCids
);
DataSetTerminated
event DataSetTerminated (
uint256 indexed dataSetId ,
uint256 terminationEpoch
);
Listen for Events
import { watchContractEvent } from 'viem/actions'
const unwatch = watchContractEvent ( client , {
address: calibration . contracts . fwss . address ,
abi: calibration . contracts . fwss . abi ,
eventName: 'DataSetCreated' ,
args: {
client: account . address ,
},
onLogs : ( logs ) => {
for ( const log of logs ) {
console . log ( 'New data set:' , log . args . dataSetId )
console . log ( 'Rail ID:' , log . args . railId )
}
},
})
import { combineMetadata } from '@filoz/synapse-sdk'
// Combine user metadata with system metadata
const metadata = combineMetadata (
{ category: 'documents' , project: 'acme' },
true // withCDN
)
// Results in:
// [
// { key: 'category', value: 'documents' },
// { key: 'project', value: 'acme' },
// { key: 'withCDN', value: '' },
// ]
Limits:
Max 10 entries per data set
Max 5 entries per piece
Keys: max 32 characters
Values: max 128 characters
The withCDN key is special:
// Check if data set has CDN enabled
const dataSet = await WarmStorage . getDataSet ( client , { dataSetId: 123 n })
const hasCDN = dataSet . metadata . some ( m => m . key === 'withCDN' )
console . log ( 'CDN enabled:' , hasCDN )
Provider Management
Check Provider Status
const isApproved = await WarmStorage . isApprovedProvider ( client , {
providerId: 1 n ,
})
const isEndorsed = await WarmStorage . isEndorsedProvider ( client , {
providerId: 1 n ,
})
console . log ( 'Approved:' , isApproved )
console . log ( 'Endorsed:' , isEndorsed )
Get Provider Counts
const approvedCount = await WarmStorage . getApprovedProviderCount ( client )
const endorsedCount = await WarmStorage . getEndorsedProviderCount ( client )
console . log ( ` ${ approvedCount } approved, ${ endorsedCount } endorsed` )
Cost Estimation
import { SIZE_CONSTANTS , TIME_CONSTANTS } from '@filoz/synapse-sdk'
const price = await WarmStorage . getServicePrice ( client )
// Calculate monthly cost for 1 TiB
const sizeInTiB = 1
const monthlyCost = price . pricePerTiBPerMonthNoCDN * BigInt ( sizeInTiB )
console . log ( `1 TiB/month: ${ monthlyCost } (base units)` )
// Calculate per-epoch cost
const perEpochCost = monthlyCost / price . epochsPerMonth
console . log ( `Per epoch: ${ perEpochCost } (base units)` )
// Calculate per-day cost
const perDayCost = monthlyCost / TIME_CONSTANTS . DAYS_PER_MONTH
console . log ( `Per day: ${ perDayCost } (base units)` )
Integration with Payments
Data Set → Rail Mapping
Each data set has an associated payment rail:
import * as Pay from '@filoz/synapse-core/pay'
const dataSet = await WarmStorage . getDataSet ( client , { dataSetId: 123 n })
const rail = await Pay . getRail ( client , { railId: dataSet . railId })
console . log ( 'Rail details:' )
console . log ( ' Client:' , rail . client )
console . log ( ' Payee:' , rail . payee )
console . log ( ' Rate:' , rail . rate )
console . log ( ' Last settled:' , rail . lastSettledEpoch )
Terminate → Rail Termination
Terminating a data set also terminates the payment rail:
const hash = await WarmStorage . terminateDataSet ( walletClient , {
dataSetId: 123 n ,
})
// After termination, rail.endEpoch will be set
const dataSet = await WarmStorage . getDataSet ( client , { dataSetId: 123 n })
console . log ( 'Terminated at epoch:' , dataSet . terminatedAtEpoch )
const rail = await Pay . getRail ( client , { railId: dataSet . railId })
console . log ( 'Rail ended at epoch:' , rail . endEpoch )
Nonce Management
import { getNonce } from '@filoz/synapse-core/warm-storage'
// Get current nonce for signing
const nonce = await getNonce ( client , {
address: account . address ,
})
console . log ( 'Current nonce:' , nonce )
Nonce increments after each signed operation (CreateDataSet, AddPieces, etc.).
Best Practices
Use SDK Use Synapse SDK instead of direct contract calls
Check Provider Status Verify providers are approved before use
Monitor Metadata Use metadata for filtering and organization
Track Rail Status Monitor associated payment rails
Source Code
FWSS Contract View the FilecoinWarmStorageService contract source
Next Steps
PDP Verifier Learn about proof verification
Filecoin Pay Understand payment rails