-
Notifications
You must be signed in to change notification settings - Fork 19
feat: Add API for querying remaining egress #430
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
pyropy
wants to merge
12
commits into
master
Choose a base branch
from
feat/api-to-query-remaining-egress-stats
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 9 commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
3dd8255
feat: Add API for querying remaining FilBeam egress
pyropy 2c8d4f7
fix: add filbeam response validation
pyropy f5d1af6
chore: add doc header to synapse-sdk/filbeam/index.ts
pyropy 00e3fb4
chore: update docs
pyropy f583ddc
chore: update docs
pyropy 8b66b70
chore: add filbeam tsconfig entrypoint
pyropy e013d01
chore: export filbeam package
pyropy 1600f78
chore: fix docs
pyropy d4efc26
chore: fix pricing in docs
pyropy b6d70f3
Merge branch 'master' into feat/api-to-query-remaining-egress-stats
pyropy 7c4e29a
chore: remove create factory method
pyropy 8be35a1
Merge branch 'feat/api-to-query-remaining-egress-stats' of github.com…
pyropy File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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' |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 { | ||
| 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), | ||
| } | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
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