From eca0ad8bcc387f0bf73fea769ccbe8a7a83ba0b8 Mon Sep 17 00:00:00 2001 From: Rod Vagg Date: Mon, 23 Mar 2026 13:54:31 +1100 Subject: [PATCH] feat(core): preserve non-standard SP registry capabilities in PDPOffering There's potentially useful signals in here downstream from our consumption of the capabilities providers publish. --- .../synapse-core/src/sp-registry/types.ts | 6 +++ .../src/utils/pdp-capabilities.ts | 39 ++++++++++++------- .../test/pdp-capabilities.test.ts | 37 ++++++++++++++++++ packages/synapse-sdk/src/test/storage.test.ts | 1 + 4 files changed, 70 insertions(+), 13 deletions(-) diff --git a/packages/synapse-core/src/sp-registry/types.ts b/packages/synapse-core/src/sp-registry/types.ts index a215ec55f..ad688ad46 100644 --- a/packages/synapse-core/src/sp-registry/types.ts +++ b/packages/synapse-core/src/sp-registry/types.ts @@ -71,4 +71,10 @@ export interface PDPOffering { * The IPNI peer ID. */ ipniPeerId?: string + /** + * Additional non-standard capabilities declared by the provider. + * Keys are capability names as registered on-chain (plain strings), + * values are the raw hex-encoded bytes from the contract. + */ + extraCapabilities?: Record } diff --git a/packages/synapse-core/src/utils/pdp-capabilities.ts b/packages/synapse-core/src/utils/pdp-capabilities.ts index 3cc8d04b9..dbf7dbf85 100644 --- a/packages/synapse-core/src/utils/pdp-capabilities.ts +++ b/packages/synapse-core/src/utils/pdp-capabilities.ts @@ -12,18 +12,20 @@ import { zHex } from './schemas.ts' * * @see https://github.com/FilOzone/filecoin-services/blob/a86e4a5018133f17a25b4bb6b5b99da4d34fe664/service_contracts/src/ServiceProviderRegistry.sol#L14 */ -export const PDPOfferingSchema = z.object({ - serviceURL: zHex, - minPieceSizeInBytes: zHex, - maxPieceSizeInBytes: zHex, - storagePricePerTibPerDay: zHex, - minProvingPeriodInEpochs: zHex, - location: zHex, - paymentTokenAddress: zHex, - ipniPiece: zHex.optional(), - ipniIpfs: zHex.optional(), - ipniPeerId: zHex.optional(), -}) +export const PDPOfferingSchema = z + .object({ + serviceURL: zHex, + minPieceSizeInBytes: zHex, + maxPieceSizeInBytes: zHex, + storagePricePerTibPerDay: zHex, + minProvingPeriodInEpochs: zHex, + location: zHex, + paymentTokenAddress: zHex, + ipniPiece: zHex.optional(), + ipniIpfs: zHex.optional(), + ipniPeerId: zHex.optional(), + }) + .catchall(zHex) // Standard capability keys for PDP product type (must match ServiceProviderRegistry.sol REQUIRED_PDP_KEYS) export const CAP_SERVICE_URL = 'serviceURL' export const CAP_MIN_PIECE_SIZE = 'minPieceSizeInBytes' @@ -47,6 +49,9 @@ export function decodePDPOffering(provider: ProviderWithProduct): PDPOffering { return decodePDPCapabilities(parsed.data) } +/** Capability keys that are decoded into typed PDPOffering fields, derived from the schema */ +const KNOWN_CAPABILITY_KEYS = new Set([...Object.keys(PDPOfferingSchema.shape), CAP_IPNI_PEER_ID_LEGACY]) + /** * Decode PDP capabilities from keys/values arrays into a PDPOffering object. * Based on Curio's capabilitiesToOffering function. @@ -71,7 +76,15 @@ export function decodePDPCapabilities(capabilities: Record): PDPOff ? base58btc.encode(fromHex(capabilities[CAP_IPNI_PEER_ID_LEGACY], 'bytes')) : undefined, } - return { ...required, ...optional } + + const extraCapabilities: Record = Object.create(null) + for (const key of Object.keys(capabilities)) { + if (!KNOWN_CAPABILITY_KEYS.has(key)) { + extraCapabilities[key] = capabilities[key] + } + } + + return { ...required, ...optional, extraCapabilities } } /** diff --git a/packages/synapse-core/test/pdp-capabilities.test.ts b/packages/synapse-core/test/pdp-capabilities.test.ts index ed494f004..d3cd20bd7 100644 --- a/packages/synapse-core/test/pdp-capabilities.test.ts +++ b/packages/synapse-core/test/pdp-capabilities.test.ts @@ -27,6 +27,43 @@ describe('decodePDPCapabilities', () => { assert.strictEqual(result.ipniPeerId, undefined) }) }) + + describe('extraCapabilities', () => { + it('preserves non-standard capabilities in extraCapabilities', () => { + const capabilities = createMinimalCapabilities({ + serviceStatus: toHex('dev'), + customFlag: '0x01', + }) + + const result = decodePDPCapabilities(capabilities) + + assert.ok(result.extraCapabilities) + assert.strictEqual(result.extraCapabilities.serviceStatus, toHex('dev')) + assert.strictEqual(result.extraCapabilities.customFlag, '0x01') + }) + + it('returns empty extraCapabilities when no non-standard capabilities exist', () => { + const capabilities = createMinimalCapabilities() + + const result = decodePDPCapabilities(capabilities) + + assert.ok(result.extraCapabilities) + assert.strictEqual(Object.keys(result.extraCapabilities).length, 0) + }) + + it('does not include standard capabilities in extraCapabilities', () => { + const capabilities = createMinimalCapabilities({ + serviceStatus: toHex('dev'), + }) + + const result = decodePDPCapabilities(capabilities) + + assert.ok(result.extraCapabilities) + assert.strictEqual(Object.keys(result.extraCapabilities).length, 1) + assert.strictEqual(result.extraCapabilities.serviceURL, undefined) + assert.strictEqual(result.extraCapabilities.location, undefined) + }) + }) }) // Minimal valid capabilities for testing (all required fields) diff --git a/packages/synapse-sdk/src/test/storage.test.ts b/packages/synapse-sdk/src/test/storage.test.ts index 7822735a2..d0490eca5 100644 --- a/packages/synapse-sdk/src/test/storage.test.ts +++ b/packages/synapse-sdk/src/test/storage.test.ts @@ -1270,6 +1270,7 @@ describe('StorageService', () => { minProvingPeriodInEpochs: 30n, location: 'us-east', paymentTokenAddress: '0xb3042734b608a1b16e9e86b374a3f3e389b4cdf0', + extraCapabilities: {}, }, }) })