Skip to content
Open
2 changes: 1 addition & 1 deletion docs/astro.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ export default defineConfig({
{
label: 'Developer Guides',
collapsed: false,
autogenerate: { directory: 'developer-guides' , collapsed: true},
autogenerate: { directory: 'developer-guides', collapsed: true },
},
{
label: 'CookBooks',
Expand Down
4 changes: 4 additions & 0 deletions packages/synapse-sdk/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@
"./sp-registry": {
"import": "./dist/src/sp-registry/index.js",
"types": "./dist/src/sp-registry/index.d.ts"
},
"./filbeam": {
"import": "./dist/src/filbeam/index.js",
"types": "./dist/src/filbeam/index.d.ts"
}
},
"scripts": {
Expand Down
94 changes: 94 additions & 0 deletions packages/synapse-sdk/src/filbeam/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/**
* FilBeam Service
*
* This module provides access to FilBeam's services and trusted measurement layer.
* FilBeam solves the fundamental challenge that proving data retrieval
* is cryptographically impossible without enabling fraud, by acting
* as a trusted intermediary that measures actual egress volumes through real client traffic.
*
* ## Key Features
*
* - **Trusted Measurement**: Accurately tracks data egress from storage providers
* - **Dual-Tier Architecture**: Differentiates between CDN cache hits and cache misses
* - **Economic Incentives**: Enables storage providers to earn 7 USDFC per TiB served
* - **Pay-As-You-Go**: Clients pay only for what they use (~0.014 USDFC per GiB)
* - **No Subscriptions**: Wallet-centric model without monthly fees
*
* ## Architecture
*
* FilBeam operates as a caching layer between clients and storage providers:
*
* 1. **Cache Hits**: Data served directly from FilBeam's CDN (fast, efficient)
* 2. **Cache Misses**: Data retrieved from storage providers and cached for future use
*
* Both scenarios generate billable egress events, transforming Filecoin from passive
* archival storage to an active "serve many" data delivery infrastructure.
*
* @module FilBeam
*
* @example Basic Usage
* ```typescript
* import { FilBeamService } from '@filoz/synapse-sdk/filbeam'
*
* // Create service for mainnet
* const service = FilBeamService.create('mainnet')
*
* // Get remaining data set statistics
* const stats = await service.getDataSetStats('dataset-id')
* console.log('Remaining CDN Egress:', stats.cdnEgressQuota)
* console.log('Remaining Cache Miss:', stats.cacheMissEgressQuota)
* ```
*
* @example Integration with Synapse SDK
* ```typescript
* import { Synapse } from '@filoz/synapse-sdk'
*
* // Initialize Synapse
* const synapse = await Synapse.create({
* privateKey: process.env.PRIVATE_KEY,
* rpcURL: 'https://api.node.glif.io/rpc/v1'
* })
*
* // Access FilBeam service through Synapse
* const stats = await synapse.filbeam.getDataSetStats('my-dataset')
*
* // Monitor remaining quotas over time
* setInterval(async () => {
* const currentStats = await synapse.filbeam.getDataSetStats('my-dataset')
* console.log('Remaining quotas:', currentStats)
*
* // Alert if running low
* const TiB = BigInt(1024 ** 4)
* const remainingTiB = Number((currentStats.cdnEgressQuota + currentStats.cacheMissEgressQuota) / TiB)
* if (remainingTiB < 1) {
* console.warn('Low quota warning: Less than 1 TiB remaining')
* }
* }, 60000) // Check every minute
* ```
*
* @example Testing with Mock Fetch
* ```typescript
* import { FilBeamService } from '@filoz/synapse-sdk/filbeam'
*
* // Create service with mock fetch for testing
* const mockFetch = async (url: string) => {
* return {
* status: 200,
* json: async () => ({
* cdnEgressQuota: '1099511627776', // 1 TiB in bytes
* cacheMissEgressQuota: '549755813888' // 0.5 TiB in bytes
* })
* } as Response
* }
*
* const service = new FilBeamService('mainnet', mockFetch)
* const stats = await service.getDataSetStats('test')
* ```
*
* @see {@link https://docs.filbeam.com | FilBeam Documentation} - Official FilBeam documentation
* @see {@link https://meridian.space/blog/introducing-pay-per-byte-a-new-era-for-filecoin-retrieval | Pay Per Byte Blog Post} - Introduction to the pay-per-byte pricing model
* @see {@link DataSetStats} for the structure of returned statistics
* @see {@link FilBeamService} for the main service class
*/

export { type DataSetStats, FilBeamService } from './service.ts'
178 changes: 178 additions & 0 deletions packages/synapse-sdk/src/filbeam/service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
/**
* @module FilBeamService
* @description FilBeam service integration for Filecoin's pay-per-byte infrastructure.
*
* This module provides integration with FilBeam's services, including querying egress quotas
* and managing pay-per-byte data delivery metrics. FilBeam serves as
* the trusted measurement layer that enables Filecoin's "Pay Per Byte" pricing model,
* transforming Filecoin from a passive archival system to an active data delivery network.
*
* FilBeam measures actual egress volumes through real client traffic, enabling storage providers
* to be compensated for data retrieval at 7 USDFC per TiB of egress.
*
* @see {@link https://docs.filbeam.com | FilBeam Documentation} for comprehensive API documentation
*/

import type { FilecoinNetworkType } from '../types.ts'
import { createError } from '../utils/errors.ts'

/**
* Data set statistics from FilBeam.
*
* These quotas represent the remaining pay-per-byte allocation available for data retrieval
* through FilBeam's trusted measurement layer. The values decrease as data is served and
* represent how many bytes can still be retrieved before needing to add more credits.
*
* @interface DataSetStats
* @property {bigint} cdnEgressQuota - The remaining CDN egress quota for cache hits (data served directly from FilBeam's cache) in bytes
* @property {bigint} cacheMissEgressQuota - The remaining egress quota for cache misses (data retrieved from storage providers) in bytes
*/
export interface DataSetStats {
cdnEgressQuota: bigint
cacheMissEgressQuota: bigint
}

/**
* Service for interacting with FilBeam infrastructure and APIs.
*
* @example
* ```typescript
* // Create service with network detection
* const synapse = await Synapse.create({ privateKey, rpcURL })
* const stats = await synapse.filbeam.getDataSetStats(12345)
*
* // Monitor remaining pay-per-byte quotas
* const service = new FilBeamService('mainnet')
* const stats = await service.getDataSetStats(12345)
* console.log('Remaining CDN Egress (cache hits):', stats.cdnEgressQuota)
* console.log('Remaining Cache Miss Egress:', stats.cacheMissEgressQuota)
* ```
*
* @remarks
* All quota values are returned as BigInt for precision when handling large byte values.
*
* @see {@link https://docs.filbeam.com | FilBeam Documentation} for detailed API specifications and usage guides
*/
export class FilBeamService {
private readonly _network: FilecoinNetworkType
private readonly _fetch: typeof fetch

constructor(network: FilecoinNetworkType, fetchImpl: typeof fetch = globalThis.fetch) {
this._network = network
this._fetch = fetchImpl
}

/**
* Creates a new FilBeamService instance.
*
* @param network - The Filecoin network type ('mainnet' or 'calibration')
* @param fetchImpl - Optional custom fetch implementation for testing
* @returns A new FilBeamService instance
*
* @example
* ```typescript
* const service = FilBeamService.create('mainnet')
* ```
*/
static create(network: FilecoinNetworkType, fetchImpl?: typeof fetch): FilBeamService {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wouldn't bother with this, the only reason we use a factory in other places is to deal with async initialisation, but where you only have sync you really just need a constructor, so you've now got two constructors here

return new FilBeamService(network, fetchImpl)
}

/**
* Get the base stats URL for the current network
*/
private _getStatsBaseUrl(): string {
return this._network === 'mainnet' ? 'https://stats.filbeam.io' : 'https://calibration.stats.filbeam.io'
}

/**
* Validates the response from FilBeam stats API
*/
private _validateStatsResponse(data: unknown): { cdnEgressQuota: string; cacheMissEgressQuota: string } {
if (typeof data !== 'object' || data === null) {
throw createError('FilBeamService', 'validateStatsResponse', 'Response is not an object')
}

const response = data as Record<string, unknown>

if (typeof response.cdnEgressQuota !== 'string') {
throw createError('FilBeamService', 'validateStatsResponse', 'cdnEgressQuota must be a string')
}

if (typeof response.cacheMissEgressQuota !== 'string') {
throw createError('FilBeamService', 'validateStatsResponse', 'cacheMissEgressQuota must be a string')
}

return {
cdnEgressQuota: response.cdnEgressQuota,
cacheMissEgressQuota: response.cacheMissEgressQuota,
}
}

/**
* Retrieves remaining pay-per-byte statistics for a specific data set from FilBeam.
*
* Fetches the remaining CDN and cache miss egress quotas for a data set. These quotas
* track how many bytes can still be retrieved through FilBeam's trusted measurement layer
* before needing to add more credits:
*
* - **CDN Egress Quota**: Remaining bytes that can be served from FilBeam's cache (fast, direct delivery)
* - **Cache Miss Egress Quota**: Remaining bytes that can be retrieved from storage providers (triggers caching)
*
* Both types of egress are billed at approximately 0.014 USDFC per GiB, with storage
* providers receiving 7 USDFC per TiB for data served to FilBeam.
*
* @param dataSetId - The unique identifier of the data set to query
* @returns A promise that resolves to the data set statistics with remaining quotas as BigInt values
*
* @throws {Error} Throws an error if:
* - The data set is not found (404)
* - The API returns an invalid response format
* - Network or other HTTP errors occur
*
* @example
* ```typescript
* try {
* const stats = await service.getDataSetStats('my-dataset-123')
*
* // Display remaining quotas
* console.log(`Remaining CDN Egress: ${stats.cdnEgressQuota} bytes`)
* console.log(`Remaining Cache Miss: ${stats.cacheMissEgressQuota} bytes`)
* } catch (error) {
* console.error('Failed to get stats:', error.message)
* }
* ```
*/
async getDataSetStats(dataSetId: string | number): Promise<DataSetStats> {
const baseUrl = this._getStatsBaseUrl()
const url = `${baseUrl}/data-set/${dataSetId}`

const response = await this._fetch(url, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
})

if (response.status === 404) {
throw createError('FilBeamService', 'getDataSetStats', `Data set not found: ${dataSetId}`)
}

if (response.status !== 200) {
const errorText = await response.text().catch(() => 'Unknown error')
throw createError(
'FilBeamService',
'getDataSetStats',
`HTTP ${response.status} ${response.statusText}: ${errorText}`
)
}

const data = await response.json()
const validated = this._validateStatsResponse(data)

return {
cdnEgressQuota: BigInt(validated.cdnEgressQuota),
cacheMissEgressQuota: BigInt(validated.cacheMissEgressQuota),
}
}
}
17 changes: 17 additions & 0 deletions packages/synapse-sdk/src/synapse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
*/

import { ethers } from 'ethers'
import { FilBeamService } from './filbeam/index.ts'
import { PaymentsService } from './payments/index.ts'
import { ChainRetriever, FilBeamRetriever, SubgraphRetriever } from './retriever/index.ts'
import { SessionKey } from './session/key.ts'
Expand Down Expand Up @@ -35,6 +36,7 @@ export class Synapse {
private readonly _warmStorageService: WarmStorageService
private readonly _pieceRetriever: PieceRetriever
private readonly _storageManager: StorageManager
private readonly _filbeamService: FilBeamService
private _session: SessionKey | null = null

/**
Expand Down Expand Up @@ -166,6 +168,9 @@ export class Synapse {
pieceRetriever = new FilBeamRetriever(baseRetriever, network)
}

// Create FilBeamService
const filbeamService = FilBeamService.create(network)

// Create and initialize the global TelemetryService.
// If telemetry is disabled, this will do nothing.
await initGlobalTelemetry(options.telemetry || {}, { filecoinNetwork: network })
Expand All @@ -179,6 +184,7 @@ export class Synapse {
warmStorageAddress,
warmStorageService,
pieceRetriever,
filbeamService,
options.dev === false,
options.withIpni
)
Expand All @@ -194,6 +200,7 @@ export class Synapse {
warmStorageAddress: string,
warmStorageService: WarmStorageService,
pieceRetriever: PieceRetriever,
filbeamService: FilBeamService,
dev: boolean,
withIpni?: boolean
) {
Expand All @@ -205,6 +212,7 @@ export class Synapse {
this._warmStorageService = warmStorageService
this._pieceRetriever = pieceRetriever
this._warmStorageAddress = warmStorageAddress
this._filbeamService = filbeamService
this._session = null

// Initialize StorageManager
Expand Down Expand Up @@ -358,6 +366,15 @@ export class Synapse {
return this._storageManager
}

/**
* Gets the FilBeam service instance
*
* @returns The FilBeam service for interacting with FilBeam infrastructure
*/
get filbeam(): FilBeamService {
return this._filbeamService
}

/**
* Create a storage service instance.
*
Expand Down
Loading
Loading